Skip to content

Commit cd19f8e

Browse files
committed
Initial package separation
1 parent 70ca6be commit cd19f8e

12 files changed

+541
-0
lines changed

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include LICENSE
2+
include README.rst
3+
include requirements.txt
4+
include requirements_dev.txt

README.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
************************
2+
openapi-schema-validator
3+
************************
4+
5+
.. image:: https://img.shields.io/pypi/v/openapi-schema-validator.svg
6+
:target: https://pypi.python.org/pypi/openapi-schema-validator
7+
.. image:: https://travis-ci.org/p1c2u/openapi-schema-validator.svg?branch=master
8+
:target: https://travis-ci.org/p1c2u/openapi-schema-validator
9+
.. image:: https://img.shields.io/codecov/c/github/p1c2u/openapi-schema-validator/master.svg?style=flat
10+
:target: https://codecov.io/github/p1c2u/openapi-schema-validator?branch=master
11+
.. image:: https://img.shields.io/pypi/pyversions/openapi-schema-validator.svg
12+
:target: https://pypi.python.org/pypi/openapi-schema-validator
13+
.. image:: https://img.shields.io/pypi/format/openapi-schema-validator.svg
14+
:target: https://pypi.python.org/pypi/openapi-schema-validator
15+
.. image:: https://img.shields.io/pypi/status/openapi-schema-validator.svg
16+
:target: https://pypi.python.org/pypi/openapi-schema-validator
17+
18+
About
19+
#####
20+
21+
Openapi-schema-validator is a Python library that validates schema against the `OpenAPI Schema Specification v3.0 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject>`__ which is an extended subset of the `JSON Schema Specification Wright Draft 00 <http://json-schema.org/>`__.
22+
23+
Installation
24+
############
25+
26+
Recommended way (via pip):
27+
28+
::
29+
30+
$ pip install openapi-schema-validator
31+
32+
Alternatively you can download the code and install from the repository:
33+
34+
.. code-block:: bash
35+
36+
$ pip install -e git+https://github.com/p1c2u/openapi-schema-validator.git#egg=openapi_schema_validator
37+
38+
39+
Usage
40+
#####
41+
42+
Simple usage
43+
44+
.. code-block:: python
45+
46+
from openapi_schema_validator import OAS30Validator, oas30_format_checker
47+
48+
# A sample schema
49+
schema = {
50+
"type" : "object",
51+
"required": [
52+
"name"
53+
],
54+
"properties": {
55+
"name": {
56+
"type": "string"
57+
},
58+
"age": {
59+
"type": "integer",
60+
"format": "int32",
61+
"minimum": 0,
62+
"nullable": True,
63+
},
64+
},
65+
"additionalProperties": False,
66+
}
67+
68+
validator = OAS30Validator(schema)
69+
# If no exception is raised by validate(), the instance is valid.
70+
validator.validate({"name": "John", "age": 23})
71+
72+
validator.validate({"name": "John", "city": "London"})
73+
74+
Traceback (most recent call last):
75+
...
76+
ValidationError: Additional properties are not allowed ('city' was unexpected)
77+
78+
Related projects
79+
################
80+
* `openapi-core <https://github.com/p1c2u/openapi-core>`__
81+
* `openapi-spec-validator <https://github.com/p1c2u/openapi-spec-validator>`__

openapi_schema_validator/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# -*- coding: utf-8 -*-
2+
from openapi_schema_validator._format import oas30_format_checker
3+
from openapi_schema_validator.validators import OAS30Validator
4+
5+
__author__ = 'Artur Maciag'
6+
__email__ = 'maciag.artur@gmail.com'
7+
__version__ = '0.1.0'
8+
__url__ = 'https://github.com/p1c2u/openapi-schema-validator'
9+
__license__ = 'BSD 3-Clause License'
10+
11+
__all__ = ['OAS30Validator', 'oas30_format_checker']

