Skip to content

Commit

Permalink
Initial package separation
Browse files Browse the repository at this point in the history
  • Loading branch information
p1c2u committed Mar 5, 2020
1 parent 70ca6be commit cd19f8e
Show file tree
Hide file tree
Showing 12 changed files with 541 additions and 0 deletions.
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include LICENSE
include README.rst
include requirements.txt
include requirements_dev.txt
81 changes: 81 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
************************
openapi-schema-validator
************************

.. image:: https://img.shields.io/pypi/v/openapi-schema-validator.svg
:target: https://pypi.python.org/pypi/openapi-schema-validator
.. image:: https://travis-ci.org/p1c2u/openapi-schema-validator.svg?branch=master
:target: https://travis-ci.org/p1c2u/openapi-schema-validator
.. image:: https://img.shields.io/codecov/c/github/p1c2u/openapi-schema-validator/master.svg?style=flat
:target: https://codecov.io/github/p1c2u/openapi-schema-validator?branch=master
.. image:: https://img.shields.io/pypi/pyversions/openapi-schema-validator.svg
:target: https://pypi.python.org/pypi/openapi-schema-validator
.. image:: https://img.shields.io/pypi/format/openapi-schema-validator.svg
:target: https://pypi.python.org/pypi/openapi-schema-validator
.. image:: https://img.shields.io/pypi/status/openapi-schema-validator.svg
:target: https://pypi.python.org/pypi/openapi-schema-validator

About
#####

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/>`__.

Installation
############

Recommended way (via pip):

::

$ pip install openapi-schema-validator

Alternatively you can download the code and install from the repository:

.. code-block:: bash
$ pip install -e git+https://github.com/p1c2u/openapi-schema-validator.git#egg=openapi_schema_validator
Usage
#####

Simple usage

.. code-block:: python
from openapi_schema_validator import OAS30Validator, oas30_format_checker
# A sample schema
schema = {
"type" : "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "integer",
"format": "int32",
"minimum": 0,
"nullable": True,
},
},
"additionalProperties": False,
}
validator = OAS30Validator(schema)
# If no exception is raised by validate(), the instance is valid.
validator.validate({"name": "John", "age": 23})
validator.validate({"name": "John", "city": "London"})
Traceback (most recent call last):
...
ValidationError: Additional properties are not allowed ('city' was unexpected)
Related projects
################
* `openapi-core <https://github.com/p1c2u/openapi-core>`__
* `openapi-spec-validator <https://github.com/p1c2u/openapi-spec-validator>`__
11 changes: 11 additions & 0 deletions openapi_schema_validator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from openapi_schema_validator._format import oas30_format_checker
from openapi_schema_validator.validators import OAS30Validator

__author__ = 'Artur Maciag'
__email__ = 'maciag.artur@gmail.com'
__version__ = '0.1.0'
__url__ = 'https://github.com/p1c2u/openapi-schema-validator'
__license__ = 'BSD 3-Clause License'

__all__ = ['OAS30Validator', 'oas30_format_checker']
135 changes: 135 additions & 0 deletions openapi_schema_validator/_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from base64 import b64encode, b64decode
import binascii
from datetime import datetime
from uuid import UUID

from jsonschema._format import FormatChecker
from jsonschema.exceptions import FormatError
from six import binary_type, text_type, integer_types

DATETIME_HAS_STRICT_RFC3339 = False
DATETIME_HAS_ISODATE = False
DATETIME_RAISES = ()

try:
import isodate
except ImportError:
pass
else:
DATETIME_HAS_ISODATE = True
DATETIME_RAISES += (ValueError, isodate.ISO8601Error)

try:
import strict_rfc3339
except ImportError:
pass
else:
DATETIME_HAS_STRICT_RFC3339 = True
DATETIME_RAISES += (ValueError, TypeError)


def is_int32(instance):
return isinstance(instance, integer_types)


def is_int64(instance):
return isinstance(instance, integer_types)


def is_float(instance):
return isinstance(instance, float)


def is_double(instance):
# float has double precision in Python
# It's double in CPython and Jython
return isinstance(instance, float)


def is_binary(instance):
return isinstance(instance, binary_type)


def is_byte(instance):
if isinstance(instance, text_type):
instance = instance.encode()

try:
return b64encode(b64decode(instance)) == instance
except TypeError:
return False


