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)

Classes

class DecoratedFunction (func: Callable[..., Any])
Expand source code
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)

Instance variables

var annotations : Dict[str, Any]
Expand source code
@property
def annotations(self) -> Dict[str, Any]:
    return self._full_arg_spec.annotations
var docstring : Optional[docstring_parser.common.Docstring]

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

Expand source code
@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
var err : str
Expand source code
@property
def err(self) -> str:
    return self._err
var full_name : str
Expand source code
@property
def full_name(self) -> str:
    return self._func.__qualname__
var func : Callable[..., Any]
Expand source code
@property
def func(self) -> Callable[..., Any]:
    return self._func
var 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
@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)
var is_coroutine : bool
Expand source code
@property
def is_coroutine(self) -> bool:
    return inspect.iscoroutinefunction(self._func)
var is_generator : bool
Expand source code
@property
def is_generator(self) -> bool:
    return inspect.isgeneratorfunction(self._func)
var is_instance_method : bool
Expand source code
@property
def is_instance_method(self) -> bool:
    return self._full_arg_spec.args != [] and self._full_arg_spec.args[0] == 'self'
var is_pedantic : bool
Expand source code
@property
def is_pedantic(self) -> bool:
    return '@pedantic' in self.source or '@require_kwargs' in self.source
var is_property_setter : bool
Expand source code
@property
def is_property_setter(self) -> bool:
    return f'@{self.name}.setter' in self.source
var is_static_method : bool

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

Expand source code
@property
def is_static_method(self) -> bool:
    """ I honestly have no idea how to do this better :( """

    return '@staticmethod' in self.source
var name : str
Expand source code
@property
def name(self) -> str:
    return self._func.__name__
var num_of_decorators : int
Expand source code
@property
def num_of_decorators(self) -> int:
    return len(re.findall('@', self.source.split('def')[0]))
var raw_doc : Optional[str]
Expand source code
@property
def raw_doc(self) -> Optional[str]:
    return self._func.__doc__
var should_have_kwargs : bool
Expand source code
@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
var signature : inspect.Signature
Expand source code
@property
def signature(self) -> inspect.Signature:
    return self._signature
var source : str
Expand source code
@property
def source(self) -> str:
    return self._source
var wants_args : bool
Expand source code
@property
def wants_args(self) -> bool:
    return '*args' in self.source