Skip to main content

No project description provided

Project description

This is a fork of Erick Peirson's original jsonschema_typed.

JSON Schema-powered type annotations

The goal of this project is to use JSON Schema for type checking in Python.

While there is not a perfect 1:1 mapping between concepts in JSON Schema and Python's typing system, there is enough of an isomorphism to warrant some exploration of the possibilities. Since a JSON document is generally represented as a dict in Python programs, this project looks specifically at interpreting JSON schema as TypedDict definitions.

Warning: there are bound to be some abuses of the mypy plugin system here. You have been warned.

This leverages (and is inspired by) https://github.com/Julian/jsonschema.

Overview

A JSON schema:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "http://foo.qwerty/some/schema#",
    "title": "Foo Schema",
    "type": "object",
    "properties": {
        "title": {
            "type": "string"
        },
        "awesome": {
            "type": "number"
        }
    },
    "required": ["title"]
}

A TypedDict:

from jsonschema_typed import JSONSchema

data: JSONSchema['path/to/schema.json'] = dict(title='baz')
data['description'] = 'there is no description'  # TypedDict "FooSchema" has no key 'description'
data['awesome'] = 42
data['awesome'] = None  # Argument 2 has incompatible type "None"; expected "Union[int, float]"

Installation

pip install jsonschema-typed

or

git clone git@github.com:erickpeirson/jsonschema-typed.git
cd jsonschema-typed
python setup.py install

Requirements

So far I have only tried this with:

  • mypy==0.701
  • jsonschema==3.0.1

But probably older versions work. You could try it out and let me know.

Limitations

  • additionalProperties doesn't really have an equivalent in TypedDict. Yet.
  • Cases in which the root of the schema is anything other than an object are not terribly interesting for this project, so we ignore them for now. Array values for type (e.g. "type": ["object", "boolean"]) are otherwise supported with Union.
  • The default keyword is not supported; but see: https://github.com/python/mypy/issues/6131.
  • Self-references (e.g. "#") can't really work properly until nested forward-references are supported; see https://github.com/python/mypy/issues/731.

There are probably others.

Approach

So far two approaches are attempted:

  1. Annotating a dict instance that will be a TypedDict that conforms to the JSON Schema (as best we can enforce it).
  2. Using a dynamic base class that is typed as a TypedDict.

Both examples below use the schema:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "http://foo.qwerty/some/schema#",
    "title": "Foo Schema",
    "type": "object",
    "properties": {
        "title": {
            "type": "string"
        },
        "awesome": {
            "type": "number"
        }
    }
}

First approach: annotating a dict

This has the advantage of being fairly simple. It is implemented via jsonschema_typed.plugin.JSONSchemaPlugin.get_type_analyze_hook.

from jsonschema_typed import JSONSchema

data: JSONSchema['path/to/schema.json'] = dict(title='baz')
reveal_type(data)  # Revealed type is 'TypedDict('FooSchema', {'title'?: builtins.str, 'awesome'?: Union[builtins.int, builtins.float]})'
data['description'] = 'there is no description'  # TypedDict "FooSchema" has no key 'description'
data['awesome'] = 42
data['awesome'] = None  # Argument 2 has incompatible type "None"; expected "Union[int, float]"

Here is the mypy output:

main.py:4: error: Revealed type is 'TypedDict('FooSchema', {'title'?: builtins.str, 'awesome'?: Union[builtins.int, builtins.float]})'
main.py:5: error: TypedDict "FooSchema" has no key 'description'
main.py:7: error: Argument 2 has incompatible type "None"; expected "Union[int, float]"

Note that the right-hand side can be a dict or a subclass of dict, so you could define a subclass like:

class Foo(dict):
    """Some domain logic on your object."""

    def do_something(self, arg: int) -> int:
        """Do something awesome."""
        return arg * self['awesome']

data: JSONSchema['schema/readme_example.json'] = Foo(title='baz')
reveal_type(data)  # Revealed type is 'TypedDict('FooSchema', {'title'?: builtins.str, 'awesome'?: Union[builtins.int, builtins.float]})'
data['description'] = 'there is no description'  # TypedDict "FooSchema" has no key 'description'
data['awesome'] = 42
data['awesome'] = None  # Argument 2 has incompatible type "None"; expected "Union[int, float]"

Of course this isn't quite consistent with PEP-589 which states that:

Methods are not allowed, since the runtime type of a TypedDict object will always be just dict (it is never a subclass of dict).

So use at your own risk.

Second approach: dynamic base class

This has the advantage of being able to add some runtime-functionality, e.g. use jsonschema to actually validate data at runtime. It is implemented via jsonschema_typed.plugin.JSONSchemaPlugin.get_dynamic_class_hook.

But again, this isn't quite consistent with PEP-589 which states, so use at your own risk.

from jsonschema_typed.types import JSONSchemaBase


Base = JSONSchemaBase('path/to/schema.json')

class Foo(Base):
    """All your base in one place."""

    def do_something(self, arg: int) -> int:
        """Do something awesome."""
        return arg * self['awesome']


data = Foo(title='baz')
reveal_type(data)  # Revealed type is 'TypedDict('FooSchema', {'title'?: builtins.str, 'awesome'?: Union[builtins.int, builtins.float]})'
data['description'] = 'there is no description'  # TypedDict "FooSchema" has no key 'description'
data['awesome'] = 42
data['awesome'] = None  # Argument 2 has incompatible type "None"; expected "Union[int, float]"

TODO

  • Decide whether to stick with just one approach (and which one)
  • Write some tests
  • Test against other versions of mypy + jsonschema

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jsonschema_typed_v2-0.3.1-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

File details

Details for the file jsonschema_typed_v2-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: jsonschema_typed_v2-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 9.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.41.0 CPython/3.8.0

File hashes

Hashes for jsonschema_typed_v2-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 00097767942d3bd77d0b25a72cf18cab8e54ed18952025e607dce9f532e7df8a
MD5 9754246252d742d3732bd52539b1493f
BLAKE2b-256 ffb2021d7ef5d1e7cd33125105c7c7b31d432f3bcd7d0bf01c92be33bbe346e4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page