diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..078457d --- /dev/null +++ b/.coveragerc @@ -0,0 +1,16 @@ +[paths] +source = + src + */site-packages + +[run] +branch = True +source = wtfjson + +[report] +show_missing = True + +[html] +directory = reports/coverage_html/ +skip_empty = True +show_contexts = True diff --git a/.gitignore b/.gitignore index 9ede894..52ab07b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ __pycache__/ /venv /.tox /.eggs +/.coverage /build /dist +/reports /src/wtfjson.egg-info diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2842915 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +.PHONY: all tox open-coverage clean clean-all + +# Default target +all: tox + + +# Test suite +# ---------- +tox: + tox + +flake8: + tox -e flake8 + +# Open HTML coverage report in browser +open-coverage: + $(or $(BROWSER),firefox) ./reports/coverage_html/index.html + + +# Cleanup +# ------- +clean: + rm -rf .coverage reports + +clean-all: clean + rm -rf .tox .eggs venv diff --git a/pytest.ini b/pytest.ini index 42c85e6..60081eb 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,13 @@ [pytest] -python_files = *Test.py +addopts = + -ra + --import-mode=importlib + --cov-context=test + --cov-report= + +testpaths = tests +python_files = *_test.py *Test.py python_classes = *Test + +# Fail on warnings +filterwarnings = error diff --git a/src/wtfjson/__init__.py b/src/wtfjson/__init__.py index c359ea3..b564934 100644 --- a/src/wtfjson/__init__.py +++ b/src/wtfjson/__init__.py @@ -6,6 +6,6 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from .DictInput import DictInput -from .ListInput import ListInput - +from .abstract_input import AbstractInput +from .dict_input import DictInput +from .list_input import ListInput diff --git a/src/wtfjson/abstract_input.py b/src/wtfjson/abstract_input.py new file mode 100644 index 0000000..ab94bae --- /dev/null +++ b/src/wtfjson/abstract_input.py @@ -0,0 +1,52 @@ +# encoding: utf-8 + +""" +binary butterfly validator +Copyright (c) 2021, binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from abc import ABC, abstractmethod + +from .exceptions import NotValidated, InvalidData + + +class AbstractInput(ABC): + _errors: dict + _validated: bool = False + + def __init__(self): + self._errors = {} + self._validated = False + + @abstractmethod # pragma: nocover + def validate(self) -> bool: + raise NotImplementedError() + + @property + def has_errors(self) -> bool: + if not self._validated: + raise NotValidated() + return len(self._errors.keys()) > 0 + + @property + def errors(self) -> dict: + if not self._validated: + raise NotValidated() + return self._errors + + def _ensure_validated(self) -> None: + if not self._validated: + raise NotValidated() + if self.has_errors: + raise InvalidData() + + @property + @abstractmethod # pragma: nocover + def data(self): + raise NotImplementedError() + + @property + @abstractmethod # pragma: nocover + def out(self): + raise NotImplementedError() diff --git a/src/wtfjson/DictInput.py b/src/wtfjson/dict_input.py similarity index 70% rename from src/wtfjson/DictInput.py rename to src/wtfjson/dict_input.py index e7ef289..d6c221e 100644 --- a/src/wtfjson/DictInput.py +++ b/src/wtfjson/dict_input.py @@ -6,25 +6,23 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from abc import ABC from typing import List, Any, Optional +from .abstract_input import AbstractInput from .fields import UnboundField from .validators import Validator -from .exceptions import NotValidated, InvalidData from .util import unset_value -class DictInput(ABC): +class DictInput(AbstractInput): _fields: dict - _errors: dict - _validated: bool = False _validators: List[Validator] def __init__(self, data: Any): + super().__init__() + # first: init vars self._fields = {} - self._errors = {} self._validators = [] # second: init fields @@ -49,45 +47,24 @@ def validate(self) -> bool: return not self.has_errors def populate_obj(self, obj, exclude: Optional[List[str]] = None): - if not self._validated: - raise NotValidated() - if self.has_errors: - raise InvalidData() + self._ensure_validated() for field_name, field in self._fields.items(): if exclude is None or field_name not in exclude: if field.out is not unset_value: setattr(obj, field.populate_to if field.populate_to else field_name, field.out) def to_dataclass(self, dataclass): + self._ensure_validated() if hasattr(dataclass, 'from_dict'): return dataclass.from_dict(**self.out) return dataclass(**self.out) - @property - def has_errors(self) -> bool: - if not self._validated: - raise NotValidated() - return len(self._errors.keys()) > 0 - - @property - def errors(self) -> dict: - if not self._validated: - raise NotValidated() - return self._errors - @property def data(self): - if not self._validated: - raise NotValidated() - if self.has_errors: - raise InvalidData() - return {field_name: field.data for field_name, field in self._fields.items()} #TODO: get fields back + self._ensure_validated() + return {field_name: field.data for field_name, field in self._fields.items()} # TODO: get fields back @property def out(self): - if not self._validated: - raise NotValidated() - if self.has_errors: - raise InvalidData() + self._ensure_validated() return {field_name: field.out for field_name, field in self._fields.items() if field.out is not unset_value} - diff --git a/src/wtfjson/exceptions.py b/src/wtfjson/exceptions.py index 1240355..d5a4b0b 100644 --- a/src/wtfjson/exceptions.py +++ b/src/wtfjson/exceptions.py @@ -47,7 +47,7 @@ class NotValidated(Exception): class InvalidData(Exception): """ - will be thrown if somebodu tries to access data which is not there + will be thrown if somebody tries to access data which is not there """ pass diff --git a/src/wtfjson/fields/SqlachemyField.py b/src/wtfjson/fields/SqlachemyField.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/wtfjson/fields/__init__.py b/src/wtfjson/fields/__init__.py index 822f841..ddbdfa4 100644 --- a/src/wtfjson/fields/__init__.py +++ b/src/wtfjson/fields/__init__.py @@ -6,15 +6,15 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from .Field import Field -from .UnboundField import UnboundField -from .BooleanField import BooleanField -from .StringField import StringField -from .IntegerField import IntegerField -from .EnumField import EnumField -from .DecimalField import DecimalField -from .ListField import ListField -from .ObjectField import ObjectField -from .DateField import DateField -from .DateTimeField import DateTimeField -from .FloatField import FloatField +from .field import Field +from .unbound_field import UnboundField +from .boolean_field import BooleanField +from .string_field import StringField +from .integer_field import IntegerField +from .enum_field import EnumField +from .decimal_field import DecimalField +from .list_field import ListField +from .object_field import ObjectField +from .date_field import DateField +from .date_time_field import DateTimeField +from .float_field import FloatField diff --git a/src/wtfjson/fields/BooleanField.py b/src/wtfjson/fields/boolean_field.py similarity index 81% rename from src/wtfjson/fields/BooleanField.py rename to src/wtfjson/fields/boolean_field.py index ffe1749..90e9d39 100644 --- a/src/wtfjson/fields/BooleanField.py +++ b/src/wtfjson/fields/boolean_field.py @@ -7,10 +7,10 @@ """ from ..fields import Field -from ..validators import Type +from ..validators import IsType class BooleanField(Field): pre_validators = [ - Type(data_type=bool) + IsType(data_type=bool) ] diff --git a/src/wtfjson/fields/DateField.py b/src/wtfjson/fields/date_field.py similarity index 88% rename from src/wtfjson/fields/DateField.py rename to src/wtfjson/fields/date_field.py index fbfbae6..63f3c60 100644 --- a/src/wtfjson/fields/DateField.py +++ b/src/wtfjson/fields/date_field.py @@ -8,14 +8,15 @@ from typing import Union from datetime import date + from ..fields import Field -from ..validators import Type, Date +from ..validators import IsType, Date from ..util import UnsetValue class DateField(Field): pre_validators = [ - Type(data_type=str), + IsType(data_type=str), Date() ] diff --git a/src/wtfjson/fields/DateTimeField.py b/src/wtfjson/fields/date_time_field.py similarity index 90% rename from src/wtfjson/fields/DateTimeField.py rename to src/wtfjson/fields/date_time_field.py index 307d4ce..660aac5 100644 --- a/src/wtfjson/fields/DateTimeField.py +++ b/src/wtfjson/fields/date_time_field.py @@ -8,8 +8,9 @@ from typing import Union from datetime import datetime + from ..fields import Field -from ..validators import Type, DateTime +from ..validators import IsType, DateTime from ..util import UnsetValue @@ -17,7 +18,7 @@ class DateTimeField(Field): def __init__(self, localized: bool = False, accept_utc=False, *args, **kwargs): super().__init__(*args, **kwargs) self.pre_validators = [ - Type(data_type=str), + IsType(data_type=str), DateTime(localized=localized, accept_utc=accept_utc) ] diff --git a/src/wtfjson/fields/DecimalField.py b/src/wtfjson/fields/decimal_field.py similarity index 87% rename from src/wtfjson/fields/DecimalField.py rename to src/wtfjson/fields/decimal_field.py index f3837c8..4473699 100644 --- a/src/wtfjson/fields/DecimalField.py +++ b/src/wtfjson/fields/decimal_field.py @@ -8,14 +8,15 @@ from typing import Union from decimal import Decimal + from ..fields import Field -from ..validators import Type, DecimalValidator +from ..validators import IsType, DecimalValidator from ..util import UnsetValue class DecimalField(Field): pre_validators = [ - Type(data_type=str), + IsType(data_type=str), DecimalValidator() ] diff --git a/src/wtfjson/fields/EnumField.py b/src/wtfjson/fields/enum_field.py similarity index 89% rename from src/wtfjson/fields/EnumField.py rename to src/wtfjson/fields/enum_field.py index 5467855..0456a4b 100644 --- a/src/wtfjson/fields/EnumField.py +++ b/src/wtfjson/fields/enum_field.py @@ -8,8 +8,9 @@ from enum import Enum from typing import Union + from ..fields import Field -from ..validators import Type, EnumValidator +from ..validators import IsType, EnumValidator from ..util import UnsetValue @@ -19,7 +20,7 @@ def __init__(self, enum, *args, **kwargs): super().__init__(*args, **kwargs) self.enum = enum self.default_validators = [ - Type(data_type=str), + IsType(data_type=str), EnumValidator(enum=enum) ] diff --git a/src/wtfjson/fields/Field.py b/src/wtfjson/fields/field.py similarity index 95% rename from src/wtfjson/fields/Field.py rename to src/wtfjson/fields/field.py index facc295..9377211 100644 --- a/src/wtfjson/fields/Field.py +++ b/src/wtfjson/fields/field.py @@ -10,13 +10,11 @@ from enum import Enum from copy import deepcopy from typing import List, Callable, Optional, Any + +from ..abstract_input import AbstractInput from ..util import unset_value from ..exceptions import ValidationError, StopValidation, ClearValidation -from .UnboundField import UnboundField - -from typing import TYPE_CHECKING -if TYPE_CHECKING: - import Form +from .unbound_field import UnboundField class FieldState(Enum): @@ -28,7 +26,7 @@ class FieldState(Enum): class Field(ABC): state: FieldState - _form: 'Form' + _form: AbstractInput _field_name: str data_raw: Any # raw input data data_processed: Any # data after input filters @@ -46,7 +44,7 @@ class Field(ABC): required: bool def __init__(self, - form: 'Form' = None, + form: AbstractInput = None, field_name: str = None, description: Optional[str] = None, input_filters: Optional[list] = None, @@ -100,7 +98,7 @@ def pre_validate(self): except StopValidation as error: self.append_error(error.message) self.validation_stopped = True - except ClearValidation as no_error: + except ClearValidation: self._errors = {} self.validation_stopped = False @@ -127,7 +125,7 @@ def validate(self) -> bool: self.append_error(error.message) except StopValidation as error: self.append_error(error.message) - except ClearValidation as no_error: + except ClearValidation: self._errors = {} self.state = FieldState.validated return not self.has_errors diff --git a/src/wtfjson/fields/FloatField.py b/src/wtfjson/fields/float_field.py similarity index 88% rename from src/wtfjson/fields/FloatField.py rename to src/wtfjson/fields/float_field.py index 14fd45f..49550e0 100644 --- a/src/wtfjson/fields/FloatField.py +++ b/src/wtfjson/fields/float_field.py @@ -7,14 +7,15 @@ """ from typing import Union + from ..fields import Field -from ..validators import Type +from ..validators import IsType from ..util import UnsetValue class FloatField(Field): pre_validators = [ - Type(data_type=float) + IsType(data_type=float) ] @property diff --git a/src/wtfjson/fields/IntegerField.py b/src/wtfjson/fields/integer_field.py similarity index 89% rename from src/wtfjson/fields/IntegerField.py rename to src/wtfjson/fields/integer_field.py index 9da53f8..d90aa21 100644 --- a/src/wtfjson/fields/IntegerField.py +++ b/src/wtfjson/fields/integer_field.py @@ -7,14 +7,15 @@ """ from typing import Union + from ..fields import Field -from ..validators import Type +from ..validators import IsType from ..util import UnsetValue class IntegerField(Field): pre_validators = [ - Type(data_type=int) + IsType(data_type=int) ] @property diff --git a/src/wtfjson/fields/ListField.py b/src/wtfjson/fields/list_field.py similarity index 95% rename from src/wtfjson/fields/ListField.py rename to src/wtfjson/fields/list_field.py index 6044b39..b5bff4b 100644 --- a/src/wtfjson/fields/ListField.py +++ b/src/wtfjson/fields/list_field.py @@ -8,9 +8,9 @@ from typing import Optional, Any, List, Union -from .Field import FieldState +from .field import FieldState from ..fields import Field, UnboundField -from ..validators import Type, ListLength +from ..validators import IsType, ListLength from ..util import unset_value, UnsetValue @@ -27,7 +27,7 @@ def __init__(self, assert min_entries >= 0 assert max_entries is None or max_entries >= min_entries self.pre_validators = [ - Type(data_type=list), + IsType(data_type=list), ListLength(min_entries, max_entries) ] self.unbound_field = unbound_field @@ -35,7 +35,7 @@ def __init__(self, def process_in(self, data_raw: Any): super().process_in(data_raw) if self.validation_stopped: - return + return self.entries = [] for item in self.data_processed: entry = self.unbound_field.bind(self._form, '%s.%s' % (self._field_name, len(self.entries))) diff --git a/src/wtfjson/fields/ObjectField.py b/src/wtfjson/fields/object_field.py similarity index 80% rename from src/wtfjson/fields/ObjectField.py rename to src/wtfjson/fields/object_field.py index 557b72d..dc096c3 100644 --- a/src/wtfjson/fields/ObjectField.py +++ b/src/wtfjson/fields/object_field.py @@ -6,29 +6,31 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Any, Union, TYPE_CHECKING +from typing import Any, Union, TYPE_CHECKING, Type + from ..fields import Field -from ..validators import Type +from ..validators import IsType from ..util import unset_value, UnsetValue -if TYPE_CHECKING: - from ..DictInput import DictInput + +if TYPE_CHECKING: # pragma: nocover + from ..dict_input import DictInput class ObjectField(Field): _obj: 'DictInput' = unset_value pre_validators = [ - Type(data_type=dict) + IsType(data_type=dict) ] - def __init__(self, form_class: 'DictInput', *args, **kwargs): + def __init__(self, input_class: Type['DictInput'], *args, **kwargs): super().__init__(*args, **kwargs) - self.form_class = form_class + self.input_class = input_class def process_in(self, data_raw: Any): super().process_in(data_raw) if self.validation_stopped: return - self._obj = self.form_class(data_raw) + self._obj = self.input_class(data_raw) def validate(self) -> bool: super().validate() diff --git a/src/wtfjson/fields/StringField.py b/src/wtfjson/fields/string_field.py similarity index 89% rename from src/wtfjson/fields/StringField.py rename to src/wtfjson/fields/string_field.py index 23b4a89..5163e2c 100644 --- a/src/wtfjson/fields/StringField.py +++ b/src/wtfjson/fields/string_field.py @@ -7,14 +7,15 @@ """ from typing import Union + from ..fields import Field -from ..validators import Type +from ..validators import IsType from ..util import UnsetValue class StringField(Field): pre_validators = [ - Type(data_type=str) + IsType(data_type=str) ] @property diff --git a/src/wtfjson/fields/UnboundField.py b/src/wtfjson/fields/unbound_field.py similarity index 100% rename from src/wtfjson/fields/UnboundField.py rename to src/wtfjson/fields/unbound_field.py diff --git a/src/wtfjson/filter/__init__.py b/src/wtfjson/filter/__init__.py index ce66427..9d3180f 100644 --- a/src/wtfjson/filter/__init__.py +++ b/src/wtfjson/filter/__init__.py @@ -5,4 +5,3 @@ Copyright (c) 2021, binary butterfly GmbH Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ - diff --git a/src/wtfjson/ListInput.py b/src/wtfjson/list_input.py similarity index 64% rename from src/wtfjson/ListInput.py rename to src/wtfjson/list_input.py index 5bc012f..0a32790 100644 --- a/src/wtfjson/ListInput.py +++ b/src/wtfjson/list_input.py @@ -6,25 +6,24 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from copy import deepcopy from typing import Any -from abc import ABC, abstractmethod +from abc import abstractmethod -from .exceptions import NotValidated, InvalidData +from .abstract_input import AbstractInput from .util import unset_value -class ListInput(ABC): +class ListInput(AbstractInput): _fields: list - _errors: dict - _validated: bool = False _validators: list def __init__(self, data: Any): + super().__init__() + if self.field is unset_value: raise Exception('field is required') + # first: init vars - self._errors = {} self._validators = [] # second: init field @@ -39,7 +38,7 @@ def __init__(self, data: Any): self._fields.append(field) @property - @abstractmethod + @abstractmethod # pragma: nocover def field(self): raise NotImplementedError() @@ -52,30 +51,12 @@ def validate(self) -> bool: self._validated = True return not self.has_errors - @property - def has_errors(self) -> bool: - if not self._validated: - raise NotValidated() - return len(self._errors.keys()) > 0 - - @property - def errors(self) -> dict: - if not self._validated: - raise NotValidated() - return self._errors - @property def data(self): - if not self._validated: - raise NotValidated() - if self.has_errors: - raise InvalidData() + self._ensure_validated() return [field.data for field in self._fields] @property def out(self): - if not self._validated: - raise NotValidated() - if self.has_errors: - raise InvalidData() + self._ensure_validated() return [field.out for field in self._fields if field.out is not unset_value] diff --git a/src/wtfjson/validators/__init__.py b/src/wtfjson/validators/__init__.py index a2243b2..48f26d0 100644 --- a/src/wtfjson/validators/__init__.py +++ b/src/wtfjson/validators/__init__.py @@ -6,19 +6,19 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from .Validator import Validator -from .Type import Type -from .EnumValidator import EnumValidator -from .DecimalValidator import DecimalValidator -from .ListLength import ListLength -from .Email import Email -from .Regexp import Regexp -from .URL import URL -from .NoneOf import NoneOf -from .AnyOf import AnyOf -from .Length import Length -from .NumberRange import NumberRange -from .InputRequired import InputRequired -from .Date import Date -from .DateTime import DateTime -from .DateTimeRange import DateTimeRange +from .validator import Validator +from .type import IsType, Type +from .enum_validator import EnumValidator +from .decimal_validator import DecimalValidator +from .list_length import ListLength +from .email import Email +from .regexp import Regexp +from .url import URL +from .none_of import NoneOf +from .any_of import AnyOf +from .length import Length +from .number_range import NumberRange +from .input_required import InputRequired +from .date import Date +from .date_time import DateTime +from .date_time_range import DateTimeRange diff --git a/src/wtfjson/validators/AnyOf.py b/src/wtfjson/validators/any_of.py similarity index 72% rename from src/wtfjson/validators/AnyOf.py rename to src/wtfjson/validators/any_of.py index 9b93488..9079030 100644 --- a/src/wtfjson/validators/AnyOf.py +++ b/src/wtfjson/validators/any_of.py @@ -6,14 +6,12 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Any, Optional, TYPE_CHECKING, Union +from typing import Any, Optional +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class AnyOf(Validator): @@ -23,6 +21,6 @@ def __init__(self, any_of: list, message: Optional[str] = None): super().__init__(message) self.any_of = any_of - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): if value not in self.any_of: raise ValidationError(self.message) diff --git a/src/wtfjson/validators/Date.py b/src/wtfjson/validators/date.py similarity index 75% rename from src/wtfjson/validators/Date.py rename to src/wtfjson/validators/date.py index 5dc5231..a405b99 100644 --- a/src/wtfjson/validators/Date.py +++ b/src/wtfjson/validators/date.py @@ -6,21 +6,18 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Union, TYPE_CHECKING from datetime import date +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class Date(Validator): default_message = 'invalid date' - def __call__(self, value: str, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: str, form: AbstractInput, field: Field): if len(value) != 10 or value[4] != '-' or value[7] != '-': raise ValidationError(self.default_message) try: diff --git a/src/wtfjson/validators/DateTime.py b/src/wtfjson/validators/date_time.py similarity index 87% rename from src/wtfjson/validators/DateTime.py rename to src/wtfjson/validators/date_time.py index 0dfddc2..544df7d 100644 --- a/src/wtfjson/validators/DateTime.py +++ b/src/wtfjson/validators/date_time.py @@ -6,16 +6,12 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Union, TYPE_CHECKING from datetime import datetime, timezone +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError -from ..util import unset_value -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class DateTime(Validator): @@ -26,7 +22,7 @@ def __init__(self, localized: bool = False, accept_utc=False, *args, **kwargs): self.localized = localized self.accept_utc = accept_utc - def __call__(self, value: str, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: str, form: AbstractInput, field: Field): if not self.localized: if self.accept_utc and value[-1] == 'Z': value = value[:-1] diff --git a/src/wtfjson/validators/DateTimeRange.py b/src/wtfjson/validators/date_time_range.py similarity index 86% rename from src/wtfjson/validators/DateTimeRange.py rename to src/wtfjson/validators/date_time_range.py index 034115a..3a1656a 100644 --- a/src/wtfjson/validators/DateTimeRange.py +++ b/src/wtfjson/validators/date_time_range.py @@ -7,15 +7,12 @@ """ from datetime import datetime, timedelta -from typing import Any, Optional, Union, TYPE_CHECKING, Callable -from ..util import unset_value +from typing import Any, Optional, Union, Callable +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError, InvalidData -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class DateTimeRange(Validator): @@ -35,7 +32,7 @@ def __init__(self, self.plus = plus self.orientation = orientation - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): if type(value) is not datetime: return if self.orientation is None: diff --git a/src/wtfjson/validators/DecimalValidator.py b/src/wtfjson/validators/decimal_validator.py similarity index 71% rename from src/wtfjson/validators/DecimalValidator.py rename to src/wtfjson/validators/decimal_validator.py index 908a2e2..a017f8d 100644 --- a/src/wtfjson/validators/DecimalValidator.py +++ b/src/wtfjson/validators/decimal_validator.py @@ -6,21 +6,19 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Any, Union, TYPE_CHECKING +from typing import Any from decimal import Decimal, InvalidOperation +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class DecimalValidator(Validator): default_message = 'invalid decimal' - def __call__(self, value: Any, form: Union['ListInput', 'DictInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): try: field.data_processed = Decimal(value) except InvalidOperation: diff --git a/src/wtfjson/validators/Email.py b/src/wtfjson/validators/email.py similarity index 70% rename from src/wtfjson/validators/Email.py rename to src/wtfjson/validators/email.py index 1735e07..2db0cbe 100644 --- a/src/wtfjson/validators/Email.py +++ b/src/wtfjson/validators/email.py @@ -6,21 +6,19 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Any, Union, TYPE_CHECKING +from typing import Any from email_validator import validate_email, EmailNotValidError +from ..abstract_input import AbstractInput from ..validators import Validator from ..exceptions import ValidationError from ..fields import Field -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class Email(Validator): default_message = 'invalid email' - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): try: validate_email(value) except EmailNotValidError: diff --git a/src/wtfjson/validators/EnumValidator.py b/src/wtfjson/validators/enum_validator.py similarity index 75% rename from src/wtfjson/validators/EnumValidator.py rename to src/wtfjson/validators/enum_validator.py index 49f50a4..d744cdd 100644 --- a/src/wtfjson/validators/EnumValidator.py +++ b/src/wtfjson/validators/enum_validator.py @@ -7,14 +7,12 @@ """ from enum import Enum -from typing import Optional, Any, Union, TYPE_CHECKING, Type +from typing import Optional, Any, Type +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class EnumValidator(Validator): @@ -24,7 +22,7 @@ def __init__(self, enum: Type[Enum], message: Optional[str] = None): super().__init__(message) self.enum: Type[Enum] = enum - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): if type(field.data_processed) is not str or field.data_processed not in [item.value for item in self.enum]: raise ValidationError(self.message) field.data_processed = self.enum(field.data_processed) diff --git a/src/wtfjson/validators/InputRequired.py b/src/wtfjson/validators/input_required.py similarity index 99% rename from src/wtfjson/validators/InputRequired.py rename to src/wtfjson/validators/input_required.py index b3e4572..9a45244 100644 --- a/src/wtfjson/validators/InputRequired.py +++ b/src/wtfjson/validators/input_required.py @@ -7,6 +7,7 @@ """ from typing import Optional + from ..validators import Length diff --git a/src/wtfjson/validators/Length.py b/src/wtfjson/validators/length.py similarity index 80% rename from src/wtfjson/validators/Length.py rename to src/wtfjson/validators/length.py index 6e2feb0..f4ae0a1 100644 --- a/src/wtfjson/validators/Length.py +++ b/src/wtfjson/validators/length.py @@ -6,14 +6,12 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Any, Optional, Union, TYPE_CHECKING +from typing import Any, Optional +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError, InvalidData -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class Length(Validator): @@ -28,9 +26,8 @@ def __init__(self, min: Optional[int] = None, max: Optional[int] = None, message self.min = min self.max = max - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): if self.min is not None and len(value) < self.min: raise ValidationError(self.message) if self.max is not None and len(value) > self.max: raise ValidationError(self.message) - diff --git a/src/wtfjson/validators/ListLength.py b/src/wtfjson/validators/list_length.py similarity index 77% rename from src/wtfjson/validators/ListLength.py rename to src/wtfjson/validators/list_length.py index f78c2fa..d152a48 100644 --- a/src/wtfjson/validators/ListLength.py +++ b/src/wtfjson/validators/list_length.py @@ -6,14 +6,12 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Optional, Any, Union, TYPE_CHECKING +from typing import Optional, Any +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class ListLength(Validator): @@ -27,6 +25,6 @@ def __init__(self, self.min_entries = min_entries self.max_entries = max_entries - def __call__(self, value: Any, form: Union['ListInput', 'DictInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): if len(value) < self.min_entries or (self.max_entries is not None and len(value) > self.max_entries): raise ValidationError(self.message) diff --git a/src/wtfjson/validators/NoneOf.py b/src/wtfjson/validators/none_of.py similarity index 72% rename from src/wtfjson/validators/NoneOf.py rename to src/wtfjson/validators/none_of.py index 931dcfd..f50f669 100644 --- a/src/wtfjson/validators/NoneOf.py +++ b/src/wtfjson/validators/none_of.py @@ -6,14 +6,12 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Any, Optional, Union, TYPE_CHECKING +from typing import Any, Optional +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class NoneOf(Validator): @@ -23,6 +21,6 @@ def __init__(self, none_of: list, message: Optional[str] = None): super().__init__(message) self.none_of = none_of - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): if value in self.none_of: raise ValidationError(self.message) diff --git a/src/wtfjson/validators/NumberRange.py b/src/wtfjson/validators/number_range.py similarity index 80% rename from src/wtfjson/validators/NumberRange.py rename to src/wtfjson/validators/number_range.py index 85d1396..65ba259 100644 --- a/src/wtfjson/validators/NumberRange.py +++ b/src/wtfjson/validators/number_range.py @@ -6,14 +6,12 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Any, Optional, Union, TYPE_CHECKING +from typing import Any, Optional +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError, InvalidData -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class NumberRange(Validator): @@ -28,9 +26,8 @@ def __init__(self, min: Optional[int] = None, max: Optional[int] = None, message self.min = min self.max = max - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): if self.min is not None and value < self.min: raise ValidationError(self.message) if self.max is not None and value > self.max: raise ValidationError(self.message) - diff --git a/src/wtfjson/validators/Regexp.py b/src/wtfjson/validators/regexp.py similarity index 73% rename from src/wtfjson/validators/Regexp.py rename to src/wtfjson/validators/regexp.py index bcab512..da5e4fe 100644 --- a/src/wtfjson/validators/Regexp.py +++ b/src/wtfjson/validators/regexp.py @@ -7,14 +7,12 @@ """ import re -from typing import Any, Optional, Union, Pattern, Match, TYPE_CHECKING +from typing import Any, Optional, Union, Pattern, Match +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import ValidationError -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class Regexp(Validator): @@ -25,7 +23,7 @@ def __init__(self, rule: Union[str, Pattern], flags: int = 0, message: Optional[ super().__init__(message) self.rule = rule if type(rule) is Pattern else re.compile(rule, flags) - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field) -> Match: + def __call__(self, value: Any, form: AbstractInput, field: Field) -> Match: match = self.rule.match(value) if match is None: raise ValidationError(self.message) diff --git a/src/wtfjson/validators/Type.py b/src/wtfjson/validators/type.py similarity index 51% rename from src/wtfjson/validators/Type.py rename to src/wtfjson/validators/type.py index 0f87c17..6a6d521 100644 --- a/src/wtfjson/validators/Type.py +++ b/src/wtfjson/validators/type.py @@ -6,23 +6,32 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Optional, Any, Union, TYPE_CHECKING +from typing import Optional, Any +from warnings import warn +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Validator from ..exceptions import StopValidation -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput -class Type(Validator): +class IsType(Validator): default_message = 'invalid type' def __init__(self, data_type, message: Optional[str] = None): super().__init__(message) self.data_type = data_type - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): if type(value) is not self.data_type: raise StopValidation(self.message) + + +# Deprecated alias for 'IsType' to keep compatibility. Will be removed in a future version. +def Type(*args, **kwargs): # noqa + warn( + "'Type' was renamed to 'IsType' to avoid confusion with 'typing.Type'. Please use 'IsType' instead.", + DeprecationWarning, + stacklevel=2 + ) + return IsType(*args, **kwargs) diff --git a/src/wtfjson/validators/URL.py b/src/wtfjson/validators/url.py similarity index 80% rename from src/wtfjson/validators/URL.py rename to src/wtfjson/validators/url.py index 9442d2c..debd457 100644 --- a/src/wtfjson/validators/URL.py +++ b/src/wtfjson/validators/url.py @@ -7,15 +7,13 @@ """ import re -from typing import Any, Optional, Union, TYPE_CHECKING +from typing import Any, Optional +from ..abstract_input import AbstractInput from ..fields import Field from ..validators import Regexp from ..exceptions import ValidationError from ..external import HostnameValidation -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput class URL(Regexp): @@ -32,7 +30,7 @@ def __init__(self, require_tld: bool = True, allow_ip: bool = True, message: Opt allow_ip=allow_ip, ) - def __call__(self, value: Any, form: Union['DictInput', 'ListInput'], field: Field): + def __call__(self, value: Any, form: AbstractInput, field: Field): match = super().__call__(value, form, field) if not self.validate_hostname(match.group('host')): raise ValidationError(self.message) diff --git a/src/wtfjson/validators/Validator.py b/src/wtfjson/validators/validator.py similarity index 63% rename from src/wtfjson/validators/Validator.py rename to src/wtfjson/validators/validator.py index bd79b70..c26fa37 100644 --- a/src/wtfjson/validators/Validator.py +++ b/src/wtfjson/validators/validator.py @@ -7,12 +7,10 @@ """ from abc import ABC, abstractmethod -from typing import Optional, Any, Union, TYPE_CHECKING -from ..fields import Field +from typing import Optional, Any -if TYPE_CHECKING: - from ..DictInput import DictInput - from ..ListInput import ListInput +from ..abstract_input import AbstractInput +from ..fields import Field class Validator(ABC): @@ -21,8 +19,6 @@ class Validator(ABC): def __init__(self, message: Optional[str] = None): self.message = message if message is not None else self.default_message - @abstractmethod - def __call__(self, value: Any, parent: Union['DictInput', 'ListInput'], field: Field) -> None: + @abstractmethod # pragma: nocover + def __call__(self, value: Any, parent: AbstractInput, field: Field) -> None: pass - - diff --git a/tests/Sqlalchemy.py b/tests/Sqlalchemy.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/BooleanTest.py b/tests/fields/boolean_field_test.py similarity index 71% rename from tests/BooleanTest.py rename to tests/fields/boolean_field_test.py index 0deb800..3dd6a05 100644 --- a/tests/BooleanTest.py +++ b/tests/fields/boolean_field_test.py @@ -15,8 +15,15 @@ class BooleanDictInput(DictInput): test_field = BooleanField() -class BooleanTest(TestCase): - def test_success(self): +class BooleanFieldTest(TestCase): + def test_valid_true(self): + form = BooleanDictInput(data={'test_field': True}) + assert form.validate() is True + assert form.has_errors is False + assert form.errors == {} + assert form.out == {'test_field': True} + + def test_valid_false(self): form = BooleanDictInput(data={'test_field': False}) assert form.validate() is True assert form.has_errors is False diff --git a/tests/DateTest.py b/tests/fields/date_field_test.py similarity index 97% rename from tests/DateTest.py rename to tests/fields/date_field_test.py index e21fa42..68f1804 100644 --- a/tests/DateTest.py +++ b/tests/fields/date_field_test.py @@ -16,7 +16,7 @@ class DateDictInput(DictInput): test_field = DateField() -class DateTest(TestCase): +class DateFieldTest(TestCase): def test_success(self): form = DateDictInput(data={'test_field': '2020-10-01'}) assert form.validate() is True diff --git a/tests/DateTimeTest.py b/tests/fields/date_time_field_test.py similarity index 74% rename from tests/DateTimeTest.py rename to tests/fields/date_time_field_test.py index 4c65d30..ce5198f 100644 --- a/tests/DateTimeTest.py +++ b/tests/fields/date_time_field_test.py @@ -12,75 +12,75 @@ from wtfjson.fields import DateTimeField -class DateDictInput(DictInput): +class DateTimeDictInput(DictInput): test_field = DateTimeField() -class DateDictInputWithZ(DictInput): +class DateTimeDictInputWithZ(DictInput): test_field = DateTimeField(accept_utc=True) -class LocalizedDateDictInput(DictInput): +class LocalizedDateTimeDictInput(DictInput): test_field = DateTimeField(localized=True) -class DateTimeTest(TestCase): +class DateTimeFieldTest(TestCase): def test_success(self): - form = DateDictInput(data={'test_field': '2020-10-01T10:10:12'}) + form = DateTimeDictInput(data={'test_field': '2020-10-01T10:10:12'}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': datetime(2020, 10, 1, 10, 10, 12)} def test_success_with_z(self): - form = DateDictInputWithZ(data={'test_field': '2020-10-01T10:10:12Z'}) + form = DateTimeDictInputWithZ(data={'test_field': '2020-10-01T10:10:12Z'}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': datetime(2020, 10, 1, 10, 10, 12)} def test_fail_with_z(self): - form = DateDictInput(data={'test_field': '2020-10-01T10:10:12Z'}) + form = DateTimeDictInput(data={'test_field': '2020-10-01T10:10:12Z'}) assert form.validate() is False assert form.has_errors is True print(form.errors) assert form.errors == {'test_field': ['invalid datetime']} def test_localized_success(self): - form = LocalizedDateDictInput(data={'test_field': '2020-10-01T10:10:12'}) + form = LocalizedDateTimeDictInput(data={'test_field': '2020-10-01T10:10:12'}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': datetime(2020, 10, 1, 10, 10, 12, tzinfo=timezone.utc)} def test_localized_success_with_z(self): - form = LocalizedDateDictInput(data={'test_field': '2020-10-01T10:10:12Z'}) + form = LocalizedDateTimeDictInput(data={'test_field': '2020-10-01T10:10:12Z'}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': datetime(2020, 10, 1, 10, 10, 12, tzinfo=timezone.utc)} def test_localized_success_with_offset(self): - form = LocalizedDateDictInput(data={'test_field': '2020-10-01T10:10:12+02:00'}) + form = LocalizedDateTimeDictInput(data={'test_field': '2020-10-01T10:10:12+02:00'}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': datetime(2020, 10, 1, 10, 10, 12, tzinfo=timezone(timedelta(seconds=7200)))} def test_invalid_type(self): - form = DateDictInput(data={'test_field': 1}) + form = DateTimeDictInput(data={'test_field': 1}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid type']} def test_invalid_format(self): - form = DateDictInput(data={'test_field': '2020-1x-30T10:10:10'}) + form = DateTimeDictInput(data={'test_field': '2020-1x-30T10:10:10'}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid datetime']} def test_invalid_date(self): - form = DateDictInput(data={'test_field': '2020-10-40T10:10:70'}) + form = DateTimeDictInput(data={'test_field': '2020-10-40T10:10:70'}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid datetime']} diff --git a/tests/DecimalTest.py b/tests/fields/decimal_field_test.py similarity index 78% rename from tests/DecimalTest.py rename to tests/fields/decimal_field_test.py index f30e59c..d979783 100644 --- a/tests/DecimalTest.py +++ b/tests/fields/decimal_field_test.py @@ -12,26 +12,26 @@ from wtfjson.fields import DecimalField -class EnumDictInput(DictInput): +class DecimalDictInput(DictInput): test_field = DecimalField() -class EnumTest(TestCase): +class DecimalFieldTest(TestCase): def test_success(self): - form = EnumDictInput(data={'test_field': '1.3'}) + form = DecimalDictInput(data={'test_field': '1.3'}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': Decimal('1.3')} def test_char_string_input(self): - form = EnumDictInput(data={'test_field': 'cookie'}) + form = DecimalDictInput(data={'test_field': 'cookie'}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid decimal']} def test_float_input(self): - form = EnumDictInput(data={'test_field': 1.3}) + form = DecimalDictInput(data={'test_field': 1.3}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid type']} diff --git a/tests/EnumTest.py b/tests/fields/enum_field_test.py similarity index 97% rename from tests/EnumTest.py rename to tests/fields/enum_field_test.py index 5551fc6..c6d272c 100644 --- a/tests/EnumTest.py +++ b/tests/fields/enum_field_test.py @@ -21,7 +21,7 @@ class EnumDictInput(DictInput): test_field = EnumField(TestEnum) -class EnumTest(TestCase): +class EnumFieldTest(TestCase): def test_success(self): form = EnumDictInput(data={'test_field': 'juicy apple'}) assert form.validate() is True diff --git a/tests/IntegerTest.py b/tests/fields/integer_field_test.py similarity index 96% rename from tests/IntegerTest.py rename to tests/fields/integer_field_test.py index e7261af..42846cc 100644 --- a/tests/IntegerTest.py +++ b/tests/fields/integer_field_test.py @@ -15,7 +15,7 @@ class IntegerDictInput(DictInput): test_field = IntegerField() -class IntegerTest(TestCase): +class IntegerFieldTest(TestCase): def test_success(self): form = IntegerDictInput(data={'test_field': 20}) assert form.validate() is True diff --git a/tests/ListTest.py b/tests/fields/list_field_test.py similarity index 95% rename from tests/ListTest.py rename to tests/fields/list_field_test.py index dbeafd1..8566869 100644 --- a/tests/ListTest.py +++ b/tests/fields/list_field_test.py @@ -7,7 +7,7 @@ """ from unittest import TestCase -from wtfjson import DictInput, ListInput +from wtfjson import DictInput from wtfjson.fields import StringField, ListField @@ -19,7 +19,7 @@ class ListInListDictInput(DictInput): test_field = ListField(ListField(StringField())) -class ListTest(TestCase): +class ListFieldTest(TestCase): def test_success(self): form = StringListDictInput(data={'test_field': ['keks', 'lecker']}) assert form.validate() is True diff --git a/tests/ObjectTest.py b/tests/fields/object_field_test.py similarity index 97% rename from tests/ObjectTest.py rename to tests/fields/object_field_test.py index bb685a2..78eb95d 100644 --- a/tests/ObjectTest.py +++ b/tests/fields/object_field_test.py @@ -20,7 +20,7 @@ class ObjectDictInput(DictInput): test_field = ObjectField(SubObjectDictInput) -class ObjectTest(TestCase): +class ObjectFieldTest(TestCase): def test_success(self): form = ObjectDictInput(data={'test_field': {'test_field_string': 'lecker', 'test_field_int': 10}}) assert form.validate() is True diff --git a/tests/StringTest.py b/tests/fields/string_field_test.py similarity index 96% rename from tests/StringTest.py rename to tests/fields/string_field_test.py index b593614..e9299b8 100644 --- a/tests/StringTest.py +++ b/tests/fields/string_field_test.py @@ -15,7 +15,7 @@ class StringDictInput(DictInput): test_field = StringField() -class StringTest(TestCase): +class StringFieldTest(TestCase): def test_success(self): form = StringDictInput(data={'test_field': 'string'}) assert form.validate() is True diff --git a/tests/UnboundFieldTest.py b/tests/fields/unbound_field_test.py similarity index 94% rename from tests/UnboundFieldTest.py rename to tests/fields/unbound_field_test.py index bfe83dc..f3ca7bb 100644 --- a/tests/UnboundFieldTest.py +++ b/tests/fields/unbound_field_test.py @@ -34,6 +34,6 @@ def test_dict_input(self): assert type(form.field) is StringField def test_list_input(self): - assert type(TestDictInput.field) is UnboundField + assert type(StringListInput.field) is UnboundField form = StringListInput(['string']) assert type(form._fields[0]) is StringField diff --git a/tests/ListInputTest.py b/tests/list_input_test.py similarity index 100% rename from tests/ListInputTest.py rename to tests/list_input_test.py diff --git a/tests/OptionalRequiredTest.py b/tests/optional_required_test.py similarity index 92% rename from tests/OptionalRequiredTest.py rename to tests/optional_required_test.py index acee23c..42a6b9f 100644 --- a/tests/OptionalRequiredTest.py +++ b/tests/optional_required_test.py @@ -21,7 +21,7 @@ class OptionalDictInput(DictInput): ) -class OptionalDictListInput(DictInput): +class OptionalListDictInput(DictInput): test_field = ListField( StringField( validators=[ @@ -49,21 +49,21 @@ def test_optional(self): assert form.out == {} def test_optional_list(self): - form = OptionalDictListInput(data={}) + form = OptionalListDictInput(data={}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {} def test_optional_list_empty_data(self): - form = OptionalDictListInput(data={'test_field': []}) + form = OptionalListDictInput(data={'test_field': []}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': []} def test_optional_list_data(self): - form = OptionalDictListInput(data={'test_field': ['cookie', 'cupcake']}) + form = OptionalListDictInput(data={'test_field': ['cookie', 'cupcake']}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} diff --git a/tests/PopulateToTest.py b/tests/populate_to_test.py similarity index 100% rename from tests/PopulateToTest.py rename to tests/populate_to_test.py diff --git a/tests/ToDataclassTest.py b/tests/to_dataclass_test.py similarity index 95% rename from tests/ToDataclassTest.py rename to tests/to_dataclass_test.py index c631279..db865e2 100644 --- a/tests/ToDataclassTest.py +++ b/tests/to_dataclass_test.py @@ -6,7 +6,6 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from typing import Optional from unittest import TestCase from dataclasses import dataclass from wtfjson import DictInput @@ -32,7 +31,7 @@ def from_dict(cls, **data): test_field: str -class BooleanTest(TestCase): +class ToDataclassTest(TestCase): def test_success(self): form = PopulateDictInput(data={'test_field': 'cookie'}) assert form.validate() is True diff --git a/tests/AnyOfValidatorTest.py b/tests/validators/any_of_test.py similarity index 79% rename from tests/AnyOfValidatorTest.py rename to tests/validators/any_of_test.py index a53b39c..9b93407 100644 --- a/tests/AnyOfValidatorTest.py +++ b/tests/validators/any_of_test.py @@ -12,7 +12,7 @@ from wtfjson.validators import AnyOf -class EnumDictInput(DictInput): +class AnyOfStringDictInput(DictInput): test_field = StringField( validators=[ AnyOf(['cookie', 'vanilla']) @@ -20,22 +20,22 @@ class EnumDictInput(DictInput): ) -class AnyOfValidatorTest(TestCase): +class AnyOfTest(TestCase): def test_success(self): - form = EnumDictInput(data={'test_field': 'cookie'}) + form = AnyOfStringDictInput(data={'test_field': 'cookie'}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': 'cookie'} def test_invalid_type(self): - form = EnumDictInput(data={'test_field': 12}) + form = AnyOfStringDictInput(data={'test_field': 12}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid type']} def test_invalid_value(self): - form = EnumDictInput(data={'test_field': 'chocolate'}) + form = AnyOfStringDictInput(data={'test_field': 'chocolate'}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['is not in any-of list']} diff --git a/tests/DateTimeRangeValidatorTest.py b/tests/validators/date_time_range_test.py similarity index 98% rename from tests/DateTimeRangeValidatorTest.py rename to tests/validators/date_time_range_test.py index 199df95..2a0e020 100644 --- a/tests/DateTimeRangeValidatorTest.py +++ b/tests/validators/date_time_range_test.py @@ -51,7 +51,7 @@ class DateTimeRangeFunctionInput(DictInput): ) -class AnyOfValidatorTest(TestCase): +class DateTimeRangeTest(TestCase): def test_invalid_type(self): form = DateTimeRangeFixedInput(data={'test_field': 12}) assert form.validate() is False @@ -115,6 +115,7 @@ def test_success_function(self): assert form.out == {'test_field': now} def test_success_function_wait(self): + # TODO aaaaaa sleep(1.5) now = datetime.utcnow().replace(microsecond=0) + timedelta(minutes=10) form = DateTimeRangeFunctionInput(data={'test_field': now.strftime('%Y-%m-%dT%H:%M:%S')}) diff --git a/tests/EmailValidatorTest.py b/tests/validators/email_test.py similarity index 97% rename from tests/EmailValidatorTest.py rename to tests/validators/email_test.py index 7b99385..806d5ad 100644 --- a/tests/EmailValidatorTest.py +++ b/tests/validators/email_test.py @@ -20,7 +20,7 @@ class EnumDictInput(DictInput): ) -class EmailValidatorTest(TestCase): +class EmailTest(TestCase): def test_success(self): form = EnumDictInput(data={'test_field': 'mail@binary-butterfly.de'}) assert form.validate() is True diff --git a/tests/LengthValidatorTest.py b/tests/validators/length_test.py similarity index 97% rename from tests/LengthValidatorTest.py rename to tests/validators/length_test.py index 1932833..7403fa1 100644 --- a/tests/LengthValidatorTest.py +++ b/tests/validators/length_test.py @@ -20,7 +20,7 @@ class LengthDictInput(DictInput): ) -class AnyOfValidatorTest(TestCase): +class LengthTest(TestCase): def test_success_min(self): form = LengthDictInput(data={'test_field': 'cookie'}) assert form.validate() is True diff --git a/tests/NoneOfValidatorTest.py b/tests/validators/none_of_test.py similarity index 78% rename from tests/NoneOfValidatorTest.py rename to tests/validators/none_of_test.py index 894707b..f459ded 100644 --- a/tests/NoneOfValidatorTest.py +++ b/tests/validators/none_of_test.py @@ -12,7 +12,7 @@ from wtfjson.validators import NoneOf -class EnumDictInput(DictInput): +class NoneOfStringDictInput(DictInput): test_field = StringField( validators=[ NoneOf(['cookie']) @@ -20,22 +20,22 @@ class EnumDictInput(DictInput): ) -class NoneOfValidatorTest(TestCase): +class NoneOfTest(TestCase): def test_success(self): - form = EnumDictInput(data={'test_field': 'chocolate'}) + form = NoneOfStringDictInput(data={'test_field': 'chocolate'}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': 'chocolate'} def test_invalid_type(self): - form = EnumDictInput(data={'test_field': 12}) + form = NoneOfStringDictInput(data={'test_field': 12}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid type']} def test_invalid_value(self): - form = EnumDictInput(data={'test_field': 'cookie'}) + form = NoneOfStringDictInput(data={'test_field': 'cookie'}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['is in none-of list']} diff --git a/tests/NumberRangeValidatorTest.py b/tests/validators/number_range_test.py similarity index 78% rename from tests/NumberRangeValidatorTest.py rename to tests/validators/number_range_test.py index b81162f..bc0097b 100644 --- a/tests/NumberRangeValidatorTest.py +++ b/tests/validators/number_range_test.py @@ -12,7 +12,7 @@ from wtfjson.validators import NumberRange -class LengthDictInput(DictInput): +class NumberRangeDictInput(DictInput): test_field = IntegerField( validators=[ NumberRange(min=5, max=10) @@ -20,35 +20,35 @@ class LengthDictInput(DictInput): ) -class AnyOfValidatorTest(TestCase): +class NumberRangeTest(TestCase): def test_success_min(self): - form = LengthDictInput(data={'test_field': 5}) + form = NumberRangeDictInput(data={'test_field': 5}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': 5} def test_success_max(self): - form = LengthDictInput(data={'test_field': 10}) + form = NumberRangeDictInput(data={'test_field': 10}) assert form.validate() is True assert form.has_errors is False assert form.errors == {} assert form.out == {'test_field': 10} def test_invalid_type(self): - form = LengthDictInput(data={'test_field': 'cookie'}) + form = NumberRangeDictInput(data={'test_field': 'cookie'}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid type']} def test_invalid_value_min(self): - form = LengthDictInput(data={'test_field': 2}) + form = NumberRangeDictInput(data={'test_field': 2}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid length']} def test_invalid_value_max(self): - form = LengthDictInput(data={'test_field': 20}) + form = NumberRangeDictInput(data={'test_field': 20}) assert form.validate() is False assert form.has_errors is True assert form.errors == {'test_field': ['invalid length']} diff --git a/tests/RegexpValidatorTest.py b/tests/validators/regexp_test.py similarity index 97% rename from tests/RegexpValidatorTest.py rename to tests/validators/regexp_test.py index 7bd3b10..8ce02d9 100644 --- a/tests/RegexpValidatorTest.py +++ b/tests/validators/regexp_test.py @@ -30,7 +30,7 @@ class RegexpStringDictInput(DictInput): ) -class RegexpValidatorTest(TestCase): +class RegexpTest(TestCase): def test_success_pattern(self): form = RegexpPatternDictInput(data={'test_field': '1cookie'}) assert form.validate() is True diff --git a/tests/URLValidatorTest.py b/tests/validators/url_test.py similarity index 98% rename from tests/URLValidatorTest.py rename to tests/validators/url_test.py index 1f92abf..dfe8cc8 100644 --- a/tests/URLValidatorTest.py +++ b/tests/validators/url_test.py @@ -36,7 +36,7 @@ class URLNoTldDictInput(DictInput): ) -class URLValidatorTest(TestCase): +class URLTest(TestCase): def test_success(self): form = URLDictInput(data={'test_field': 'https://binary-butterfly.de'}) assert form.validate() is True diff --git a/tox.ini b/tox.ini index 9e57758..12b87df 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,37 @@ [tox] -envlist = py{39,38,37,py3} +envlist = clean,py{39,38,37,py3},flake8,report skip_missing_interpreters = true +[flake8] +max-line-length = 139 +ignore = +per-file-ignores = + # False positives for "unused imports" in __init__.py + __init__.py: F401 + # Ignore too long lines in external.py + src/wtfjson/external.py: E501 + [testenv] -deps = pytest -commands = pytest +commands = pytest --cov --cov-append +deps = + pytest + pytest-cov + +[testenv:flake8] +skip_install = true +deps = + flake8 +commands = + flake8 {posargs} src/ tests/ + +[testenv:clean] +deps = coverage +skip_install = true +commands = coverage erase + +[testenv:report] +deps = coverage +skip_install = true +commands = + coverage report --skip-empty + coverage html