GithubHelp home page GithubHelp logo

s-knibbs / dataclasses-jsonschema Goto Github PK

View Code? Open in Web Editor NEW
165.0 7.0 38.0 232 KB

JSON schema generation from dataclasses

License: MIT License

Python 100.00%
python jsonschema dataclasses validation serialization deserialization type-hints openapi

dataclasses-jsonschema's Introduction

Dataclasses JSON Schema

https://github.com/s-knibbs/dataclasses-jsonschema/workflows/Tox%20tests/badge.svg?branch=master

⚠️Please Note⚠️: Because of health reasons I'm not longer able to make changes to this project or make further releases via PyPI.

A library to generate JSON Schema from python 3.7 dataclasses. Python 3.6 is supported through the dataclasses backport. Aims to be a more lightweight alternative to similar projects such as marshmallow & pydantic.

Feature Overview

  • Support for draft-04, draft-06, Swagger 2.0 & OpenAPI 3 schema types
  • Serialisation and deserialisation
  • Data validation against the generated schema
  • APISpec support. Example below:

Installation

~$ pip install dataclasses-jsonschema

For improved validation performance using fastjsonschema, install with:

~$ pip install dataclasses-jsonschema[fast-validation]

For improved uuid performance using fastuuid, install with:

~$ pip install dataclasses-jsonschema[fast-uuid]

For improved date and datetime parsing performance using ciso8601, install with:

~$ pip install dataclasses-jsonschema[fast-dateparsing]

Beware ciso8601 doesn’t support the entirety of the ISO 8601 spec, only a popular subset.

Examples

from dataclasses import dataclass

from dataclasses_jsonschema import JsonSchemaMixin


@dataclass
class Point(JsonSchemaMixin):
    "A 2D point"
    x: float
    y: float

Schema Generation

>>> pprint(Point.json_schema())
{
    'description': 'A 2D point',
    'type': 'object',
    'properties': {
        'x': {'format': 'float', 'type': 'number'},
        'y': {'format': 'float', 'type': 'number'}
    },
    'required': ['x', 'y']
}

Data Serialisation

>>> Point(x=3.5, y=10.1).to_dict()
{'x': 3.5, 'y': 10.1}

Deserialisation

>>> Point.from_dict({'x': 3.14, 'y': 1.5})
Point(x=3.14, y=1.5)
>>> Point.from_dict({'x': 3.14, y: 'wrong'})
dataclasses_jsonschema.ValidationError: 'wrong' is not of type 'number'

Generating multiple schemas

from dataclasses_jsonschema import JsonSchemaMixin, SchemaType

@dataclass
class Address(JsonSchemaMixin):
    """Postal Address"""
    building: str
    street: str
    city: str

@dataclass
class Company(JsonSchemaMixin):
    """Company Details"""
    name: str
    address: Address

>>> pprint(JsonSchemaMixin.all_json_schemas(schema_type=SchemaType.SWAGGER_V3))
{'Address': {'description': 'Postal Address',
             'properties': {'building': {'type': 'string'},
                            'city': {'type': 'string'},
                            'street': {'type': 'string'}},
             'required': ['building', 'street', 'city'],
             'type': 'object'},
 'Company': {'description': 'Company Details',
             'properties': {'address': {'$ref': '#/components/schemas/Address'},
                            'name': {'type': 'string'}},
             'required': ['name', 'address'],
             'type': 'object'}}

Custom validation using NewType

from dataclasses_jsonschema import JsonSchemaMixin, FieldEncoder

PhoneNumber = NewType('PhoneNumber', str)

class PhoneNumberField(FieldEncoder):

    @property
    def json_schema(self):
        return {'type': 'string', 'pattern': r'^(\([0-9]{3}\))?[0-9]{3}-[0-9]{4}$'}

JsonSchemaMixin.register_field_encoders({PhoneNumber: PhoneNumberField()})

@dataclass
class Person(JsonSchemaMixin):
    name: str
    phone_number: PhoneNumber

For more examples see the tests

APISpec Plugin

New in v2.5.0

OpenAPI & Swagger specs can be generated using the apispec plugin:

from typing import Optional, List
from dataclasses import dataclass

from apispec import APISpec
from apispec_webframeworks.flask import FlaskPlugin
from flask import Flask, jsonify
import pytest

from dataclasses_jsonschema.apispec import DataclassesPlugin
from dataclasses_jsonschema import JsonSchemaMixin


# Create an APISpec
spec = APISpec(
    title="Swagger Petstore",
    version="1.0.0",
    openapi_version="3.0.2",
    plugins=[FlaskPlugin(), DataclassesPlugin()],
)


@dataclass
class Category(JsonSchemaMixin):
    """Pet category"""
    name: str
    id: Optional[int]

@dataclass
class Pet(JsonSchemaMixin):
    """A pet"""
    categories: List[Category]
    name: str


app = Flask(__name__)


@app.route("/random")
def random_pet():
    """A cute furry animal endpoint.
    ---
    get:
      description: Get a random pet
      responses:
        200:
          content:
            application/json:
              schema: Pet
    """
    pet = get_random_pet()
    return jsonify(pet.to_dict())

# Dependant schemas (e.g. 'Category') are added automatically
spec.components.schema("Pet", schema=Pet)
with app.test_request_context():
    spec.path(view=random_pet)

TODO

dataclasses-jsonschema's People

Contributors

enerqi avatar jameskeane avatar robinoburka avatar s-knibbs avatar snorfalorpagus avatar tirkarthi avatar vbrulis avatar y-mironov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

dataclasses-jsonschema's Issues

Cannot install with pipenv SSLCertVerificationError

Hi,

I have managed to install dataclasses-jsonschema on a local machine, but it seems to be impossible to install on a remote machine (GCE instance).
They are both Windows 10, same Python 3.7 build, same pipenv build, same pip build.

$ pipenv --support

Pipenv version: '2018.10.13'

Pipenv location: 'C:\\Python37\\lib\\site-packages\\pipenv'

Python location: 'C:\\Python37\\python.exe'

Python installations found:

  • 3.7.0: C:\Python37\python.exe
  • 2.7: C:\Python27\python.exe

PEP 508 Information:

{'implementation_name': 'cpython',
 'implementation_version': '3.7.0',
 'os_name': 'nt',
 'platform_machine': 'AMD64',
 'platform_python_implementation': 'CPython',
 'platform_release': '10',
 'platform_system': 'Windows',
 'platform_version': '10.0.14393',
 'python_full_version': '3.7.0',
 'python_version': '3.7',
 'sys_platform': 'win32'}