openapi_schema_validator/_format.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from base64 import b64encode, b64decode
2+
import binascii
3+
from datetime import datetime
4+
from uuid import UUID
5+
6+
from jsonschema._format import FormatChecker
7+
from jsonschema.exceptions import FormatError
8+
from six import binary_type, text_type, integer_types
9+
10+
DATETIME_HAS_STRICT_RFC3339 = False
11+
DATETIME_HAS_ISODATE = False
12+
DATETIME_RAISES = ()
13+
14+
try:
15+
import isodate
16+
except ImportError:
17+
pass
18+
else:
19+
DATETIME_HAS_ISODATE = True
20+
DATETIME_RAISES += (ValueError, isodate.ISO8601Error)
21+
22+
try:
23+
import strict_rfc3339
24+
except ImportError:
25+
pass
26+
else:
27+
DATETIME_HAS_STRICT_RFC3339 = True
28+
DATETIME_RAISES += (ValueError, TypeError)
29+
30+
31+
def is_int32(instance):
32+
return isinstance(instance, integer_types)
33+
34+
35+
def is_int64(instance):
36+
return isinstance(instance, integer_types)
37+
38+
39+
def is_float(instance):
40+
return isinstance(instance, float)
41+
42+
43+
def is_double(instance):
44+
# float has double precision in Python
45+
# It's double in CPython and Jython
46+
return isinstance(instance, float)
47+
48+
49+
def is_binary(instance):
50+
return isinstance(instance, binary_type)
51+
52+
53+
def is_byte(instance):
54+
if isinstance(instance, text_type):
55+
instance = instance.encode()
56+
57+
try:
58+
return b64encode(b64decode(instance)) == instance
59+
except TypeError:
60+
return False
61+
62+
63+
def is_datetime(instance):
64+
if not isinstance(instance, (binary_type, text_type)):
65+
return False
66+
67+
if DATETIME_HAS_STRICT_RFC3339:
68+
return strict_rfc3339.validate_rfc3339(instance)
69+
70+
if DATETIME_HAS_ISODATE:
71+
return isodate.parse_datetime(instance)
72+
73+
return True
74+
75+
76+
def is_date(instance):
77+
if not isinstance(instance, (binary_type, text_type)):
78+
return False
79+
80+
if isinstance(instance, binary_type):
81+
instance = instance.decode()
82+
83+
return datetime.strptime(instance, "%Y-%m-%d")
84+
85+
86+
def is_uuid(instance):
87+
if not isinstance(instance, (binary_type, text_type)):
88+
return False
89+
90+
if isinstance(instance, binary_type):
91+
instance = instance.decode()
92+
93+
return text_type(UUID(instance)) == instance
94+
95+
96+
def is_password(instance):
97+
return True
98+
99+
100+
class OASFormatChecker(FormatChecker):
101+
102+
checkers = {
103+
'int32': (is_int32, ()),
104+
'int64': (is_int64, ()),
105+
'float': (is_float, ()),
106+
'double': (is_double, ()),
107+
'byte': (is_byte, (binascii.Error, TypeError)),
108+
'binary': (is_binary, ()),
109+
'date': (is_date, (ValueError, )),
110+
'date-time': (is_datetime, DATETIME_RAISES),
111+
'password': (is_password, ()),
112+
# non standard
113+
'uuid': (is_uuid, (AttributeError, ValueError)),
114+
}
115+
116+
def check(self, instance, format):
117+
if format not in self.checkers:
118+
raise FormatError(
119+
"Format checker for %r format not found" % (format, ))
120+
121+
func, raises = self.checkers[format]
122+
result, cause = None, None
123+
try:
124+
result = func(instance)
125+
except raises as e:
126+
cause = e
127+
128+
if not result:
129+
raise FormatError(
130+
"%r is not a %r" % (instance, format), cause=cause,
131+
)
132+
return result
133+
134+
135+
oas30_format_checker = OASFormatChecker()

openapi_schema_validator/_types.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from jsonschema._types import (
2+
TypeChecker, is_array, is_bool, is_integer,
3+
is_object, is_number,
4+
)
5+
from six import text_type, binary_type
6+
7+
8+
def is_string(checker, instance):
9+
return isinstance(instance, (text_type, binary_type))
10+
11+
12+
oas30_type_checker = TypeChecker(
13+
{
14+
u"string": is_string,
15+
u"number": is_number,
16+
u"integer": is_integer,
17+
u"boolean": is_bool,
18+
u"array": is_array,
19+
u"object": is_object,
20+
},
21+
)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from jsonschema._utils import find_additional_properties, extras_msg
2+
from jsonschema.exceptions import ValidationError, FormatError
3+
4+
5+
def type(validator, data_type, instance, schema):
6+
if instance is None:
7+
return
8+
9+
if not validator.is_type(instance, data_type):
10+
yield ValidationError("%r is not of type %s" % (instance, data_type))
11+
12+
13+
def format(validator, format, instance, schema):
14+
if instance is None:
15+
return
16+
17+
if validator.format_checker is not None:
18+
try:
19+
validator.format_checker.check(instance, format)
20+
except FormatError as error:
21+
yield ValidationError(error.message, cause=error.cause)
22+
23+
24+
def items(validator, items, instance, schema):
25+
if not validator.is_type(instance, "array"):
26+
return
27+
28+
for index, item in enumerate(instance):
29+
for error in validator.descend(item, items, path=index):
30+
yield error
31+
32+
33+
def nullable(validator, is_nullable, instance, schema):
34+
if instance is None and not is_nullable:
35+
yield ValidationError("None for not nullable")
36+
37+
38+
def required(validator, required, instance, schema):
39+
if not validator.is_type(instance, "object"):
40+
return
41+
for property in required:
42+
if property not in instance:
43+
prop_schema = schema['properties'][property]
44+
read_only = prop_schema.get('readOnly', False)
45+
write_only = prop_schema.get('writeOnly', False)
46+
if validator.write and read_only or validator.read and write_only:
47+
continue
48+
yield ValidationError("%r is a required property" % property)
49+
50+
51+
def additionalProperties(validator, aP, instance, schema):
52+
if not validator.is_type(instance, "object"):
53+
return
54+
55+
extras = set(find_additional_properties(instance, schema))
56+
57+
if not extras:
58+
return
59+
60+
if validator.is_type(aP, "object"):
61+
for extra in extras:
62+
for error in validator.descend(instance[extra], aP, path=extra):
63+
yield error
64+
elif validator.is_type(aP, "boolean"):
65+
if not aP:
66+
error = "Additional properties are not allowed (%s %s unexpected)"
67+
yield ValidationError(error % extras_msg(extras))
68+
69+
70+
def readOnly(validator, ro, instance, schema):
71+
if not validator.write or not ro:
72+
return
73+
74+
yield ValidationError(
75+
"Tried to write read-only proparty with %s" % (instance))
76+
77+
78+
def writeOnly(validator, wo, instance, schema):
79+
if not validator.read or not wo:
80+
return
81+
82+
yield ValidationError(
83+
"Tried to read write-only proparty with %s" % (instance))
84+
85+
86+
def not_implemented(validator, value, instance, schema):
87+
pass

0 commit comments

Comments
 (0)