generated from jCloud/repository-template
The '=' character at a parameter in the signature was surrounded with spaces previously. That is no longer the case, unless there is a type hint.
296 lines
9.2 KiB
Python
296 lines
9.2 KiB
Python
# Copyright 2026 jCloud
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import keyword
|
|
import types
|
|
from enum import Enum
|
|
|
|
from ...exceptions import (
|
|
InvalidPythonAnnotationError,
|
|
InvalidPythonIdentifierError,
|
|
PythonArgumentStructureError,
|
|
)
|
|
from ...utils import assert_that_is_instance
|
|
|
|
__all__ = [
|
|
'PythonArgumentKind',
|
|
'PythonFunctionArgument',
|
|
'PythonASTArgumentsListParser',
|
|
]
|
|
|
|
|
|
class PythonArgumentKind(Enum):
|
|
NORMAL = 0
|
|
POSITIONAL_ONLY = 1
|
|
KEYWORD_ONLY = 2
|
|
VARARG = 3
|
|
KWARGS = 4
|
|
|
|
|
|
class PythonFunctionArgument:
|
|
"""
|
|
Represents an argument of a Python function.
|
|
|
|
:param name: The name of the argument.
|
|
:type name: str
|
|
:param kind: The kind of the argument.
|
|
:type kind: ArgumentKind
|
|
:param default: The default value of the argument.
|
|
:type default: str | None
|
|
:param annotation: The type annotation of the argument.
|
|
:type annotation: str | None
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
kind: PythonArgumentKind,
|
|
default: str | None,
|
|
annotation: str | None,
|
|
) -> None:
|
|
assert_that_is_instance(name, str)
|
|
assert_that_is_instance(kind, PythonArgumentKind)
|
|
assert_that_is_instance(default, (str, types.NoneType))
|
|
assert_that_is_instance(annotation, (str, types.NoneType))
|
|
|
|
# check whether name is a valid identifier
|
|
if not name or not name.isidentifier() or keyword.iskeyword(name):
|
|
raise InvalidPythonIdentifierError(
|
|
'invalid identifier',
|
|
identifier=name,
|
|
)
|
|
|
|
# check whether the annotation is a valid annotation
|
|
# Currently, it is only checked whether the parameter is not
|
|
# empty.
|
|
if annotation is not None and not annotation:
|
|
raise InvalidPythonAnnotationError(
|
|
'invalid annotation',
|
|
annotation=annotation,
|
|
)
|
|
|
|
self.name = name
|
|
self.kind = kind
|
|
self.default = default
|
|
self.annotation = annotation
|
|
|
|
if (
|
|
self.kind
|
|
in (
|
|
PythonArgumentKind.VARARG,
|
|
PythonArgumentKind.KWARGS,
|
|
)
|
|
and self.default is not None
|
|
):
|
|
raise PythonArgumentStructureError(
|
|
'* or ** arguments cannot have default values',
|
|
definition=self.signature_repr(),
|
|
)
|
|
|
|
def __eq__(self, value: PythonFunctionArgument) -> bool:
|
|
if not isinstance(value, PythonFunctionArgument):
|
|
return False
|
|
return (
|
|
self.name == value.name
|
|
and self.kind == value.kind
|
|
and self.default == value.default
|
|
and self.annotation == value.annotation
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return type(self).__name__ + repr(
|
|
(
|
|
self.name,
|
|
self.kind,
|
|
self.default,
|
|
self.annotation,
|
|
)
|
|
)
|
|
|
|
def signature_repr(self) -> str:
|
|
"""
|
|
Returns the function signature representation of the argument.
|
|
|
|
:return: The string representation of the argument.
|
|
:rtype: str
|
|
"""
|
|
|
|
if self.kind == PythonArgumentKind.KWARGS:
|
|
str_repr = '**'
|
|
elif self.kind == PythonArgumentKind.VARARG:
|
|
str_repr = '*'
|
|
else:
|
|
str_repr = ''
|
|
|
|
str_repr += self.name
|
|
|
|
if self.annotation is not None:
|
|
str_repr += ': ' + self.annotation
|
|
|
|
if self.default is not None:
|
|
if self.annotation is None:
|
|
str_repr += '=' + self.default
|
|
else:
|
|
str_repr += ' = ' + self.default
|
|
|
|
return str_repr
|
|
|
|
|
|
class PythonASTArgumentsListParser:
|
|
"""
|
|
A parser for making ``PythonFunctionArgument`` objects from an
|
|
``ast.arguments`` arguments list.
|
|
|
|
:param ast_arguments_list: The ``ast.arguments`` arguments list.
|
|
:type ast_arguments_list: ast.arguments
|
|
"""
|
|
|
|
def __init__(self, ast_arguments_list: ast.arguments) -> None:
|
|
self.ast_arguments_list = ast_arguments_list
|
|
|
|
def to_argument_list(self) -> list[PythonFunctionArgument]:
|
|
"""
|
|
Converts the AST arguments list to a list of Python function
|
|
argument objects.
|
|
|
|
:return: The list of Python function argument objects.
|
|
:rtype: list[PythonFunctionArgument]
|
|
"""
|
|
|
|
defaults = (
|
|
[
|
|
None
|
|
for _ in range(
|
|
len(
|
|
self.ast_arguments_list.posonlyargs
|
|
+ self.ast_arguments_list.args
|
|
+ self.ast_arguments_list.kwonlyargs
|
|
)
|
|
- len(self.ast_arguments_list.defaults)
|
|
- len(self.ast_arguments_list.kw_defaults)
|
|
)
|
|
]
|
|
+ [ast.unparse(d) for d in self.ast_arguments_list.defaults]
|
|
+ [
|
|
ast.unparse(d) if d is not None else None
|
|
for d in self.ast_arguments_list.kw_defaults
|
|
]
|
|
)
|
|
|
|
i = 0
|
|
arguments = []
|
|
for arg in self.ast_arguments_list.posonlyargs:
|
|
if arg.annotation is None:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
arg.arg,
|
|
PythonArgumentKind.POSITIONAL_ONLY,
|
|
defaults[i],
|
|
None,
|
|
),
|
|
)
|
|
else:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
arg.arg,
|
|
PythonArgumentKind.POSITIONAL_ONLY,
|
|
defaults[i],
|
|
ast.unparse(arg.annotation),
|
|
),
|
|
)
|
|
i += 1
|
|
for arg in self.ast_arguments_list.args:
|
|
if arg.annotation is None:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
arg.arg,
|
|
PythonArgumentKind.NORMAL,
|
|
defaults[i],
|
|
None,
|
|
),
|
|
)
|
|
else:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
arg.arg,
|
|
PythonArgumentKind.NORMAL,
|
|
defaults[i],
|
|
ast.unparse(arg.annotation),
|
|
),
|
|
)
|
|
i += 1
|
|
if self.ast_arguments_list.vararg is not None:
|
|
if self.ast_arguments_list.vararg.annotation is None:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
self.ast_arguments_list.vararg.arg,
|
|
PythonArgumentKind.VARARG,
|
|
None,
|
|
None,
|
|
),
|
|
)
|
|
else:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
self.ast_arguments_list.vararg.arg,
|
|
PythonArgumentKind.VARARG,
|
|
None,
|
|
ast.unparse(self.ast_arguments_list.vararg.annotation),
|
|
),
|
|
)
|
|
for arg in self.ast_arguments_list.kwonlyargs:
|
|
if arg.annotation is None:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
arg.arg,
|
|
PythonArgumentKind.KEYWORD_ONLY,
|
|
defaults[i],
|
|
None,
|
|
),
|
|
)
|
|
else:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
arg.arg,
|
|
PythonArgumentKind.KEYWORD_ONLY,
|
|
defaults[i],
|
|
ast.unparse(arg.annotation),
|
|
),
|
|
)
|
|
i += 1
|
|
if self.ast_arguments_list.kwarg is not None:
|
|
if self.ast_arguments_list.kwarg.annotation is None:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
self.ast_arguments_list.kwarg.arg,
|
|
PythonArgumentKind.KWARGS,
|
|
None,
|
|
None,
|
|
),
|
|
)
|
|
else:
|
|
arguments.append(
|
|
PythonFunctionArgument(
|
|
self.ast_arguments_list.kwarg.arg,
|
|
PythonArgumentKind.KWARGS,
|
|
None,
|
|
ast.unparse(self.ast_arguments_list.kwarg.annotation),
|
|
),
|
|
)
|
|
|
|
return arguments
|