summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDS/Charlie <82801887+ds-cbo@users.noreply.github.com>2023-02-13 10:41:18 +0100
committerGitHub <noreply@github.com>2023-02-13 20:41:18 +1100
commitbd2f9adc7b38ba28b93bf00fdbd931843a48300c (patch)
treedbf1ac0ac1dba6e69f44e9401f6ed9ad918e0416
parent1105b31432ac5ebb3b0e3238be94b4231c427c67 (diff)
downloadvoluptuous-bd2f9adc7b38ba28b93bf00fdbd931843a48300c.tar.gz
add typing information (#475)
-rw-r--r--.github/workflows/tests.yml7
-rw-r--r--setup.py3
-rw-r--r--tox.ini2
-rw-r--r--voluptuous/error.py26
-rw-r--r--voluptuous/humanize.py11
-rw-r--r--voluptuous/py.typed0
-rw-r--r--voluptuous/schema_builder.py49
-rw-r--r--voluptuous/tests/tests.py27
-rw-r--r--voluptuous/util.py33
-rw-r--r--voluptuous/validators.py94
10 files changed, 147 insertions, 105 deletions
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 9b9413f..7d28ec4 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -19,8 +19,6 @@ jobs:
- { python-version: "3.9", session: "py39" }
- { python-version: "3.8", session: "py38" }
- { python-version: "3.7", session: "py37" }
- - { python-version: "3.6", session: "py36" }
- - { python-version: "2.7", session: "py27" }
steps:
- name: Check out the repository
@@ -31,11 +29,6 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- - name: Install tox-setuptools-version
- if: ${{ matrix.session != 'py27' }}
- run: |
- pip install tox-setuptools-version
-
- name: Run tox
run: |
pip install tox
diff --git a/setup.py b/setup.py
index be6b6b3..cd0254f 100644
--- a/setup.py
+++ b/setup.py
@@ -22,6 +22,9 @@ setup(
license='BSD-3-Clause',
platforms=['any'],
packages=['voluptuous'],
+ package_data={
+ 'voluptuous': ['py.typed'],
+ },
author='Alec Thomas',
author_email='alec@swapoff.org',
classifiers=[
diff --git a/tox.ini b/tox.ini
index e09e222..25d2eb8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = flake8,py27,py36,py37,py38,py39,py310
+envlist = flake8,py37,py38,py39,py310
[flake8]
; E501: line too long (X > 79 characters)
diff --git a/voluptuous/error.py b/voluptuous/error.py
index 97f37d2..35d392e 100644
--- a/voluptuous/error.py
+++ b/voluptuous/error.py
@@ -1,3 +1,5 @@
+import typing
+
class Error(Exception):
"""Base validation exception."""
@@ -17,17 +19,17 @@ class Invalid(Error):
"""
- def __init__(self, message, path=None, error_message=None, error_type=None):
+ def __init__(self, message: str, path: typing.Optional[typing.List[str]] = None, error_message: typing.Optional[str] = None, error_type: typing.Optional[str] = None) -> None:
Error.__init__(self, message)
self.path = path or []
self.error_message = error_message or message
self.error_type = error_type
@property
- def msg(self):
+ def msg(self) -> str:
return self.args[0]
- def __str__(self):
+ def __str__(self) -> str:
path = ' @ data[%s]' % ']['.join(map(repr, self.path)) \
if self.path else ''
output = Exception.__str__(self)
@@ -35,36 +37,36 @@ class Invalid(Error):
output += ' for ' + self.error_type
return output + path
- def prepend(self, path):
+ def prepend(self, path: typing.List[str]) -> None:
self.path = path + self.path
class MultipleInvalid(Invalid):
- def __init__(self, errors=None):
+ def __init__(self, errors: typing.Optional[typing.List[Invalid]] = None) -> None:
self.errors = errors[:] if errors else []
- def __repr__(self):
+ def __repr__(self) -> str:
return 'MultipleInvalid(%r)' % self.errors
@property
- def msg(self):
+ def msg(self) -> str:
return self.errors[0].msg
@property
- def path(self):
+ def path(self) -> typing.List[str]:
return self.errors[0].path
@property
- def error_message(self):
+ def error_message(self) -> str:
return self.errors[0].error_message
- def add(self, error):
+ def add(self, error: Invalid) -> None:
self.errors.append(error)
- def __str__(self):
+ def __str__(self) -> str:
return str(self.errors[0])
- def prepend(self, path):
+ def prepend(self, path: typing.List[str]) -> None:
for error in self.errors:
error.prepend(path)
diff --git a/voluptuous/humanize.py b/voluptuous/humanize.py
index 91ab201..734f367 100644
--- a/voluptuous/humanize.py
+++ b/voluptuous/humanize.py
@@ -1,11 +1,16 @@
from voluptuous import Invalid, MultipleInvalid
from voluptuous.error import Error
+from voluptuous.schema_builder import Schema
+import typing
MAX_VALIDATION_ERROR_ITEM_LENGTH = 500
-def _nested_getitem(data, path):
+IndexT = typing.TypeVar("IndexT")
+
+
+def _nested_getitem(data: typing.Dict[IndexT, typing.Any], path: typing.List[IndexT]) -> typing.Optional[typing.Any]:
for item_index in path:
try:
data = data[item_index]
@@ -16,7 +21,7 @@ def _nested_getitem(data, path):
return data
-def humanize_error(data, validation_error, max_sub_error_length=MAX_VALIDATION_ERROR_ITEM_LENGTH):
+def humanize_error(data, validation_error: Invalid, max_sub_error_length: int = MAX_VALIDATION_ERROR_ITEM_LENGTH) -> str:
""" Provide a more helpful + complete validation error message than that provided automatically
Invalid and MultipleInvalid do not include the offending value in error messages,
and MultipleInvalid.__str__ only provides the first error.
@@ -33,7 +38,7 @@ def humanize_error(data, validation_error, max_sub_error_length=MAX_VALIDATION_E
return '%s. Got %s' % (validation_error, offending_item_summary)
-def validate_with_humanized_errors(data, schema, max_sub_error_length=MAX_VALIDATION_ERROR_ITEM_LENGTH):
+def validate_with_humanized_errors(data, schema: Schema, max_sub_error_length: int = MAX_VALIDATION_ERROR_ITEM_LENGTH) -> typing.Any:
try:
return schema(data)
except (Invalid, MultipleInvalid) as e:
diff --git a/voluptuous/py.typed b/voluptuous/py.typed
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/voluptuous/py.typed
diff --git a/voluptuous/schema_builder.py b/voluptuous/schema_builder.py
index 0cb207f..e7e8d35 100644
--- a/voluptuous/schema_builder.py
+++ b/voluptuous/schema_builder.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import collections
import inspect
import re
@@ -7,6 +9,9 @@ from contextlib import contextmanager
import itertools
from voluptuous import error as er
+from collections.abc import Generator
+import typing
+from voluptuous.error import Error
if sys.version_info >= (3,):
long = int
@@ -127,18 +132,21 @@ class Undefined(object):
UNDEFINED = Undefined()
-def Self():
+def Self() -> None:
raise er.SchemaError('"Self" should never be called')
-def default_factory(value):
+DefaultFactory = typing.Union[Undefined, typing.Callable[[], typing.Any]]
+
+
+def default_factory(value) -> DefaultFactory:
if value is UNDEFINED or callable(value):
return value
return lambda: value
@contextmanager
-def raises(exc, msg=None, regex=None):
+def raises(exc, msg: typing.Optional[str] = None, regex: typing.Optional[re.Pattern] = None) -> Generator[None, None, None]:
try:
yield
except exc as e:
@@ -148,7 +156,7 @@ def raises(exc, msg=None, regex=None):
assert re.search(regex, str(e)), '%r does not match %r' % (str(e), regex)
-def Extra(_):
+def Extra(_) -> None:
"""Allow keys in the data that are not present in the schema."""
raise er.SchemaError('"Extra" should never be called')
@@ -157,6 +165,8 @@ def Extra(_):
# deprecated object, so we just leave an alias here instead.
extra = Extra
+Schemable = typing.Union[dict, list, type, typing.Callable]
+
class Schema(object):
"""A validation schema.
@@ -186,7 +196,7 @@ class Schema(object):
PREVENT_EXTRA: 'PREVENT_EXTRA',
}
- def __init__(self, schema, required=False, extra=PREVENT_EXTRA):
+ def __init__(self, schema: Schemable, required: bool = False, extra: int = PREVENT_EXTRA) -> None:
"""Create a new Schema.
:param schema: Validation schema. See :module:`voluptuous` for details.
@@ -207,7 +217,7 @@ class Schema(object):
self._compiled = self._compile(schema)
@classmethod
- def infer(cls, data, **kwargs):
+ def infer(cls, data, **kwargs) -> Schema:
"""Create a Schema from concrete data (e.g. an API response).
For example, this will take a dict like:
@@ -723,7 +733,7 @@ class Schema(object):
return validate_set
- def extend(self, schema, required=None, extra=None):
+ def extend(self, schema: dict, required: typing.Optional[bool] = None, extra: typing.Optional[int] = None) -> Schema:
"""Create a new `Schema` by merging this and the provided `schema`.
Neither this `Schema` nor the provided `schema` are modified. The
@@ -738,6 +748,7 @@ class Schema(object):
"""
assert type(self.schema) == dict and type(schema) == dict, 'Both schemas must be dictionary-based'
+ assert isinstance(self.schema, dict)
result = self.schema.copy()
@@ -936,7 +947,7 @@ class Msg(object):
... assert isinstance(e.errors[0], er.RangeInvalid)
"""
- def __init__(self, schema, msg, cls=None):
+ def __init__(self, schema: dict, msg: str, cls: typing.Optional[typing.Type[Error]] = None) -> None:
if cls and not issubclass(cls, er.Invalid):
raise er.SchemaError("Msg can only use subclases of"
" Invalid as custom class")
@@ -961,7 +972,7 @@ class Msg(object):
class Object(dict):
"""Indicate that we should work with attributes, not keys."""
- def __init__(self, schema, cls=UNDEFINED):
+ def __init__(self, schema, cls: object = UNDEFINED) -> None:
self.cls = cls
super(Object, self).__init__(schema)
@@ -977,7 +988,7 @@ class VirtualPathComponent(str):
class Marker(object):
"""Mark nodes for special treatment."""
- def __init__(self, schema_, msg=None, description=None):
+ def __init__(self, schema_: dict, msg: typing.Optional[str] = None, description: typing.Optional[str] = None) -> None:
self.schema = schema_
self._schema = Schema(schema_)
self.msg = msg
@@ -1009,7 +1020,7 @@ class Marker(object):
return self.schema == other
def __ne__(self, other):
- return not(self.schema == other)
+ return not (self.schema == other)
class Optional(Marker):
@@ -1035,7 +1046,7 @@ class Optional(Marker):
{'key2': 'value'}
"""
- def __init__(self, schema, msg=None, default=UNDEFINED, description=None):
+ def __init__(self, schema: dict, msg: typing.Optional[str] = None, default=UNDEFINED, description: typing.Optional[str] = None) -> None:
super(Optional, self).__init__(schema, msg=msg,
description=description)
self.default = default_factory(default)
@@ -1077,7 +1088,7 @@ class Exclusive(Optional):
... 'social': {'social_network': 'barfoo', 'token': 'tEMp'}})
"""
- def __init__(self, schema, group_of_exclusion, msg=None, description=None):
+ def __init__(self, schema: dict, group_of_exclusion: str, msg: typing.Optional[str] = None, description: typing.Optional[str] = None) -> None:
super(Exclusive, self).__init__(schema, msg=msg,
description=description)
self.group_of_exclusion = group_of_exclusion
@@ -1125,8 +1136,8 @@ class Inclusive(Optional):
True
"""
- def __init__(self, schema, group_of_inclusion,
- msg=None, description=None, default=UNDEFINED):
+ def __init__(self, schema: dict, group_of_inclusion: str,
+ msg: typing.Optional[str] = None, description: typing.Optional[str] = None, default=UNDEFINED) -> None:
super(Inclusive, self).__init__(schema, msg=msg,
default=default,
description=description)
@@ -1148,7 +1159,7 @@ class Required(Marker):
{'key': []}
"""
- def __init__(self, schema, msg=None, default=UNDEFINED, description=None):
+ def __init__(self, schema: dict, msg: typing.Optional[str] = None, default=UNDEFINED, description: typing.Optional[str] = None) -> None:
super(Required, self).__init__(schema, msg=msg,
description=description)
self.default = default_factory(default)
@@ -1169,7 +1180,7 @@ class Remove(Marker):
[1, 2, 3, 5, '7']
"""
- def __call__(self, v):
+ def __call__(self, v: object):
super(Remove, self).__call__(v)
return self.__class__
@@ -1180,7 +1191,7 @@ class Remove(Marker):
return object.__hash__(self)
-def message(default=None, cls=None):
+def message(default: typing.Optional[str] = None, cls: typing.Optional[typing.Type[Error]] = None) -> typing.Callable:
"""Convenience decorator to allow functions to provide a message.
Set a default message:
@@ -1251,7 +1262,7 @@ def _merge_args_with_kwargs(args_dict, kwargs_dict):
return ret
-def validate(*a, **kw):
+def validate(*a, **kw) -> typing.Callable:
"""Decorator for validating arguments of a function against a given schema.
Set restrictions for arguments:
diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py
index 58512df..ef4580b 100644
--- a/voluptuous/tests/tests.py
+++ b/voluptuous/tests/tests.py
@@ -1,14 +1,5 @@
-import collections
-import copy
-
-try:
- from enum import Enum
-except ImportError:
- Enum = None
-import os
-import sys
-
-import pytest
+from voluptuous.util import Capitalize, Lower, Strip, Title, Upper, u
+from voluptuous.humanize import humanize_error
from voluptuous import (ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Clamp, Coerce,
Contains, Date, Datetime, Email, Equal, ExactSequence,
Exclusive, Extra, FqdnUrl, In, Inclusive, Invalid,
@@ -17,8 +8,18 @@ from voluptuous import (ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Clamp, Coerce,
Optional, PathExists, Range, Remove, Replace, Required,
Schema, Self, SomeOf, TooManyValid, TypeInvalid, Union,
Unordered, Url, raises, validate)
-from voluptuous.humanize import humanize_error
-from voluptuous.util import Capitalize, Lower, Strip, Title, Upper, u
+import pytest
+import sys
+import os
+import collections
+import copy
+import typing
+
+Enum: typing.Union[type, None]
+try:
+ from enum import Enum
+except ImportError:
+ Enum = None
def test_new_required_test():
diff --git a/voluptuous/util.py b/voluptuous/util.py
index f57b8d7..58b9195 100644
--- a/voluptuous/util.py
+++ b/voluptuous/util.py
@@ -1,13 +1,16 @@
import sys
-from voluptuous.error import LiteralInvalid, TypeInvalid, Invalid
-from voluptuous.schema_builder import Schema, default_factory, raises
-from voluptuous import validators
+# F401: "imported but unused"
+from voluptuous.error import LiteralInvalid, TypeInvalid, Invalid # noqa: F401
+from voluptuous.schema_builder import Schema, default_factory, raises # noqa: F401
+from voluptuous import validators # noqa: F401
+from voluptuous.schema_builder import DefaultFactory # noqa: F401
+import typing
__author__ = 'tusharmakkar08'
-def _fix_str(v):
+def _fix_str(v: str) -> str:
if sys.version_info[0] == 2 and isinstance(v, unicode): # noqa: F821
s = v
else:
@@ -15,7 +18,7 @@ def _fix_str(v):
return s
-def Lower(v):
+def Lower(v: str) -> str:
"""Transform a string to lower case.
>>> s = Schema(Lower)
@@ -25,7 +28,7 @@ def Lower(v):
return _fix_str(v).lower()
-def Upper(v):
+def Upper(v: str) -> str:
"""Transform a string to upper case.
>>> s = Schema(Upper)
@@ -35,7 +38,7 @@ def Upper(v):
return _fix_str(v).upper()
-def Capitalize(v):
+def Capitalize(v: str) -> str:
"""Capitalise a string.
>>> s = Schema(Capitalize)
@@ -45,7 +48,7 @@ def Capitalize(v):
return _fix_str(v).capitalize()
-def Title(v):
+def Title(v: str) -> str:
"""Title case a string.
>>> s = Schema(Title)
@@ -55,7 +58,7 @@ def Title(v):
return _fix_str(v).title()
-def Strip(v):
+def Strip(v: str) -> str:
"""Strip whitespace from a string.
>>> s = Schema(Strip)
@@ -76,7 +79,7 @@ class DefaultTo(object):
[]
"""
- def __init__(self, default_value, msg=None):
+ def __init__(self, default_value, msg: typing.Optional[str] = None) -> None:
self.default_value = default_factory(default_value)
self.msg = msg
@@ -99,7 +102,7 @@ class SetTo(object):
42
"""
- def __init__(self, value):
+ def __init__(self, value) -> None:
self.value = default_factory(value)
def __call__(self, v):
@@ -121,7 +124,7 @@ class Set(object):
... s([set([1, 2]), set([3, 4])])
"""
- def __init__(self, msg=None):
+ def __init__(self, msg: typing.Optional[str] = None) -> None:
self.msg = msg
def __call__(self, v):
@@ -137,10 +140,10 @@ class Set(object):
class Literal(object):
- def __init__(self, lit):
+ def __init__(self, lit) -> None:
self.lit = lit
- def __call__(self, value, msg=None):
+ def __call__(self, value, msg: typing.Optional[str] = None):
if self.lit != value:
raise LiteralInvalid(
msg or '%s not match for %s' % (value, self.lit)
@@ -155,7 +158,7 @@ class Literal(object):
return repr(self.lit)
-def u(x):
+def u(x: str) -> str:
if sys.version_info < (3,):
return unicode(x) # noqa: F821
else:
diff --git a/voluptuous/validators.py b/voluptuous/validators.py
index 35a80e6..776adb8 100644
--- a/voluptuous/validators.py
+++ b/voluptuous/validators.py
@@ -1,20 +1,25 @@
+from voluptuous.error import (MultipleInvalid, CoerceInvalid, TrueInvalid, FalseInvalid, BooleanInvalid, Invalid,
+ AnyInvalid, AllInvalid, MatchInvalid, UrlInvalid, EmailInvalid, FileInvalid, DirInvalid,
+ RangeInvalid, PathInvalid, ExactSequenceInvalid, LengthInvalid, DatetimeInvalid,
+ DateInvalid, InInvalid, TypeInvalid, NotInInvalid, ContainsInvalid, NotEnoughValid,
+ TooManyValid)
+
+# F401: flake8 complains about 'raises' not being used, but it is used in doctests
+from voluptuous.schema_builder import Schema, raises, message, Schemable # noqa: F401
import os
import re
import datetime
import sys
from functools import wraps
from decimal import Decimal, InvalidOperation
+import typing
+
+Enum: typing.Union[type, None]
try:
from enum import Enum
except ImportError:
Enum = None
-from voluptuous.schema_builder import Schema, raises, message
-from voluptuous.error import (MultipleInvalid, CoerceInvalid, TrueInvalid, FalseInvalid, BooleanInvalid, Invalid,
- AnyInvalid, AllInvalid, MatchInvalid, UrlInvalid, EmailInvalid, FileInvalid, DirInvalid,
- RangeInvalid, PathInvalid, ExactSequenceInvalid, LengthInvalid, DatetimeInvalid,
- DateInvalid, InInvalid, TypeInvalid, NotInInvalid, ContainsInvalid, NotEnoughValid,
- TooManyValid)
if sys.version_info >= (3,):
import urllib.parse as urlparse
@@ -53,7 +58,7 @@ DOMAIN_REGEX = re.compile(
__author__ = 'tusharmakkar08'
-def truth(f):
+def truth(f: typing.Callable) -> typing.Callable:
"""Convenience decorator to convert truth functions into validators.
>>> @truth
@@ -97,7 +102,7 @@ class Coerce(object):
... validate('foo')
"""
- def __init__(self, type, msg=None):
+ def __init__(self, type: type, msg: typing.Optional[str] = None) -> None:
self.type = type
self.msg = msg
self.type_name = type.__name__
@@ -203,13 +208,13 @@ class _WithSubValidators(object):
sub-validators are compiled by the parent `Schema`.
"""
- def __init__(self, *validators, **kwargs):
+ def __init__(self, *validators, msg=None, required=False, discriminant=None, **kwargs) -> None:
self.validators = validators
- self.msg = kwargs.pop('msg', None)
- self.required = kwargs.pop('required', False)
- self.discriminant = kwargs.pop('discriminant', None)
+ self.msg = msg
+ self.required = required
+ self.discriminant = discriminant
- def __voluptuous_compile__(self, schema):
+ def __voluptuous_compile__(self, schema: Schema) -> typing.Callable:
self._compiled = []
old_required = schema.required
self.schema = schema
@@ -219,7 +224,7 @@ class _WithSubValidators(object):
schema.required = old_required
return self._run
- def _run(self, path, value):
+ def _run(self, path: typing.List[str], value):
if self.discriminant is not None:
self._compiled = [
self.schema._compile(v)
@@ -238,6 +243,9 @@ class _WithSubValidators(object):
self.msg
)
+ def _exec(self, funcs: typing.Iterable, v, path: typing.Optional[typing.List[str]] = None):
+ raise NotImplementedError()
+
class Any(_WithSubValidators):
"""Use the first validated value.
@@ -379,7 +387,7 @@ class Match(object):
'0x123ef4'
"""
- def __init__(self, pattern, msg=None):
+ def __init__(self, pattern: typing.Union[re.Pattern, str], msg: typing.Optional[str] = None) -> None:
if isinstance(pattern, basestring):
pattern = re.compile(pattern)
self.pattern = pattern
@@ -407,7 +415,7 @@ class Replace(object):
'I say goodbye'
"""
- def __init__(self, pattern, substitution, msg=None):
+ def __init__(self, pattern: typing.Union[re.Pattern, str], substitution: str, msg: typing.Optional[str] = None) -> None:
if isinstance(pattern, basestring):
pattern = re.compile(pattern)
self.pattern = pattern
@@ -423,7 +431,7 @@ class Replace(object):
self.msg)
-def _url_validation(v):
+def _url_validation(v: str) -> urlparse.ParseResult:
parsed = urlparse.urlparse(v)
if not parsed.scheme or not parsed.netloc:
raise UrlInvalid("must have a URL scheme and host")
@@ -556,7 +564,7 @@ def PathExists(v):
raise PathInvalid("Not a Path")
-def Maybe(validator, msg=None):
+def Maybe(validator: typing.Callable, msg: typing.Optional[str] = None):
"""Validate that the object matches given validator or is None.
:raises Invalid: If the value does not match the given validator and is not
@@ -572,6 +580,9 @@ def Maybe(validator, msg=None):
return Any(None, validator, msg=msg)
+NullableNumber = typing.Union[int, float, None]
+
+
class Range(object):
"""Limit a value to a range.
@@ -593,8 +604,9 @@ class Range(object):
... Schema(Range(max=10, max_included=False))(20)
"""
- def __init__(self, min=None, max=None, min_included=True,
- max_included=True, msg=None):
+ def __init__(self, min: NullableNumber = None, max: NullableNumber = None,
+ min_included: bool = True, max_included: bool = True,
+ msg: typing.Optional[str] = None) -> None:
self.min = min
self.max = max
self.min_included = min_included
@@ -649,7 +661,8 @@ class Clamp(object):
0
"""
- def __init__(self, min=None, max=None, msg=None):
+ def __init__(self, min: NullableNumber = None, max: NullableNumber = None,
+ msg: typing.Optional[str] = None) -> None:
self.min = min
self.max = max
self.msg = msg
@@ -674,7 +687,8 @@ class Clamp(object):
class Length(object):
"""The length of a value must be in a certain range."""
- def __init__(self, min=None, max=None, msg=None):
+ def __init__(self, min: NullableNumber = None, max: NullableNumber = None,
+ msg: typing.Optional[str] = None) -> None:
self.min = min
self.max = max
self.msg = msg
@@ -703,7 +717,7 @@ class Datetime(object):
DEFAULT_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
- def __init__(self, format=None, msg=None):
+ def __init__(self, format: typing.Optional[str] = None, msg: typing.Optional[str] = None) -> None:
self.format = format or self.DEFAULT_FORMAT
self.msg = msg
@@ -741,7 +755,7 @@ class Date(Datetime):
class In(object):
"""Validate that a value is in a collection."""
- def __init__(self, container, msg=None):
+ def __init__(self, container: typing.Iterable, msg: typing.Optional[str] = None) -> None:
self.container = container
self.msg = msg
@@ -762,7 +776,7 @@ class In(object):
class NotIn(object):
"""Validate that a value is not in a collection."""
- def __init__(self, container, msg=None):
+ def __init__(self, container: typing.Iterable, msg: typing.Optional[str] = None) -> None:
self.container = container
self.msg = msg
@@ -790,7 +804,7 @@ class Contains(object):
... s([3, 2])
"""
- def __init__(self, item, msg=None):
+ def __init__(self, item, msg: typing.Optional[str] = None) -> None:
self.item = item
self.msg = msg
@@ -823,9 +837,9 @@ class ExactSequence(object):
('hourly_report', 10, [], [])
"""
- def __init__(self, validators, **kwargs):
+ def __init__(self, validators: typing.Iterable[Schemable], msg: typing.Optional[str] = None, **kwargs) -> None:
self.validators = validators
- self.msg = kwargs.pop('msg', None)
+ self.msg = msg
self._schemas = [Schema(val, **kwargs) for val in validators]
def __call__(self, v):
@@ -868,7 +882,7 @@ class Unique(object):
... s('aabbc')
"""
- def __init__(self, msg=None):
+ def __init__(self, msg: typing.Optional[str] = None) -> None:
self.msg = msg
def __call__(self, v):
@@ -904,7 +918,7 @@ class Equal(object):
... s('foo')
"""
- def __init__(self, target, msg=None):
+ def __init__(self, target, msg: typing.Optional[str] = None) -> None:
self.target = target
self.msg = msg
@@ -932,7 +946,8 @@ class Unordered(object):
[1, 'foo']
"""
- def __init__(self, validators, msg=None, **kwargs):
+ def __init__(self, validators: typing.Iterable[Schemable],
+ msg: typing.Optional[str] = None, **kwargs) -> None:
self.validators = validators
self.msg = msg
self._schemas = [Schema(val, **kwargs) for val in validators]
@@ -989,7 +1004,8 @@ class Number(object):
Decimal('1234.01')
"""
- def __init__(self, precision=None, scale=None, msg=None, yield_decimal=False):
+ def __init__(self, precision: typing.Optional[int] = None, scale: typing.Optional[int] = None,
+ msg: typing.Optional[str] = None, yield_decimal: bool = False) -> None:
self.precision = precision
self.scale = scale
self.msg = msg
@@ -1021,7 +1037,7 @@ class Number(object):
def __repr__(self):
return ('Number(precision=%s, scale=%s, msg=%s)' % (self.precision, self.scale, self.msg))
- def _get_precision_scale(self, number):
+ def _get_precision_scale(self, number) -> typing.Tuple[int, int, Decimal]:
"""
:param number:
:return: tuple(precision, scale, decimal_number)
@@ -1031,7 +1047,13 @@ class Number(object):
except InvalidOperation:
raise Invalid(self.msg or 'Value must be a number enclosed with string')
- return (len(decimal_num.as_tuple().digits), -(decimal_num.as_tuple().exponent), decimal_num)
+ exp = decimal_num.as_tuple().exponent
+ if isinstance(exp, int):
+ return (len(decimal_num.as_tuple().digits), -exp, decimal_num)
+ else:
+ # TODO: handle infinity and NaN
+ # raise Invalid(self.msg or 'Value has no precision')
+ raise TypeError("infinity and NaN have no precision")
class SomeOf(_WithSubValidators):
@@ -1058,7 +1080,9 @@ class SomeOf(_WithSubValidators):
... validate(6.2)
"""
- def __init__(self, validators, min_valid=None, max_valid=None, **kwargs):
+ def __init__(self, validators: typing.List[Schemable],
+ min_valid: typing.Optional[int] = None, max_valid: typing.Optional[int] = None,
+ **kwargs) -> None:
assert min_valid is not None or max_valid is not None, \
'when using "%s" you should specify at least one of min_valid and max_valid' % (type(self).__name__,)
self.min_valid = min_valid or 0