|
Packit |
534379 |
import datetime
|
|
Packit |
534379 |
import re
|
|
Packit |
534379 |
import socket
|
|
Packit |
534379 |
|
|
Packit |
534379 |
from jsonschema.compat import str_types
|
|
Packit |
534379 |
from jsonschema.exceptions import FormatError
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
class FormatChecker(object):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
A ``format`` property checker.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
JSON Schema does not mandate that the ``format`` property actually do any
|
|
Packit |
534379 |
validation. If validation is desired however, instances of this class can
|
|
Packit |
534379 |
be hooked into validators to enable format validation.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
:class:`FormatChecker` objects always return ``True`` when asked about
|
|
Packit |
534379 |
formats that they do not know how to validate.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
To check a custom format using a function that takes an instance and
|
|
Packit |
534379 |
returns a ``bool``, use the :meth:`FormatChecker.checks` or
|
|
Packit |
534379 |
:meth:`FormatChecker.cls_checks` decorators.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
:argument iterable formats: the known formats to validate. This argument
|
|
Packit |
534379 |
can be used to limit which formats will be used
|
|
Packit |
534379 |
during validation.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
checkers = {}
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def __init__(self, formats=None):
|
|
Packit |
534379 |
if formats is None:
|
|
Packit |
534379 |
self.checkers = self.checkers.copy()
|
|
Packit |
534379 |
else:
|
|
Packit |
534379 |
self.checkers = dict((k, self.checkers[k]) for k in formats)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def checks(self, format, raises=()):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
Register a decorated function as validating a new format.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
:argument str format: the format that the decorated function will check
|
|
Packit |
534379 |
:argument Exception raises: the exception(s) raised by the decorated
|
|
Packit |
534379 |
function when an invalid instance is found. The exception object
|
|
Packit |
534379 |
will be accessible as the :attr:`ValidationError.cause` attribute
|
|
Packit |
534379 |
of the resulting validation error.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def _checks(func):
|
|
Packit |
534379 |
self.checkers[format] = (func, raises)
|
|
Packit |
534379 |
return func
|
|
Packit |
534379 |
return _checks
|
|
Packit |
534379 |
|
|
Packit |
534379 |
cls_checks = classmethod(checks)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def check(self, instance, format):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
Check whether the instance conforms to the given format.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
:argument instance: the instance to check
|
|
Packit |
534379 |
:type: any primitive type (str, number, bool)
|
|
Packit |
534379 |
:argument str format: the format that instance should conform to
|
|
Packit |
534379 |
:raises: :exc:`FormatError` if instance does not conform to format
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
if format not in self.checkers:
|
|
Packit |
534379 |
return
|
|
Packit |
534379 |
|
|
Packit |
534379 |
func, raises = self.checkers[format]
|
|
Packit |
534379 |
result, cause = None, None
|
|
Packit |
534379 |
try:
|
|
Packit |
534379 |
result = func(instance)
|
|
Packit |
534379 |
except raises as e:
|
|
Packit |
534379 |
cause = e
|
|
Packit |
534379 |
if not result:
|
|
Packit |
534379 |
raise FormatError(
|
|
Packit |
534379 |
"%r is not a %r" % (instance, format), cause=cause,
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def conforms(self, instance, format):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
Check whether the instance conforms to the given format.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
:argument instance: the instance to check
|
|
Packit |
534379 |
:type: any primitive type (str, number, bool)
|
|
Packit |
534379 |
:argument str format: the format that instance should conform to
|
|
Packit |
534379 |
:rtype: bool
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
try:
|
|
Packit |
534379 |
self.check(instance, format)
|
|
Packit |
534379 |
except FormatError:
|
|
Packit |
534379 |
return False
|
|
Packit |
534379 |
else:
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
_draft_checkers = {"draft3": [], "draft4": []}
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def _checks_drafts(both=None, draft3=None, draft4=None, raises=()):
|
|
Packit |
534379 |
draft3 = draft3 or both
|
|
Packit |
534379 |
draft4 = draft4 or both
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def wrap(func):
|
|
Packit |
534379 |
if draft3:
|
|
Packit |
534379 |
_draft_checkers["draft3"].append(draft3)
|
|
Packit |
534379 |
func = FormatChecker.cls_checks(draft3, raises)(func)
|
|
Packit |
534379 |
if draft4:
|
|
Packit |
534379 |
_draft_checkers["draft4"].append(draft4)
|
|
Packit |
534379 |
func = FormatChecker.cls_checks(draft4, raises)(func)
|
|
Packit |
534379 |
return func
|
|
Packit |
534379 |
return wrap
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
@_checks_drafts("email")
|
|
Packit |
534379 |
def is_email(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return "@" in instance
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
@_checks_drafts(draft3="ip-address", draft4="ipv4", raises=socket.error)
|
|
Packit |
534379 |
def is_ipv4(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return socket.inet_aton(instance)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
if hasattr(socket, "inet_pton"):
|
|
Packit |
534379 |
@_checks_drafts("ipv6", raises=socket.error)
|
|
Packit |
534379 |
def is_ipv6(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return socket.inet_pton(socket.AF_INET6, instance)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
_host_name_re = re.compile(r"^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
@_checks_drafts(draft3="host-name", draft4="hostname")
|
|
Packit |
534379 |
def is_host_name(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
if not _host_name_re.match(instance):
|
|
Packit |
534379 |
return False
|
|
Packit |
534379 |
components = instance.split(".")
|
|
Packit |
534379 |
for component in components:
|
|
Packit |
534379 |
if len(component) > 63:
|
|
Packit |
534379 |
return False
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
try:
|
|
Packit |
534379 |
import rfc3987
|
|
Packit |
534379 |
except ImportError:
|
|
Packit |
534379 |
pass
|
|
Packit |
534379 |
else:
|
|
Packit |
534379 |
@_checks_drafts("uri", raises=ValueError)
|
|
Packit |
534379 |
def is_uri(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return rfc3987.parse(instance, rule="URI")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
try:
|
|
Packit |
534379 |
import strict_rfc3339
|
|
Packit |
534379 |
except ImportError:
|
|
Packit |
534379 |
try:
|
|
Packit |
534379 |
import isodate
|
|
Packit |
534379 |
except ImportError:
|
|
Packit |
534379 |
pass
|
|
Packit |
534379 |
else:
|
|
Packit |
534379 |
@_checks_drafts("date-time", raises=(ValueError, isodate.ISO8601Error))
|
|
Packit |
534379 |
def is_date(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return isodate.parse_datetime(instance)
|
|
Packit |
534379 |
else:
|
|
Packit |
534379 |
@_checks_drafts("date-time")
|
|
Packit |
534379 |
def is_date(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return strict_rfc3339.validate_rfc3339(instance)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
@_checks_drafts("regex", raises=re.error)
|
|
Packit |
534379 |
def is_regex(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return re.compile(instance)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
@_checks_drafts(draft3="date", raises=ValueError)
|
|
Packit |
534379 |
def is_date(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return datetime.datetime.strptime(instance, "%Y-%m-%d")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
@_checks_drafts(draft3="time", raises=ValueError)
|
|
Packit |
534379 |
def is_time(instance):
|
|
Packit |
534379 |
if not isinstance(instance, str_types):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return datetime.datetime.strptime(instance, "%H:%M:%S")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
try:
|
|
Packit |
534379 |
import webcolors
|
|
Packit |
534379 |
except ImportError:
|
|
Packit |
534379 |
pass
|
|
Packit |
534379 |
else:
|
|
Packit |
534379 |
def is_css_color_code(instance):
|
|
Packit |
534379 |
return webcolors.normalize_hex(instance)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
@_checks_drafts(draft3="color", raises=(ValueError, TypeError))
|
|
Packit |
534379 |
def is_css21_color(instance):
|
|
Packit |
534379 |
if (
|
|
Packit |
534379 |
not isinstance(instance, str_types) or
|
|
Packit |
534379 |
instance.lower() in webcolors.css21_names_to_hex
|
|
Packit |
534379 |
):
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return is_css_color_code(instance)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def is_css3_color(instance):
|
|
Packit |
534379 |
if instance.lower() in webcolors.css3_names_to_hex:
|
|
Packit |
534379 |
return True
|
|
Packit |
534379 |
return is_css_color_code(instance)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
draft3_format_checker = FormatChecker(_draft_checkers["draft3"])
|
|
Packit |
534379 |
draft4_format_checker = FormatChecker(_draft_checkers["draft4"])
|