System environment variables:

  • ALLUSERSPROFILE
  • APPDATA
  • CHOCOLATEYINSTALL
  • CHOCOLATEYLASTPATHUPDATE
  • CHOCOLATEYTOOLSLOCATION
  • CLIENTNAME
  • COMMONPROGRAMFILES
  • COMMONPROGRAMFILES(X86)
  • COMMONPROGRAMW6432
  • COMPUTERNAME
  • COMSPEC
  • GOOGETROOT
  • GROOVY_HOME
  • HOMEDRIVE
  • HOMEPATH
  • LOCALAPPDATA
  • LOGONSERVER
  • NUMBER_OF_PROCESSORS
  • OS
  • PATH
  • PATHEXT
  • PROCESSOR_ARCHITECTURE
  • PROCESSOR_IDENTIFIER
  • PROCESSOR_LEVEL
  • PROCESSOR_REVISION
  • PROGRAMDATA
  • PROGRAMFILES
  • PROGRAMFILES(X86)
  • PROGRAMW6432
  • PROMPT
  • PSMODULEPATH
  • PUBLIC
  • SESSIONNAME
  • SYSTEMDRIVE
  • SYSTEMROOT
  • TEMP
  • TMP
  • USERDOMAIN
  • USERDOMAIN_ROAMINGPROFILE
  • USERNAME
  • USERPROFILE
  • WINDIR
  • WIXSHARP_DIR
  • WIXSHARP_WIXDIR
  • PYTHONDONTWRITEBYTECODE
  • PIP_SHIMS_BASE_MODULE
  • PIP_PYTHON_PATH

Pipenv–specific environment variables:

Debug–specific environment variables:

  • PATH: C:\Python37\Scripts\;C:\Python37\;C:\Python27\;C:\Python27\Scripts;C:\ProgramData\Oracle\Java\javapath;C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\ProgramData\GooGet;C:\Program Files\Google\Compute Engine\metadata_scripts;C:\Program Files (x86)\Google\Cloud SDK\google-cloud-sdk\bin;C:\Program Files\Google\Compute Engine\sysprep;C:\ProgramData\chocolatey\bin;C:\Program Files\Git\cmd;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\tools\groovy-2.4.13\bin;C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\;C:\Program Files\Perforce;C:\Program Files\Perforce\;C:\Users\admin\AppData\Local\Microsoft\WindowsApps

Contents of Pipfile ('C:\jenkins\workspace\mpto-assets-migrator_master-W24OXHX6O6XT4BHM3AUCEGUVPKCE3IDTNCLQDKID4BJYVHOO6L6A\Pipfile'):

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[dev-packages]

[packages]
aiohttp = "==3.4.4"
click = "==6.7"
aiostream = "==0.3.1"
aiofiles = "==0.4.0"
black = "==18.9b0"
dataclasses-jsonschema = "==1.3.0"
asyncwrap = "==0.1.0"

[requires]
python_version = "3.7"

Contents of Pipfile.lock ('C:\jenkins\workspace\mpto-assets-migrator_master-W24OXHX6O6XT4BHM3AUCEGUVPKCE3IDTNCLQDKID4BJYVHOO6L6A\Pipfile.lock'):

