generated from jCloud/repository-template
Add classes for Python function arguments
This commit is contained in:
@@ -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
|
||||
@@ -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): ...
|
||||
Reference in New Issue
Block a user