Add classes for Python function arguments

This commit is contained in:
2026-04-11 13:33:42 +02:00
parent d0e4ae54c3
commit 91cecae230
6 changed files with 508 additions and 2 deletions
+152
View File
@@ -0,0 +1,152 @@
# Copyright 2026 jCloud Services GbR
# 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
from enum import Enum
from typing import Union
import ast
from ...utils import assert_that_is_instance
from ...exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError
import keyword
import types
__all__ = [
'PythonArgumentKind',
'PythonFunctionArgumentDefault',
'PythonFunctionArgument',
'PythonASTArgumentsListParser'
]
class PythonArgumentKind(Enum):
NORMAL = 0
POSITIONAL_ONLY = 1
KEYWORD_ONLY = 2
VARARG = 3
KWARGS = 4
class PythonFunctionArgumentDefault:
'''
Represents the default value of a Python function argument.
:param value: The value
:type value: ast.expr
'''
def __init__(self, value: ast.expr) -> None:
assert_that_is_instance(value, ast.expr)
self.value = value
def __eq__(self, value: PythonFunctionArgumentDefault) -> bool:
if not isinstance(value, PythonFunctionArgumentDefault):
return False
return ast.dump(value.value, include_attributes = False) == ast.dump(self.value, include_attributes = False)
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: Union[PythonFunctionArgumentDefault, None]
:param annotation: The type annotation of the argument.
:type annotation: Union[str, None]
'''
def __init__(self, name: str, kind: PythonArgumentKind, default: Union[PythonFunctionArgumentDefault, None], annotation: Union[str, None]) -> None:
assert_that_is_instance(name, str)
assert_that_is_instance(kind, PythonArgumentKind)
assert_that_is_instance(default, (PythonFunctionArgumentDefault, 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
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
))
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]
'''
arguments = []
for arg in self.ast_arguments_list.posonlyargs:
if arg.annotation is None:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.POSITIONAL_ONLY, None, None))
else:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.POSITIONAL_ONLY, None, arg.annotation.id))
for arg in self.ast_arguments_list.args:
if arg.annotation is None:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.NORMAL, None, None))
else:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.NORMAL, None, arg.annotation.id))
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, self.ast_arguments_list.vararg.annotation.id))
for arg in self.ast_arguments_list.kwonlyargs:
if arg.annotation is None:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.KEYWORD_ONLY, None, None))
else:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.KEYWORD_ONLY, None, arg.annotation.id))
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, self.ast_arguments_list.kwarg.annotation.id))
return arguments
+33 -1
View File
@@ -28,4 +28,36 @@ class NamespaceError(ValueError):
class InvalidNamespaceError(NamespaceError): ...
class NamespaceNotFoundError(NamespaceError): ...
class NamespaceExistsError(NamespaceError): ...
class NamespaceExistsError(NamespaceError): ...
class PythonIdentifierError(ValueError):
'''
Base class for Python identifier errors.
'''
def __init__(self, *args: object, identifier: str = '') -> None:
super().__init__(*args)
self.identifier = identifier
def __str__(self):
if not self.args:
return ''
else:
return f'{self.args[0]}{": " if self.identifier and self.args[0] else ""}{self.identifier if self.args[0] else ""}'
class InvalidPythonIdentifierError(PythonIdentifierError): ...
class PythonAnnotationError(ValueError):
'''
Base class for Python annotation errors.
'''
def __init__(self, *args: object, annotation: str = '') -> None:
super().__init__(*args)
self.annotation = annotation
def __str__(self):
if not self.args:
return ''
else:
return f'{self.args[0]}{": " if self.annotation and self.args[0] else ""}{self.annotation if self.args[0] else ""}'
class InvalidPythonAnnotationError(PythonAnnotationError): ...