{
    "_meta": {
        "hash": {
            "sha256": "affdff9fcf0faffc5e0db889cb25f6c677c6c46040ab6045aafbed864920befc"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.7"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "aiofiles": {
            "hashes": [
                "sha256:021ea0ba314a86027c166ecc4b4c07f2d40fc0f4b3a950d1868a0f2571c2bbee",
                "sha256:1e644c2573f953664368de28d2aa4c89dfd64550429d0c27c4680ccd3aa4985d"
            ],
            "index": "pypi",
            "version": "==0.4.0"
        },
        "aiohttp": {
            "hashes": [
                "sha256:0419705a36b43c0ac6f15469f9c2a08cad5c939d78bd12a5c23ea167c8253b2b",
                "sha256:1812fc4bc6ac1bde007daa05d2d0f61199324e0cc893b11523e646595047ca08",
                "sha256:2214b5c0153f45256d5d52d1e0cafe53f9905ed035a142191727a5fb620c03dd",
                "sha256:275909137f0c92c61ba6bb1af856a522d5546f1de8ea01e4e726321c697754ac",
                "sha256:3983611922b561868428ea1e7269e757803713f55b53502423decc509fef1650",
                "sha256:51afec6ffa50a9da4cdef188971a802beb1ca8e8edb40fa429e5e529db3475fa",
                "sha256:589f2ec8a101a0f340453ee6945bdfea8e1cd84c8d88e5be08716c34c0799d95",
                "sha256:789820ddc65e1f5e71516adaca2e9022498fa5a837c79ba9c692a9f8f916c330",
                "sha256:7a968a0bdaaf9abacc260911775611c9a602214a23aeb846f2eb2eeaa350c4dc",
                "sha256:7aeefbed253f59ea39e70c5848de42ed85cb941165357fc7e87ab5d8f1f9592b",
                "sha256:7b2eb55c66512405103485bd7d285a839d53e7fdc261ab20e5bcc51d7aaff5de",
                "sha256:87bc95d3d333bb689c8d755b4a9d7095a2356108002149523dfc8e607d5d32a4",
                "sha256:9d80e40db208e29168d3723d1440ecbb06054d349c5ece6a2c5a611490830dd7",
                "sha256:a1b442195c2a77d33e4dbee67c9877ccbdd3a1f686f91eb479a9577ed8cc326b",
                "sha256:ab3d769413b322d6092f169f316f7b21cd261a7589f7e31db779d5731b0480d8",
                "sha256:b066d3dec5d0f5aee6e34e5765095dc3d6d78ef9839640141a2b20816a0642bd",
                "sha256:b24e7845ae8de3e388ef4bcfcf7f96b05f52c8e633b33cf8003a6b1d726fc7c2",
                "sha256:c59a953c3f8524a7c86eaeaef5bf702555be12f5668f6384149fe4bb75c52698",
                "sha256:cf2cc6c2c10d242790412bea7ccf73726a9a44b4c4b073d2699ef3b48971fd95",
                "sha256:e0c9c8d4150ae904f308ff27b35446990d2b1dfc944702a21925937e937394c6",
                "sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0",
                "sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07"
            ],
            "index": "pypi",
            "version": "==3.4.4"
        },
        "aiostream": {
            "hashes": [
                "sha256:2f7be31c02789975269d91c5fa5764661fae9562682e29630b964f5f83383e2e"
            ],
            "index": "pypi",
            "version": "==0.3.1"
        },
        "appdirs": {
            "hashes": [
                "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
                "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
            ],
            "version": "==1.4.3"
        },
        "async-timeout": {
            "hashes": [
                "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
                "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
            ],
            "markers": "python_version >= '3.5.3'",
            "version": "==3.0.1"
        },
        "asyncwrap": {
            "hashes": [
                "sha256:f0e06f02cc20807309687a989cb47fbe824482cbd4b657bc9f510596f9ccaf9a"
            ],
            "index": "pypi",
            "version": "==0.1.0"
        },
        "attrs": {
            "hashes": [
                "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
                "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
            ],
            "version": "==18.2.0"
        },
        "black": {
            "hashes": [
                "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
                "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"
            ],
            "index": "pypi",
            "version": "==18.9b0"
        },
        "chardet": {
            "hashes": [
                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
            ],
            "version": "==3.0.4"
        },
        "click": {
            "hashes": [
                "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
                "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
            ],
            "index": "pypi",
            "version": "==6.7"
        },
        "dataclasses-jsonschema": {
            "hashes": [
                "sha256:b95008ff766221f310949057a1719b1962bd67da774b43902cbad14f7b92e7ce"
            ],
            "index": "pypi",
            "version": "==1.3.0"
        },
        "idna": {
            "hashes": [
                "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
                "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
            ],
            "version": "==2.7"
        },
        "jsonschema": {
            "hashes": [
                "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08",
                "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"
            ],
            "version": "==2.6.0"
        },
        "multidict": {
            "hashes": [
                "sha256:05eeab69bf2b0664644c62bd92fabb045163e5b8d4376a31dfb52ce0210ced7b",
                "sha256:0c85880efa7cadb18e3b5eef0aa075dc9c0a3064cbbaef2e20be264b9cf47a64",
                "sha256:136f5a4a6a4adeacc4dc820b8b22f0a378fb74f326e259c54d1817639d1d40a0",
                "sha256:14906ad3347c7d03e9101749b16611cf2028547716d0840838d3c5e2b3b0f2d3",
                "sha256:1ade4a3b71b1bf9e90c5f3d034a87fe4949c087ef1f6cd727fdd766fe8bbd121",
                "sha256:22939a00a511a59f9ecc0158b8db728afef57975ce3782b3a265a319d05b9b12",
                "sha256:2b86b02d872bc5ba5b3a4530f6a7ba0b541458ab4f7c1429a12ac326231203f7",
                "sha256:3c11e92c3dfc321014e22fb442bc9eb70e01af30d6ce442026b0c35723448c66",
                "sha256:4ba3bd26f282b201fdbce351f1c5d17ceb224cbedb73d6e96e6ce391b354aacc",
                "sha256:4c6e78d042e93751f60672989efbd6a6bc54213ed7ff695fff82784bbb9ea035",
                "sha256:4d80d1901b89cc935a6cf5b9fd89df66565272722fe2e5473168927a9937e0ca",
                "sha256:4fcf71d33178a00cc34a57b29f5dab1734b9ce0f1c97fb34666deefac6f92037",
                "sha256:52f7670b41d4b4d97866ebc38121de8bcb9813128b7c4942b07794d08193c0ab",
                "sha256:5368e2b7649a26b7253c6c9e53241248aab9da49099442f5be238fde436f18c9",
                "sha256:5bb65fbb48999044938f0c0508e929b14a9b8bf4939d8263e9ea6691f7b54663",
                "sha256:60672bb5577472800fcca1ac9dae232d1461db9f20f055184be8ce54b0052572",
                "sha256:669e9be6d148fc0283f53e17dd140cde4dc7c87edac8319147edd5aa2a830771",
                "sha256:6a0b7a804e8d1716aa2c72e73210b48be83d25ba9ec5cf52cf91122285707bb1",
                "sha256:79034ea3da3cf2a815e3e52afdc1f6c1894468c98bdce5d2546fa2342585497f",
                "sha256:79247feeef6abcc11137ad17922e865052f23447152059402fc320f99ff544bb",
                "sha256:81671c2049e6bf42c7fd11a060f8bc58f58b7b3d6f3f951fc0b15e376a6a5a98",
                "sha256:82ac4a5cb56cc9280d4ae52c2d2ebcd6e0668dd0f9ef17f0a9d7c82bd61e24fa",
                "sha256:9436267dbbaa49dad18fbbb54f85386b0f5818d055e7b8e01d219661b6745279",
                "sha256:94e4140bb1343115a1afd6d84ebf8fca5fb7bfb50e1c2cbd6f2fb5d3117ef102",
                "sha256:a2cab366eae8a0ffe0813fd8e335cf0d6b9bb6c5227315f53bb457519b811537",
                "sha256:a596019c3eafb1b0ae07db9f55a08578b43c79adb1fe1ab1fd818430ae59ee6f",
                "sha256:e8848ae3cd6a784c29fae5055028bee9bffcc704d8bcad09bd46b42b44a833e2",
                "sha256:e8a048bfd7d5a280f27527d11449a509ddedf08b58a09a24314828631c099306",
                "sha256:f6dd28a0ac60e2426a6918f36f1b4e2620fc785a0de7654cd206ba842eee57fd"
            ],
            "markers": "python_version >= '3.4.1'",
            "version": "==4.4.2"
        },
        "python-dateutil": {
            "hashes": [
                "sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0",
                "sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8"
            ],
            "version": "==2.7.3"
        },
        "six": {
            "hashes": [
                "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
                "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
            ],
            "version": "==1.11.0"
        },
        "toml": {
            "hashes": [
                "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
                "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
            ],
            "version": "==0.10.0"
        },
        "yarl": {
            "hashes": [
                "sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9",
                "sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee",
                "sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308",
                "sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357",
                "sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78",
                "sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8",
                "sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1",
                "sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4",
                "sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7"
            ],
            "version": "==1.2.6"
        }
    },
    "develop": {}
}

When I do python -m pipenv install dataclasses-jsonschema==1.3.0 the same thing happens as when I do python -m pipenv install:

