[Python-modules-commits] [coreschema] 01/06: Import Upstream version 0.0.4

Pierre-Elliott Bécue peb-guest at moszumanska.debian.org
Thu Jan 11 12:56:50 UTC 2018


This is an automated email from the git hooks/post-receive script.

peb-guest pushed a commit to branch master
in repository coreschema.

commit 58d945fd43655bf1b8293093ad7ccfe573678de8
Author: Pierre-Elliott Bécue <becue at crans.org>
Date:   Thu Jan 11 13:11:33 2018 +0100

    Import Upstream version 0.0.4
---
 PKG-INFO                                           |  17 +
 coreschema.egg-info/PKG-INFO                       |  17 +
 coreschema.egg-info/SOURCES.txt                    |  23 +
 coreschema.egg-info/dependency_links.txt           |   1 +
 coreschema.egg-info/requires.txt                   |   1 +
 coreschema.egg-info/top_level.txt                  |   2 +
 coreschema/__init__.py                             |  16 +
 coreschema/compat.py                               |   8 +
 coreschema/encodings/__init__.py                   |   0
 coreschema/encodings/corejson.py                   |  19 +
 coreschema/encodings/html.py                       |  74 +++
 coreschema/encodings/jsonschema.py                 | 215 +++++++++
 coreschema/formats.py                              |  25 +
 coreschema/schemas.py                              | 506 +++++++++++++++++++++
 coreschema/templates/base.html                     | 162 +++++++
 coreschema/templates/bootstrap3/form.html          |   0
 .../templates/bootstrap3/inputs/checkbox.html      |  14 +
 coreschema/templates/bootstrap3/inputs/input.html  |  12 +
 coreschema/templates/bootstrap3/inputs/select.html |  16 +
 .../bootstrap3/inputs/select_multiple.html         |  15 +
 .../templates/bootstrap3/inputs/textarea.html      |  12 +
 coreschema/templates/form.html                     |   5 +
 coreschema/utils.py                                |  63 +++
 setup.cfg                                          |   4 +
 setup.py                                           |  77 ++++
 25 files changed, 1304 insertions(+)

diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..2748779
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,17 @@
+Metadata-Version: 1.1
+Name: coreschema
+Version: 0.0.4
+Summary: Core Schema.
+Home-page: https://github.com/core-api/python-coreschema
+Author: Tom Christie
+Author-email: tom at tomchristie.com
+License: BSD
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Internet :: WWW/HTTP
diff --git a/coreschema.egg-info/PKG-INFO b/coreschema.egg-info/PKG-INFO
new file mode 100644
index 0000000..2748779
--- /dev/null
+++ b/coreschema.egg-info/PKG-INFO
@@ -0,0 +1,17 @@
+Metadata-Version: 1.1
+Name: coreschema
+Version: 0.0.4
+Summary: Core Schema.
+Home-page: https://github.com/core-api/python-coreschema
+Author: Tom Christie
+Author-email: tom at tomchristie.com
+License: BSD
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Internet :: WWW/HTTP
diff --git a/coreschema.egg-info/SOURCES.txt b/coreschema.egg-info/SOURCES.txt
new file mode 100644
index 0000000..f9e15e0
--- /dev/null
+++ b/coreschema.egg-info/SOURCES.txt
@@ -0,0 +1,23 @@
+setup.py
+coreschema/__init__.py
+coreschema/compat.py
+coreschema/formats.py
+coreschema/schemas.py
+coreschema/utils.py
+coreschema.egg-info/PKG-INFO
+coreschema.egg-info/SOURCES.txt
+coreschema.egg-info/dependency_links.txt
+coreschema.egg-info/requires.txt
+coreschema.egg-info/top_level.txt
+coreschema/encodings/__init__.py
+coreschema/encodings/corejson.py
+coreschema/encodings/html.py
+coreschema/encodings/jsonschema.py
+coreschema/templates/base.html
+coreschema/templates/form.html
+coreschema/templates/bootstrap3/form.html
+coreschema/templates/bootstrap3/inputs/checkbox.html
+coreschema/templates/bootstrap3/inputs/input.html
+coreschema/templates/bootstrap3/inputs/select.html
+coreschema/templates/bootstrap3/inputs/select_multiple.html
+coreschema/templates/bootstrap3/inputs/textarea.html
\ No newline at end of file
diff --git a/coreschema.egg-info/dependency_links.txt b/coreschema.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/coreschema.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/coreschema.egg-info/requires.txt b/coreschema.egg-info/requires.txt
new file mode 100644
index 0000000..7f7afbf
--- /dev/null
+++ b/coreschema.egg-info/requires.txt
@@ -0,0 +1 @@
+jinja2
diff --git a/coreschema.egg-info/top_level.txt b/coreschema.egg-info/top_level.txt
new file mode 100644
index 0000000..66dc8fe
--- /dev/null
+++ b/coreschema.egg-info/top_level.txt
@@ -0,0 +1,2 @@
+coreschema
+coreschema/encodings
diff --git a/coreschema/__init__.py b/coreschema/__init__.py
new file mode 100644
index 0000000..ccbc0ce
--- /dev/null
+++ b/coreschema/__init__.py
@@ -0,0 +1,16 @@
+from coreschema.schemas import (
+    Object, Array, Integer, Number, String, Boolean, Null,
+    Enum, Anything, Ref, RefSpace,
+    Union, Intersection, ExclusiveUnion, Not
+)
+from coreschema.encodings.html import render_to_form
+
+
+__version__ = '0.0.4'
+
+__all__ = [
+    Object, Array, Integer, Number, String, Boolean, Null,
+    Enum, Anything, Ref, RefSpace,
+    Union, Intersection, ExclusiveUnion, Not,
+    render_to_form
+]
diff --git a/coreschema/compat.py b/coreschema/compat.py
new file mode 100644
index 0000000..a998663
--- /dev/null
+++ b/coreschema/compat.py
@@ -0,0 +1,8 @@
+import sys
+
+if sys.version_info.major == 2:
+    text_types = (str, unicode)
+    numeric_types = (float, int, long)
+else:
+    text_types = (str,)
+    numeric_types = (float, int)
diff --git a/coreschema/encodings/__init__.py b/coreschema/encodings/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/coreschema/encodings/corejson.py b/coreschema/encodings/corejson.py
new file mode 100644
index 0000000..ad2b460
--- /dev/null
+++ b/coreschema/encodings/corejson.py
@@ -0,0 +1,19 @@
+jsonschema = coreschema.RefSpace({
+    'Document': coreschema.Object(
+        properties={
+            '_type': coreschema.Enum(['document']),
+            '_meta': coreschema.Object(
+                properties={
+                    'url': coreschema.String(),
+                    'title': coreschema.String(),
+                    'description': coreschema.String(),
+                }
+            )
+        }
+    ),
+    'Link': coreschema.Object(
+        properties={
+            '_type': coreschema.Enum(['link'])
+        }
+    )
+})
diff --git a/coreschema/encodings/html.py b/coreschema/encodings/html.py
new file mode 100644
index 0000000..4cad462
--- /dev/null
+++ b/coreschema/encodings/html.py
@@ -0,0 +1,74 @@
+from coreschema import Object, Array, String, Integer, Number, Boolean, Enum
+import jinja2
+
+
+env = jinja2.Environment(loader=jinja2.PackageLoader('coreschema', 'templates'))
+
+
+# TODO: required
+# TODO: initial values, errors, input control
+
+
+def render_to_form(schema):
+    template = env.get_template('form.html')
+    return template.render({
+        'parent': schema,
+        'determine_html_template': determine_html_template,
+        'get_textarea_value': get_textarea_value,
+        'get_attrs': get_attrs
+    })
+
+
+def determine_html_template(schema):
+    if isinstance(schema, Array):
+        if schema.unique_items and isinstance(schema.items, Enum):
+            # Actually only for *unordered* input
+            return 'bootstrap3/inputs/select_multiple.html'
+        # TODO: Comma seperated inputs
+        return 'bootstrap3/inputs/textarea.html'
+    elif isinstance(schema, Object):
+        # TODO: Fieldsets
+        return 'bootstrap3/inputs/textarea.html'
+    elif isinstance(schema, Number):
+        return 'bootstrap3/inputs/input.html'
+    elif isinstance(schema, Boolean):
+        # TODO: nullable boolean
+        return 'bootstrap3/inputs/checkbox.html'
+    elif isinstance(schema, Enum):
+        # TODO: display values
+        return 'bootstrap3/inputs/select.html'
+    # String:
+    if schema.format == 'textarea':
+        return 'bootstrap3/inputs/textarea.html'
+    return 'bootstrap3/inputs/input.html'
+
+
+def get_textarea_value(schema):
+    if isinstance(schema, Array):
+        return "[ ]"
+    elif isinstance(schema, Object):
+        return "{ }"
+    return ""
+
+
+def get_attrs(schema):
+    if isinstance(schema, Array):
+        # TODO: Add data-child-type and use with selects
+        return "data-empty=[] data-type='array'"
+    elif isinstance(schema, Object):
+        return "data-empty={} data-type='object'"
+    elif isinstance(schema, Integer):
+        return "data-empty=null data-type='integer' type='number' step=1"
+    elif isinstance(schema, Number):
+        return "data-empty=null data-type='number' type='number' step=any"
+    elif isinstance(schema, Boolean):
+        return "data-empty=false data-type='boolean'"
+    elif isinstance(schema, Enum):
+        # TODO: Non-string Enum
+        return "data-empty='' data-type='string'"
+    # String:
+    if schema.format:
+        # TODO: Only include valid HTML5 formats.
+        #       Coerce datetime to datetime-local.
+        return "data-empty='' data-type='string' type='%s'" % schema.format
+    return "data-empty='' data-type='string'"
diff --git a/coreschema/encodings/jsonschema.py b/coreschema/encodings/jsonschema.py
new file mode 100644
index 0000000..df6e76b
--- /dev/null
+++ b/coreschema/encodings/jsonschema.py
@@ -0,0 +1,215 @@
+from coreschema.compat import text_types
+import coreschema
+import re
+
+
+jsonschema = coreschema.RefSpace({
+    'Schema': coreschema.Object(
+        properties={
+            # Meta
+            'id': coreschema.String(format='uri'),
+            '$schema': coreschema.String(format='uri'),
+            'title': coreschema.String(),
+            'description': coreschema.String(),
+            'default': coreschema.Anything(),
+            'definitions': coreschema.Ref('SchemaMap'),
+            # Type
+            'type': coreschema.Ref('SimpleTypes') | coreschema.Array(items=coreschema.Ref('SimpleTypes'), min_items=1, unique_items=True),
+            # Number validators
+            'minimum': coreschema.Number(),
+            'maximum': coreschema.Number(),
+            'exclusiveMinimum': coreschema.Boolean(default=False),
+            'exclusiveMaximum': coreschema.Boolean(default=False),
+            'multipleOf': coreschema.Number(minimum=0, exclusive_minimum=True),
+            # String validators
+            'minLength': coreschema.Integer(minimum=0, default=0),
+            'maxLength': coreschema.Integer(minimum=0),
+            'pattern': coreschema.String(format='regex'),
+            'format': coreschema.String(),
+            # Array validators
+            'items': coreschema.Ref('Schema') | coreschema.Ref('SchemaArray'), # TODO: default={}
+            'additionalItems': coreschema.Boolean() | coreschema.Ref('Schema'),  # TODO: default={}
+            'minItems': coreschema.Integer(minimum=0, default=0),
+            'maxItems': coreschema.Integer(minimum=0),
+            'uniqueItems': coreschema.Boolean(default=False),
+            # Object validators
+            'properties': coreschema.Ref('SchemaMap'),
+            'patternProperties': coreschema.Ref('SchemaMap'),
+            'additionalProperties': coreschema.Boolean() | coreschema.Ref('Schema'),
+            'minProperties': coreschema.Integer(minimum=0, default=0),
+            'maxProperties': coreschema.Integer(minimum=0),
+            'required': coreschema.Ref('StringArray'),
+            'dependancies': coreschema.Object(additional_properties=coreschema.Ref('Schema') | coreschema.Ref('StringArray')),
+            # Enum validators
+            'enum': coreschema.Array(min_items=1, unique_items=True),
+            # Composites
+            'allOf': coreschema.Ref('SchemaArray'),
+            'anyOf': coreschema.Ref('SchemaArray'),
+            'oneOf': coreschema.Ref('SchemaArray'),
+            'not': coreschema.Ref('Schema')
+        },
+        # dependancies=..., TODO
+        default={},
+    ),
+    'SchemaArray': coreschema.Array(
+        items=coreschema.Ref('Schema'),
+        min_items=1,
+    ),
+    'SchemaMap': coreschema.Object(
+        additional_properties=coreschema.Ref('Schema'),
+        default={},
+    ),
+    'SimpleTypes': coreschema.Enum(
+        enum=['array', 'boolean', 'integer', 'null', 'number', 'object', 'string']
+    ),
+    'StringArray': coreschema.Array(
+        items=coreschema.String(),
+        min_items=1,
+        unique_items=True,
+    )
+}, root='Schema')
+
+
+KEYWORD_TO_TYPE = {
+    'minimum': 'number',
+    'maximum': 'number',
+    'exclusiveMinimum': 'number',
+    'exclusiveMaximum': 'number',
+    'multipleOf': 'number',
+    #
+    'minLength': 'string',
+    'maxLength': 'string',
+    'pattern': 'string',
+    'format': 'string',
+    #
+    'items': 'array',
+    'maxItems': 'array',
+    'minItems': 'array',
+    'uniqueItems': 'array',
+    'additionalItems': 'array',
+    #
+    'properties': 'object',
+    'maxProperties': 'object',
+    'minProperties': 'object',
+    'additionalProperties': 'object',
+    'patternProperties': 'object',
+    'required': 'object',
+}
+TYPE_NAMES = [
+    'array', 'boolean', 'integer', 'null', 'number', 'object', 'string'
+]
+CLS_MAP = {
+    'array': coreschema.Array,
+    'boolean': coreschema.Boolean,
+    'integer': coreschema.Integer,
+    'null': coreschema.Null,
+    'number': coreschema.Number,
+    'object': coreschema.Object,
+    'string': coreschema.String,
+}
+
+
+def camelcase_to_snakecase(name):
+    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
+
+
+def get_typed_schemas(data):
+    """
+    Return a list of schemas for any primitive type restrictions.
+    """
+    has_type = False
+    type_kwargs = {type_name: {} for type_name in TYPE_NAMES}
+    for keyword, value in data.items():
+        if keyword not in KEYWORD_TO_TYPE:
+            continue
+
+        # Load any nested schemas
+        if keyword == 'items' and isinstance(value, dict):
+            value = load_jsonschema(value)
+        elif keyword == 'items' and isinstance(value, list):
+            value = [load_jsonschema(item) for item in value]
+        elif keyword == 'additionalItems' and isinstance(value, dict):
+            value = load_jsonschema(value)
+        elif keyword == 'properties' and isinstance(value, dict):
+            value = {key: load_jsonschema(item) for key, item in value.items()}
+        elif keyword == 'additionalProperties' and isinstance(value, dict):
+            value = load_jsonschema(value)
+        elif keyword == 'patternProperties' and isinstance(value, dict):
+            value = {key: load_jsonschema(item) for key, item in value.items()}
+
+        type_name = KEYWORD_TO_TYPE[keyword]
+        has_type = True
+        argument_name = camelcase_to_snakecase(keyword)
+        type_kwargs[type_name][argument_name] = value
+
+    type_kwargs['integer'] = type_kwargs['number']
+
+    if 'type' in data:
+        has_type = True
+        types = data.get('type')
+        types = types if isinstance(types, list) else [types]
+        for type_name in list(type_kwargs.keys()):
+            if type_name not in types:
+                type_kwargs.pop(type_name)
+
+    schemas = []
+    if has_type:
+        for type_name, kwargs in type_kwargs.items():
+            cls = CLS_MAP[type_name]
+            schemas.append(cls(**kwargs))
+
+    return schemas
+
+
+def get_composite_schemas(data):
+    schemas = []
+    if 'anyOf' in data:
+        value = data['anyOf']
+        schema = coreschema.Union([
+            load_jsonschema(item) for item in value
+        ])
+        schemas.append(schema)
+    if 'allOf' in data:
+        value = data['allOf']
+        schema = coreschema.Intersection([
+            load_jsonschema(item) for item in value
+        ])
+        schemas.append(schema)
+    if 'oneOf' in data:
+        value = data['oneOf']
+        schema = coreschema.ExclusiveUnion([
+            load_jsonschema(item) for item in value
+        ])
+        schemas.append(schema)
+    if 'not' in data:
+        value = data['not']
+        schema = coreschema.Not(load_jsonschema(value))
+        schemas.append(schema)
+    return schemas
+
+
+
+def load_jsonschema(data):
+    schemas = get_typed_schemas(data)
+    if len(schemas) > 1:
+        schemas = [coreschema.Union(schemas)]
+    schemas += get_composite_schemas(data)
+
+    if not schemas:
+        schema = coreschema.Anything()
+    elif len(schemas) == 1:
+        schema = schemas[0]
+    else:
+        schema = coreschema.Intersection(schemas)
+
+    if 'enum' in data:
+        # Restrict enum values by any existing type constraints,
+        # and then use an Enum type.
+        enum_values = [
+            value for value in data['enum']
+            if schema.validate(value) == []
+        ]
+        return coreschema.Enum(enum_values)
+
+    return schema
diff --git a/coreschema/formats.py b/coreschema/formats.py
new file mode 100644
index 0000000..2b5417a
--- /dev/null
+++ b/coreschema/formats.py
@@ -0,0 +1,25 @@
+import re
+
+
+email_pattern = re.compile('^[^@]+@[^@]')
+uri_pattern = re.compile('^[A-Za-z][A-Za-z0-9+.-]+:')
+
+
+def validate_format(value, format):
+    function = {
+        'email': validate_email,
+        'uri': validate_uri
+    }.get(format, unknown_format)
+    return function(value)
+
+
+def unknown_format(value):
+    return value
+
+
+def validate_email(value):
+    return bool(re.match(email_pattern, value))
+
+
+def validate_uri(value):
+    return bool(re.match(uri_pattern, value))
diff --git a/coreschema/schemas.py b/coreschema/schemas.py
new file mode 100644
index 0000000..8a60380
--- /dev/null
+++ b/coreschema/schemas.py
@@ -0,0 +1,506 @@
+from collections import namedtuple
+from coreschema.compat import text_types, numeric_types
+from coreschema.formats import validate_format
+from coreschema.utils import uniq
+import re
+
+
+Error = namedtuple('Error', ['text', 'index'])
+
+
+def push_index(errors, key):
+    return [
+        Error(error.text, [key] + error.index)
+        for error in errors
+    ]
+
+
+# TODO: Properties as OrderedDict if from list of tuples.
+# TODO: null keyword / Nullable
+# TODO: dependancies
+# TODO: remote ref
+# TODO: remaining formats
+# LATER: Enum display values
+# LATER: File
+# LATER: strict, coerce float etc...
+# LATER: decimals
+# LATER: override errors
+
+
+class Schema(object):
+    errors = {}
+
+    def __init__(self, title='', description='', default=None):
+        self.title = title
+        self.description = description
+        self.default = default
+
+    def make_error(self, code):
+        error_string = self.errors[code]
+        params = self.__dict__
+        return Error(error_string.format(**params), [])
+
+    def __or__(self, other):
+        if isinstance(self, Union):
+            self_children = self.children
+        else:
+            self_children = [self]
+
+        if isinstance(other, Union):
+            other_children = other.children
+        else:
+            other_children = [other]
+
+        return Union(self_children + other_children)
+
+    def __and__(self, other):
+        if isinstance(self, Intersection):
+            self_children = self.children
+        else:
+            self_children = [self]
+
+        if isinstance(other, Intersection):
+            other_children = other.children
+        else:
+            other_children = [other]
+
+        return Intersection(self_children + other_children)
+
+    def __xor__(self, other):
+        return ExclusiveUnion([self, other])
+
+    def __invert__(self):
+        return Not(self)
+
+    def __eq__(self, other):
+        return (
+            self.__class__ == other.__class__ and
+            self.__dict__ == other.__dict__
+        )
+
+
+class Object(Schema):
+    errors = {
+        'type': 'Must be an object.',
+        'invalid_key': 'Object keys must be strings.',
+        'empty': 'Must not be empty.',
+        'required': 'This field is required.',
+        'max_properties': 'Must have no more than {max_properties} properties.',
+        'min_properties': 'Must have at least {min_properties} properties.',
+        'invalid_property': 'Invalid property.'
+    }
+
+    def __init__(self, properties=None, required=None, max_properties=None, min_properties=None, pattern_properties=None, additional_properties=True, **kwargs):
+        super(Object, self).__init__(**kwargs)
+
+        if isinstance(additional_properties, bool):
+            # Handle `additional_properties` set to a boolean.
+            self.additional_properties_schema = Anything()
+        else:
+            # Handle `additional_properties` set to a schema.
+            self.additional_properties_schema = additional_properties
+            additional_properties = True
+
+        self.properties = properties
+        self.required = required or []
+        self.max_properties = max_properties
+        self.min_properties = min_properties
+        self.pattern_properties = pattern_properties
+        self.additional_properties = additional_properties
+
+        # Compile pattern regexes.
+        self.pattern_properties_regex = None
+        if pattern_properties is not None:
+            self.pattern_properties_regex = {
+                re.compile(key): value
+                for key, value
+                in pattern_properties.items()
+            }
+
+    def validate(self, value, context=None):
+        if not isinstance(value, dict):
+            return [self.make_error('type')]
+
+        errors = []
+        if any(not isinstance(key, text_types) for key in value.keys()):
+            errors += [self.make_error('invalid_key')]
+        if self.required is not None:
+            for key in self.required:
+                if key not in value:
+                    error_items = [self.make_error('required')]
+                    errors += push_index(error_items, key)
+        if self.min_properties is not None:
+            if len(value) < self.min_properties:
+                if self.min_properties == 1:
+                    errors += [self.make_error('empty')]
+                else:
+                    errors += [self.make_error('min_properties')]
+        if self.max_properties is not None:
+            if len(value) > self.max_properties:
+                errors += [self.make_error('max_properties')]
+
+        # Properties
+        remaining_keys = set(value.keys())
+        if self.properties is not None:
+            remaining_keys -= set(self.properties.keys())
+            for key, property_item in self.properties.items():
+                if key not in value:
+                    continue
+                error_items = property_item.validate(value[key], context)
+                errors += push_index(error_items, key)
+
+        # Pattern properties
+        if self.pattern_properties is not None:
+            for key in list(remaining_keys):
+                for pattern, schema in self.pattern_properties_regex.items():
+                    if re.search(pattern, key):
+                        error_items = schema.validate(value[key], context)
+                        errors += push_index(error_items, key)
+                        remaining_keys.discard(key)
+
+        # Additional properties
+        if self.additional_properties:
+            for key in remaining_keys:
+                error_items = self.additional_properties_schema.validate(value[key], context)
+                errors += push_index(error_items, key)
+        else:
+            for key in remaining_keys:
+                error_items = [self.make_error('invalid_property')]
+                errors += push_index(error_items, key)
+
+        return errors
+
+
+class Array(Schema):
+    errors = {
+        'type': 'Must be an array.',
+        'empty': 'Must not be empty.',
+        'max_items': 'Must have no more than {max_items} items.',
+        'min_items': 'Must have at least {min_items} items.',
+        'unique': 'Must not contain duplicate items.'
+    }
+
+    def __init__(self, items=None, max_items=None, min_items=None, unique_items=False, additional_items=True, **kwargs):
+        super(Array, self).__init__(**kwargs)
+
+        if items is None:
+            items = Anything()
+
+        if isinstance(items, list) and additional_items is False:
+            # Setting additional_items==False implies a value for max_items.
+            if max_items is None or max_items > len(items):
+                max_items = len(items)
+
+        self.items = items
+        self.max_items = max_items
+        self.min_items = min_items
+        self.unique_items = unique_items
+        self.additional_items = additional_items
+
+    def validate(self, value, context=None):
+        if not isinstance(value, list):
+            return [self.make_error('type')]
+
+        errors = []
+        if self.items is not None:
+            child_schema = self.items
+            is_list = isinstance(self.items, list)
+            for idx, item in enumerate(value):
+                if is_list:
+                    # Case where `items` is a list of schemas.
+                    if idx < len(self.items):
+                        # Handle each item in the list.
+                        child_schema = self.items[idx]
+                    else:
+                        # Handle any additional items.
+                        if isinstance(self.additional_items, bool):
+                            break
+                        else:
+                            child_schema = self.additional_items
+                error_items = child_schema.validate(item, context)
+                errors += push_index(error_items, idx)
+        if self.min_items is not None:
+            if len(value) < self.min_items:
+                if self.min_items == 1:
+                    errors += [self.make_error('empty')]
+                else:
+                    errors += [self.make_error('min_items')]
+        if self.max_items is not None:
+            if len(value) > self.max_items:
+                errors += [self.make_error('max_items')]
+        if self.unique_items:
+            if not(uniq(value)):
+                errors += [self.make_error('unique')]
+
+        return errors
+
+
+class Number(Schema):
+    integer_only = False
+    errors = {
+        'type': 'Must be a number.',
+        'minimum': 'Must be greater than or equal to {minimum}.',
+        'exclusive_minimum': 'Must be greater than {minimum}.',
+        'maximum': 'Must be less than or equal to {maximum}.',
+        'exclusive_maximum': 'Must be less than {maximum}.',
+        'multiple_of': 'Must be a multiple of {multiple_of}.',
+    }
+
+    def __init__(self, minimum=None, maximum=None, exclusive_minimum=False, exclusive_maximum=False, multiple_of=None, **kwargs):
+        super(Number, self).__init__(**kwargs)
+        self.minimum = minimum
+        self.maximum = maximum
+        self.exclusive_minimum = exclusive_minimum
+        self.exclusive_maximum = exclusive_maximum
+        self.multiple_of = multiple_of
+
+    def validate(self, value, context=None):
+        if isinstance(value, bool):
+            # In Python `bool` subclasses `int`, so handle that case explicitly.
+            return [self.make_error('type')]
+        if not isinstance(value, numeric_types):
+            return [self.make_error('type')]
+        if self.integer_only and isinstance(value, float) and not value.is_integer():
+            return [self.make_error('type')]
+
+        errors = []
+        if self.minimum is not None:
+            if self.exclusive_minimum:
+                if value <= self.minimum:
+                    errors += [self.make_error('exclusive_minimum')]
+            else:
+                if value < self.minimum:
+                    errors += [self.make_error('minimum')]
+        if self.maximum is not None:
+            if self.exclusive_maximum:
+                if value >= self.maximum:
+                    errors += [self.make_error('exclusive_maximum')]
+            else:
+                if value > self.maximum:
+                    errors += [self.make_error('maximum')]
+        if self.multiple_of is not None:
+            if isinstance(self.multiple_of, float):
+                failed = not (float(value) / self.multiple_of).is_integer()
+            else:
+                failed = value % self.multiple_of
+            if failed:
+                errors += [self.make_error('multiple_of')]
+        return errors
+
+
+class Integer(Number):
+    errors = {
+        'type': 'Must be an integer.',
+        'minimum': 'Must be greater than or equal to {minimum}.',
+        'exclusive_minimum': 'Must be greater than {minimum}.',
+        'maximum': 'Must be less than or equal to {maximum}.',
+        'exclusive_maximum': 'Must be less than {maximum}.',
+        'multiple_of': 'Must be a multiple of {multiple_of}.',
+    }
+    integer_only = True
+
+
+class String(Schema):
+    errors = {
+        'type': 'Must be a string.',
+        'blank': 'Must not be blank.',
+        'max_length': 'Must have no more than {max_length} characters.',
+        'min_length': 'Must have at least {min_length} characters.',
+        'pattern': 'Must match the pattern /{pattern}/.',
+        'format': 'Must be a valid {format}.',
+    }
+
+    def __init__(self, max_length=None, min_length=None, pattern=None, format=None, **kwargs):
+        super(String, self).__init__(**kwargs)
+        self.max_length = max_length
+        self.min_length = min_length
+        self.pattern = pattern
+        self.format = format
+
+        self.pattern_regex = None
+        if self.pattern is not None:
+            self.pattern_regex = re.compile(pattern)
+
+    def validate(self, value, context=None):
+        if not isinstance(value, text_types):
+            return [self.make_error('type')]
+
+        errors = []
+        if self.min_length is not None:
+            if len(value) < self.min_length:
+                if self.min_length == 1:
+                    errors += [self.make_error('blank')]
+                else:
+                    errors += [self.make_error('min_length')]
+        if self.max_length is not None:
+            if len(value) > self.max_length:
+                errors += [self.make_error('max_length')]
+        if self.pattern is not None:
+            if not re.search(self.pattern_regex, value):
+                errors += [self.make_error('pattern')]
+        if self.format is not None:
+            if not validate_format(value, self.format):
+                errors += [self.make_error('format')]
+        return errors
+
+
+class Boolean(Schema):
+    errors = {
+        'type': 'Must be a boolean.'
+    }
+
+    def validate(self, value, context=None):
+        if not isinstance(value, bool):
+            return [self.make_error('type')]
+        return []
+
+
+class Null(Schema):
+    errors = {
+        'type': 'Must be null.'
+    }
+
+    def validate(self, value, context=None):
+        if value is not None:
+            return [self.make_error('type')]
+        return []
+
+
+class Enum(Schema):
+    errors = {
+        'enum': 'Must be one of {enum}.',
+        'exact': 'Must be {exact}.',
+    }
+
+    def __init__(self, enum, **kwargs):
+        super(Enum, self).__init__(**kwargs)
+
+        self.enum = enum
+        if len(enum) == 1:
+            self.exact = repr(enum[0])
+
+    def validate(self, value, context=None):
+        if value not in self.enum:
+            if len(self.enum) == 1:
+                return [self.make_error('exact')]
+            return [self.make_error('enum')]
+        return []
+
+
+class Anything(Schema):
+    errors = {
+        'type': 'Must be a valid primitive type.'
+    }
+    types = text_types + (dict, list, int, float, bool, type(None))
+
+    def validate(self, value, context=None):
+        if not isinstance(value, self.types):
+            return [self.make_error('type')]
+
+        errors = []
+        if isinstance(value, list):
+            schema = Array()
+            errors += schema.validate(value, context)
+        elif isinstance(value, dict):
+            schema = Object()
+            errors += schema.validate(value, context)
+        return errors
+
+
+# Composites
+
+class Union(Schema):
+    errors = {
+        'match': 'Must match one of the options.'
+    }
+
+    def __init__(self, children, **kwargs):
+        super(Union, self).__init__(**kwargs)
+
+        self.children = children
+
+    def validate(self, value, context=None):
+        for child in self.children:
+            if child.validate(value, context) == []:
+                return []
+        return [self.make_error('match')]
+
+
+class Intersection(Schema):
+    def __init__(self, children, **kwargs):
+        super(Intersection, self).__init__(**kwargs)
+        self.children = children
+
+    def validate(self, value, context=None):
+        errors = []
+        for child in self.children:
+            errors.extend(child.validate(value, context))
+        return errors
+
+
+class ExclusiveUnion(Schema):
+    errors = {
+        'match': 'Must match one of the options.',
+        'match_only_one': 'Must match only one of the options.'
+    }
+
+    def __init__(self, children, **kwargs):
+        super(ExclusiveUnion, self).__init__(**kwargs)
+
+        self.children = children
+
+    def validate(self, value, context=None):
+        matches = 0
+        for child in self.children:
+            if child.validate(value, context) == []:
+                matches += 1
+        if not matches:
+            return [self.make_error('match')]
+        elif matches > 1:
+            return [self.make_error('match_only_one')]
+        return []
+
+
+class Not(Schema):
+    errors = {
+        'must_not_match': 'Must not match the option.'
+    }
... 482 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/coreschema.git



More information about the Python-modules-commits mailing list