diff --git a/CHANGELOG.md b/CHANGELOG.md index ee20e34e..79faf4b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # Changelog +## Pedantic 2.3.3 +- [fixed validation error message in MinLength validator](https://github.com/LostInDarkMath/pedantic-python-decorators/issues/111) +- removed unused scripts +- updated dependencies +- updated documentation + ## Pedantic 2.3.2 - removed workaround for Python version `3.14.0` in `@in_subprocess` decorator - see https://github.com/python/cpython/issues/139894 diff --git a/docs/pedantic/decorators/class_decorators.html b/docs/pedantic/decorators/class_decorators.html index 8c360341..9ce85e5d 100644 --- a/docs/pedantic/decorators/class_decorators.html +++ b/docs/pedantic/decorators/class_decorators.html @@ -78,7 +78,7 @@

Functions

for attr in cls.__dict__: attr_value = getattr(cls, attr) - if isinstance(attr_value, (types.FunctionType, types.MethodType)): + if isinstance(attr_value, (types.FunctionType, types.MethodType)) and attr != '__annotate_func__': setattr(cls, attr, decorator(attr_value)) elif isinstance(attr_value, property): prop = attr_value diff --git a/docs/pedantic/decorators/fn_deco_validate/validators/min_length.html b/docs/pedantic/decorators/fn_deco_validate/validators/min_length.html index 3cb71e7c..cf64d410 100644 --- a/docs/pedantic/decorators/fn_deco_validate/validators/min_length.html +++ b/docs/pedantic/decorators/fn_deco_validate/validators/min_length.html @@ -65,7 +65,7 @@

Classes

self.raise_exception(msg=f'{value} has no length.', value=value) if len(value) < self._length: - self.raise_exception(msg=f'{value} is too long with length {len(value)}.', value=value) + self.raise_exception(msg=f'{value} is too short with length {len(value)}.', value=value) return value diff --git a/docs/pedantic/mixins/generic_mixin.html b/docs/pedantic/mixins/generic_mixin.html new file mode 100644 index 00000000..6f648901 --- /dev/null +++ b/docs/pedantic/mixins/generic_mixin.html @@ -0,0 +1,345 @@ + + + + + + +pedantic.mixins.generic_mixin API documentation + + + + + + + + + + + +
+
+
+

Module pedantic.mixins.generic_mixin