An error occurred while installing dataclasses-jsonschema==1.3.0 --hash=sha256:b95008ff766221f310949057a1719b1962bd67da774b43902cbad14f7b92e7ce! Will try again.
Installing initially failed dependencies…
Collecting dataclasses-jsonschema==1.3.0 
  Using cached https://files.pythonhosted.org/packages/81/70/6a2ea82f8c7bb297bab4a172febdb3b161b907b2cab521d919401652cf5e/dataclasses-jsonschema-1.3.0.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "C:\Python37\Lib\urllib\request.py", line 1317, in do_open
        encode_chunked=req.has_header('Transfer-encoding'))
      File "C:\Python37\Lib\http\client.py", line 1229, in request
        self._send_request(method, url, body, headers, encode_chunked)
      File "C:\Python37\Lib\http\client.py", line 1275, in _send_request
        self.endheaders(body, encode_chunked=encode_chunked)
      File "C:\Python37\Lib\http\client.py", line 1224, in endheaders
        self._send_output(message_body, encode_chunked=encode_chunked)
      File "C:\Python37\Lib\http\client.py", line 1016, in _send_output
        self.send(msg)
      File "C:\Python37\Lib\http\client.py", line 956, in send
        self.connect()
      File "C:\Python37\Lib\http\client.py", line 1392, in connect
        server_hostname=server_hostname)
      File "C:\Python37\Lib\ssl.py", line 412, in wrap_socket
        session=session
      File "C:\Python37\Lib\ssl.py", line 850, in _create
        self.do_handshake()
      File "C:\Python37\Lib\ssl.py", line 1108, in do_handshake
        self._sslobj.do_handshake()
    ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1045)
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 765, in open_url
        return open_with_auth(url, self.opener)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 959, in _socket_timeout
        return func(*args, **kwargs)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 1078, in open_with_auth
        fp = opener(request)
      File "C:\Python37\Lib\urllib\request.py", line 222, in urlopen
        return opener.open(url, data, timeout)
      File "C:\Python37\Lib\urllib\request.py", line 525, in open
        response = self._open(req, data)
      File "C:\Python37\Lib\urllib\request.py", line 543, in _open
        '_open', req)
      File "C:\Python37\Lib\urllib\request.py", line 503, in _call_chain
        result = func(*args)
      File "C:\Python37\Lib\urllib\request.py", line 1360, in https_open
        context=self._context, check_hostname=self._check_hostname)
      File "C:\Python37\Lib\urllib\request.py", line 1319, in do_open
        raise URLError(err)
    urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1045)>
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "C:\Users\admin\AppData\Local\Temp\pip-install-7r15ad19\dataclasses-jsonschema\setup.py", line 37, in <module>
        'Topic :: Software Development :: Libraries'
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\__init__.py", line 139, in setup
        _install_setup_requires(attrs)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\__init__.py", line 134, in _install_setup_requires
        dist.fetch_build_eggs(dist.setup_requires)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\dist.py", line 514, in fetch_build_eggs
        replace_conflicting=True,
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\pkg_resources\__init__.py", line 777, in resolve
        replace_conflicting=replace_conflicting
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\pkg_resources\__init__.py", line 1060, in best_match
        return self.obtain(req, installer)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\pkg_resources\__init__.py", line 1072, in obtain
        return installer(requirement)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\dist.py", line 581, in fetch_build_egg
        return cmd.easy_install(req)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\command\easy_install.py", line 664, in easy_install
        not self.always_copy, self.local_index
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 654, in fetch_distribution
        dist = find(requirement)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 634, in find
        loc = self.download(dist.location, tmpdir)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 578, in download
        found = self._download_url(scheme.group(1), spec, tmpdir)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 823, in _download_url
        return self._attempt_download(url, filename)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 829, in _attempt_download
        headers = self._download_to(url, filename)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 728, in _download_to
        fp = self.open_url(url)
      File "c:\users\admin\.virtualenvs\mpto-assets-migrator_master-w24oxhx6o6xt4b-g2dsurfx\lib\site-packages\setuptools\package_index.py", line 779, in open_url
        % (url, v.reason))
    distutils.errors.DistutilsError: Download error for https://files.pythonhosted.org/packages/b2/d5/970632917c53a1fb2751f7da8b288d26546f2b113e4321674051fc9f81e4/setuptools_scm-3.1.0-py2.py3-none-any.whl#sha256=cc6953d224a22f10e933fa2f55c95979317c55259016adcf93310ba2997febfa: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1045)
    
    ----------------------------------------

Command "python setup.py egg_info" failed with error code 1 in C:\Users\admin\AppData\Local\Temp\pip-install-7r15ad19\dataclasses-jsonschema\
You are using pip version 18.0, however version 18.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.

Regards

NewType of dataclass is not serialised

Please see the minimal example, below:

from dataclasses import dataclass
from dataclasses_jsonschema import JsonSchemaMixin
from typing import NewType


@dataclass
class Location(JsonSchemaMixin):
    x: float
    y: float

NewLocation = NewType('NewLocation', Location)

@dataclass
class Working(JsonSchemaMixin):
    location: Location

@dataclass
class NotWorking(JsonSchemaMixin):
    newlocation: NewLocation


w = Working(location=Location(x=1.234, y=5.678))
print(w.to_dict())
n = NotWorking(newlocation=NewLocation(Location(x=1.234, y=5.678)))
print(n.to_dict())

The output from this isn't serialised in to JSON, but seemingly in to the str/repr of the NotWorking.newlocation:

{'location': {'x': 1.234, 'y': 5.678}}
{'newlocation': Location(x=1.234, y=5.678)}

Tested with dataclasses_jsonschema 1.4.2 and python 3.7.0

Field Encoders do not override

Hi!

While trying this library, I've stumbled upon a problem.
I have:

from dataclasses import dataclass, field
from dataclasses_jsonschema import JsonSchemaMixin, FieldEncoder
from enum import Enum

class Environment(str, Enum):
    DEV = "development"
    STAGING = "staging"
    PROD = "production"
    CUSTOM = "custom"

class EnvironmentField(FieldEncoder):
    def to_wire(self, value: Environment) -> str:
        return str(value.capitalize())
    def to_python(self, value: str) -> Environment:
        return Environment(value.lower())
    @property
    def json_schema(self):
        return {'type': 'string', 'format': 'environment'}

JsonSchemaMixin.register_field_encoders({Environment: EnvironmentField()})

@dataclass
class UserSession(JsonSchemaMixin):
    vaultId: str
    sessionToken: str
    serverEnvironment: Environment = field(default_factory=Environment)

