From 7bdef9e701e29daad21a46759fe34537ef37ec25 Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Fri, 14 Oct 2011 11:46:07 +0200 Subject: [PATCH 01/20] Nicer docstrings --- contract.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/contract.py b/contract.py index c7a4d15..76cdb21 100644 --- a/contract.py +++ b/contract.py @@ -865,7 +865,40 @@ def decor(*args, **kwargs): except ContractValidationError as err: raise GuardValidationError(unicode(err)) return fn(*args, **kwargs) - decor.__doc__ = "guarded with %r\n\n" % contract + (decor.__doc__ or "") + + doc_contract = repr(contract) + + # find the first '(' and last ')' and strip anything around it + doc_contract = doc_contract[doc_contract.index("(")+1:] + doc_contract = doc_contract[:-1 - doc_contract[::-1].index(")")] + + # iter over the characters building a list of params + brackets_open = 0 + current_token = " - " + params = [] + + for ch in doc_contract: + + if not (current_token.strip() == "" and ch == " "): + current_token += ch + + if ch == '(' or ch == ',': + if brackets_open == 0 and '=' in current_token: + current_token = " - " + current_token[3:] + + params.append(current_token) + + if ch == '(': + brackets_open += 1 + + current_token = " " * (brackets_open+1) + + if ch == ')': + brackets_open -= 1 + + doc_contract = "\n".join(params) + + decor.__doc__ = "guarded with \n%s\n\n" % doc_contract + (decor.__doc__ or "") return decor return wrapper From e0209358c9fd7925b72d335873e90604a2c7fdb9 Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Fri, 14 Oct 2011 14:30:49 +0200 Subject: [PATCH 02/20] Better rst docstrings and isodate parsing --- contract.py | 73 +++++++++++++++++++++++++++++++++++------------------ setup.py | 3 +++ 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/contract.py b/contract.py index 76cdb21..8a7b956 100644 --- a/contract.py +++ b/contract.py @@ -3,6 +3,7 @@ import functools import inspect import re +import dateutil.parser """ Contract is tiny library for data validation @@ -115,7 +116,7 @@ def check(self, value): self._failure("value is not %s" % self.type_.__name__) def __repr__(self): - return "" % self.type_.__name__ + return "" % self.type_.__name__ class AnyC(Contract): @@ -130,7 +131,7 @@ def check(self, value): pass def __repr__(self): - return "" + return "" class OrCMeta(ContractMeta): @@ -184,7 +185,7 @@ def __or__(self, contract): return self def __repr__(self): - return "" % (", ".join(map(repr, self.contracts))) + return "either (%s)" % (", ".join(map(repr, self.contracts))) class NullC(Contract): @@ -204,7 +205,7 @@ def check(self, value): self._failure("value should be None") def __repr__(self): - return "" + return "None" class BoolC(Contract): @@ -225,7 +226,7 @@ def check(self, value): self._failure("value should be True or False") def __repr__(self): - return "" + return "" class NumberCMeta(ContractMeta): @@ -324,8 +325,11 @@ def __lt__(self, lt): def __gt__(self, gt): return type(self)(gte=self.gte, lte=self.lte, gt=gt, lt=self.lt) + def __reprname__(self): + return type(self).__name__.lower()[:-1] + def __repr__(self): - r = "<%s" % type(self).__name__ + r = "<%s" % self.__reprname__() options = [] for param in ("gte", "lte", "gt", "lt"): if getattr(self, param) is not None: @@ -380,7 +384,7 @@ def check(self, value): self._failure("blank value is not allowed") def __repr__(self): - return "" if self.allow_blank else "" + return "" if self.allow_blank else "" class EmailC(Contract): @@ -415,7 +419,22 @@ def check(self, value): self._failure('value is not email') def __repr__(self): - return "" + return "" + +class IsoDateC(Contract): + def _rant(self, value): + self._failure("value is not an iso formatted date: "+value) + + def check(self, value): + if not value: + self._rant(value) + try: + dateutil.parser.parse(value) + except: + self._rant(value) + + def __repr__(self): + return "" class SquareBracketsMeta(ContractMeta): @@ -507,7 +526,7 @@ def check(self, value): raise ContractValidationError(err.msg, name) def __repr__(self): - r = " %r)>" % (self.keyC, self.valueC) + return "<%r => %r>" % (self.keyC, self.valueC) class EnumC(Contract): @@ -692,7 +711,7 @@ def check(self, value): self._failure("value doesn't match any variant") def __repr__(self): - return "" % (", ".join(map(repr, self.variants))) + return "one of (%s)" % (", ".join(map(repr, self.variants))) class CallableC(Contract): @@ -710,7 +729,7 @@ def check(self, value): self._failure("value is not callable") def __repr__(self): - return "" + return "" class CallC(Contract): @@ -745,7 +764,7 @@ def check(self, value): self._failure(error) def __repr__(self): - return "" % self.fn.__name__ + return "" % self.fn.__name__ class ForwardC(Contract): @@ -874,31 +893,37 @@ def decor(*args, **kwargs): # iter over the characters building a list of params brackets_open = 0 - current_token = " - " + current_token = "| " params = [] for ch in doc_contract: - if not (current_token.strip() == "" and ch == " "): + if not (current_token.strip('| ') == "" and ch == " "): current_token += ch if ch == '(' or ch == ',': if brackets_open == 0 and '=' in current_token: - current_token = " - " + current_token[3:] + current_token = "| " + current_token[8:] + current_token = current_token.replace("=", ":\n| ") params.append(current_token) if ch == '(': brackets_open += 1 - current_token = " " * (brackets_open+1) + current_token = '|' + (" " * (brackets_open+2)) if ch == ')': brackets_open -= 1 + if '=' in current_token: + current_token = "| " + current_token[8:] + current_token = current_token.replace("=", ":\n| ") + params.append(current_token) + doc_contract = "\n".join(params) - decor.__doc__ = "guarded with \n%s\n\n" % doc_contract + (decor.__doc__ or "") + decor.__doc__ = "Guarded with::\n\n%s\n" % doc_contract + (decor.__doc__ or "") return decor return wrapper @@ -913,7 +938,7 @@ def check(self, value): self._failure("value is not a number") def __repr__(self): - return '' + return '' if __name__ == "__main__": diff --git a/setup.py b/setup.py index d7164e6..107565a 100755 --- a/setup.py +++ b/setup.py @@ -15,4 +15,7 @@ author='barbuza', author_email='', py_modules=['contract'], + install_requires=[ + 'python-dateutil', + ] ) From ed08c94fe0f97cdf5b91d262b41929321113a3cc Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Fri, 14 Oct 2011 14:53:32 +0200 Subject: [PATCH 03/20] Better rst docstrings and isodate parsing --- contract.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contract.py b/contract.py index 8a7b956..c14a5b2 100644 --- a/contract.py +++ b/contract.py @@ -3,7 +3,7 @@ import functools import inspect import re -import dateutil.parser +import datetime """ Contract is tiny library for data validation @@ -423,13 +423,13 @@ def __repr__(self): class IsoDateC(Contract): def _rant(self, value): - self._failure("value is not an iso formatted date: "+value) + self._failure("value is not an iso formatted (yyyy-mm-dd) date: "+value) def check(self, value): if not value: self._rant(value) try: - dateutil.parser.parse(value) + datetime.datetime(*[int(v) for v in value.split("-")]) except: self._rant(value) From 8aef7cb5639d04c4545dfa755fb363cef11a04a1 Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Fri, 14 Oct 2011 14:54:08 +0200 Subject: [PATCH 04/20] Forgot to update setup.py --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index 107565a..d7164e6 100755 --- a/setup.py +++ b/setup.py @@ -15,7 +15,4 @@ author='barbuza', author_email='', py_modules=['contract'], - install_requires=[ - 'python-dateutil', - ] ) From c2cce5eba2046cda7724665d5d1c77faf63b252d Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Fri, 14 Oct 2011 14:56:24 +0200 Subject: [PATCH 05/20] New version. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d7164e6..d286935 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ name='contractplus', description='contract forked from https://github.com/barbuza/contract', license='none', - version='1.0', + version='1.1', author='barbuza', author_email='', py_modules=['contract'], From d65c3b3c744298877cadc64dd3fb9e1601ba17da Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Wed, 19 Oct 2011 09:38:53 +0200 Subject: [PATCH 06/20] Fixed bug if isodate value was None --- contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract.py b/contract.py index c14a5b2..e9b7bd4 100644 --- a/contract.py +++ b/contract.py @@ -423,7 +423,7 @@ def __repr__(self): class IsoDateC(Contract): def _rant(self, value): - self._failure("value is not an iso formatted (yyyy-mm-dd) date: "+value) + self._failure("value is not an iso formatted (yyyy-mm-dd) date: %r" % value) def check(self, value): if not value: From 9a5457882f03d3e139bd7d12516191c67295d720 Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Wed, 19 Oct 2011 09:40:28 +0200 Subject: [PATCH 07/20] New version with bugfixes --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d286935..8377292 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ name='contractplus', description='contract forked from https://github.com/barbuza/contract', license='none', - version='1.1', + version='1.1.1', author='barbuza', author_email='', py_modules=['contract'], From 5db73b74c57c61fecc62f82fec3414991dd86121 Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Wed, 19 Oct 2011 09:45:45 +0200 Subject: [PATCH 08/20] New version with bugfixes --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8377292..dc0547e 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ name='contractplus', description='contract forked from https://github.com/barbuza/contract', license='none', - version='1.1.1', + version='1.2', author='barbuza', author_email='', py_modules=['contract'], From 3b3e500a4dc95014e5dd806b3c967b8fc0b65852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Wed, 19 Oct 2011 14:02:16 +0200 Subject: [PATCH 09/20] improved auto documentation --- contract.py | 87 +++++++++++++++++++---------------------------------- 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/contract.py b/contract.py index e9b7bd4..de2e696 100644 --- a/contract.py +++ b/contract.py @@ -131,7 +131,7 @@ def check(self, value): pass def __repr__(self): - return "" + return "Any" class OrCMeta(ContractMeta): @@ -185,7 +185,8 @@ def __or__(self, contract): return self def __repr__(self): - return "either (%s)" % (", ".join(map(repr, self.contracts))) + #return "\n\n\t- %s\n" % ("\n\t- ".join(map(repr, self.contracts))) + return "%s" % (" or ".join(map(repr, self.contracts))) class NullC(Contract): @@ -205,7 +206,7 @@ def check(self, value): self._failure("value should be None") def __repr__(self): - return "None" + return "Empty" class BoolC(Contract): @@ -226,7 +227,7 @@ def check(self, value): self._failure("value should be True or False") def __repr__(self): - return "" + return "Boolean" class NumberCMeta(ContractMeta): @@ -384,7 +385,7 @@ def check(self, value): self._failure("blank value is not allowed") def __repr__(self): - return "" if self.allow_blank else "" + return "String (could be blank)" if self.allow_blank else "String" class EmailC(Contract): @@ -419,7 +420,7 @@ def check(self, value): self._failure('value is not email') def __repr__(self): - return "" + return "String with email format" class IsoDateC(Contract): def _rant(self, value): @@ -434,7 +435,7 @@ def check(self, value): self._rant(value) def __repr__(self): - return "" + return "ISODate YYYY-MM-DD" class SquareBracketsMeta(ContractMeta): @@ -526,17 +527,8 @@ def check(self, value): raise ContractValidationError(err.msg, name) def __repr__(self): - r = "[(" - options = [] - if self.min_length: - options.append("min_length=%s" % self.min_length) - if self.max_length: - options.append("max_length=%s" % self.max_length) - r += ", ".join(options) - if options: - r += " :- " - r += repr(self.contract) - r += ")]" + r = "%s with minimal length of %s and maximum length %s\n" % ( + self.contract, self.min_length, self.max_length) return r @@ -711,7 +703,7 @@ def check(self, value): self._failure("value doesn't match any variant") def __repr__(self): - return "one of (%s)" % (", ".join(map(repr, self.variants))) + return "%s" % (" or ".join(map(repr, self.variants))) class CallableC(Contract): @@ -816,6 +808,15 @@ class GuardValidationError(ContractValidationError): pass +def get_array_from_contract(doc_contract): + contracts = {} + for attribute in doc_contract.split(','): + (key, value) = attribute.split('=') + key = key.strip() + contracts.update({key: value}) + return contracts + + def guard(contract=None, **kwargs): """ Decorator for protecting function with contracts @@ -877,7 +878,7 @@ def decor(*args, **kwargs): try: call_args = dict(zip(fnargs, checkargs) + kwargs.items()) for name, default in zip(reversed(fnargs), - argspec.defaults or ()): + argspec.defaults or ()): if name not in call_args: call_args[name] = default contract.check(call_args) @@ -887,43 +888,17 @@ def decor(*args, **kwargs): doc_contract = repr(contract) - # find the first '(' and last ')' and strip anything around it - doc_contract = doc_contract[doc_contract.index("(")+1:] - doc_contract = doc_contract[:-1 - doc_contract[::-1].index(")")] - - # iter over the characters building a list of params - brackets_open = 0 - current_token = "| " - params = [] - - for ch in doc_contract: - - if not (current_token.strip('| ') == "" and ch == " "): - current_token += ch - - if ch == '(' or ch == ',': - if brackets_open == 0 and '=' in current_token: - current_token = "| " + current_token[8:] - current_token = current_token.replace("=", ":\n| ") - - params.append(current_token) - - if ch == '(': - brackets_open += 1 - - current_token = '|' + (" " * (brackets_open+2)) - - if ch == ')': - brackets_open -= 1 - - if '=' in current_token: - current_token = "| " + current_token[8:] - current_token = current_token.replace("=", ":\n| ") - params.append(current_token) + # find the first ( and strip anything around it + garbage_index = doc_contract.index("(") + 1 + doc_contract = doc_contract[garbage_index:-garbage_index] + guarded_with = get_array_from_contract(doc_contract) - doc_contract = "\n".join(params) + old_documentation = re.sub('^( ){8}', '', decor.__doc__, flags=re.M) + decor.__doc__ = "Guarded with:\n\n" + for param in guarded_with: + decor.__doc__ += '- ``%s``: %s\n' % (param, guarded_with[param]) + decor.__doc__ += old_documentation - decor.__doc__ = "Guarded with::\n\n%s\n" % doc_contract + (decor.__doc__ or "") return decor return wrapper @@ -938,7 +913,7 @@ def check(self, value): self._failure("value is not a number") def __repr__(self): - return '' + return 'Digits' if __name__ == "__main__": From b9497a0461c46682ca3551dd7601b63ed9ce243a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Wed, 19 Oct 2011 16:27:35 +0200 Subject: [PATCH 10/20] compatible with python 2.6 --- contract.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contract.py b/contract.py index de2e696..9af7cca 100644 --- a/contract.py +++ b/contract.py @@ -893,7 +893,8 @@ def decor(*args, **kwargs): doc_contract = doc_contract[garbage_index:-garbage_index] guarded_with = get_array_from_contract(doc_contract) - old_documentation = re.sub('^( ){8}', '', decor.__doc__, flags=re.M) + pattern = re.compile('^( ){8}', flags=re.MULTILINE) + old_documentation = pattern.sub('', decor.__doc__) decor.__doc__ = "Guarded with:\n\n" for param in guarded_with: decor.__doc__ += '- ``%s``: %s\n' % (param, guarded_with[param]) From 2b9aa3b0b4ff70f111f64042a922ecbfe67c46d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Thu, 20 Oct 2011 11:07:14 +0200 Subject: [PATCH 11/20] fixed: when no documentation on the method the regular expresion breaks --- contract.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contract.py b/contract.py index 9af7cca..e46e481 100644 --- a/contract.py +++ b/contract.py @@ -893,8 +893,11 @@ def decor(*args, **kwargs): doc_contract = doc_contract[garbage_index:-garbage_index] guarded_with = get_array_from_contract(doc_contract) - pattern = re.compile('^( ){8}', flags=re.MULTILINE) - old_documentation = pattern.sub('', decor.__doc__) + try: + pattern = re.compile('^( ){8}', flags=re.MULTILINE) + old_documentation = pattern.sub('', decor.__doc__) + except TypeError: + old_documentation = '' decor.__doc__ = "Guarded with:\n\n" for param in guarded_with: decor.__doc__ += '- ``%s``: %s\n' % (param, guarded_with[param]) From 94b4fd2b3a7a04891280f127d28579a46e08899f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Fri, 21 Oct 2011 14:54:17 +0200 Subject: [PATCH 12/20] Empty to Null --- contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract.py b/contract.py index e46e481..b6029e9 100644 --- a/contract.py +++ b/contract.py @@ -206,7 +206,7 @@ def check(self, value): self._failure("value should be None") def __repr__(self): - return "Empty" + return "Null" class BoolC(Contract): From f6d13984cc6b1e467018a6ddaf4dab04f29a2790 Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Mon, 24 Oct 2011 15:11:55 +0200 Subject: [PATCH 13/20] Better ISO date documentation --- contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contract.py b/contract.py index b6029e9..2c59a74 100644 --- a/contract.py +++ b/contract.py @@ -424,7 +424,7 @@ def __repr__(self): class IsoDateC(Contract): def _rant(self, value): - self._failure("value is not an iso formatted (yyyy-mm-dd) date: %r" % value) + self._failure("value is not an iso formatted date: %r" % value) def check(self, value): if not value: @@ -435,7 +435,7 @@ def check(self, value): self._rant(value) def __repr__(self): - return "ISODate YYYY-MM-DD" + return "ISO formatted date" class SquareBracketsMeta(ContractMeta): From 8c92c748bb0e49b7ad00484e2d985b86af083270 Mon Sep 17 00:00:00 2001 From: Ivan Metzlar Date: Fri, 28 Oct 2011 10:36:08 +0200 Subject: [PATCH 14/20] Using dateutil to validate proper iso formatted dates --- contract.py | 3 ++- setup.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/contract.py b/contract.py index 2c59a74..34b3124 100644 --- a/contract.py +++ b/contract.py @@ -4,6 +4,7 @@ import inspect import re import datetime +from dateutil.parser import parse as dateutil_parse """ Contract is tiny library for data validation @@ -430,7 +431,7 @@ def check(self, value): if not value: self._rant(value) try: - datetime.datetime(*[int(v) for v in value.split("-")]) + dateutil_parse(value) except: self._rant(value) diff --git a/setup.py b/setup.py index dc0547e..a0909ec 100755 --- a/setup.py +++ b/setup.py @@ -15,4 +15,5 @@ author='barbuza', author_email='', py_modules=['contract'], + install_requires=['python-dateutil>=1.5.0,<2.0.0'], ) From 22d306b11419bac0f34306e90cc5cffc12400291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Tue, 15 Nov 2011 17:35:56 +0100 Subject: [PATCH 15/20] improved the layout of the documentation and all the test fixed --- contract.py | 115 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 48 deletions(-) diff --git a/contract.py b/contract.py index 34b3124..fbcaf33 100644 --- a/contract.py +++ b/contract.py @@ -3,7 +3,6 @@ import functools import inspect import re -import datetime from dateutil.parser import parse as dateutil_parse """ @@ -38,9 +37,9 @@ class ContractMeta(type): on instances but on classes >>> IntC | StringC - , )> + Integer or String >>> IntC | StringC | NullC - , , )> + Integer or String or Null """ def __or__(cls, other): @@ -88,14 +87,21 @@ def _contract(self, contract): def __or__(self, other): return OrC(self, other) + def get_full_condition_name(self, condition): + conditions = {"gt": "greater than", + "lt": "less than", + "gte": "greater or equal than", + "lte": "less or equal than"} + return conditions.get(condition) + class TypeC(Contract): """ >>> TypeC(int) - + >>> TypeC[int] - + >>> c = TypeC[int] >>> c.check(1) >>> c.check("foo") @@ -124,7 +130,7 @@ class AnyC(Contract): """ >>> AnyC() - + Any >>> AnyC().check(object()) """ @@ -141,7 +147,7 @@ class OrCMeta(ContractMeta): Allows to use "<<" operator on OrC class >>> OrC << IntC << StringC - , )> + Integer or String """ def __lshift__(cls, other): @@ -153,7 +159,7 @@ class OrC(Contract): """ >>> nullString = OrC(StringC, NullC) >>> nullString - , )> + String or Null >>> nullString.check(None) >>> nullString.check("test") >>> nullString.check(1) @@ -194,7 +200,7 @@ class NullC(Contract): """ >>> NullC() - + Null >>> NullC().check(None) >>> NullC().check(1) Traceback (most recent call last): @@ -214,7 +220,7 @@ class BoolC(Contract): """ >>> BoolC() - + Boolean >>> BoolC().check(True) >>> BoolC().check(False) >>> BoolC().check(1) @@ -238,17 +244,17 @@ class NumberCMeta(ContractMeta): number contracts >>> IntC[1:] - + Integer (greater or equal than 1) >>> IntC[1:10] - + Integer (greater or equal than 1, less or equal than 10) >>> IntC[:10] - + Integer (less or equal than 10) >>> FloatC[1:] - + Float (greater or equal than 1) >>> IntC > 3 - + Integer (greater than 3) >>> 1 < (FloatC < 10) - + Float (greater than 1, less than 10) >>> (IntC > 5).check(10) >>> (IntC > 5).check(1) Traceback (most recent call last): @@ -275,13 +281,13 @@ class FloatC(Contract): """ >>> FloatC() - + Float >>> FloatC(gte=1) - + Float (greater or equal than 1) >>> FloatC(lte=10) - + Float (less or equal than 10) >>> FloatC(gte=1, lte=10) - + Float (greater or equal than 1, less or equal than 10) >>> FloatC().check(1.0) >>> FloatC().check(1) Traceback (most recent call last): @@ -331,14 +337,17 @@ def __reprname__(self): return type(self).__name__.lower()[:-1] def __repr__(self): - r = "<%s" % self.__reprname__() + if type(self) is IntC: + r = "Integer" + else: + r = "Float" options = [] for param in ("gte", "lte", "gt", "lt"): if getattr(self, param) is not None: - options.append("%s=%s" % (param, getattr(self, param))) + condition = self.get_full_condition_name(param) + options.append("%s %s" % (condition, getattr(self, param))) if options: - r += "(%s)" % (", ".join(options)) - r += ">" + r += " (%s)" % (", ".join(options)) return r @@ -346,7 +355,7 @@ class IntC(FloatC): """ >>> IntC() - + Integer >>> IntC().check(5) >>> IntC().check(1.1) Traceback (most recent call last): @@ -361,9 +370,9 @@ class StringC(Contract): """ >>> StringC() - + String >>> StringC(allow_blank=True) - + String (could be blank) >>> StringC().check("foo") >>> StringC().check("") Traceback (most recent call last): @@ -445,11 +454,11 @@ class SquareBracketsMeta(ContractMeta): Allows usage of square brackets for ListC initialization >>> ListC[IntC] - )> + List of Integer >>> ListC[IntC, 1:] - )> + List of Integer (minimum length of 1) >>> ListC[:10, IntC] - )> + List of Integer (maximum length of 10) >>> ListC[1:10] Traceback (most recent call last): ... @@ -479,11 +488,11 @@ class ListC(Contract): """ >>> ListC(IntC) - )> + List of Integer >>> ListC(IntC, min_length=1) - )> + List of Integer (minimum length of 1) >>> ListC(IntC, min_length=1, max_length=10) - )> + List of Integer (minimum length of 1, maximum length of 10) >>> ListC(IntC).check(1) Traceback (most recent call last): ... @@ -528,8 +537,14 @@ def check(self, value): raise ContractValidationError(err.msg, name) def __repr__(self): - r = "%s with minimal length of %s and maximum length %s\n" % ( - self.contract, self.min_length, self.max_length) + r = "List of %s" % self.contract + options = [] + if self.min_length: + options.append("minimum length of %s" % self.min_length) + if self.max_length: + options.append("maximum length of %s" % self.max_length) + if options: + r = "%s (%s)" % (r, ', '.join(options)) return r @@ -551,7 +566,7 @@ class DictC(Contract): ... ContractValidationError: eggs is not allowed key >>> contract.allow_extra("eggs") - , foo=)> + >>> contract.check({"foo": 1, "bar": "spam", "eggs": None}) >>> contract.check({"foo": 1, "bar": "spam"}) >>> contract.check({"foo": 1, "bar": "spam", "ham": 100}) @@ -559,7 +574,7 @@ class DictC(Contract): ... ContractValidationError: ham is not allowed key >>> contract.allow_extra("*") - , foo=)> + >>> contract.check({"foo": 1, "bar": "spam", "ham": 100}) >>> contract.check({"foo": 1, "bar": "spam", "ham": 100, "baz": None}) >>> contract.check({"foo": 1, "ham": 100, "baz": None}) @@ -567,7 +582,7 @@ class DictC(Contract): ... ContractValidationError: bar is required >>> contract.allow_optionals("bar") - , foo=)> + >>> contract.check({"foo": 1, "ham": 100, "baz": None}) >>> contract.check({"bar": 1, "ham": 100, "baz": None}) Traceback (most recent call last): @@ -626,7 +641,7 @@ def check_item(self, item): self._failure("%s is not allowed key" % key) def __repr__(self): - r = "{(" + r = ">> contract = MappingC(StringC, IntC) >>> contract - => )> + Integer> >>> contract.check({"foo": 1, "bar": 2}) >>> contract.check({"foo": 1, "bar": None}) Traceback (most recent call last): @@ -687,7 +702,7 @@ class EnumC(Contract): """ >>> contract = EnumC("foo", "bar", 1) >>> contract - + 'foo' or 'bar' or 1 >>> contract.check("foo") >>> contract.check(1) >>> contract.check(2) @@ -757,7 +772,7 @@ def check(self, value): self._failure(error) def __repr__(self): - return "" % self.fn.__name__ + return "" % self.fn.__name__ class ForwardC(Contract): @@ -766,7 +781,7 @@ class ForwardC(Contract): >>> nodeC = ForwardC() >>> nodeC << DictC(name=StringC, children=ListC[nodeC]) >>> nodeC - )>, name=)>)> + , name=String)>)> >>> nodeC.check({"name": "foo", "children": []}) >>> nodeC.check({"name": "foo", "children": [1]}) Traceback (most recent call last): @@ -832,8 +847,11 @@ def guard(contract=None, **kwargs): Help on function fn: fn(*args, **kwargs) - guarded with , b=, c=)> + Guarded with: + - ``a``: String + - ``c``: String + - ``b``: Integer docstring >>> fn("foo", 1) @@ -890,8 +908,9 @@ def decor(*args, **kwargs): doc_contract = repr(contract) # find the first ( and strip anything around it - garbage_index = doc_contract.index("(") + 1 - doc_contract = doc_contract[garbage_index:-garbage_index] + min_garbage_index = doc_contract.index("(") + 1 + max_garbage_index = doc_contract.index(")") + doc_contract = doc_contract[min_garbage_index:max_garbage_index] guarded_with = get_array_from_contract(doc_contract) try: From 698c623c69beb13583e4346fd8e78c487fec657d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Tue, 15 Nov 2011 17:36:48 +0100 Subject: [PATCH 16/20] added a README file --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5072cf --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Fork from https://github.com/barbuza/contract to improve the documentation layout and add some new contracts. From 160769a45fd274d809fed59e97d07781e5512e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Wed, 16 Nov 2011 11:18:40 +0100 Subject: [PATCH 17/20] Added tests to EmailC and to NumberC & from "Digits" to "Digit" --- contract.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/contract.py b/contract.py index fbcaf33..eec102f 100644 --- a/contract.py +++ b/contract.py @@ -401,6 +401,17 @@ def __repr__(self): class EmailC(Contract): """ + >>> EmailC() + String with email format + >>> EmailC().check('alex.gonzalez@paylogic.eu') + >>> EmailC().check(1) + Traceback (most recent call last): + ... + ContractValidationError: value is not email + >>> EmailC().check('alex') + Traceback (most recent call last): + ... + ContractValidationError: value is not email """ email_re = re.compile( @@ -413,7 +424,7 @@ def __init__(self): pass def check(self, value): - if not value: + if not value or type(value) is not str: self._failure("value is not email") if self.email_re.search(value): return @@ -928,16 +939,32 @@ def decor(*args, **kwargs): class NumberC(StringC): + """ + >>> NumberC() + Digit + >>> NumberC().check(5) + >>> NumberC().check('alex') + Traceback (most recent call last): + ... + ContractValidationError: value is not a number + """ def __init__(self): super(NumberC, self).__init__(allow_blank=False) def check(self, value): - super(NumberC, self).check(value) + try: + super(NumberC, self).check(value) + except ContractValidationError as e: + try: + float(value) + return + except ValueError: + return e if not value.isdigit(): self._failure("value is not a number") def __repr__(self): - return 'Digits' + return 'Digit' if __name__ == "__main__": From 2c959b7603f830da59b24f9d8ef9fcd349007ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Wed, 16 Nov 2011 14:27:13 +0100 Subject: [PATCH 18/20] solved problem with unicode and contract --- contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract.py b/contract.py index eec102f..6cd3cd3 100644 --- a/contract.py +++ b/contract.py @@ -424,7 +424,7 @@ def __init__(self): pass def check(self, value): - if not value or type(value) is not str: + if not value or not isinstance(value, basestring): self._failure("value is not email") if self.email_re.search(value): return From 157603a7cf39b2188b463d141e4d5abed6893dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Wed, 16 Nov 2011 15:07:06 +0100 Subject: [PATCH 19/20] solved problems with float contract --- contract.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contract.py b/contract.py index 6cd3cd3..a9f39aa 100644 --- a/contract.py +++ b/contract.py @@ -952,6 +952,8 @@ def __init__(self): super(NumberC, self).__init__(allow_blank=False) def check(self, value): + if value == None: + self._failure("value is None") try: super(NumberC, self).check(value) except ContractValidationError as e: @@ -959,7 +961,7 @@ def check(self, value): float(value) return except ValueError: - return e + raise e if not value.isdigit(): self._failure("value is not a number") From c9e73ee97801dd19dee309a5ff315ea4230a1694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Gonz=C3=A1lez?= Date: Wed, 16 Nov 2011 17:50:29 +0100 Subject: [PATCH 20/20] Added a extra \n after list to avoid the warning at sphinx --- contract.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contract.py b/contract.py index a9f39aa..a8880b8 100644 --- a/contract.py +++ b/contract.py @@ -863,6 +863,7 @@ def guard(contract=None, **kwargs): - ``a``: String - ``c``: String - ``b``: Integer + docstring >>> fn("foo", 1) @@ -932,6 +933,8 @@ def decor(*args, **kwargs): decor.__doc__ = "Guarded with:\n\n" for param in guarded_with: decor.__doc__ += '- ``%s``: %s\n' % (param, guarded_with[param]) + if len(guarded_with) > 0: + decor.__doc__ += '\n' decor.__doc__ += old_documentation return decor