+
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class GenericMixin +
+
+
+ +Expand source code + +
class GenericMixin:
+    """
+        A mixin that provides easy access to given type variables.
+
+        Example:
+            >>> from typing import Generic, TypeVar
+            >>> T = TypeVar('T')
+            >>> U = TypeVar('U')
+            >>> class Foo(Generic[T, U], GenericMixin):
+            ...     values: list[T]
+            ...     value: U
+            >>> f = Foo[str, int]()
+            >>> f.type_vars
+            {~T: <class 'str'>, ~U: <class 'int'>}
+    """
+
+    @property
+    def type_var(self) -> Type:
+        """
+            Get the type variable for this class.
+            Use this for convenience if your class has only one type parameter.
+
+            DO NOT call this inside __init__()!
+
+            Example:
+                >>> from typing import Generic, TypeVar
+                >>> T = TypeVar('T')
+                >>> class Foo(Generic[T], GenericMixin):
+                ...     value: T
+                >>> f = Foo[float]()
+                >>> f.type_var
+                <class 'float'>
+        """
+
+        types = self._get_resolved_typevars()
+        assert len(types) == 1, f'You have multiple type parameters. Please use "type_vars" instead of "type_var".'
+        return list(types.values())[0]  # type: ignore
+
+    @property
+    def type_vars(self) -> Dict[TypeVar, Type]:
+        """
+            Returns the mapping of type variables to types.
+
+            DO NOT call this inside __init__()!
+
+            Example:
+                >>> from typing import Generic, TypeVar
+                >>> T = TypeVar('T')
+                >>> U = TypeVar('U')
+                >>> class Foo(Generic[T, U], GenericMixin):
+                ...     values: list[T]
+                ...     value: U
+                >>> f = Foo[str, int]()
+                >>> f.type_vars
+                {~T: <class 'str'>, ~U: <class 'int'>}
+        """
+
+        return self._get_resolved_typevars()
+
+    def _get_resolved_typevars(self) -> Dict[TypeVar, Type]:
+        """
+        Do not call this inside the __init__() method, because at that point the relevant information are not present.
+        See also https://github.com/python/cpython/issues/90899'
+        """
+
+        mapping: dict[TypeVar, type] = {}
+
+        if not hasattr(self, '__orig_bases__'):
+            raise AssertionError(
+                f'{self.class_name} is not a generic class. To make it generic, declare it like: '
+                f'class {self.class_name}(Generic[T], GenericMixin):...'
+            )
+
+        def collect(base, substitutions: dict[TypeVar, type]):
+            """Recursively collect type var mappings from a generic base."""
+            origin = get_origin(base) or base
+            args = get_args(base)
+
+            params = getattr(origin, '__parameters__', ())
+            # copy substitutions so each recursion has its own view
+            resolved = substitutions.copy()
+
+            for param, arg in zip(params, args):
+                if isinstance(arg, TypeVar):
+                    arg = substitutions.get(arg, arg)
+                mapping[param] = arg
+                resolved[param] = arg
+
+            # Recurse into base classes, applying current substitutions
+            for super_base in getattr(origin, '__orig_bases__', []):
+                super_origin = get_origin(super_base) or super_base
+                super_args = get_args(super_base)
+
+                if super_args:
+                    # Substitute any TypeVars in the super_base's args using resolved
+                    substituted_args = tuple(
+                        resolved.get(a, a) if isinstance(a, TypeVar) else a
+                        for a in super_args
+                    )
+                    # Build a new parametrized base so get_args() inside collect sees substituted_args
+                    try:
+                        substituted_base = super_origin[substituted_args]  # type: ignore[index]
+                    except TypeError:
+                        # Some origins won't accept subscription; fall back to passing the origin and trusting resolved
+                        substituted_base = super_base
+                    collect(base=substituted_base, substitutions=resolved)
+                else:
+                    collect(base=super_base, substitutions=resolved)
+
+        # Start from __orig_class__ if present, else walk the declared MRO bases
+        cls = getattr(self, '__orig_class__', None)
+        if cls is not None:
+            collect(base=cls, substitutions={})
+        else:
+            # Walk the full MRO to catch indirect generic ancestors
+            for c in self.__class__.__mro__:
+                for base in getattr(c, '__orig_bases__', []):
+                    collect(base=base, substitutions=mapping)
+
+        # Ensure no unresolved TypeVars remain
+        all_params = set()
+        for c in self.__class__.__mro__:
+            all_params.update(getattr(c, '__parameters__', ()))
+
+        unresolved = {p for p in all_params if p not in mapping or isinstance(mapping[p], TypeVar)}
+        if unresolved:
+            raise AssertionError(
+                f'You need to instantiate this class with type parameters! Example: {self.class_name}[int]()\n'
+                f'Also make sure that you do not call this in the __init__() method of your class!\n'
+                f'Unresolved type variables: {unresolved}\n'
+                f'See also https://github.com/python/cpython/issues/90899'
+            )
+
+        return mapping
+    @property
+    def class_name(self) -> str:
+        """ Get the name of the class of this instance. """
+
+        return type(self).__name__
+
+

A mixin that provides easy access to given type variables.

+

Example

+
>>> from typing import Generic, TypeVar
+>>> T = TypeVar('T')
+>>> U = TypeVar('U')
+>>> class Foo(Generic[T, U], GenericMixin):
+...     values: list[T]
+...     value: U
+>>> f = Foo[str, int]()
+>>> f.type_vars
+{~T: <class 'str'>, ~U: <class 'int'>}
+
+

Subclasses

+ +

Instance variables

