Package: pytojsonschema
pytojsonschemaPackage that uses static analysis -ast- to convert Python 3 function type annotations to JSON schemas.https://docs.python.org/3/library/typing.htmlhttps://json-schema.org/This allows you to auto-generate the validation schemas for JSON-RPC backend functions written in Python.Current support is for Python 3.8+ and JSON schema draft 7+.Getting startedInstallationFrom a Python 3.8+ environment, runpip install pytojsonschema.Scan a packageAfter installing the package, you can open a python terminal from the root of the repo and run:importosimportpprintfrompytojsonschema.functionsimportprocess_packagepprint.pprint(process_package(os.path.join("test","example")))The example package will be scanned and JSON schemas will be generated for all the top level functions it can find.Scan a fileYou can also target specific files, which won't include the package namespacing in the result value.
Following on the same terminal:frompytojsonschema.functionsimportprocess_filepprint.pprint(process_file(os.path.join("test","example","service.py")))Include and exclude patternsInclude and exclude unix-like patterns can be used to filter function and module names we want to allow/disallow for
scanning. See the difference when you now run this instead:pprint.pprint(process_package(os.path.join("test","example"),exclude_patterns=["_*"]))Similarly, but applied to specific files:pprint.pprint(process_file(os.path.join("test","example","service.py"),exclude_patterns=["_*"]))Things to take into account:Exclude pattern matching overwrite include matches.__init__.pyfiles are not affected by pattern rules and are always scanned. However, you can still filter its
internal functions.Type annotation rulesFitting Python's typing model to JSON means not everything is allowed in your function signatures.
This is a natural restriction that comes with JSON data serialization. Hopefully, most of the useful stuff you need is
allowed.Allowed typesBase typesBasic typesbool,int,float,str,Noneandtyping.Anyare allowed. Also, you can build more complex, nested
structures with the usage oftyping.Union,typing.Optional,typing.Dict(Onlystrkeys are allowed) andtyping.List. All these types have a direct, non-ambiguous representation in both JSON and JSON schema.Custom typesYour functions can also use custom types like the ones defined using an assignment oftyping.Union,typing.List,typing.Dictandtyping.Optional, as in:ServicePort=typing.Union[int,float]ServiceConfig=typing.Dict[str,typing.Any]You can use one of the new Python 3.8 features,typing.TypedDict, to build stronger validation on dict-like
objects (Only class-based syntax). As you can see, you can chain types with no restrictions:classService(typing.TypedDict):address:strport:ServicePortconfig:ServiceConfigtags:typing.List[str]debug:bool=FalseNote: Whilst Python itself will not auto-populate default values, you can use them to make the property not required.Also, if you need to restrict the choices for a type, you can use Python enums:importenumclassHTTPMethod(enum.Enum):GET="GET"POST="POST"PATCH="PATCH"DELETE="DELETE"defmy_func(http_method:HTTPMethod):pass# My codeNote: All properties of the enum must be constants:None,int,float,bool,str.Note: The resulting validation uses the different enum values, e.g.HTTPMethod.GET.valueor simply"GET", as the
valid choices and not the enum instance itself, e.g.HTTPMethod.GET, as that is what JSON schema can understand. This
can lead to some incompatibilities with other static analysis tools likehttp://mypy-lang.org/,
so bear that in mind.Importing types from other filesYou can import these custom types within your package and they will be picked up. However, due to the static nature of
the scan, custom types coming from external packages can't be followed and hence not supported. In other words, you can
only share these types within your package, using relative imports.Other static analysis tools likemypyuse a repository with stub files to solve this issue, seehttps://mypy.readthedocs.io/en/stable/stubs.html. This is out of the
scope for a tiny project like this, at least for now.RulesThe functions you want to scan need to be type annotated. Kind of obvious requirement, right?Only the types defined in the previous section can be used. They are the types that can be safely serialised as JSON.Function arguments are meant to be passed in key-value format, like a json object. This puts a couple of restrictions
regarding *args, **kwargs, positional-only and keyword-only arguments:The following is allowed:**kwargs:def func(**kwargs): passkeyword-only arguments:def func(*, a): passThe following is not allowed:*args:def func(*args): passpositional-only arguments:def func(a, /): pass