[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