+
+
prop class_name : str
+
+
+ +Expand source code + +
@property
+def class_name(self) -> str:
+    """ Get the name of the class of this instance. """
+
+    return type(self).__name__
+
+

Get the name of the class of this instance.

+
+
prop type_var : Type
+
+
+ +Expand source code + +
@property
+def type_var(self) -> Type:
+    """
+        Get the type variable for this class.
+        Use this for convenience if your class has only one type parameter.
+
+        DO NOT call this inside __init__()!
+
+        Example:
+            >>> from typing import Generic, TypeVar
+            >>> T = TypeVar('T')
+            >>> class Foo(Generic[T], GenericMixin):
+            ...     value: T
+            >>> f = Foo[float]()
+            >>> f.type_var
+            <class 'float'>
+    """
+
+    types = self._get_resolved_typevars()
+    assert len(types) == 1, f'You have multiple type parameters. Please use "type_vars" instead of "type_var".'
+    return list(types.values())[0]  # type: ignore
+
+

Get the type variable for this class. +Use this for convenience if your class has only one type parameter.

+

DO NOT call this inside init()!

+

Example

+
>>> from typing import Generic, TypeVar
+>>> T = TypeVar('T')
+>>> class Foo(Generic[T], GenericMixin):
+...     value: T
+>>> f = Foo[float]()
+>>> f.type_var
+<class 'float'>
+
+
+
prop type_vars : Dict[TypeVar, Type]
+
+
+ +Expand source code + +
@property
+def type_vars(self) -> Dict[TypeVar, Type]:
+    """
+        Returns the mapping of type variables to types.
+
+        DO NOT call this inside __init__()!
+
+        Example:
+            >>> from typing import Generic, TypeVar
+            >>> T = TypeVar('T')
+            >>> U = TypeVar('U')
+            >>> class Foo(Generic[T, U], GenericMixin):
+            ...     values: list[T]
+            ...     value: U
+            >>> f = Foo[str, int]()
+            >>> f.type_vars
+            {~T: <class 'str'>, ~U: <class 'int'>}
+    """
+
+    return self._get_resolved_typevars()
+
+

Returns the mapping of type variables to types.

+

DO NOT call this inside init()!

+

Example

+
>>> from typing import Generic, TypeVar
+>>> T = TypeVar('T')
+>>> U = TypeVar('U')
+>>> class Foo(Generic[T, U], GenericMixin):
+...     values: list[T]
+...     value: U
+>>> f = Foo[str, int]()
+>>> f.type_vars
+{~T: <class 'str'>, ~U: <class 'int'>}
+
+
+
+
+
+
+
+ +
+ + + diff --git a/docs/pedantic/mixins/with_decorated_methods.html b/docs/pedantic/mixins/with_decorated_methods.html index 2864e1dc..e77e037e 100644 --- a/docs/pedantic/mixins/with_decorated_methods.html +++ b/docs/pedantic/mixins/with_decorated_methods.html @@ -2,18 +2,32 @@ - - + + pedantic.mixins.with_decorated_methods API documentation - - - - - - + + + + + + - - + +
@@ -22,107 +36,6 @@

Module pedantic.mixins.with_decorated_methods

