diff --git a/requirements.txt b/requirements.txt index 3148426d..50f90c6e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ pyparsing==2.2.2 decorator==4.0.10 six==1.10.0 +backports.functools-lru-cache==1.5 +qualname==0.1.0 future numpy<1.16 diff --git a/setup.py b/setup.py index 19883a46..33ce604b 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ import os - from setuptools import setup, find_packages description = ( @@ -65,7 +64,7 @@ def get_version(filename): package_dir={'': 'src'}, packages=find_packages('src'), - install_requires=['pyparsing', 'decorator', 'six', 'future'], + install_requires=['pyparsing', 'decorator', 'six', 'future', 'backports.functools-lru-cache', 'qualname', ], tests_require=['nose'], entry_points={}, ) diff --git a/src/contracts/__init__.py b/src/contracts/__init__.py index eea09362..813e7a76 100644 --- a/src/contracts/__init__.py +++ b/src/contracts/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.8.12' +__version__ = '1.8.13' import logging diff --git a/src/contracts/integrations/AttrValidator.py b/src/contracts/integrations/AttrValidator.py new file mode 100644 index 00000000..9673bfaa --- /dev/null +++ b/src/contracts/integrations/AttrValidator.py @@ -0,0 +1,58 @@ +import functools +try: + # python 3.5+ + from functools import lru_cache +except ImportError: + # python 2 + from backports.functools_lru_cache import lru_cache + +try: + # python 3.3+ + + object.__qualname__ + + import operator + qualname = operator.attrgetter("__qualname__") +except AttributeError: + # python 2 + from qualname import qualname + +import attr # attr is only a dependency of pycontracts if this module is imported. Otherwise, it does not depend on it. +from contracts.interface import ContractNotRespected +from contracts.main import new_contract + + +@attr.s(repr=False, slots=True, hash=True) +class AttrValidator(object): + ''' + Validator linking C{pycontracts} with C{attr}'s validators + ''' + + def __call__(self, inst, attr, value): + """ + Executes the validator by checking it against the contract it was configured with. + """ + try: + AttrValidator.__get_contract(inst.__class__, attr.name, self.contract).check( + value, + ) + except ContractNotRespected as e: + # adding formatted debug information to the error + e.error = "{!r}.{}\n{}".format(inst, attr.name, e.error) + raise e + + def __repr__(self): + return ( + "" + .format(contract=self.contract) + ) + + @staticmethod + @lru_cache(typed=True) + def __get_contract(inst_class, name, contract): + # compiling the contract takes time, we want to cache active contracts as they come up + return new_contract("{}___{}".format(qualname(inst_class).replace('.', '___'), name), contract,) + + contract = attr.ib() + """ Contract honoured by the given validator""" + diff --git a/src/contracts/integrations/__init__.py b/src/contracts/integrations/__init__.py new file mode 100644 index 00000000..0ff63401 --- /dev/null +++ b/src/contracts/integrations/__init__.py @@ -0,0 +1 @@ +# This space for rent