From 5e140de81c618f879158032658665d09e0a335cf Mon Sep 17 00:00:00 2001 From: cr0hn Date: Wed, 23 Mar 2016 16:19:19 +0100 Subject: [PATCH 01/21] Update models.py Added new property to extract all descriptions for the fields and a function to get single description value --- booby/models.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/booby/models.py b/booby/models.py index 1008aac..3b8c82e 100644 --- a/booby/models.py +++ b/booby/models.py @@ -172,6 +172,22 @@ def is_valid(self): else: return True + @property + def descriptions(self): + """This property returns a dict of strings with the name of propery as a key + and their description as a value""" + + res = {} + for name, field in list(self._fields.items()): + res[name] = field.description + return res + + def description(self, key): + try: + return self._fields[key].description + except KeyError: + return None + def validate(self): """This method validates the entire `model`. That is, validates all the :mod:`fields` within this model. From ff6e179a1786328e7e420f8bfeb5e3d8effc93ca Mon Sep 17 00:00:00 2001 From: cr0hn Date: Wed, 23 Mar 2016 16:22:09 +0100 Subject: [PATCH 02/21] Update fields.py Added new description field. It'll be useful to build UI to get additional information about each field meaning --- booby/fields.py | 1 + 1 file changed, 1 insertion(+) diff --git a/booby/fields.py b/booby/fields.py index 01f1452..cc9d8da 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -67,6 +67,7 @@ def __init__(self, *validators, **kwargs): self.options = kwargs self.default = kwargs.get('default') + self.description = kwargs.get('description') # Setup field validators self.validators = [] From 926de33167bf25e649c9bc2bee45644cf28b0f0b Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 26 Aug 2016 11:58:33 +0200 Subject: [PATCH 03/21] * Added new type: URL. * Added new property to fields: Description. * Added the optional use os UstraJSON, ultra fast JSON serializer. * Added 'properties' classmethod to Models. With it, you can get the fields defined in a Model, without instantiate it. * Improved validation. Now, types derived from basic types has strong checks: int, fload, bool, string, list. * Improved compatibility with Python 3. --- CHANGES.rst | 11 +++++++++ README.rst | 3 +++ booby/__init__.py | 5 ++--- booby/fields.py | 54 +++++++++++++++++++++++++++++++++++++++------ booby/models.py | 28 ++++++++++++++++++++--- booby/validators.py | 32 +++++++++++++++++++++++++-- requirements.txt | 1 + setup.py | 12 +++++++++- 8 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 requirements.txt diff --git a/CHANGES.rst b/CHANGES.rst index 3b7f350..afcefeb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,17 @@ Changes ======= +0.7.1 (Aug 26, 2016) +-------------------- + +* Added new type: URL. +* Added new property to fields: Description. +* Added the optional use os UstraJSON, ultra fast JSON serializer. +* Added 'properties' classmethod to Models. With it, you can get the fields defined in a Model, without instantiate it. +* Improved validation. Now, types derived from basic types has strong checks: int, fload, bool, string, list. +* Improved compatibility with Python 3. + + 0.7.0 (Dec 3, 2014) ------------------- diff --git a/README.rst b/README.rst index f517be8..eef2bc7 100644 --- a/README.rst +++ b/README.rst @@ -47,6 +47,7 @@ See the sample code below to get an idea of the main features. login = fields.String(required=True) name = fields.String() email = fields.Email() + site = fields.URL() token = fields.Embedded(Token, required=True) addresses = fields.Collection(Address) @@ -54,6 +55,7 @@ See the sample code below to get an idea of the main features. login='jack', name='Jack', email='jack@example.com', + url='http://mysite.com', token={ 'key': 'vs7dfxxx', 'secret': 'ds5ds4xxx' @@ -73,6 +75,7 @@ See the sample code below to get an idea of the main features. { "email": "jack@example.com", + "url": "http://mysite.com", "login": "jack", "token": { "secret": "ds5ds4xxx", diff --git a/booby/__init__.py b/booby/__init__.py index 7436e94..7e2d8d2 100644 --- a/booby/__init__.py +++ b/booby/__init__.py @@ -15,6 +15,5 @@ # limitations under the License. -from booby.models import Model - -__all__ = ['Model'] +from .fields import * # noqa +from .models import * # noqa diff --git a/booby/fields.py b/booby/fields.py index cc9d8da..2b018c7 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -32,6 +32,7 @@ class User(Model): is_active = Boolean(default=False) """ +import six import collections from booby import ( @@ -67,18 +68,20 @@ def __init__(self, *validators, **kwargs): self.options = kwargs self.default = kwargs.get('default') - self.description = kwargs.get('description') + self.description = kwargs.get('description', '') + self.required = kwargs.get('required', False) + self.choices = kwargs.get('choices', []) + + assert isinstance(self.choices, list) # Setup field validators self.validators = [] - if kwargs.get('required'): + if self.required: self.validators.append(builtin_validators.Required()) - choices = kwargs.get('choices') - - if choices: - self.validators.append(builtin_validators.In(choices)) + if self.choices: + self.validators.append(builtin_validators.In(self.choices)) self.validators.extend(validators) @@ -134,6 +137,14 @@ def encode(self, value): return value + @property + def field_type(self): + """ + :return: Python native type: int, str, float... + :rtype: object + """ + raise NotImplemented("This property must be implemented by the subclass") + class String(Field): """:class:`Field` subclass with builtin `string` validation.""" @@ -141,6 +152,10 @@ class String(Field): def __init__(self, *args, **kwargs): super(String, self).__init__(builtin_validators.String(), *args, **kwargs) + @property + def field_type(self): + return six.string_types + class Integer(Field): """:class:`Field` subclass with builtin `integer` validation.""" @@ -148,6 +163,10 @@ class Integer(Field): def __init__(self, *args, **kwargs): super(Integer, self).__init__(builtin_validators.Integer(), *args, **kwargs) + @property + def field_type(self): + return six.integer_types + class Float(Field): """:class:`Field` subclass with builtin `float` validation.""" @@ -155,6 +174,10 @@ class Float(Field): def __init__(self, *args, **kwargs): super(Float, self).__init__(builtin_validators.Float(), *args, **kwargs) + @property + def field_type(self): + return float + class Boolean(Field): """:class:`Field` subclass with builtin `bool` validation.""" @@ -162,6 +185,10 @@ class Boolean(Field): def __init__(self, *args, **kwargs): super(Boolean, self).__init__(builtin_validators.Boolean(), *args, **kwargs) + @property + def field_type(self): + return bool + class Embedded(Field): """:class:`Field` subclass with builtin embedded :class:`models.Model` @@ -184,13 +211,20 @@ def __set__(self, instance, value): super(Embedded, self).__set__(instance, value) -class Email(Field): +class Email(String): """:class:`Field` subclass with builtin `email` validation.""" def __init__(self, *args, **kwargs): super(Email, self).__init__(builtin_validators.Email(), *args, **kwargs) +class URL(String): + """:class:`Field` subclass with builtin `email` validation.""" + + def __init__(self, *args, **kwargs): + super(URL, self).__init__(builtin_validators.Email(), *args, **kwargs) + + class List(Field): """:class:`Field` subclass with builtin `list` validation and default value. @@ -204,6 +238,10 @@ def __init__(self, *args, **kwargs): super(List, self).__init__( builtin_validators.List(*kwargs.get('inner_validators', [])), *args, **kwargs) + + @property + def field_type(self): + return list class Collection(Field): @@ -258,3 +296,5 @@ def _resolve(self, value): item = self.model(**item) result.append(item) return result + +__all__ = ("Field", "String", "Integer", "Float", "Boolean", "Embedded", "Email", "URL", "List", "Collection") diff --git a/booby/models.py b/booby/models.py index 3b8c82e..b0b7d90 100644 --- a/booby/models.py +++ b/booby/models.py @@ -38,8 +38,13 @@ class Repo(Model): '{"owner": {"login": "jaimegildesagredo", "name": "Jaime Gil de Sagredo"}, "name": "Booby"}' """ -import json +import six import collections +try: + import ujson as json +except ImportError: + import json + from booby import mixins, fields, errors, _utils @@ -67,6 +72,7 @@ def __repr__(cls): _utils.repr_options(cls._fields)) +@six.add_metaclass(ModelMeta) class Model(mixins.Encoder): """The `Model` class. All Booby models should subclass this. @@ -90,8 +96,6 @@ class Model(mixins.Encoder): """ - __metaclass__ = ModelMeta - def __new__(cls, *args, **kwargs): model = super(Model, cls).__new__(cls) model._data = {} @@ -243,3 +247,21 @@ def decode(self, raw): result[name] = value return result + + @classmethod + def properties(cls): + """Get properties defined in Model without instantiate them""" + if hasattr(cls, "_fields"): + return cls._fields + else: + ret = {} + for k, v in six.iteritems(cls.__dict__): + if k.startswith("_"): + continue + + ret[k] = v + + return ret + +__all__ = ("Model", ) + diff --git a/booby/validators.py b/booby/validators.py index 5e4ff2a..15d7ebc 100644 --- a/booby/validators.py +++ b/booby/validators.py @@ -26,6 +26,7 @@ """ import re +import six import collections import datetime @@ -69,7 +70,7 @@ class String(Validator): @nullable def validate(self, value): - if not isinstance(value, basestring): + if not isinstance(value, six.string_types): raise errors.ValidationError('should be a string') @@ -78,7 +79,7 @@ class Integer(Validator): @nullable def validate(self, value): - if not isinstance(value, int): + if not isinstance(value, six.integer_types): raise errors.ValidationError('should be an integer') @@ -140,6 +141,33 @@ def validate(self, value): raise errors.ValidationError('should be a valid email') +class URL(String): + """This validator forces fields values to be strings and match a + valid email address. + + """ + + def __init__(self): + super(URL, self).__init__() + + # From Django validator: + # https://github.com/django/django/blob/master/django/core/validators.py#L47 + self.pattern = re.compile( + r'^https?://' # http:// or https:// + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain... + r'localhost|' # localhost... + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip + r'(?::\d+)?' # optional port + r'(?:/?|[/?]\S+)$', re.IGNORECASE) + + @nullable + def validate(self, value): + super(URL, self).validate(value) + + if self.pattern.match(value) is None: + raise errors.ValidationError('should be a valid URL') + + class List(Validator): """This validator forces field values to be a :keyword:`list`. Also a list of inner :mod:`validators` could be specified to validate diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..64c56a3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +six \ No newline at end of file diff --git a/setup.py b/setup.py index 991b8a6..87546f3 100644 --- a/setup.py +++ b/setup.py @@ -2,18 +2,26 @@ from setuptools import setup, find_packages +# Import requirements +with open('requirements.txt') as f: + required = f.read().splitlines() + with open('README.rst') as f: long_description = f.read() setup( name='booby', - version='0.7.0', + version='0.7.1', + install_requires=required, description='Data modeling and validation Python library', long_description=long_description, url='https://github.com/jaimegildesagredo/booby', author='Jaime Gil de Sagredo Luna', author_email='jaimegildesagredo@gmail.com', packages=find_packages(exclude=['tests', 'tests.*']), + extras_require={ + 'ustraJSON': ["ujson"], + }, use_2to3=True, classifiers=[ 'Development Status :: 3 - Alpha', @@ -23,6 +31,8 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) From 77f07a129e508075803aa2cd916298a93614626d Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 26 Aug 2016 13:35:54 +0200 Subject: [PATCH 04/21] * Added IP (IPv4 and IPv6 address) field * Minor spell fixes --- booby/fields.py | 11 +++++++++-- booby/validators.py | 24 +++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/booby/fields.py b/booby/fields.py index 2b018c7..eeb40e0 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -219,10 +219,17 @@ def __init__(self, *args, **kwargs): class URL(String): - """:class:`Field` subclass with builtin `email` validation.""" + """:class:`Field` subclass with builtin `URL` validation.""" + + def __init__(self, *args, **kwargs): + super(URL, self).__init__(builtin_validators.URL(), *args, **kwargs) + + +class IP(String): + """:class:`Field` subclass with builtin `ip` validation.""" def __init__(self, *args, **kwargs): - super(URL, self).__init__(builtin_validators.Email(), *args, **kwargs) + super(IP, self).__init__(builtin_validators.Email(), *args, **kwargs) class List(Field): diff --git a/booby/validators.py b/booby/validators.py index 15d7ebc..8fe6502 100644 --- a/booby/validators.py +++ b/booby/validators.py @@ -143,7 +143,7 @@ def validate(self, value): class URL(String): """This validator forces fields values to be strings and match a - valid email address. + valid URL. """ @@ -168,6 +168,28 @@ def validate(self, value): raise errors.ValidationError('should be a valid URL') +class IP(String): + """This validator forces fields values to be strings and match a + valid IP address. + + """ + + def __init__(self): + super(IP, self).__init__() + + # Regex from: + # https://gist.github.com/mnordhoff/2213179 + self.ipv4 = re.compile('^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$') + self.ipv6 = re.compile('^(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){,6}[0-9A-Fa-f]{1,4})?::)$') + + @nullable + def validate(self, value): + super(IP, self).validate(value) + + if self.ipv4.match(value) is None and self.ipv6 is None: + raise errors.ValidationError('should be a valid IPv4 or IPv6 address') + + class List(Validator): """This validator forces field values to be a :keyword:`list`. Also a list of inner :mod:`validators` could be specified to validate From e900accf7d36847faebdd4f154fd11ef4e6e2b81 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 26 Aug 2016 13:37:12 +0200 Subject: [PATCH 05/21] * Added IP (IPv4 and IPv6 address) field --- booby/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booby/fields.py b/booby/fields.py index eeb40e0..ef7c235 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -304,4 +304,4 @@ def _resolve(self, value): result.append(item) return result -__all__ = ("Field", "String", "Integer", "Float", "Boolean", "Embedded", "Email", "URL", "List", "Collection") +__all__ = ("Field", "String", "Integer", "Float", "Boolean", "Embedded", "Email", "URL", "List", "Collection", "IP") From f2356dc9e61d0c97d9e6344355fe625e83641c14 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 26 Aug 2016 15:15:38 +0200 Subject: [PATCH 06/21] fix IP validator --- booby/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booby/fields.py b/booby/fields.py index ef7c235..753d1e6 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -229,7 +229,7 @@ class IP(String): """:class:`Field` subclass with builtin `ip` validation.""" def __init__(self, *args, **kwargs): - super(IP, self).__init__(builtin_validators.Email(), *args, **kwargs) + super(IP, self).__init__(builtin_validators.IP(), *args, **kwargs) class List(Field): From ddfc4bc10a4525f8bf07a94ca059ebd9b9ebd6fd Mon Sep 17 00:00:00 2001 From: cr0hn Date: Thu, 1 Sep 2016 17:48:47 +0200 Subject: [PATCH 07/21] Fix: set default value if value provided is None --- booby/fields.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/booby/fields.py b/booby/fields.py index 753d1e6..2c77806 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -104,6 +104,8 @@ def __get__(self, instance, owner): return self def __set__(self, instance, value): + if not value: + value = self.default instance._data[self] = value def _default(self, model): From 49bd68bba51c3a885a7343f7b7149de0b629808c Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 2 Sep 2016 13:22:55 +0200 Subject: [PATCH 08/21] Added Raw data type --- booby/fields.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/booby/fields.py b/booby/fields.py index 2c77806..99fd7e8 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -234,6 +234,13 @@ def __init__(self, *args, **kwargs): super(IP, self).__init__(builtin_validators.IP(), *args, **kwargs) +class Raw(Field): + """:class:`Field` raw input data""" + + def __init__(self, *args, **kwargs): + super(Raw, self).__init__([], *args, **kwargs) + + class List(Field): """:class:`Field` subclass with builtin `list` validation and default value. From da087ef790340b2b5b41c7dc802025899210f910 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 2 Sep 2016 13:23:54 +0200 Subject: [PATCH 09/21] Add Raw to __all__ --- booby/fields.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/booby/fields.py b/booby/fields.py index 99fd7e8..0fc9d24 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -313,4 +313,5 @@ def _resolve(self, value): result.append(item) return result -__all__ = ("Field", "String", "Integer", "Float", "Boolean", "Embedded", "Email", "URL", "List", "Collection", "IP") +__all__ = ("Field", "String", "Integer", "Float", "Boolean", "Embedded", "Email", "URL", "List", "Collection", "IP", + "Raw") From 2c9d868e5584aefe34c74341f7e07903ebe87f38 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 2 Sep 2016 13:29:03 +0200 Subject: [PATCH 10/21] Fix: Error in constructor of Raw type --- booby/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booby/fields.py b/booby/fields.py index 0fc9d24..1520888 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -238,7 +238,7 @@ class Raw(Field): """:class:`Field` raw input data""" def __init__(self, *args, **kwargs): - super(Raw, self).__init__([], *args, **kwargs) + super(Raw, self).__init__(*args, **kwargs) class List(Field): From 90c860d7da052187d76ed862dc5561dce66e79fc Mon Sep 17 00:00:00 2001 From: cr0hn Date: Mon, 5 Sep 2016 12:58:13 +0200 Subject: [PATCH 11/21] changes to publish in pypi --- README.rst | 2 ++ setup.py | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index eef2bc7..4aedd2e 100644 --- a/README.rst +++ b/README.rst @@ -21,6 +21,8 @@ Booby: data modeling and validation :target: http://travis-ci.org/jaimegildesagredo/booby :alt: Build status +**This project is a fork of official project*** with some new features. + Booby is a standalone data `modeling` and `validation` library written in Python. Booby is under active development (visit `this blog post `_ for more info and the roadmap) and licensed under the `Apache2 license `_, so feel free to `contribute `_ and `report errors and suggestions `_. Usage diff --git a/setup.py b/setup.py index 87546f3..c9584e5 100644 --- a/setup.py +++ b/setup.py @@ -10,14 +10,16 @@ long_description = f.read() setup( - name='booby', - version='0.7.1', + name='booby-ng', + version='0.8.0', install_requires=required, description='Data modeling and validation Python library', long_description=long_description, - url='https://github.com/jaimegildesagredo/booby', + url='https://github.com/cr0hn/booby', author='Jaime Gil de Sagredo Luna', author_email='jaimegildesagredo@gmail.com', + maintainer='Daniel Garcia (cr0hn)', + maintainer_email='cr0hn@cr0hn.com', packages=find_packages(exclude=['tests', 'tests.*']), extras_require={ 'ustraJSON': ["ujson"], From 6edfc7b396bc3fbd92e736b98d5dce623b59da0a Mon Sep 17 00:00:00 2001 From: cr0hn Date: Mon, 5 Sep 2016 18:25:59 +0200 Subject: [PATCH 12/21] imp: list Type --- booby/fields.py | 3 ++- setup.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/booby/fields.py b/booby/fields.py index 1520888..2121fcd 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -33,6 +33,7 @@ class User(Model): """ import six +import datetime import collections from booby import ( @@ -248,7 +249,7 @@ class List(Field): """ def __init__(self, *args, **kwargs): - kwargs.setdefault('default', list) + kwargs.setdefault('default', []) kwargs.setdefault('encoders', []).append(builtin_encoders.List()) super(List, self).__init__( diff --git a/setup.py b/setup.py index c9584e5..5e25ccc 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='booby-ng', - version='0.8.0', + version='0.8.1', install_requires=required, description='Data modeling and validation Python library', long_description=long_description, From da74cb73ed34da8c2ea92aeee88b4dae2a046fa9 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 16 Sep 2016 14:11:54 +0200 Subject: [PATCH 13/21] added URI data type --- booby/fields.py | 16 ++++++++++++++-- booby/validators.py | 24 ++++++++++++++++++++++-- setup.py | 2 +- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/booby/fields.py b/booby/fields.py index 2121fcd..1802f88 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -33,9 +33,14 @@ class User(Model): """ import six -import datetime import collections +try: + # Python 3 compatiblity + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse + from booby import ( validators as builtin_validators, encoders as builtin_encoders, @@ -235,6 +240,13 @@ def __init__(self, *args, **kwargs): super(IP, self).__init__(builtin_validators.IP(), *args, **kwargs) +class URI(String): + """:class:`Field` subclass with builtin `ip` validation.""" + + def __init__(self, *args, **kwargs): + super(URI, self).__init__(builtin_validators.IP(), *args, **kwargs) + + class Raw(Field): """:class:`Field` raw input data""" @@ -315,4 +327,4 @@ def _resolve(self, value): return result __all__ = ("Field", "String", "Integer", "Float", "Boolean", "Embedded", "Email", "URL", "List", "Collection", "IP", - "Raw") + "Raw", "URI") diff --git a/booby/validators.py b/booby/validators.py index 8fe6502..ad44414 100644 --- a/booby/validators.py +++ b/booby/validators.py @@ -26,6 +26,7 @@ """ import re + import six import collections import datetime @@ -179,8 +180,8 @@ def __init__(self): # Regex from: # https://gist.github.com/mnordhoff/2213179 - self.ipv4 = re.compile('^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$') - self.ipv6 = re.compile('^(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){,6}[0-9A-Fa-f]{1,4})?::)$') + self.ipv4 = re.compile(r'^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$') + self.ipv6 = re.compile(r'^(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){,6}[0-9A-Fa-f]{1,4})?::)$') @nullable def validate(self, value): @@ -190,6 +191,25 @@ def validate(self, value): raise errors.ValidationError('should be a valid IPv4 or IPv6 address') +class URI(String): + """This validator forces fields values to be strings and match a + valid URI. + + """ + + def __init__(self): + super(URI, self).__init__() + + self.uri_regex = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+') + + @nullable + def validate(self, value): + super(URI, self).validate(value) + + if self.uri_regex.match(value) is None: + raise errors.ValidationError('should be a valid URI') + + class List(Validator): """This validator forces field values to be a :keyword:`list`. Also a list of inner :mod:`validators` could be specified to validate diff --git a/setup.py b/setup.py index 5e25ccc..8f86089 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='booby-ng', - version='0.8.1', + version='0.8.2', install_requires=required, description='Data modeling and validation Python library', long_description=long_description, From 2a50a1ce5bb835b8901ae300aa0dec4ae27bc161 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 16 Sep 2016 23:18:47 +0200 Subject: [PATCH 14/21] added URI data type --- booby/fields.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/booby/fields.py b/booby/fields.py index 1802f88..2e672d5 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -244,7 +244,7 @@ class URI(String): """:class:`Field` subclass with builtin `ip` validation.""" def __init__(self, *args, **kwargs): - super(URI, self).__init__(builtin_validators.IP(), *args, **kwargs) + super(URI, self).__init__(builtin_validators.URL(), *args, **kwargs) class Raw(Field): diff --git a/setup.py b/setup.py index 8f86089..9d03bb0 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='booby-ng', - version='0.8.2', + version='0.8.2-1', install_requires=required, description='Data modeling and validation Python library', long_description=long_description, From 9a13ecffd5e5312bca419977ac145409a4ebf531 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 16 Sep 2016 23:22:41 +0200 Subject: [PATCH 15/21] added URI data type --- booby/fields.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/booby/fields.py b/booby/fields.py index 2e672d5..ddc4f56 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -244,7 +244,7 @@ class URI(String): """:class:`Field` subclass with builtin `ip` validation.""" def __init__(self, *args, **kwargs): - super(URI, self).__init__(builtin_validators.URL(), *args, **kwargs) + super(URI, self).__init__(builtin_validators.URI(), *args, **kwargs) class Raw(Field): diff --git a/setup.py b/setup.py index 9d03bb0..53d73e8 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='booby-ng', - version='0.8.2-1', + version='0.8.2-2', install_requires=required, description='Data modeling and validation Python library', long_description=long_description, From 76306e5df6fa3c600cd66e8542b928d6947eed63 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 16 Sep 2016 23:28:13 +0200 Subject: [PATCH 16/21] added URI data type --- booby/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booby/validators.py b/booby/validators.py index ad44414..a3b9f7c 100644 --- a/booby/validators.py +++ b/booby/validators.py @@ -200,7 +200,7 @@ class URI(String): def __init__(self): super(URI, self).__init__() - self.uri_regex = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+') + self.uri_regex = re.compile(r'([a-z0-9+.-]+)://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+') @nullable def validate(self, value): From 8e4d11493ee712ff8918dbbb6d9e7e78e3aeddf4 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Fri, 16 Sep 2016 23:28:22 +0200 Subject: [PATCH 17/21] added URI data type --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 53d73e8..3b614ad 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='booby-ng', - version='0.8.2-2', + version='0.8.2-3', install_requires=required, description='Data modeling and validation Python library', long_description=long_description, From 2a37d2c77f006279e7898116cb3d5f73542769f1 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Wed, 21 Sep 2016 16:02:29 +0200 Subject: [PATCH 18/21] imp: list Type --- README.rst | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ booby/models.py | 1 + 2 files changed, 65 insertions(+) diff --git a/README.rst b/README.rst index 4aedd2e..e97493f 100644 --- a/README.rst +++ b/README.rst @@ -96,6 +96,70 @@ See the sample code below to get an idea of the main features. ] } +Advanced +-------- + +Booby raises when you try to pass a property as input dict thata not exist as property. It's very annoying when you want to pass a big JSON and want that Booby take only their parameters that it understand. + +For example: + +.. code-block:: python + + from booby import Model, fields + + class User(Model): + login = fields.String(required=True) + name = fields.String() + + input_info = dict(login="john", name="doe", address="other field") + + jack = User(**input_info) + +This code raise the exception: + +.. code-block:: bash + + > python example.py + Traceback (most recent call last): + File "/Users/Dani/Projects/apitest/apitest/actions/analyze/console.py", line 39, in launch_analyze_in_console + postman_parser(json_info) + File "/Users/Dani/Projects/apitest/apitest/actions/analyze/postman.py", line 52, in postman_parser + end_points=postman_info.get("item"))) + File "/Users/Dani/.virtualenvs/apitest/lib/python3.5/site-packages/booby/models.py", line 108, in __init__ + self._update(kwargs) + File "/Users/Dani/.virtualenvs/apitest/lib/python3.5/site-packages/booby/models.py", line 163, in _update + self[k] = v + File "/Users/Dani/.virtualenvs/apitest/lib/python3.5/site-packages/booby/models.py", line 150, in __setitem__ + setattr(self, k, v) + File "/Users/Dani/.virtualenvs/apitest/lib/python3.5/site-packages/booby/fields.py", line 317, in __set__ + value = self._resolve(value) + File "/Users/Dani/.virtualenvs/apitest/lib/python3.5/site-packages/booby/fields.py", line 325, in _resolve + item = self.model(**item) + File "/Users/Dani/.virtualenvs/apitest/lib/python3.5/site-packages/booby/models.py", line 108, in __init__ + self._update(kwargs) + File "/Users/Dani/.virtualenvs/apitest/lib/python3.5/site-packages/booby/models.py", line 163, in _update + self[k] = v + File "/Users/Dani/.virtualenvs/apitest/lib/python3.5/site-packages/booby/models.py", line 148, in __setitem__ + + booby.errors.FieldError: address + +If you want Booby ignore these properties that don't understand, you can define the Model class adding the property `__ignore_missing__` + +.. code-block:: python + + from booby import Model, fields + + class User(Model): + __ignore_missing__ = True + + login = fields.String(required=True) + name = fields.String() + + input_info = dict(login="john", name="doe", address="other field") + + jack = User(**input_info) + + Installation ------------ diff --git a/booby/models.py b/booby/models.py index b0b7d90..edeb980 100644 --- a/booby/models.py +++ b/booby/models.py @@ -99,6 +99,7 @@ class Model(mixins.Encoder): def __new__(cls, *args, **kwargs): model = super(Model, cls).__new__(cls) model._data = {} + model.ignore_missing = bool(getattr(model, "__ignore_missing__", False)) return model From 8b261e2a06e53380d08bd728ce1cb3cbd282ea15 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Wed, 21 Sep 2016 16:03:23 +0200 Subject: [PATCH 19/21] fix version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3b614ad..7ac6b92 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='booby-ng', - version='0.8.2-3', + version='0.8.3', install_requires=required, description='Data modeling and validation Python library', long_description=long_description, From d20ed7790c0005fdd632541852e32c8e50f38d3a Mon Sep 17 00:00:00 2001 From: cr0hn Date: Wed, 21 Sep 2016 18:33:59 +0200 Subject: [PATCH 20/21] fix: default type for collections --- booby/fields.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/booby/fields.py b/booby/fields.py index ddc4f56..3b20488 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -305,7 +305,7 @@ class User(Model): """ def __init__(self, model, *args, **kwargs): - kwargs.setdefault('default', list) + kwargs.setdefault('default', []) kwargs.setdefault('encoders', []).append(builtin_encoders.Collection()) kwargs.setdefault('decoders', []).append(builtin_decoders.Collection(model)) diff --git a/setup.py b/setup.py index 7ac6b92..0e89cdf 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='booby-ng', - version='0.8.3', + version='0.8.3-1', install_requires=required, description='Data modeling and validation Python library', long_description=long_description, From 3db644ec75b10f999424a0e61082a47ffb3a7cb0 Mon Sep 17 00:00:00 2001 From: cr0hn Date: Tue, 13 Dec 2016 15:21:18 +0100 Subject: [PATCH 21/21] fix: typo in URI fix: type check in List --- booby/fields.py | 4 ++-- booby/validators.py | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/booby/fields.py b/booby/fields.py index 3b20488..3de2bb2 100644 --- a/booby/fields.py +++ b/booby/fields.py @@ -241,8 +241,8 @@ def __init__(self, *args, **kwargs): class URI(String): - """:class:`Field` subclass with builtin `ip` validation.""" - + """:class:`Field` subclass with builtin `URI` validation.""" + def __init__(self, *args, **kwargs): super(URI, self).__init__(builtin_validators.URI(), *args, **kwargs) diff --git a/booby/validators.py b/booby/validators.py index a3b9f7c..46fe411 100644 --- a/booby/validators.py +++ b/booby/validators.py @@ -227,7 +227,7 @@ def __init__(self, *validators): @nullable def validate(self, value): - if not isinstance(value, collections.MutableSequence): + if not isinstance(value, collections.Sequence): raise errors.ValidationError('should be a list') for i in value: diff --git a/setup.py b/setup.py index 0e89cdf..cfb21f6 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='booby-ng', - version='0.8.3-1', + version='0.8.4', install_requires=required, description='Data modeling and validation Python library', long_description=long_description,