{ "info": { "author": "Erick Peirson", "author_email": "erick.peirson@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "# JSON Schema-powered type annotations\n\nThe goal of this project is to use JSON Schema for type checking in Python.\n\nWhile there is not a perfect 1:1 mapping between concepts in JSON Schema and\nPython's typing system, there is enough of an isomorphism to warrant some\nexploration of the possibilities. Since a JSON document is generally\nrepresented as a ``dict`` in Python programs, this project looks specifically\nat interpreting JSON schema as\n[``TypedDict``](https://www.python.org/dev/peps/pep-0589/) definitions.\n\n**Warning:** there are bound to be some abuses of the [mypy plugin\nsystem](https://mypy.readthedocs.io/en/latest/extending_mypy.html) here. You\nhave been warned.\n\nThis leverages (and is inspired by) https://github.com/Julian/jsonschema.\n\n## Overview\n\nA JSON schema:\n\n```json\n{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"http://foo.qwerty/some/schema#\",\n \"title\": \"Foo Schema\",\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"awesome\": {\n \"type\": \"number\"\n }\n },\n \"required\": [\"title\"]\n}\n```\n\nA TypedDict:\n\n```python\nfrom jsonschema_typed.types import JSONSchema\n\ndata: JSONSchema['path/to/schema.json'] = dict(title='baz')\ndata['description'] = 'there is no description' # TypedDict \"FooSchema\" has no key 'description'\ndata['awesome'] = 42\ndata['awesome'] = None # Argument 2 has incompatible type \"None\"; expected \"Union[int, float]\"\n```\n\n## Installation\n\n```bash\npip install jsonschema-typed\n```\n\nor\n\n```bash\ngit clone git@github.com:erickpeirson/jsonschema-typed.git\ncd jsonschema-typed\npython setup.py install\n```\n\n## Requirements\n\nSo far I have only tried this with:\n\n- mypy==0.701\n- jsonschema==3.0.1\n\nBut probably older versions work. You could try it out and\n[let me know](https://github.com/erickpeirson/jsonschema-typed/issues).\n\n## Limitations\n\n- ``additionalProperties`` doesn't really have an equivalent in TypedDict. Yet.\n- Cases in which the root of the schema is anything other than an ``object``\n are not terribly interesting for this project, so we ignore them for now.\n Array values for ``type`` (e.g. ``\"type\": [\"object\", \"boolean\"]``) are\n otherwise supported with ``Union``.\n- The ``default`` keyword is not supported; but see:\n https://github.com/python/mypy/issues/6131.\n- Self-references (e.g. ``\"#\"``) can't really work properly until nested\n forward-references are supported; see\n https://github.com/python/mypy/issues/731.\n\nThere are probably others.\n\n\n## Approach\n\nSo far two approaches are attempted:\n\n1. Annotating a ``dict`` instance that will be a ``TypedDict`` that conforms to\n the JSON Schema (as best we can enforce it).\n2. Using a dynamic base class that is typed as a ``TypedDict``.\n\nBoth examples below use the schema:\n\n```json\n{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"http://foo.qwerty/some/schema#\",\n \"title\": \"Foo Schema\",\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"awesome\": {\n \"type\": \"number\"\n }\n }\n}\n```\n\n\n### First approach: annotating a ``dict``\n\nThis has the advantage of being fairly simple. It is implemented via\n``jsonschema_typed.plugin.JSONSchemaPlugin.get_type_analyze_hook``.\n\n```python\nfrom jsonschema_typed.types import JSONSchema\n\ndata: JSONSchema['path/to/schema.json'] = dict(title='baz')\nreveal_type(data) # Revealed type is 'TypedDict('FooSchema', {'title'?: builtins.str, 'awesome'?: Union[builtins.int, builtins.float]})'\ndata['description'] = 'there is no description' # TypedDict \"FooSchema\" has no key 'description'\ndata['awesome'] = 42\ndata['awesome'] = None # Argument 2 has incompatible type \"None\"; expected \"Union[int, float]\"\n```\n\nHere is the mypy output:\n\n```\nmain.py:4: error: Revealed type is 'TypedDict('FooSchema', {'title'?: builtins.str, 'awesome'?: Union[builtins.int, builtins.float]})'\nmain.py:5: error: TypedDict \"FooSchema\" has no key 'description'\nmain.py:7: error: Argument 2 has incompatible type \"None\"; expected \"Union[int, float]\"\n```\n\nNote that the right-hand side can be a ``dict`` or a subclass of ``dict``, so\nyou could define a subclass like:\n\n```python\nclass Foo(dict):\n \"\"\"Some domain logic on your object.\"\"\"\n\n def do_something(self, arg: int) -> int:\n \"\"\"Do something awesome.\"\"\"\n return arg * self['awesome']\n\ndata: JSONSchema['schema/test.json'] = Foo(title='baz')\nreveal_type(data) # Revealed type is 'TypedDict('FooSchema', {'title'?: builtins.str, 'awesome'?: Union[builtins.int, builtins.float]})'\ndata['description'] = 'there is no description' # TypedDict \"FooSchema\" has no key 'description'\ndata['awesome'] = 42\ndata['awesome'] = None # Argument 2 has incompatible type \"None\"; expected \"Union[int, float]\"\n```\n\nOf course this isn't quite consistent with PEP-589 which\n[states](https://www.python.org/dev/peps/pep-0589/#class-based-syntax) that:\n\n> Methods are not allowed, since the runtime type of a TypedDict object will\n> always be just dict (it is never a subclass of dict).\n\nSo use at your own risk.\n\n### Second approach: dynamic base class\n\nThis has the advantage of being able to add some runtime-functionality, e.g.\nuse ``jsonschema`` to actually validate data at runtime. It is implemented via\n``jsonschema_typed.plugin.JSONSchemaPlugin.get_dynamic_class_hook``.\n\nBut again, this isn't quite consistent with PEP-589 which\n[states](https://www.python.org/dev/peps/pep-0589/#class-based-syntax), so\nuse at your own risk.\n\n```python\nfrom jsonschema_typed.types import JSONSchemaBase\n\n\nBase = JSONSchemaBase('path/to/schema.json')\n\nclass Foo(Base):\n \"\"\"All your base in one place.\"\"\"\n\n def do_something(self, arg: int) -> int:\n \"\"\"Do something awesome.\"\"\"\n return arg * self['awesome']\n\n\ndata = Foo(title='baz')\nreveal_type(data) # Revealed type is 'TypedDict('FooSchema', {'title'?: builtins.str, 'awesome'?: Union[builtins.int, builtins.float]})'\ndata['description'] = 'there is no description' # TypedDict \"FooSchema\" has no key 'description'\ndata['awesome'] = 42\ndata['awesome'] = None # Argument 2 has incompatible type \"None\"; expected \"Union[int, float]\"\n```\n\n## TODO\n\n- [ ] Decide whether to stick with just one approach (and which one)\n- [ ] Write some tests\n- [ ] Test against other versions of mypy + jsonschema", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/erickpeirson/jsonschema-typed", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "jsonschema-typed", "package_url": "https://pypi.org/project/jsonschema-typed/", "platform": "", "project_url": "https://pypi.org/project/jsonschema-typed/", "project_urls": { "Homepage": "https://github.com/erickpeirson/jsonschema-typed" }, "release_url": "https://pypi.org/project/jsonschema-typed/0.1.0/", "requires_dist": null, "requires_python": "~=3.6", "summary": "", "version": "0.1.0" }, "last_serial": 5189403, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "5da3221dfe65d3bae9386393f7278e9f", "sha256": "b0a04ba1571563deaa54423c8b8be1a08db99735df1598b7ed3dffb10a5c49a7" }, "downloads": -1, "filename": "jsonschema-typed-0.1.0.tar.gz", "has_sig": false, "md5_digest": "5da3221dfe65d3bae9386393f7278e9f", "packagetype": "sdist", "python_version": "source", "requires_python": "~=3.6", "size": 8318, "upload_time": "2019-04-25T19:04:06", "url": "https://files.pythonhosted.org/packages/a1/1e/2eb6d612341ba74b9ffb945591430bbb70179da8b71d1ac28b2f10e543fb/jsonschema-typed-0.1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "5da3221dfe65d3bae9386393f7278e9f", "sha256": "b0a04ba1571563deaa54423c8b8be1a08db99735df1598b7ed3dffb10a5c49a7" }, "downloads": -1, "filename": "jsonschema-typed-0.1.0.tar.gz", "has_sig": false, "md5_digest": "5da3221dfe65d3bae9386393f7278e9f", "packagetype": "sdist", "python_version": "source", "requires_python": "~=3.6", "size": 8318, "upload_time": "2019-04-25T19:04:06", "url": "https://files.pythonhosted.org/packages/a1/1e/2eb6d612341ba74b9ffb945591430bbb70179da8b71d1ac28b2f10e543fb/jsonschema-typed-0.1.0.tar.gz" } ] }