-
- -Expand source code - -
from abc import ABC
-from enum import StrEnum
-from typing import TypeVar, Callable, Generic
-
-from pedantic.mixins.generic_mixin import GenericMixin
-
-
-class DecoratorType(StrEnum):
-    """
-    The interface that defines all possible decorators types.
-
-    The values of this enum are used as property names and the properties are added to the decorated functions.
-    So I would recommend naming them with a leading underscore to keep them private and also write it lowercase.
-    Example:
-        >>> class Decorators(DecoratorType):
-        ...     FOO = '_foo'
-    """
-
-
-E = TypeVar('E', bound=DecoratorType)
-T = TypeVar('T')
-C = TypeVar('C', bound=Callable)
-
-
-def create_decorator(
-    decorator_type: DecoratorType,
-    transformation: Callable[[C, DecoratorType, T], C] = None,
-) -> Callable[[T], Callable[[C], C]]:
-    """
-    Creates a new decorator that is parametrized with one argument of an arbitrary type.
-    You can also pass an arbitrary [transformation] to add custom behavior to the decorator.
-    """
-
-    def decorator(value: T) -> Callable[[C], C]:
-        def fun(f: C) -> C:
-            setattr(f, decorator_type, value)
-
-            if transformation is None:
-                return f
-
-            return transformation(f, decorator_type, value)
-
-        return fun  # we do not need functools.wraps, because we return the original function here
-
-    return decorator
-
-
-class WithDecoratedMethods(ABC, Generic[E], GenericMixin):
-    """
-    A mixin that is used to figure out which method is decorated with custom parameterized decorators.
-    Example:
-        >>> class Decorators(DecoratorType):
-        ...     FOO = '_foo'
-        ...     BAR = '_bar'
-        >>> foo = create_decorator(decorator_type=Decorators.FOO)
-        >>> bar = create_decorator(decorator_type=Decorators.BAR)
-        >>> class MyClass(WithDecoratedMethods[Decorators]):
-        ...    @foo(42)
-        ...    def m1(self) -> None:
-        ...        print('bar')
-        ...
-        ...    @foo(value=43)
-        ...    def m2(self) -> None:
-        ...        print('bar')
-        ...
-        ...    @bar(value=44)
-        ...    def m3(self) -> None:
-        ...        print('bar')
-        >>> instance = MyClass()
-        >>> instance.get_decorated_functions()
-        {
-            <Decorators.FOO: '_foo'>: {
-                <bound method MyClass.m1 of <__main__.MyClass object at 0x7fea7a6e2610>>: 42,
-                <bound method MyClass.m2 of <__main__.MyClass object at 0x7fea7a6e2610>>: 43,
-            },
-            <Decorators.BAR: '_bar'>: {
-                <bound method MyClass.m3 of <__main__.MyClass object at 0x7fea7a6e2610>>: 44,
-            }
-        }
-    """
-
-    def get_decorated_functions(self) -> dict[E, dict[C, T]]:
-        decorator_types = self.type_var
-        decorated_functions = {t: dict() for t in decorator_types}  # type: ignore
-
-        for attribute_name in dir(self):
-            if attribute_name.startswith('__'):
-                continue
-
-            attribute = getattr(self, attribute_name)
-
-            for decorator_type in decorator_types:  # type: ignore
-                if hasattr(attribute, decorator_type):
-                    decorated_functions[decorator_type][attribute] = getattr(attribute, decorator_type)
-
-        return decorated_functions
-
@@ -132,11 +45,9 @@

Module pedantic.mixins.with_decorated_methods

Functions
-def create_decorator(decorator_type: DecoratorType, transformation: Callable[[~C, DecoratorType, ~T], ~C] = None) ‑> Callable[[~T], Callable[[~C], ~C]] +def create_decorator(decorator_type: DecoratorType,
transformation: Callable[[~C, DecoratorType, ~T], ~C] = None) ‑> Callable[[~T], Callable[[~C], ~C]]
-

Creates a new decorator that is parametrized with one argument of an arbitrary type. -You can also pass an arbitrary [transformation] to add custom behavior to the decorator.

Expand source code @@ -163,6 +74,8 @@

Functions

return decorator
+

Creates a new decorator that is parametrized with one argument of an arbitrary type. +You can also pass an arbitrary [transformation] to add custom behavior to the decorator.

@@ -174,13 +87,6 @@

Classes

(*args, **kwds)
-

The interface that defines all possible decorators types.

-

The values of this enum are used as property names and the properties are added to the decorated functions. -So I would recommend naming them with a leading underscore to keep them private and also write it lowercase.

-

Example

-
>>> class Decorators(DecoratorType):
-...     FOO = '_foo'
-
Expand source code @@ -191,11 +97,19 @@

Example

