Skip to content

Commit fc75480

Browse files
committed
Merge branch 'split_into_package'
Closes: #101
2 parents d8fd3f8 + caca667 commit fc75480

File tree

10 files changed

+943
-711
lines changed

10 files changed

+943
-711
lines changed

jsonschema/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
An implementation of JSON Schema for Python
3+
4+
The main functionality is provided by the validator classes for each of the
5+
supported JSON Schema versions.
6+
7+
Most commonly, :func:`validate` is the quickest way to simply validate a given
8+
instance under a schema, and will create a validator for you.
9+
10+
"""
11+
12+
from jsonschema._format import (
13+
FormatChecker, FormatError, draft3_format_checker, draft4_format_checker,
14+
)
15+
from jsonschema.validators import (
16+
RefResolutionError, SchemaError, ValidationError, UnknownType,
17+
ErrorTree, Draft3Validator, Draft4Validator, RefResolver, ValidatorMixin,
18+
validate, validates,
19+
)
20+
21+
22+
__version__ = "1.4.0-dev"
23+
24+
25+
# flake8: noqa

jsonschema/_format.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import datetime
2+
import re
3+
import socket
4+
5+
from jsonschema.compat import PY3
6+
7+
8+
class FormatError(Exception):
9+
def __init__(self, message, cause=None):
10+
super(FormatError, self).__init__(message, cause)
11+
self.message = message
12+
self.cause = self.__cause__ = cause
13+
14+
def __str__(self):
15+
return self.message.encode("utf-8")
16+
17+
def __unicode__(self):
18+
return self.message
19+
20+
if PY3:
21+
__str__ = __unicode__
22+
23+
24+
class FormatChecker(object):
25+
"""
26+
A ``format`` property checker.
27+
28+
JSON Schema does not mandate that the ``format`` property actually do any
29+
validation. If validation is desired however, instances of this class can
30+
be hooked into validators to enable format validation.
31+
32+
:class:`FormatChecker` objects always return ``True`` when asked about
33+
formats that they do not know how to validate.
34+
35+
To check a custom format using a function that takes an instance and
36+
returns a ``bool``, use the :meth:`FormatChecker.checks` or
37+
:meth:`FormatChecker.cls_checks` decorators.
38+
39+
:argument iterable formats: the known formats to validate. This argument
40+
can be used to limit which formats will be used
41+
during validation.
42+
43+
"""
44+
45+
checkers = {}
46+
47+
def __init__(self, formats=None):
48+
if formats is None:
49+
self.checkers = self.checkers.copy()
50+
else:
51+
self.checkers = dict((k, self.checkers[k]) for k in formats)
52+
53+
def checks(self, format, raises=()):
54+
"""
55+
Register a decorated function as validating a new format.
56+
57+
:argument str format: the format that the decorated function will check
58+
:argument Exception raises: the exception(s) raised by the decorated
59+
function when an invalid instance is found. The exception object
60+
will be accessible as the :attr:`ValidationError.cause` attribute
61+
of the resulting validation error.
62+
63+
"""
64+
65+
def _checks(func):
66+
self.checkers[format] = (func, raises)
67+
return func
68+
return _checks
69+
70+
cls_checks = classmethod(checks)
71+
72+
def check(self, instance, format):
73+
"""
74+
Check whether the instance conforms to the given format.
75+
76+
:argument instance: the instance to check
77+
:type: any primitive type (str, number, bool)
78+
:argument str format: the format that instance should conform to
79+
:raises: :exc:`FormatError` if instance does not conform to format
80+
81+
"""
82+
83+
if format in self.checkers:
84+
func, raises = self.checkers[format]
85+
result, cause = None, None
86+
try:
87+
result = func(instance)
88+
except raises as e:
89+
cause = e
90+
if not result:
91+
raise FormatError(
92+
"%r is not a %r" % (instance, format), cause=cause,
93+
)
94+
95+
def conforms(self, instance, format):
96+
"""
97+
Check whether the instance conforms to the given format.
98+
99+
:argument instance: the instance to check
100+
:type: any primitive type (str, number, bool)
101+
:argument str format: the format that instance should conform to
102+
:rtype: bool
103+
104+
"""
105+
106+
try:
107+
self.check(instance, format)
108+
except FormatError:
109+
return False
110+
else:
111+
return True
112+
113+
114+
_draft_checkers = {"draft3": [], "draft4": []}
115+
116+
117+
def _checks_drafts(both=None, draft3=None, draft4=None, raises=()):
118+
draft3 = draft3 or both
119+
draft4 = draft4 or both
120+
121+
def wrap(func):
122+
if draft3:
123+
_draft_checkers["draft3"].append(draft3)
124+
func = FormatChecker.cls_checks(draft3, raises)(func)
125+
if draft4:
126+
_draft_checkers["draft4"].append(draft4)
127+
func = FormatChecker.cls_checks(draft4, raises)(func)
128+
return func
129+
return wrap
130+
131+
132+
@_checks_drafts("email")
133+
def is_email(instance):
134+
return "@" in instance
135+
136+
137+
_checks_drafts(draft3="ip-address", draft4="ipv4", raises=socket.error)(
138+
socket.inet_aton
139+
)
140+
141+
142+
if hasattr(socket, "inet_pton"):
143+
@_checks_drafts("ipv6", raises=socket.error)
144+
def is_ipv6(instance):
145+
return socket.inet_pton(socket.AF_INET6, instance)
146+
147+
148+
@_checks_drafts(draft3="host-name", draft4="hostname")
149+
def is_host_name(instance):
150+
pattern = "^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$"
151+
if not re.match(pattern, instance):
152+
return False
153+
components = instance.split(".")
154+
for component in components:
155+
if len(component) > 63:
156+
return False
157+
return True
158+
159+
160+
try:
161+
import rfc3987
162+
except ImportError:
163+
pass
164+
else:
165+
@_checks_drafts("uri", raises=ValueError)
166+
def is_uri(instance):
167+
return rfc3987.parse(instance, rule="URI_reference")
168+
169+
170+
try:
171+
import isodate
172+
except ImportError:
173+
pass
174+
else:
175+
_err = (ValueError, isodate.ISO8601Error)
176+
_checks_drafts("date-time", raises=_err)(isodate.parse_datetime)
177+
178+
179+
_checks_drafts("regex", raises=re.error)(re.compile)
180+
181+
182+
@_checks_drafts(draft3="date", raises=ValueError)
183+
def is_date(instance):
184+
return datetime.datetime.strptime(instance, "%Y-%m-%d")
185+
186+
187+
@_checks_drafts(draft3="time", raises=ValueError)
188+
def is_time(instance):
189+
return datetime.datetime.strptime(instance, "%H:%M:%S")
190+
191+
192+
try:
193+
import webcolors
194+
except ImportError:
195+
pass
196+
else:
197+
def is_css_color_code(instance):
198+
return webcolors.normalize_hex(instance)
199+
200+
201+
@_checks_drafts(draft3="color", raises=(ValueError, TypeError))
202+
def is_css21_color(instance):
203+
if instance.lower() in webcolors.css21_names_to_hex:
204+
return True
205+
return is_css_color_code(instance)
206+
207+
208+
def is_css3_color(instance):
209+
if instance.lower() in webcolors.css3_names_to_hex:
210+
return True
211+
return is_css_color_code(instance)
212+
213+
214+
draft3_format_checker = FormatChecker(_draft_checkers["draft3"])
215+
draft4_format_checker = FormatChecker(_draft_checkers["draft4"])

0 commit comments

Comments
 (0)