def is_datetime(instance):
if not isinstance(instance, (binary_type, text_type)):
return False

if DATETIME_HAS_STRICT_RFC3339:
return strict_rfc3339.validate_rfc3339(instance)

if DATETIME_HAS_ISODATE:
return isodate.parse_datetime(instance)

return True


def is_date(instance):
if not isinstance(instance, (binary_type, text_type)):
return False

if isinstance(instance, binary_type):
instance = instance.decode()

return datetime.strptime(instance, "%Y-%m-%d")


def is_uuid(instance):
if not isinstance(instance, (binary_type, text_type)):
return False

if isinstance(instance, binary_type):
instance = instance.decode()

return text_type(UUID(instance)) == instance


def is_password(instance):
return True


class OASFormatChecker(FormatChecker):

checkers = {
'int32': (is_int32, ()),
'int64': (is_int64, ()),
'float': (is_float, ()),
'double': (is_double, ()),
'byte': (is_byte, (binascii.Error, TypeError)),
'binary': (is_binary, ()),
'date': (is_date, (ValueError, )),
'date-time': (is_datetime, DATETIME_RAISES),
'password': (is_password, ()),
# non standard
'uuid': (is_uuid, (AttributeError, ValueError)),
}

def check(self, instance, format):
if format not in self.checkers:
raise FormatError(
"Format checker for %r format not found" % (format, ))

func, raises = self.checkers[format]
result, cause = None, None
try:
result = func(instance)
except raises as e:
cause = e

if not result:
raise FormatError(
"%r is not a %r" % (instance, format), cause=cause,
)
return result


oas30_format_checker = OASFormatChecker()
21 changes: 21 additions & 0 deletions openapi_schema_validator/_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from jsonschema._types import (
TypeChecker, is_array, is_bool, is_integer,
is_object, is_number,
)
from six import text_type, binary_type


def is_string(checker, instance):
return isinstance(instance, (text_type, binary_type))


oas30_type_checker = TypeChecker(
{
u"string": is_string,
u"number": is_number,
u"integer": is_integer,
u"boolean": is_bool,
u"array": is_array,
u"object": is_object,
},
)
87 changes: 87 additions & 0 deletions openapi_schema_validator/_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from jsonschema._utils import find_additional_properties, extras_msg
from jsonschema.exceptions import ValidationError, FormatError


def type(validator, data_type, instance, schema):
if instance is None:
return

if not validator.is_type(instance, data_type):
yield ValidationError("%r is not of type %s" % (instance, data_type))


def format(validator, format, instance, schema):
if instance is None:
return

if validator.format_checker is not None:
try:
validator.format_checker.check(instance, format)
except FormatError as error:
yield ValidationError(error.message, cause=error.cause)


def items(validator, items, instance, schema):
if not validator.is_type(instance, "array"):
return

for index, item in enumerate(instance):
for error in validator.descend(item, items, path=index):
yield error


def nullable(validator, is_nullable, instance, schema):
if instance is None and not is_nullable:
yield ValidationError("None for not nullable")


def required(validator, required, instance, schema):
if not validator.is_type(instance, "object"):
return
for property in required:
if property not in instance:
prop_schema = schema['properties'][property]
read_only = prop_schema.get('readOnly', False)
write_only = prop_schema.get('writeOnly', False)
if validator.write and read_only or validator.read and write_only:
continue
yield ValidationError("%r is a required property" % property)


def additionalProperties(validator, aP, instance, schema):
if not validator.is_type(instance, "object"):
return

extras = set(find_additional_properties(instance, schema))

if not extras:
return

if validator.is_type(aP, "object"):
for extra in extras:
for error in validator.descend(instance[extra], aP, path=extra):
yield error
elif validator.is_type(aP, "boolean"):
if not aP:
error = "Additional properties are not allowed (%s %s unexpected)"
yield ValidationError(error % extras_msg(extras))


def readOnly(validator, ro, instance, schema):
if not validator.write or not ro:
return

yield ValidationError(
"Tried to write read-only proparty with %s" % (instance))


def writeOnly(validator, wo, instance, schema):
if not validator.read or not wo:
return

yield ValidationError(
"Tried to read write-only proparty with %s" % (instance))


def not_implemented(validator, value, instance, schema):
pass
Loading

0 comments on commit cd19f8e

Please sign in to comment.