Skip to content

Commit

Permalink
Merge pull request #13 from Ultimaker/STAR-432_remove-apispec
Browse files Browse the repository at this point in the history
New fields and exceptions
  • Loading branch information
ChrisTerBeke authored Feb 26, 2019
2 parents 40d133f + 4f45cb9 commit 149001f
Show file tree
Hide file tree
Showing 26 changed files with 805 additions and 244 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.py[cod]
.coverage.*

# C extensions
*.so
Expand Down
10 changes: 7 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Install pytest python library as well as add all files in current directory
FROM python:3 AS base
FROM python:3.7 AS base
WORKDIR /usr/src/app
RUN apt-get update \
&& apt-get install -y enchant \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --upgrade pip

RUN pip install coveralls
ADD . .
ADD requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
RUN pip install --no-cache-dir -e .

RUN python ./setup.py test
CMD ["python", "./setup.py", "test"]
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
History
-------

2.4 (2018-12-01)
++++++++++++++++

* Fixed length validator.
* Added Python 3.7 support.

2.3 (2018-02-04)
++++++++++++++++

Expand Down
14 changes: 13 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Features
>>> dog = Dog()
>>> dog.validate()
*** ValidationError: Field "name" is required!
*** FieldValidationError: Error for field 'name': Field is required!
* Cast models to python struct and JSON:
Expand Down Expand Up @@ -337,6 +337,18 @@ Features
>>> compare_schemas(schema1, schema2)
False
* Create custom reusable fields:

.. code-block:: python
class NameField(fields.StringField):
def __init__(self):
super().__init__(required=True)
class Person(models.Base):
name = NameField()
surnames = fields.DerivedListField(NameField())
More
----

Expand Down
2 changes: 1 addition & 1 deletion jsonmodels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

__author__ = 'Szczepan Cieślik'
__email__ = '[email protected]'
__version__ = '2.3'
__version__ = '2.4'
18 changes: 11 additions & 7 deletions jsonmodels/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def __init__(self, model_type, *args, **kwargs):

def add_field(self, name, field, schema):
_apply_validators_modifications(schema, field)
if isinstance(schema, dict) and field.help_text:
schema["description"] = field.help_text
self.properties[name] = schema
if field.required:
self.required.append(name)
Expand All @@ -78,7 +80,7 @@ def build(self):
[self.maybe_build(value) for _, value in self.properties.items()]
return '#/definitions/{name}'.format(name=self.type_name)
else:
return builder.build_definition(nullable=self.nullable)
return builder.build_definition()

@property
def type_name(self):
Expand All @@ -88,7 +90,7 @@ def type_name(self):
)
return module_name.replace('.', '_').lower()

def build_definition(self, add_defintitions=True, nullable=False):
def build_definition(self, add_definitions=True):
properties = dict(
(name, self.maybe_build(value))
for name, value
Expand All @@ -99,11 +101,14 @@ def build_definition(self, add_defintitions=True, nullable=False):
'additionalProperties': False,
'properties': properties,
}

if self.required:
schema['required'] = self.required
if self.definitions and add_defintitions:

if self.definitions and add_definitions:
schema['definitions'] = dict(
(builder.type_name, builder.build_definition(False, False))
(builder.type_name,
builder.build_definition(add_definitions=False))
for builder in self.definitions
)
return schema
Expand Down Expand Up @@ -155,10 +160,9 @@ def build(self):
obj_type = 'number'
elif issubclass(self.type, float):
obj_type = 'number'
schema['format'] = 'float'
else:
raise errors.FieldNotSupported(
"Can't specify value schema!", self.type
)
raise errors.FieldNotSupported(self.type)