But in the json that I have to handle, the environment is capitalized.
So, I defined a new FieldEncoder to handle it.
But it's not used, as in JsonSchemaMixin._decode_field, the custom field encoders are handled last, only if there's no other match before. However, this is an enum, so, it tries to decode it the enum way, and fails (it's capitalized).

The encoder is done correctly, btw.

Another issue that I faced (same use case) was with the validation. It tries to validate it against the enum, and not with the FieldEncoder. Which kind of make sense (maybe a FieldValidator would be needed?) but is not consistent with the FieldEncoder idea.

So, the fix for the first issue would be easy. For the second issue, I'm not sure though...

Regards

Decoding json fails if default nested classes are used

ps: Sorry for the change of names, I made a mistake

I want to load a class that contains a class from a dict. But I get the error "object` has no attribute 'get'".

Example:

@dataclass
class nested(JsonSchemaMixin):
      a: str = "hi"
      b: int = 4
@dataclass
class test_schema(JsonSchemaMixin):
            """description"""
            champ: nested = field(default=nested())

When I want to load with an empty dict:

print(test_schema.from_dict({}))

I have an error:

AttributeError: 'nested' object has no attribute 'get'

Do you have an idea ?
I have a solution, can I propose it in a pull request ?

Support default field values other than None

There's a small issue that pops up when you have non-None default values. I see the issues happens on line 290 when it does data.get(mapped_field) but I'm not sure the best way to resolve. (failed hard on test_embeddable_json_schema)

@DataClass
class DefaultVal(JsonSchemaMixin):
a: int
b: Optional[int] = 5

should pass:

assert DefaultVal.from_dict({'a': 1}) == DefaultVal(a=1)

Warning when calling .from_dict with Dict[str, Any]

Hi,
I use Dict[str, Any] as a type in a dataclass. It works when I call .from_dict function, but warning that Unable to decode value.

Example is following:

from dataclasses import dataclass
from typing import Dict, Any

from dataclasses_jsonschema import JsonSchemaMixin


@dataclass
class Condition(JsonSchemaMixin):
    meta: Dict[str, Any]


print(Condition.from_dict({"meta": {"s": 3}}))

Output:

Condition(meta={'s': 3})
/usr/local/lib/python3.7/site-packages/dataclasses_jsonschema/__init__.py:306: UserWarning: Unable to decode value for 'meta: Any'
  warnings.warn(f"Unable to decode value for '{field}: {field_type_name}'")

Python interpreter version: 3.7

dataclasses-jsonschema version: 2.3.0

Support field descriptions using field metadata

Additional metadata can be given to dataclass fields.

This could be used to provide a description of each field like so:

@dataclass
class InventoryItem(JsonSchemaMixin):
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float = field(metadata=dict(description="Unit price in £"))
    quantity_on_hand: int = 0

Test that postponed annotations work in python 3.7

We should test that postponed annotations work in python 3.7 by adding in the tests:

from __future__ import annotations

This should just work, since get_type_hints is already used to resolve the annotations

Fields with default_factory are included in required properties

Fields that use default_factory are included in the required schema arguments, so:

@dataclass
class Config(JsonSchemaMixin):
    """A configuration class""
    some_mapping: Dict[str, str] = field(default_factory=dict)

pprint(Config.json_schema())

outputs the following:

{'$schema': 'http://json-schema.org/draft-06/schema#',
 'definitions': {},
 'description': 'A configuration class',
 'properties': {'some_mapping': {'additionalProperties': {'type': 'string'},
                                 'type': 'object'}},
 'required': ['some_mapping'],
 'type': 'object'}

Nullable type causes mypy errors

The Nullable type added in #78 is currently causing mypy errors. It should be changed to:

T = TypeVar("T")
Nullable = Union[T, _NULL_TYPE]

I didn't do this initially since there doesn't seem to be an easy way to reflect on the types in python 3.6.

Support nullable fields

There should be an option to allow nullable fields. I propose the following api:

@dataclass
class Address(JsonSchemaMixin):
    company: Optional[str] = field(default=None, metadata=dict(nullable=True))
    building: str
    street: str
    ...

There are a couple of ways to support nullable fields in JSON schema, either:

{
  "properties": {
    "company": {"type": ["string", "null"]}
  }
}

or:

{
  "properties": {
    "company": {"oneOf": [{"type": "string"}, {"type": "null"}]}
  }
}

The former is not supported in Swagger / OpenAPI though. OpenAPI 3 also supports the following:

{
  "properties": {
    "company": {"type": "string", "nullable": true}
  }
}

Replace pyvalico with fastjsonschema

It seems that the valico library is not particularly well maintained.
The project website now seems to be offline and I've discovered a couple of issue with the schema parsing which seem unlikely to be fixed soon:

  • The handling of exclusiveMaximum/exclusiveMinimum is incorrect for draft-04 schemas. It's expecting a number, but in draft-04 this should be a boolean.
  • The schema parser throws an error if objects are used as default values if keys that are not jsonschema keywords are used. These should be ignored instead.

Additionally, I've discovered that the pure python fastjsonschema performs just as well, if not better and seems to be better maintained.

Change private fields from _ to __

Hi!
I'm trying to declare a class member with name
_id
as the mongodb index field of the class but the library exclude fields name with _

Would be nice to have an option to change that to another prefix like __
Thanks

Edit:
the private prefix is hardcoded here:

if f.name.startswith("_") or (not base_fields and f.name in base_field_names):

Should be as easy as define a member (_priv_prefix?) for JsonSchemaMixin?

Optional type check fails with optional union type

The following type does not currently get identified as optional:

Optional[Union[int, float]]

This is because the code is looking for type(None) as the second value in the __args__ array but does not take into account the flattening of Union types (Optional is an alias of Union[T, type(None)]).

Warning raised if None used as default for readOnly field

The following raises a warning in the latest version (2.4.0) and raises an error in the previous release:

@dataclass
class Employee(JsonSchemaMixin)
    id: Optional[int] = field(default=None, metadata=JsonSchemaMeta(read_only=True))

Support literal types with enum members

The following is not currently supported which is valid according to PEP-586

class Color(Enum):
    RED,
    GREEN,
    BLUE,
    PURPLE

@dataclass
class ColorInfo(JsonSchemaMixin):
    primary_color: Literal[Color.RED, Color.GREEN, Color.BLUE]
    ...

Improve support for non string dict keys

Currently you can serialise the following since the keys can be converted to strings:

class Foo:
    names_by_id: Dict[UUID, str]

However, when converting back from json, the dict keys are not converted back to UUIDs.

Error when generating the json schema with default values

The following error is produced when calling json_schema or all_json_schemas before calling from_dict:

../lib/python3.6/site-packages/dataclasses_jsonschema/__init__.py:406: in all_json_schemas
    definitions.update(subclass.json_schema(embeddable=True, schema_type=schema_type))
../lib/python3.6/site-packages/dataclasses_jsonschema/__init__.py:450: in json_schema
    properties[target_field], is_required = cls._get_field_schema(field, schema_type)
../lib/python3.6/site-packages/dataclasses_jsonschema/__init__.py:316: in _get_field_schema
    default = cls._decode_field(field.name, field.type, field.default)
TypeError: 'NoneType' object is not subscriptable

Version: 2.1.0
This happens because cls._decode_field is not yet initialised

Support dataclass inheritance using allOf

The allOf keyword can be used to support composition. So from the example given in the swagger docs:

@dataclass
class Pet(JsonSchemaMixin):
    name: str

@dataclass
class Cat(Pet):
    hunting_skill: HuntingSkill

would generate the following schema:

definitions:
  Pet:
    type: object
    properties:
      name:
        type: string
    required:
    - name
  Cat:
    description: A representation of a cat
    allOf:
    - $ref: '#/definitions/Pet'
    - type: object
      properties:
        hunting_skill:
          type: string
          enum:
...

Note: I don't plan to support the discriminator keyword when generating swagger schemas.

Keyword 'x-enum-name' breaks schema validation

The 'x-enum-name' attribute added in v1.5.0 breaks schema validation when using pyvalico.

Whilst it seems the swagger spec supports extension keywords beginning with 'x-', this doesn't seem to be the case with with the json schema spec, so this will need to be controlled with a parameter.

Optional fields without None default not handled correctly

The following is currently broken in v2.1.0 due to default field handling introduced in #32:

class Weekday(Enum):
    MONDAY = 'mon'
    TUESDAY = 'tue'


class Foo:
    name: str
    day: Optional[Weekday]

Foo.from_dict({'name': 'test'})

This is because the enum field is initialised with the MISSING value instead of None

Support serialisation of dataclass properties

It would be useful to be able to serialise dataclass properties as read-only properties in the json schema.

I propose to create a serialised_property subclass of property to do this, for example:

@dataclass
class Rectangle(JsonSchemaMixin):
    width: int
    height: int

    @serialised_property
    def area(self) -> int:
        return width * height

print(Rectangle(width=5, height=6).to_dict())
# Prints: {'width': 5, 'height': 6, 'area': 30}

This would generate the following json schema:

    "properties": {
        "width": {"type": "number"},
        "height": {"type": "number"},
        "area": {"type": "number", "readOnly": true},
    },
    "required": ["width", "height"]
}

Error when using Dict[str, Any] as type in dataclass

I'm getting an error when trying to use Dict[str, Any] as a type in a dataclass, see example below.

from dataclasses import dataclass
from dataclasses_jsonschema import JsonSchemaMixin
from typing import Dict, Any


@dataclass
class Feature(JsonSchemaMixin):
    id: int
    properties: Dict[str, Any]
    geometry: Dict[str, Any]


feature = Feature(
    id=1,
    properties={"a": 1, "b": "xyz"},
    geometry={"type": "Point", "coordinates": [1.2, 3.4]}
)

# round-trip
assert feature.to_dict() == Feature.from_dict(feature.to_dict()).to_dict()

The exception is:

/home/snorf/.local/share/virtualenvs/my-project-cQlQitVM/lib/python3.6/site-packages/dataclasses_jsonschema.py:219: UserWarning: Unable to create schema for 'None'
  warnings.warn(f"Unable to create schema for '{field_type_name}'")
Traceback (most recent call last):
  File "bug.py", line 20, in <module>
    assert feature.to_dict() == Feature.from_dict(feature.to_dict()).to_dict()
  File "/home/snorf/.local/share/virtualenvs/my-project-cQlQitVM/lib/python3.6/site-packages/dataclasses_jsonschema.py", line 171, in from_dict
    schema_validate(data, cls.json_schema())
  File "/home/snorf/.local/share/virtualenvs/my-project-cQlQitVM/lib/python3.6/site-packages/jsonschema/validators.py", line 541, in validate
    cls(schema, *args, **kwargs).validate(instance)
  File "/home/snorf/.local/share/virtualenvs/my-project-cQlQitVM/lib/python3.6/site-packages/jsonschema/validators.py", line 130, in validate
    raise error
jsonschema.exceptions.ValidationError: 'xyz' is not of type 'object'

Failed validating 'type' in schema['properties']['properties']['additionalProperties']:
    {'type': 'object'}

On instance['properties']['b']:
    'xyz'

Tested on Python 3.6 on Linux.

Deprecate calling json_schema on the base class

Calling json_schema on the JsonSchemaMixin class should be dreprecated and an all_json_schema method added instead.

This should make the functionality clearer. Additionally, this means that JsonSchemaMixin can be subclassed so that only a subset of the json schema are returned.

Generate dataclass from jsonschema feature

Would it be possible to generate Python from json schema ?
Usecase: I want to use dataclasses in my app but the jsonschema is generated by a third party.
Would be good if I could generate a model file with all the dataclasses from the jsonschema so that I can validate my data before sending it to their API.
I could write all the classes manually but being able to auto generate them would be a good thing!

Add apispec plugin

It would be good to add a plugin to allow this to be used with apispec which seems to be quite popular for generating openapi schemas.

Optional "types"

Hey there.
Thanks for this super useful projects. I've been using it a bit over the last few days and so far its been a smooth ride!

I was wondering about Union type support or more precisely Optionals?
I've seen you mentioned it as a todo in the Readme but I was wondering whether you are actively working on it?
If not would you welcome a contribution there? And also would you have some starting remarks?
Thanks again for sharing your work! In any case i want to express our intrest in it!

Cheers

Base classes may only be used by one derived class

If I create a base class with some common fields I can create a subclass and it works, but if I create more than one subclass from the same base class the validation fails.

I added tests/test_inheritance.py

from dataclasses import dataclass
from dataclasses_jsonschema import JsonSchemaMixin


def test_inheritance():
    @dataclass
    class Base(JsonSchemaMixin):
        base: str

    @dataclass
    class Derived1(Base):
        derived1: str

    @dataclass
    class Derived2(Base):
        derived2: str

    data1 = {"derived1": "d", "base": "b"}
    d = Derived1.from_dict(data1)
    assert d.to_dict() == data1

    data2 = {"derived2": "d", "base": "b"}
    d = Derived2.from_dict(data2)
    assert d.to_dict() == data2

Expected output: the test should pass.
Actual output:

GLOB sdist-make: /Users/duncan.booth/github/dataclasses-jsonschema/setup.py
py37 inst-nodeps: /Users/duncan.booth/github/dataclasses-jsonschema/.tox/.tmp/package/1/dataclasses-jsonschema-2.6.2.dev0+ge79e8e1.d20190712.zip
py37 installed: apispec==2.0.2,apispec-webframeworks==0.4.0,atomicwrites==1.3.0,attrs==19.1.0,Click==7.0,dataclasses-jsonschema==2.6.2.dev0+ge79e8e1.d20190712,entrypoints==0.3,flake8==3.7.8,Flask==1.1.1,importlib-metadata==0.18,itsdangerous==1.1.0,Jinja2==2.10.1,jsonschema==3.0.1,MarkupSafe==1.1.1,mccabe==0.6.1,more-itertools==7.1.0,mypy==0.711,mypy-extensions==0.4.1,packaging==19.0,pluggy==0.12.0,py==1.8.0,pycodestyle==2.5.0,pyflakes==2.1.1,pyparsing==2.4.0,pyrsistent==0.15.3,pytest==5.0.1,pytest-ordering==0.6,python-dateutil==2.8.0,PyYAML==5.1.1,six==1.12.0,typed-ast==1.4.0,typing-extensions==3.7.4,wcwidth==0.1.7,Werkzeug==0.15.4,zipp==0.5.2
py37 run-test-pre: PYTHONHASHSEED='2578755219'
py37 run-test: commands[0] | pytest tests
==================================================================== test session starts =====================================================================
platform darwin -- Python 3.7.2, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
cachedir: .tox/py37/.pytest_cache
rootdir: /Users/duncan.booth/github/dataclasses-jsonschema
plugins: ordering-0.6
collected 28 items                                                                                                                                           

tests/test_core.py ..........................                                                                                                          [ 92%]
tests/test_inheritance.py F                                                                                                                            [ 96%]
tests/test_apispec_plugin.py .                                                                                                                         [100%]

========================================================================== FAILURES ==========================================================================
______________________________________________________________________ test_inheritance ______________________________________________________________________

self = <jsonschema.validators.RefResolver object at 0x107a187b8>
document = {'$schema': 'http://json-schema.org/draft-06/schema#', 'allOf': [{'$ref': '#/definitions/Base'}, {'properties': {'deri...{'type': 'string'}}, 'required': ['derived1'], 'type': 'object'}], 'description': 'Derived1(base: str, derived1: str)'}
fragment = 'definitions/Base'

    def resolve_fragment(self, document, fragment):
        """
        Resolve a ``fragment`` within the referenced ``document``.
    
        Arguments:
    
            document:
    
                The referent document
    
            fragment (str):
    
                a URI fragment to resolve within it
        """
    
        fragment = fragment.lstrip(u"/")
        parts = unquote(fragment).split(u"/") if fragment else []
    
        for part in parts:
            part = part.replace(u"~1", u"/").replace(u"~0", u"~")
    
            if isinstance(document, Sequence):
                # Array indexes should be turned into integers
                try:
                    part = int(part)
                except ValueError:
                    pass
            try:
>               document = document[part]
E               KeyError: 'definitions'

.tox/py37/lib/python3.7/site-packages/jsonschema/validators.py:776: KeyError

During handling of the above exception, another exception occurred:

    def test_inheritance():
        @dataclass
        class Base(JsonSchemaMixin):
            base: str
    
        @dataclass
        class Derived1(Base):
            derived1: str
    
        @dataclass
        class Derived2(Base):
            derived2: str
    
        data1 = {"derived1": "d", "base": "b"}
        d = Derived1.from_dict(data1)
        assert d.to_dict() == data1
    
        data2 = {"derived2": "d", "base": "b"}
>       d = Derived2.from_dict(data2)

tests/test_inheritance.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dataclasses_jsonschema/__init__.py:396: in from_dict
    cls._validate(data)
dataclasses_jsonschema/__init__.py:383: in _validate
    validate_func(data, cls.json_schema())
.tox/py37/lib/python3.7/site-packages/jsonschema/validators.py:897: in validate
    error = exceptions.best_match(validator.iter_errors(instance))
.tox/py37/lib/python3.7/site-packages/jsonschema/exceptions.py:293: in best_match
    best = next(errors, None)
.tox/py37/lib/python3.7/site-packages/jsonschema/validators.py:323: in iter_errors
    for error in errors:
.tox/py37/lib/python3.7/site-packages/jsonschema/_validators.py:303: in allOf
    for error in validator.descend(instance, subschema, schema_path=index):
.tox/py37/lib/python3.7/site-packages/jsonschema/validators.py:339: in descend
    for error in self.iter_errors(instance, schema):
.tox/py37/lib/python3.7/site-packages/jsonschema/validators.py:323: in iter_errors
    for error in errors:
.tox/py37/lib/python3.7/site-packages/jsonschema/_validators.py:247: in ref
    scope, resolved = validator.resolver.resolve(ref)
.tox/py37/lib/python3.7/site-packages/jsonschema/validators.py:734: in resolve
    return url, self._remote_cache(url)
.tox/py37/lib/python3.7/site-packages/jsonschema/validators.py:746: in resolve_from_url
    return self.resolve_fragment(document, fragment)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <jsonschema.validators.RefResolver object at 0x107a187b8>
document = {'$schema': 'http://json-schema.org/draft-06/schema#', 'allOf': [{'$ref': '#/definitions/Base'}, {'properties': {'deri...{'type': 'string'}}, 'required': ['derived1'], 'type': 'object'}], 'description': 'Derived1(base: str, derived1: str)'}
fragment = 'definitions/Base'

    def resolve_fragment(self, document, fragment):
        """
        Resolve a ``fragment`` within the referenced ``document``.
    
        Arguments:
    
            document:
    
                The referent document
    
            fragment (str):
    
                a URI fragment to resolve within it
        """
    
        fragment = fragment.lstrip(u"/")
        parts = unquote(fragment).split(u"/") if fragment else []
    
        for part in parts:
            part = part.replace(u"~1", u"/").replace(u"~0", u"~")
    
            if isinstance(document, Sequence):
                # Array indexes should be turned into integers
                try:
                    part = int(part)
                except ValueError:
                    pass
            try:
                document = document[part]
            except (TypeError, LookupError):
                raise exceptions.RefResolutionError(
>                   "Unresolvable JSON pointer: %r" % fragment
                )
E               jsonschema.exceptions.RefResolutionError: Unresolvable JSON pointer: 'definitions/Base'

.tox/py37/lib/python3.7/site-packages/jsonschema/validators.py:779: RefResolutionError
====================================================================== warnings summary ======================================================================
.tox/py37/lib/python3.7/site-packages/jinja2/utils.py:485
  /Users/duncan.booth/github/dataclasses-jsonschema/.tox/py37/lib/python3.7/site-packages/jinja2/utils.py:485: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    from collections import MutableMapping

.tox/py37/lib/python3.7/site-packages/jinja2/runtime.py:318
  /Users/duncan.booth/github/dataclasses-jsonschema/.tox/py37/lib/python3.7/site-packages/jinja2/runtime.py:318: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    from collections import Mapping

.tox/py37/lib/python3.7/site-packages/_pytest/mark/structures.py:332
  /Users/duncan.booth/github/dataclasses-jsonschema/.tox/py37/lib/python3.7/site-packages/_pytest/mark/structures.py:332: PytestUnknownMarkWarning: Unknown pytest.mark.last - is this a typo?  You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
    PytestUnknownMarkWarning,

-- Docs: https://docs.pytest.org/en/latest/warnings.html
====================================================== 1 failed, 27 passed, 3 warnings in 0.42 seconds =======================================================
ERROR: InvocationError for command /Users/duncan.booth/github/dataclasses-jsonschema/.tox/py37/bin/pytest tests (exited with code 1)
__________________________________________________________________________ summary ___________________________________________________________________________
ERROR:   py37: commands failed

The code fails when attempting to do Derived2.from_dict(...) but only if we have previously called Derived1.from_dict(...). Whichever order I try to validate the data the first one succeeds and the second one fails.

The problem appears to be that the JsonSchemaMixin fields _schema, _compiled_schema and so on are shared between all classes in the same hierarchy. If I change the code so that Base no longer inherits from JsonSchemaMixin the two derived classes no longer share the mixin's attributes and everything works.

Don't include empty definitions object in generated schema

A minor issue, but when generating a schema from a dataclass that references no other dataclasses an empty definitions object is generated. This should be omitted instead. Example:

@dataclass
class Point(JsonSchemaMixin):
    x: int
    y: int

Current generated schema:

{
  "type": "object",
  "required": [
    "x",
    "y"
  ],
  "properties": {
    "x": {
      "type": "integer"
    },
    "y": {
      "type": "integer"
    }
  },
  "description": "Point(x: int, y: int)",
  "definitions": {},
  "$schema": "http://json-schema.org/draft-06/schema#"
}

Expected schema:

{
  "type": "object",
  "required": [
    "x",
    "y"
  ],
  "properties": {
    "x": {
      "type": "integer"
    },
    "y": {
      "type": "integer"
    }
  },
  "description": "Point(x: int, y: int)",
  "$schema": "http://json-schema.org/draft-06/schema#"
}

Support literal types

Support for the literal type annotation should be added. Example:

@dataclass
class ImageMetadata(JsonSchemaMixin):
    width: int
    height: int
    bits_per_pixel: Literal[8, 16, 24]

Generates the following schema:

{
  "type": "object",
  "required": [
    "width",
    "height",
    "bits_per_pixel"
  ],
  "properties": {
    "width": {
      "type": "integer"
    },
    "height": {
      "type": "integer"
    },
    "bits_per_pixel": {
      "type": "integer",
      "enum": [8, 16, 24]
    }
  },
}

Definitions can be missed for nested types

In the following example, the definition for Bar will be missing in the schema output:

@dataclass
class Bar(JsonSchemaMixin):
    a: int

@dataclass
class Foo(JsonSchemaMixin):
    b: Optional[List[Bar]]

This is because we are not recursing fully into the type annotation for field 'b'.

Support adding examples to the generated schema

It would useful to be able to include examples in the generated json schema. I can think of a couple of ideas for implementing this:

  1. Add an examples classmethod that can be overriden:
@dataclass
class Point(JsonSchemaMixin):
    x: float
    y: float

    @classmethod
    def examples(self) -> List['Point']:
        return [
            Point(1.0, 2.0),
            Point(0.0, -1.0)
        ]
  1. Add an examples option to the field metadata:
@dataclass
class Line(JsonSchemaMixin):
    coords: List[Point] = field(metadata=dict(examples=[Point(1.0, 2.0)]))

Both 1 and 2 could be implemented together.

Note: Examples should be ignored when generating draft 4 json schema, since this was only added in draft 6.

Support for `pattern` keyword in string parameters

The OpenAPI 2.0 specification has support for a pattern argument to strings that allows you to specify a regular expression the input should conform to.

The example given as for a social security number:

    ssn:
      type: string
      pattern: '^\d{3}-\d{2}-\d{4}$'

Support for this feature would be useful. I'm not sure how we can encode it in the dataclasses though?

Go has "tags" on struct fields for this kind of thing, e.g.

type User struct {
    Name string `json:"name" xml:"name"`
}

Is there an equivalent in Python we could use?

Support for swagger / openapi discriminators

Swagger 2.0 and OpenAPI 3 have support for polymorphism via the discriminator keyword.

Support could be added for generating this automatically via a discriminator argument on the base class:

@dataclass
class Pet(JsonSchemaMixin, discriminator=True)
    name: str

@dataclass
class Cat(Pet):
    hunting_skill: Literal["clueless", "lazy", "adventurous", "aggressive"] = "lazy"

This would generate the following schema in OpenAPI 3:

      "Pet": {
        "type": "object",
        "discriminator": {
          "propertyName": "petType"
        },
        "properties": {
          "name": {
            "type": "string"
          },
          "petType": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "petType"
        ]
      },
      "Cat": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Pet"
          },
          {
            "type": "object",
            "properties": {
              "hunting_skill": {
                "type": "string",
                "default": "lazy",
                "enum": [
                  "clueless",
                  "lazy",
                  "adventurous",
                  "aggressive"
                ]
              }
            },
            "required": [
              "huntingSkill"
            ]
          }
        ]
      },

When serialising to json via to_dict, the petType property name will be automatically set to the name of the class.

Optionally, the discriminator property name that gets created could be overriden by passing a string as the discriminator argument to the base class:

@dataclass
class Pet(JsonSchemaMixin, discriminator="propertyName"):
    ...

This issue is blocked until #40 is resolved.

Support nested literal types

It seems that the typing module doesn't flatten nested literal type definitions, so the following does not currently work:

SuccessStatus = Literal[200, 201, 204]
ClientError = Literal[404, 401, 403, 409, 400]
ServerError = Literal[500, 504, 503]

@dataclass
class HttpResponse(JsonSchemaMixin):
    status: Literal[SuccessStatus, ClientError, ServerError]
    ...

Add support for constant fields

In #62 basic support for 'Final' types was added. However the following form, which defines a constant vaued field is not currently supported:

@dataclass
class Rectangle(JsonSchemaMixin):
    max_width: Final = 20
    x: int
    y: int

This could generate one of the following schemas for the max_width field:

Draft 04:

{"enum": [20]}

Draft 06:

{"const": 20}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.