The values of this enum are used as property names and the properties are added to the decorated functions. So I would recommend naming them with a leading underscore to keep them private and also write it lowercase. + Example: >>> class Decorators(DecoratorType): ... FOO = '_foo' """
+

The interface that defines all possible decorators types.

+

The values of this enum are used as property names and the properties are added to the decorated functions. +So I would recommend naming them with a leading underscore to keep them private and also write it lowercase.

+

Example

+
>>> class Decorators(DecoratorType):
+...     FOO = '_foo'
+

Ancestors

- \ No newline at end of file + diff --git a/docs/pedantic/models/decorated_function.html b/docs/pedantic/models/decorated_function.html index 0325880d..8d427566 100644 --- a/docs/pedantic/models/decorated_function.html +++ b/docs/pedantic/models/decorated_function.html @@ -2,18 +2,32 @@ - - + + pedantic.models.decorated_function API documentation - - - - - - + + + + + + - - + +
@@ -22,140 +36,6 @@

Module pedantic.models.decorated_function

-
- -Expand source code - -
import inspect
-import re
-import types
-from typing import Any, Callable, Dict, Optional
-
-try:
-    from docstring_parser import parse, Docstring
-    IS_DOCSTRING_PARSER_INSTALLED = True
-except ImportError:
-    IS_DOCSTRING_PARSER_INSTALLED = False
-    Docstring = None
-    parse = None
-
-from pedantic.exceptions import PedanticTypeCheckException
-
-FUNCTIONS_THAT_REQUIRE_KWARGS = [
-    '__new__', '__init__', '__str__', '__del__', '__int__', '__float__', '__complex__', '__oct__', '__hex__',
-    '__index__', '__trunc__', '__repr__', '__unicode__', '__hash__', '__nonzero__', '__dir__', '__sizeof__'
-]
-
-
-class DecoratedFunction:
-    def __init__(self, func: Callable[..., Any]) -> None:
-        self._func = func
-
-        if not isinstance(func, (types.FunctionType, types.MethodType)):
-            raise PedanticTypeCheckException(f'{self.full_name} should be a method or function.')
-
-        self._full_arg_spec = inspect.getfullargspec(func)
-        self._signature = inspect.signature(func)
-        self._err = f'In function {func.__qualname__}:\n'
-        self._source: str = inspect.getsource(object=func)
-
-        if IS_DOCSTRING_PARSER_INSTALLED:
-            self._docstring = parse(func.__doc__)
-        else:  # pragma: no cover
-            self._docstring = None
-
-    @property
-    def func(self) -> Callable[..., Any]:
-        return self._func
-
-    @property
-    def annotations(self) -> Dict[str, Any]:
-        return self._full_arg_spec.annotations
-
-    @property
-    def docstring(self) -> Optional[Docstring]:
-        """
-            Returns the docstring if the docstring-parser package is installed else None.
-            See also https://pypi.org/project/docstring-parser/
-        """
-
-        return self._docstring
-
-    @property
-    def raw_doc(self) -> Optional[str]:
-        return self._func.__doc__
-
-    @property
-    def signature(self) -> inspect.Signature:
-        return self._signature
-
-    @property
-    def err(self) -> str:
-        return self._err
-
-    @property
-    def source(self) -> str:
-        return self._source
-
-    @property
-    def name(self) -> str:
-        return self._func.__name__
-
-    @property
-    def full_name(self) -> str:
-        return self._func.__qualname__
-
-    @property
-    def is_static_method(self) -> bool:
-        """ I honestly have no idea how to do this better :( """
-
-        return '@staticmethod' in self.source
-
-    @property
-    def wants_args(self) -> bool:
-        return '*args' in self.source
-
-    @property
-    def is_property_setter(self) -> bool:
-        return f'@{self.name}.setter' in self.source
-
-    @property
-    def should_have_kwargs(self) -> bool:
-        if self.is_property_setter or self.wants_args:
-            return False
-        elif not self.name.startswith('__') or not self.name.endswith('__'):
-            return True
-        return self.name in FUNCTIONS_THAT_REQUIRE_KWARGS
-
-    @property
-    def is_instance_method(self) -> bool:
-        return self._full_arg_spec.args != [] and self._full_arg_spec.args[0] == 'self'
-
-    @property
-    def is_class_method(self) -> bool:
-        """
-            Returns true if the function is decoratorated with the @classmethod decorator.
-            See also: https://stackoverflow.com/questions/19227724/check-if-a-function-uses-classmethod
-        """
-
-        return inspect.ismethod(self._func)
-
-    @property
-    def num_of_decorators(self) -> int:
-        return len(re.findall('@', self.source.split('def')[0]))
-
-    @property
-    def is_pedantic(self) -> bool:
-        return '@pedantic' in self.source or '@require_kwargs' in self.source
-
-    @property
-    def is_coroutine(self) -> bool:
-        return inspect.iscoroutinefunction(self._func)
-
-    @property
-    def is_generator(self) -> bool:
-        return inspect.isgeneratorfunction(self._func)
-
@@ -171,7 +51,6 @@

Classes

(func: Callable[..., Any])
-
Expand source code @@ -180,13 +59,19 @@

Classes

def __init__(self, func: Callable[..., Any]) -> None: self._func = func - if not isinstance(func, (types.FunctionType, types.MethodType)): + if not callable(func): raise PedanticTypeCheckException(f'{self.full_name} should be a method or function.') self._full_arg_spec = inspect.getfullargspec(func) self._signature = inspect.signature(func) - self._err = f'In function {func.__qualname__}:\n' - self._source: str = inspect.getsource(object=func) + self._err = f'In function {self.full_name}:\n' + + try: + source = inspect.getsource(object=func) + except TypeError: + source = None + + self._source: str | None = source if IS_DOCSTRING_PARSER_INSTALLED: self._docstring = parse(func.__doc__) @@ -228,24 +113,39 @@

Classes

@property def name(self) -> str: - return self._func.__name__ + if hasattr(self._func, '__name__'): + return self._func.__name__ + + return self._func.func.__name__ @property def full_name(self) -> str: - return self._func.__qualname__ + if hasattr(self._func, '__qualname__'): + return self._func.__qualname__ + + return self._func.func.__qualname__ @property def is_static_method(self) -> bool: """ I honestly have no idea how to do this better :( """ + if self.source is None: + return False + return '@staticmethod' in self.source @property def wants_args(self) -> bool: + if self.source is None: + return False + return '*args' in self.source @property def is_property_setter(self) -> bool: + if self.source is None: + return False + return f'@{self.name}.setter' in self.source @property @@ -271,10 +171,16 @@

Classes

@property def num_of_decorators(self) -> int: + if self.source is None: + return 0 + return len(re.findall('@', self.source.split('def')[0])) @property def is_pedantic(self) -> bool: + if self.source is None: + return False + return '@pedantic' in self.source or '@require_kwargs' in self.source @property @@ -285,11 +191,11 @@

Classes

def is_generator(self) -> bool: return inspect.isgeneratorfunction(self._func)
+

Instance variables

-
var annotations : Dict[str, Any]
+
prop annotations : Dict[str, Any]
-
Expand source code @@ -298,11 +204,10 @@

Instance variables

def annotations(self) -> Dict[str, Any]: return self._full_arg_spec.annotations
+
-
var docstring : Optional[docstring_parser.common.Docstring]
+
prop docstring : docstring_parser.common.Docstring | None
-

Returns the docstring if the docstring-parser package is installed else None. -See also https://pypi.org/project/docstring-parser/

Expand source code @@ -316,10 +221,11 @@

Instance variables

return self._docstring
+

Returns the docstring if the docstring-parser package is installed else None. +See also https://pypi.org/project/docstring-parser/

-
var err : str
+
prop err : str
-
Expand source code @@ -328,22 +234,25 @@

Instance variables

def err(self) -> str: return self._err
+
-
var full_name : str
+
prop full_name : str
-
Expand source code
@property
 def full_name(self) -> str:
-    return self._func.__qualname__
+ if hasattr(self._func, '__qualname__'): + return self._func.__qualname__ + + return self._func.func.__qualname__
+
-
var func : Callable[..., Any]
+
prop func : Callable[..., Any]
-
Expand source code @@ -352,11 +261,10 @@

Instance variables

def func(self) -> Callable[..., Any]: return self._func
+
-
var is_class_method : bool
+
prop is_class_method : bool
-

Returns true if the function is decoratorated with the @classmethod decorator. -See also: https://stackoverflow.com/questions/19227724/check-if-a-function-uses-classmethod

Expand source code @@ -370,10 +278,11 @@

Instance variables

return inspect.ismethod(self._func)
+

Returns true if the function is decoratorated with the @classmethod decorator. +See also: https://stackoverflow.com/questions/19227724/check-if-a-function-uses-classmethod

-
var is_coroutine : bool
+
prop is_coroutine : bool
-
Expand source code @@ -382,10 +291,10 @@

Instance variables

def is_coroutine(self) -> bool: return inspect.iscoroutinefunction(self._func)
+
-
var is_generator : bool
+
prop is_generator : bool
-
Expand source code @@ -394,10 +303,10 @@

Instance variables

def is_generator(self) -> bool: return inspect.isgeneratorfunction(self._func)
+
-
var is_instance_method : bool
+
prop is_instance_method : bool
-
Expand source code @@ -406,34 +315,40 @@

Instance variables

def is_instance_method(self) -> bool: return self._full_arg_spec.args != [] and self._full_arg_spec.args[0] == 'self'
+
-
var is_pedantic : bool
+
prop is_pedantic : bool
-
Expand source code
@property
 def is_pedantic(self) -> bool:
+    if self.source is None:
+        return False
+
     return '@pedantic' in self.source or '@require_kwargs' in self.source
+
-
var is_property_setter : bool
+
prop is_property_setter : bool
-
Expand source code
@property
 def is_property_setter(self) -> bool:
+    if self.source is None:
+        return False
+
     return f'@{self.name}.setter' in self.source
+
-
var is_static_method : bool
+
prop is_static_method : bool
-

I honestly have no idea how to do this better :(

Expand source code @@ -442,36 +357,45 @@

Instance variables

def is_static_method(self) -> bool: """ I honestly have no idea how to do this better :( """ + if self.source is None: + return False + return '@staticmethod' in self.source
+

I honestly have no idea how to do this better :(

-
var name : str
+
prop name : str
-
Expand source code
@property
 def name(self) -> str:
-    return self._func.__name__
+ if hasattr(self._func, '__name__'): + return self._func.__name__ + + return self._func.func.__name__
+
-
var num_of_decorators : int
+
prop num_of_decorators : int
-
Expand source code
@property
 def num_of_decorators(self) -> int:
+    if self.source is None:
+        return 0
+
     return len(re.findall('@', self.source.split('def')[0]))
+
-
var raw_doc : Optional[str]
+
prop raw_doc : str | None
-
Expand source code @@ -480,10 +404,10 @@

Instance variables

def raw_doc(self) -> Optional[str]: return self._func.__doc__
+
-
var should_have_kwargs : bool
+
prop should_have_kwargs : bool
-
Expand source code @@ -496,10 +420,10 @@

Instance variables

return True return self.name in FUNCTIONS_THAT_REQUIRE_KWARGS
+
-
var signature : inspect.Signature
+
prop signature : inspect.Signature
-
Expand source code @@ -508,10 +432,10 @@

Instance variables

def signature(self) -> inspect.Signature: return self._signature
+
-
var source : str
+
prop source : str
-
Expand source code @@ -520,18 +444,22 @@

Instance variables

def source(self) -> str: return self._source
+
-
var wants_args : bool
+
prop wants_args : bool
-
Expand source code
@property
 def wants_args(self) -> bool:
+    if self.source is None:
+        return False
+
     return '*args' in self.source
+
@@ -539,7 +467,6 @@

Instance variables