diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a92b6ef..0cb0f0a 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,6 +1,5 @@ [bumpversion] -current_version = 2.0.1 +current_version = 2.0.2 files = src/contracts/__init__.py commit = True tag = True - diff --git a/.travis.yml b/.travis.yml index 85c3097..50514d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,10 @@ matrix: - python: 3.3 - python: 3.4 - python: 3.5 + include: + - python: 3.7 + dist: xenial + sudo: true install: - pip install -r requirements.txt diff --git a/requirements.txt b/requirements.txt index 3148426..50f90c6 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 19883a4..33ce604 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 08937e8..d127df9 100644 --- a/src/contracts/__init__.py +++ b/src/contracts/__init__.py @@ -1,17 +1,39 @@ -__version__ = '2.0.1' +__version__ = "2.0.2" import logging -#logging.basicConfig() +# logging.basicConfig() logger = logging.getLogger(__name__) +from typing import Any, Callable, TypeVar, overload -from .interface import (Contract, ContractNotRespected, - CannotDecorateClassmethods, - ContractSyntaxError, ContractException) +from .interface import ( + Contract, + ContractNotRespected, + CannotDecorateClassmethods, + ContractSyntaxError, + ContractException, +) -from .main import (check, fail, check_multiple, contract_decorator, - contracts_decorate as decorate, - parse_flexible_spec as parse) +from .main import ( + check, + fail, + check_multiple, + contract_decorator, + contracts_decorate as decorate, + parse_flexible_spec as parse, +) + +F = TypeVar("F", bound=Callable[..., Any]) + + +@overload +def contract(func: F,) -> F: + ... + + +@overload +def contract(func: F, *arg, **kwargs) -> Callable[[F], F]: + ... # Just make them appear as belonging to the "contracts" Module @@ -19,6 +41,7 @@ def contract(*args, **kwargs): return contract_decorator(*args, **kwargs) + contract.__doc__ = contract_decorator.__doc__ from .main import new_contract as new_contract_main @@ -38,9 +61,10 @@ def new_contract(*args): from .metaclass import ContractsMeta, with_metaclass -ContractsMeta.__module__ = 'contracts' +ContractsMeta.__module__ = "contracts" # And after everything else is loaded, load the utils from .useful_contracts import * + # After everything is loaded, load aliases # from .library import miscellaneous_aliases # @UnusedImport diff --git a/src/contracts/integrations/AttrValidator.py b/src/contracts/integrations/AttrValidator.py new file mode 100644 index 0000000..9673bfa --- /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 0000000..0ff6340 --- /dev/null +++ b/src/contracts/integrations/__init__.py @@ -0,0 +1 @@ +# This space for rent diff --git a/src/contracts/interface.py b/src/contracts/interface.py index 2d28f78..276651e 100644 --- a/src/contracts/interface.py +++ b/src/contracts/interface.py @@ -12,10 +12,10 @@ class Where(object): All parsed elements contain a reference to a :py:class:`Where` object so that we can output pretty error messages. - - + + Character should be >= len(string) (possibly outside the string). - Character_end should be >= character (so that you can splice with + Character_end should be >= character (so that you can splice with string[character:character_end]) """ @@ -38,7 +38,7 @@ def __init__(self, string, character, character_end=None): # or (character <= character_end - 1)): # character += 1 # else: - # break + # break self.line, self.col = line_and_col(character, string) if character_end is not None: @@ -93,7 +93,7 @@ def __str__(self): # mark = 'here or nearby' def format_where(w, context_before=3, mark=None, arrow=True, use_unicode=True, no_mark_arrow_if_longer_than=3): - s = u'' + s = '' if w.filename: s += 'In file %r:\n' % w.filename lines = w.string.split('\n') @@ -108,7 +108,7 @@ def format_where(w, context_before=3, mark=None, arrow=True, for i in range(start, w.line + 1): # suppress empty lines if one_written or lines[i].strip(): - s += (u"%s%s\n" % (pattern % (i + 1), lines[i])) + s += ("%s%s\n".format(pattern % (i + 1), lines[i],)) one_written = True fill = len(pattern % maxi) @@ -119,29 +119,29 @@ def format_where(w, context_before=3, mark=None, arrow=True, space_before = Where(w.string, char0, char0_end) nindent = printable_length_where(space_before) - space = u' ' * fill + u' ' * nindent + space = ' ' * fill + ' ' * nindent if w.col_end is not None: if w.line == w.line_end: num_highlight = printable_length_where(w) - s += space + u'~' * num_highlight + '\n' - space += u' ' * (num_highlight / 2) + s += space + '~' * num_highlight + '\n' + space += ' ' * (num_highlight / 2) else: # cannot highlight if on different lines num_highlight = None pass else: num_highlight = None - # Do not add the arrow and the mark if we have a long underline string + # Do not add the arrow and the mark if we have a long underline string disable_mark_arrow = (num_highlight is not None) and (no_mark_arrow_if_longer_than < num_highlight) if not disable_mark_arrow: if arrow: if use_unicode: - s += space + u'↑\n' + s += space + '↑\n' else: - s += space + u'^\n' - s += space + u'|\n' + s += space + '^\n' + s += space + '|\n' if mark is not None: s += space + mark @@ -173,7 +173,7 @@ def line_and_col(loc, strg): from .utils import check_isinstance check_isinstance(loc, int) check_isinstance(strg, six.string_types) - # first find the line + # first find the line lines = strg.split('\n') if loc == len(strg): @@ -433,8 +433,8 @@ def check_contract(self, context, value, silent): # @UnusedVariable context. This is the function that subclasses must implement. If silent = False, do not bother with creating detailed error messages yet. - This is for performance optimization. - + This is for performance optimization. + :param context: The context in which expressions are evaluated. :type context: """ @@ -548,7 +548,6 @@ def clipped_repr(x, clip): s = "%s%s" % (s[:cut], clip_tag) return s - # TODO: add checks for these functions @@ -620,7 +619,7 @@ def describe_value_multiline(x): try: # This fails for classes final = "{}\n{}".format(desc, x.__repr__()) - except: # XXX + except: # XXX final = "%s\n%s" % (desc, x) return final diff --git a/src/contracts/library/map.py b/src/contracts/library/map.py index bae047d..d045dda 100644 --- a/src/contracts/library/map.py +++ b/src/contracts/library/map.py @@ -1,7 +1,11 @@ from ..interface import Contract, ContractNotRespected from ..syntax import (W, contract_expression, O, S, add_contract, add_keyword, Keyword) -import collections + +try: + import collections.abc as collectionsAbc # python 3.6+ +except ImportError: + import collections as collectionsAbc class Map(Contract): @@ -13,7 +17,7 @@ def __init__(self, length=None, key_c=None, value_c=None, where=None): self.value_c = value_c def check_contract(self, context, value, silent): - if not isinstance(value, collections.Mapping): + if not isinstance(value, collectionsAbc.Mapping): error = 'Expected a Mapping, got %r.' % value.__class__.__name__ raise ContractNotRespected(contract=self, error=error, value=value, context=context) @@ -57,7 +61,7 @@ def parse_action(s, loc, tokens): length_spec = S('[') - contract_expression('length') - S(']') kv_spec = ('(' - O(contract_expression('key')) + ':' - + O(contract_expression('value')) - ')') + +O(contract_expression('value')) - ')') dict_contract = Keyword('map') + O(length_spec) + O(kv_spec) dict_contract.setParseAction(Map.parse_action) diff --git a/src/contracts/library/miscellaneous_aliases.py b/src/contracts/library/miscellaneous_aliases.py index 8d9844d..1717c5c 100644 --- a/src/contracts/library/miscellaneous_aliases.py +++ b/src/contracts/library/miscellaneous_aliases.py @@ -1,12 +1,16 @@ -import collections - +try: + import collections.abc as collectionsAbc # python 3.6+ +except ImportError: + import collections as collectionsAbc def ist(C): + def f(x): f.__name__ = 'isinstance_of_%s' % C.__name__ if not isinstance(x, C): raise ValueError('Value is not an instance of %s.' % C.__name__) + return f @@ -14,33 +18,32 @@ def m_new_contract(name, f): from contracts.library.extensions import CheckCallable from contracts.library.extensions import Extension Extension.registrar[name] = CheckCallable(f) - -m_new_contract('Container', ist(collections.Container)) -# todo: Iterable(x) -m_new_contract('Iterable', ist(collections.Iterable)) - -m_new_contract('Hashable', ist(collections.Hashable)) +m_new_contract('Container', ist(collectionsAbc.Container)) +# todo: Iterable(x) +m_new_contract('Iterable', ist(collectionsAbc.Iterable)) +m_new_contract('Hashable', ist(collectionsAbc.Hashable)) -m_new_contract('Iterator', ist(collections.Iterator)) -m_new_contract('Sized', ist(collections.Sized)) -m_new_contract('Callable', ist(collections.Callable)) -m_new_contract('Sequence', ist(collections.Sequence)) -m_new_contract('Set', ist(collections.Set)) -m_new_contract('MutableSequence', ist(collections.MutableSequence)) -m_new_contract('MutableSet', ist(collections.MutableSet)) -m_new_contract('Mapping', ist(collections.Mapping)) -m_new_contract('MutableMapping', ist(collections.MutableMapping)) -#new_contract('MappingView', ist(collections.MappingView)) -#new_contract('ItemsView', ist(collections.ItemsView)) -#new_contract('ValuesView', ist(collections.ValuesView)) +m_new_contract('Iterator', ist(collectionsAbc.Iterator)) +m_new_contract('Sized', ist(collectionsAbc.Sized)) +m_new_contract('Callable', ist(collectionsAbc.Callable)) +m_new_contract('Sequence', ist(collectionsAbc.Sequence)) +m_new_contract('Set', ist(collectionsAbc.Set)) +m_new_contract('MutableSequence', ist(collectionsAbc.MutableSequence)) +m_new_contract('MutableSet', ist(collectionsAbc.MutableSet)) +m_new_contract('Mapping', ist(collectionsAbc.Mapping)) +m_new_contract('MutableMapping', ist(collectionsAbc.MutableMapping)) +# new_contract('MappingView', ist(collections.MappingView)) +# new_contract('ItemsView', ist(collections.ItemsView)) +# new_contract('ValuesView', ist(collections.ValuesView)) # Not a lambda to have better messages -def is_None(x): +def is_None(x): return x is None + m_new_contract('None', is_None) m_new_contract('NoneType', is_None) diff --git a/src/contracts/library/seq.py b/src/contracts/library/seq.py index 9d8586e..5e46cdc 100644 --- a/src/contracts/library/seq.py +++ b/src/contracts/library/seq.py @@ -1,7 +1,12 @@ from ..interface import Contract, ContractNotRespected from ..syntax import (add_contract, W, contract_expression, O, S, add_keyword, Keyword) -import collections + +try: + import collections.abc as collectionsAbc # python 3.6+ +except ImportError: + import collections as collectionsAbc + from past.builtins import xrange try: @@ -38,7 +43,7 @@ def check_contract(self, context, value, silent): return - if not isinstance(value, collections.Sequence): + if not isinstance(value, collectionsAbc.Sequence): error = 'Expected a Sequence, got %r.' % value.__class__.__name__ raise ContractNotRespected(self, error, value, context) diff --git a/src/contracts/library/sets.py b/src/contracts/library/sets.py index dc37279..26d9d0f 100644 --- a/src/contracts/library/sets.py +++ b/src/contracts/library/sets.py @@ -1,7 +1,10 @@ from ..interface import Contract, ContractNotRespected, describe_type from ..syntax import (Keyword, O, S, W, add_contract, add_keyword, contract_expression) -import collections +try: + import collections.abc as collectionsAbc # python 3.6+ +except ImportError: + import collections as collectionsAbc class ASet(Contract): @@ -13,7 +16,7 @@ def __init__(self, length_contract=None, self.elements_contract = elements_contract def check_contract(self, context, value, silent): - if not isinstance(value, collections.Set): + if not isinstance(value, collectionsAbc.Set): error = 'Expected a set, got %r.' % describe_type(value) raise ContractNotRespected(self, error, value, context) diff --git a/src/contracts/main.py b/src/contracts/main.py index 7722cb3..9393749 100644 --- a/src/contracts/main.py +++ b/src/contracts/main.py @@ -1,4 +1,7 @@ from __future__ import unicode_literals + +from typing import Any, Callable, TypeVar, cast, overload + import sys import types @@ -7,11 +10,18 @@ from .backported import getcallargs, getfullargspec from .docstring_parsing import Arg, DocStringInfo from .enabling import all_disabled -from .inspection import (can_accept_at_least_one_argument, can_accept_self, - can_be_used_as_a_type) -from .interface import (CannotDecorateClassmethods, Contract, - ContractDefinitionError, ContractException, ContractNotRespected, - ContractSyntaxError, MissingContract, Where, describe_value) +from .inspection import can_accept_at_least_one_argument, can_accept_self, can_be_used_as_a_type +from .interface import ( + CannotDecorateClassmethods, + Contract, + ContractDefinitionError, + ContractException, + ContractNotRespected, + ContractSyntaxError, + MissingContract, + Where, + describe_value, +) # from .library import (CheckCallable, Extension, SeparateContext, @@ -46,8 +56,7 @@ def check_contracts(contracts, values, context_variables=None): for var in context_variables: if not (isinstance(var, six.string_types) and len(var) == 1): # XXX: isalpha - msg = ('Invalid name %r for a variable. ' - 'I expect a string of length 1.' % var) + msg = "Invalid name %r for a variable. " "I expect a string of length 1." % var raise ValueError(msg) C = [] @@ -71,7 +80,7 @@ def _cacheable(string, c): """ Returns whether the contract c defined by string string is cacheable. """ # XXX need a more general way of indicating # whether a contract is safely cacheable - return '$' not in string + return "$" not in string def is_param_string(x): @@ -80,11 +89,24 @@ def is_param_string(x): def check_param_is_string(x): if not is_param_string(x): - msg = 'Expected a string, obtained %s' % type(x) + msg = "Expected a string, obtained %s" % type(x) raise ValueError(msg) + # TODO: add decorator-specific exception +F = TypeVar("F", bound=Callable[..., Any]) + + +@overload +def contract_decorator(func: F,) -> F: + ... + + +@overload +def contract_decorator(func: F, *arg, **kwargs) -> Callable[[F], F]: + ... + def contract_decorator(*arg, **kwargs): """ @@ -127,8 +149,7 @@ def my_function(a, b): # Erase the stack raise ContractSyntaxError(es.error, es.where) else: - msg = ('I expect that contracts() is called with ' - 'only keyword arguments (passed: %r)' % arg) + msg = "I expect that contracts() is called with " "only keyword arguments (passed: %r)" % arg raise ContractException(msg) else: # !!! Do not change "tmp_wrap" name; we need it for the definition @@ -146,17 +167,18 @@ def tmp_wrap(f): # do not change name (see above) try: return contracts_decorate(f, **kwargs) except ContractSyntaxError as e: - msg = u"Cannot decorate function %s:" % f.__name__ + msg = "Cannot decorate function %s:".format(f.__name__) from .utils import indent import traceback - msg += u'\n\n' + indent(traceback.format_exc(), u' ') + + msg += "\n\n" + indent(traceback.format_exc(), " ") raise ContractSyntaxError(msg, e.where) # erase the stack except ContractDefinitionError as e: raise e.copy() # raise - return tmp_wrap + return cast(F, tmp_wrap) def contracts_decorate(function_, modify_docstring=True, **kwargs): @@ -194,11 +216,11 @@ def f(cls, a): if kwargs: - returns = kwargs.pop('returns', None) + returns = kwargs.pop("returns", None) for kw in kwargs: if not kw in all_args: - msg = 'Unknown parameter %r; I know %r.' % (kw, all_args) + msg = "Unknown parameter %r; I know %r." % (kw, all_args) raise ContractException(msg) accepts_dict = dict(**kwargs) @@ -208,9 +230,9 @@ def f(cls, a): annotations = get_annotations(function_) if annotations: - if 'return' in annotations: - returns = annotations['return'] - del annotations['return'] + if "return" in annotations: + returns = annotations["return"] + del annotations["return"] else: returns = None @@ -220,23 +242,22 @@ def f(cls, a): if function_.__doc__ is None: # XXX: change name raise ContractException( - 'You did not specify a contract, nor I can ' - 'find a docstring for %r.' % function_) + "You did not specify a contract, nor I can " "find a docstring for %r." % function_ + ) accepts_dict, returns = parse_contracts_from_docstring(function_) if not accepts_dict and not returns: - raise ContractException('No contract specified in docstring.') + raise ContractException("No contract specified in docstring.") if returns is None: returns_parsed = None else: returns_parsed = parse_flexible_spec(returns) - accepts_parsed = dict([(x, parse_flexible_spec(accepts_dict[x])) - for x in accepts_dict]) + accepts_parsed = dict([(x, parse_flexible_spec(accepts_dict[x])) for x in accepts_dict]) - is_bound_method = 'self' in all_args + is_bound_method = "self" in all_args def contracts_checker(unused, *args, **kwargs): do_checks = not all_disabled() @@ -244,10 +265,10 @@ def contracts_checker(unused, *args, **kwargs): return function_(*args, **kwargs) def get_nice_function_display(): - nice_function_display = '%s()' % function_.__name__ + nice_function_display = "%s()" % function_.__name__ if is_bound_method: klass = type(args[0]).__name__ - nice_function_display = klass + ':' + nice_function_display + nice_function_display = klass + ":" + nice_function_display return nice_function_display bound = getcallargs(function_, *args, **kwargs) @@ -255,15 +276,14 @@ def get_nice_function_display(): context = {} # add self if we are a bound method if is_bound_method: - context['self'] = args[0] + context["self"] = args[0] for arg in all_args: if arg in accepts_parsed: try: accepts_parsed[arg]._check_contract(context, bound[arg], silent=False) except ContractNotRespected as e: - msg = ('Breach for argument %r to %s.\n' - % (arg, get_nice_function_display())) + msg = "Breach for argument %r to %s.\n" % (arg, get_nice_function_display()) e.error = msg + e.error raise e @@ -273,8 +293,7 @@ def get_nice_function_display(): try: returns_parsed._check_contract(context, result, silent=False) except ContractNotRespected as e: - msg = ('Breach for return value of %s.\n' - % (get_nice_function_display())) + msg = "Breach for return value of %s.\n" % (get_nice_function_display()) e.error = msg + e.error raise e @@ -285,7 +304,7 @@ def get_nice_function_display(): if modify_docstring: def write_contract_as_rst(c): - return '``%s``' % c + return "``%s``" % c if function_.__doc__ is not None: docs = DocStringInfo.parse(function_.__doc__) @@ -294,11 +313,10 @@ def write_contract_as_rst(c): for param in accepts_parsed: if not param in docs.params: # default = '*not documented*' - default = '' + default = "" docs.params[param] = Arg(default, None) - docs.params[param].type = \ - write_contract_as_rst(accepts_parsed[param]) + docs.params[param].type = write_contract_as_rst(accepts_parsed[param]) if returns_parsed is not None: if not docs.returns: @@ -310,9 +328,9 @@ def write_contract_as_rst(c): new_docs = function_.__doc__ # XXX: why doesn't this work? - name = ('checker-for-%s' % function_.__name__) + name = "checker-for-%s" % function_.__name__ if six.PY2: - name = name.encode('utf-8') + name = name.encode("utf-8") contracts_checker.__name__ = name contracts_checker.__module__ = function_.__module__ @@ -338,9 +356,10 @@ def parse_flexible_spec(spec): return parse_contract_string(spec) elif can_be_used_as_a_type(spec): from .library import CheckType + return CheckType(spec) else: - msg = 'I want either a string or a type, not %s.' % describe_value(spec) + msg = "I want either a string or a type, not %s." % describe_value(spec) raise ContractException(msg) @@ -348,16 +367,16 @@ def parse_contracts_from_docstring(function): annotations = DocStringInfo.parse(function.__doc__) if len(annotations.returns) > 1: - raise ContractException('More than one return type specified.') + raise ContractException("More than one return type specified.") def remove_quotes(x): """ Removes the double back-tick quotes if present. """ if x is None: return None - if x.startswith('``') and x.endswith('``') and len(x) > 3: + if x.startswith("``") and x.endswith("``") and len(x) > 3: return x[2:-2] - elif x.startswith('``') or x.endswith('``'): - msg = 'Malformed quoting in string %r.' % x + elif x.startswith("``") or x.endswith("``"): + msg = "Malformed quoting in string %r." % x raise ContractException(msg) else: return x @@ -369,14 +388,12 @@ def remove_quotes(x): # These are the annotations params = annotations.params - name2type = dict([(name, remove_quotes(params[name].type)) - for name in params]) + name2type = dict([(name, remove_quotes(params[name].type)) for name in params]) # Check the ones that do not have contracts specified nullparams = [name for name in params if params[name].type is None] if nullparams: - msg = ('The parameter(s) %r in this docstring have no type statement.' - % (",".join(nullparams))) + msg = "The parameter(s) %r in this docstring have no type statement." % (",".join(nullparams)) msg += """ Note: you can use the asterisk if you do not care about assigning @@ -393,9 +410,10 @@ def remove_quotes(x): # Check we don't have extra: for name in name2type: if not name in all_args: - msg = ('A contract was specified for argument %r which I cannot' - ' find in my list of arguments (%r)' % - (name, all_args)) + msg = ( + "A contract was specified for argument %r which I cannot" + " find in my list of arguments (%r)" % (name, all_args) + ) raise ContractException(msg) if len(name2type) != len(all_args): # pragma: no cover @@ -439,13 +457,14 @@ def check(contract, object, desc=None, **context): # @ReservedAssignment if not is_param_string(contract): # XXX: make it more liberal? - raise ValueError('I expect a string (contract spec) as the first ' - 'argument, not a %s.' % describe_value(contract)) + raise ValueError( + "I expect a string (contract spec) as the first " "argument, not a %s." % describe_value(contract) + ) try: return check_contracts([contract], [object], context) except ContractNotRespected as e: if desc is not None: - e.error = '%s\n%s' % (desc, e.error) + e.error = "%s\n%s" % (desc, e.error) raise e @@ -461,10 +480,10 @@ def fail(contract, value, **initial_context): except ContractNotRespected: pass else: - msg = 'I did not expect that this value would satisfy this contract.\n' - msg += '- value: %s\n' % describe_value(value) - msg += '- contract: %s\n' % parsed_contract - msg += '- context: %r' % context + msg = "I did not expect that this value would satisfy this contract.\n" + msg += "- value: %s\n" % describe_value(value) + msg += "- contract: %s\n" % parsed_contract + msg += "- context: %r" % context raise ValueError(msg) @@ -483,15 +502,14 @@ def check_multiple(couples, desc=None): :type desc: ``None|str`` """ - check('list[>0](tuple(str, *))', couples, - 'I expect a non-empty list of (object, string) tuples.') + check("list[>0](tuple(str, *))", couples, "I expect a non-empty list of (object, string) tuples.") contracts = [x[0] for x in couples] values = [x[1] for x in couples] try: return check_contracts(contracts, values) except ContractNotRespected as e: if desc is not None: - e.error = '%s\n%s' % (desc, e.error) + e.error = "%s\n%s" % (desc, e.error) raise e @@ -562,12 +580,11 @@ def new_contract_impl(identifier, condition): from .syntax import ParseException from .library.extensions import CheckCallableWithSelf - from .library import (CheckCallable, Extension, SeparateContext, - identifier_expression) + from .library import CheckCallable, Extension, SeparateContext, identifier_expression # Be friendly if not isinstance(identifier, six.string_types): - msg = 'I expect the identifier to be a string; received %s.' % describe_value(identifier) + msg = "I expect the identifier to be a string; received %s." % describe_value(identifier) raise ValueError(msg) # Make sure it is not already an expression that we know. @@ -582,9 +599,10 @@ def new_contract_impl(identifier, condition): # check it does not redefine list, tuple, etc. try: c = parse_contract_string(identifier) - msg = ('Invalid identifier %r; it overwrites an already known ' - 'expression. In fact, I can parse it as %s (%r).' % - (identifier, c, c)) + msg = ( + "Invalid identifier %r; it overwrites an already known " + "expression. In fact, I can parse it as %s (%r)." % (identifier, c, c) + ) raise ValueError(msg) except ContractSyntaxError: pass @@ -596,11 +614,12 @@ def new_contract_impl(identifier, condition): loc = e.loc if loc >= len(identifier): loc -= 1 - where = Where(identifier, character=loc) #line=e.lineno, column=e.col) + where = Where(identifier, character=loc) # line=e.lineno, column=e.col) # msg = 'Error in parsing string: %s' % e - msg = ('The given identifier %r does not correspond to my idea ' - 'of what an identifier should look like;\n%s\n%s' - % (identifier, e, where)) + msg = ( + "The given identifier %r does not correspond to my idea " + "of what an identifier should look like;\n%s\n%s" % (identifier, e, where) + ) raise ValueError(msg) # Now let's check the condition @@ -610,26 +629,27 @@ def new_contract_impl(identifier, condition): # could call parse_flexible_spec as well here bare_contract = parse_contract_string(condition) except ContractSyntaxError as e: - msg = ('The given condition %r does not parse cleanly: %s' % - (condition, e)) + msg = "The given condition %r does not parse cleanly: %s" % (condition, e) raise ValueError(msg) # Important: types are callable, so check this first. elif can_be_used_as_a_type(condition): # parse_flexible_spec can take care of types bare_contract = parse_flexible_spec(condition) # Lastly, it should be a callable - elif hasattr(condition, '__call__'): + elif hasattr(condition, "__call__"): # Check that the signature is right if can_accept_self(condition): bare_contract = CheckCallableWithSelf(condition) elif can_accept_at_least_one_argument(condition): bare_contract = CheckCallable(condition) else: - raise ValueError("The given callable %r should be able to accept " - "at least one argument" % condition) + raise ValueError( + "The given callable %r should be able to accept " "at least one argument" % condition + ) else: - raise ValueError('I need either a string or a callable for the ' - 'condition; found %s.' % describe_value(condition)) + raise ValueError( + "I need either a string or a callable for the " "condition; found %s." % describe_value(condition) + ) # Separate the context if needed if isinstance(bare_contract, (CheckCallable, CheckCallableWithSelf)): @@ -641,10 +661,9 @@ def new_contract_impl(identifier, condition): if identifier in Extension.registrar: old = Extension.registrar[identifier] if not (contract == old): - msg = ('Tried to redefine %r with a definition that looks ' - 'different to me.\n' % identifier) - msg += ' - old: %r\n' % old - msg += ' - new: %r\n' % contract + msg = "Tried to redefine %r with a definition that looks " "different to me.\n" % identifier + msg += " - old: %r\n" % old + msg += " - new: %r\n" % contract raise ValueError(msg) else: Extension.registrar[identifier] = contract @@ -656,10 +675,9 @@ def new_contract_impl(identifier, condition): try: c = parse_contract_string(identifier) expected = Extension(identifier) - assert c == expected, \ - 'Expected %r, got %r.' % (c, expected) # pragma: no cover + assert c == expected, "Expected %r, got %r." % (c, expected) # pragma: no cover except ContractSyntaxError: # pragma: no cover - #assert False, 'Cannot parse %r: %s' % (identifier, e) + # assert False, 'Cannot parse %r: %s' % (identifier, e) raise return contract @@ -667,5 +685,5 @@ def new_contract_impl(identifier, condition): def parse_contract_string(string): from .main_actual import parse_contract_string_actual - return parse_contract_string_actual(string) + return parse_contract_string_actual(string) diff --git a/src/contracts/testing/test_unicode_literals.py b/src/contracts/testing/test_unicode_literals.py index 30b2186..a0896e4 100644 --- a/src/contracts/testing/test_unicode_literals.py +++ b/src/contracts/testing/test_unicode_literals.py @@ -7,10 +7,11 @@ import unittest + class TestParsingNumbers(unittest.TestCase): def test_unicode_literal(self): - r = parse_contract_string(u'int') + r = parse_contract_string('int') print(r) def test_unicode_literal2(self): @@ -19,5 +20,4 @@ def test_unicode_literal2(self): def f(x): pass - f('') diff --git a/src/contracts/utils.py b/src/contracts/utils.py index df1ffa1..22b7157 100644 --- a/src/contracts/utils.py +++ b/src/contracts/utils.py @@ -19,17 +19,17 @@ def indent(s, prefix, first=None): if not isinstance(s, six.string_types): - s = u'{}'.format(s) + s = '{}'.format(s) assert isinstance(prefix, six.string_types) try: lines = s.split('\n') except UnicodeDecodeError: - print(type(s)) # XXX - print(s) # XXX + print(type(s)) # XXX + print(s) # XXX lines = [s] if not lines: - return u'' + return '' if first is None: first = prefix @@ -40,8 +40,8 @@ def indent(s, prefix, first=None): first = ' ' * (m - len(first)) + first # differnet first prefix - res = [u'%s%s' % (prefix, line.rstrip()) for line in lines] - res[0] = u'%s%s' % (first, lines[0].rstrip()) + res = ['%s%s'.format(prefix, line.rstrip()) for line in lines] + res[0] = '%s%s'.format(first, lines[0].rstrip()) return '\n'.join(res) @@ -117,7 +117,7 @@ def _get_str(x, informal): def format_list_long(l, informal=False): """ - - My + - My first - Second """ @@ -163,9 +163,9 @@ def raise_wrapped(etype, e, msg, compact=False, **kwargs): """ Raises an exception of type etype by wrapping another exception "e" with its backtrace and adding the objects in kwargs as formatted by format_obs. - + if compact = False, write the whole traceback, otherwise just str(e). - + exc = output of sys.exc_info() """ @@ -202,6 +202,7 @@ def raise_wrapped_make(etype, e, msg, compact=False, **kwargs): return etype(s) + def _format_exc(msg, **kwargs): check_isinstance(msg, six.text_type) s = msg @@ -212,7 +213,7 @@ def _format_exc(msg, **kwargs): def raise_desc(etype, msg, args_first=False, **kwargs): """ - + Example: raise_desc(ValueError, "I don't know", a=a, b=b) """ @@ -230,7 +231,6 @@ def raise_desc(etype, msg, args_first=False, **kwargs): raise etype(s) - # # # @@ -269,9 +269,9 @@ def raise_desc(etype, msg, args_first=False, **kwargs): # n = n+1 # return list - # from decorator import decorator # @UnresolvedImport + def ignore_typeerror(f): """ Recasts TypeError as Exception; otherwise pyparsing gets confused. """