if self.nullable:
obj_type = [obj_type, 'null']
Expand Down
1 change: 1 addition & 0 deletions jsonmodels/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ModelCollection(list):
"""

def __init__(self, field):
super(ModelCollection, self).__init__()
self.field = field

def append(self, value):
Expand Down
192 changes: 187 additions & 5 deletions jsonmodels/errors.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,197 @@
from typing import List, Tuple, Type


class ValidationError(RuntimeError):

pass
"""
The base validation error
"""


class FieldNotFound(RuntimeError):

pass
""" Error raised when a field is not found """
def __init__(self, field_name: str):
"""
:param field_name: The name of the field.
"""
super(FieldNotFound, self).__init__('Field not found', field_name)
self.field_name = field_name


class FieldNotSupported(ValueError):
def __init__(self, field_type: Type):
super(FieldNotSupported, self).__init__(
"Can't specify value schema!", field_type
)
self.field_type = field_type


class ValidatorError(ValidationError):
"""
Base error for all errors caused by a validator. These errors do not
contain any information about which field generated them. Models
should catch this error and convert it to a FieldValidationError.
"""


class FieldValidationError(ValidationError):
"""
Enriches a validator error with the name of the field that caused it.
"""
def __init__(self, model_name: str, field_name: str,
given_value: any, error: ValidatorError):
"""
:param model_name: The name of the model.
:param field_name: The name of the field.
:param error: The validator error.
"""
tpl = "Error for field '{name}': {error}"
super(FieldValidationError, self).__init__(tpl.format(
name=field_name, error=error
))
self.model_name = model_name
self.field_name = field_name
self.given_value = given_value
self.error = error


class RequiredFieldError(ValidatorError):
""" Error raised when a required field has no value """
def __init__(self):
super(RequiredFieldError, self).__init__('Field is required!')


class RegexError(ValidatorError):
""" Error raised by the Regex validator """

def __init__(self, value: str, pattern: str):
tpl = 'Value "{value}" did not match pattern "{pattern}".'
super(RegexError, self).__init__(tpl.format(
value=value, pattern=pattern
))
self.value = value
self.pattern = pattern


class BadTypeError(ValidatorError):
"""
Error raised when the user gives a type that does not match the
expected one
"""

def __init__(self, value: any, types: Tuple, is_list: bool):
"""
:param value: The given value.
:param types: The accepted types.
:param is_list: Whether the error occurred in the items of a list.
"""
if is_list:
tpl = 'All items must be instances of "{types}", and not "{type}".'
else:
tpl = 'Value is wrong, expected type "{types}", received {value}.'
super(BadTypeError, self).__init__(tpl.format(
types=', '.join([t.__name__ for t in types]),
value=value,
type=type(value).__name__
))
self.value = value
self.types = types
self.is_array = is_list


class AmbiguousTypeError(ValidatorError):
"""
Error that occurs if the user gives a dictionary to an embedded field
that supports multiple types
"""

def __init__(self, types: Tuple):
""" The types that are allowed """
tpl = 'Cannot decide which type to choose from "{types}".'
super(AmbiguousTypeError, self).__init__(tpl.format(
types=', '.join([t.__name__ for t in types])
))
self.types = types


class MinLengthError(ValidatorError):
""" Error raised by the Length validator when too few items are present """

def __init__(self, value: list, minimum_length: int):
"""
:param value: The given value.
:param minimum_length: The minimum length expected.
"""
tpl = "Value '{value}' length is lower than allowed minimum '{min}'."
super(MinLengthError, self).__init__(tpl.format(
value=value, min=minimum_length
))
self.value = value
self.minimum_length = minimum_length


class MaxLengthError(ValidatorError):
""" Error raised by the Length validator when receiving too many items """

def __init__(self, value: list, maximum_length: int):
"""
:param value: The given value.
:param maximum_length: The maximum length expected.
"""
tpl = "Value '{value}' length is bigger than allowed maximum '{max}'."
super(MaxLengthError, self).__init__(tpl.format(
value=value, max=maximum_length
))
self.value = value
self.maximum_length = maximum_length


class MinValidationError(ValidatorError):
""" Error raised by the Min validator """

def __init__(self, value, minimum_value, exclusive: bool):
"""
:param value: The given value.
:param minimum_value: The minimum value allowed.
:param exclusive: Whether the validation is inclusive or not.
"""
tpl = "'{value}' is lower or equal than minimum ('{min}')." \
if exclusive else "'{value}' is lower than minimum ('{min}')."
super(MinValidationError, self).__init__(tpl.format(
value=value, min=minimum_value
))
self.value = value
self.minimum_value = minimum_value
self.exclusive = exclusive


class MaxValidationError(ValidatorError):
""" Error raised by the Max validator """

def __init__(self, value, maximum_value, exclusive: bool):
"""
:param value: The given value.
:param maximum_value: The maximum value allowed.
:param exclusive: Whether the validation is inclusive or not.
"""
tpl = "'{value}' is bigger or equal than maximum ('{max}')." \
if exclusive else "'{value}' is bigger than maximum ('{max}')."
super(MaxValidationError, self).__init__(tpl.format(
value=value, max=maximum_value
))
self.value = value
self.maximum_value = maximum_value
self.exclusive = exclusive


class EnumError(ValidatorError):
""" Error raised by the Enum validator """

pass
def __init__(self, value: any, choices: List[any]):
"""
:param value: The given value.
:param choices: The allowed choices.
"""
tpl = "Value '{val}' is not a valid choice."
super(EnumError, self).__init__(tpl.format(val=value))
self.value = value
self.choices = choices
Loading

0 comments on commit 149001f

Please sign in to comment.