Developer Guide¶
This document describes the internal architecture and main concepts behind
validate-pyproject
and targets contributors and plugin writers.
How it works¶
validate-pyproject
relies mostly on a set of specification documents represented as JSON Schema.
To run the checks encoded under these schema files validate-pyproject
uses the fastjsonschema package.
This procedure is defined in the api
module,
specifically under the Validator
class.
Validator
objects use
SchemaRegistry
instances to store references
to the JSON schema documents being used for the validation.
The formats
module is also important to this
process, since it defines how to validate the custom values for the
"format"
field defined in JSON Schema, for "string"
values.
Checks for PEP 517, PEP 518 and PEP 621 are performed by default,
however these standards do not specify how the tool
table and its subtables
are populated.
Since different tools allow different configurations, it would be impractical
to try to create schemas for all of them inside the same project.
Instead, validate-pyproject
allows Plugins to provide extra JSON Schemas,
against which tool
subtables can be checked.
Plugins¶
Plugins are a way of extending the built-in functionality of
validate-pyproject
, can be simply described as functions that return
a JSON schema parsed as a Python dict
:
def plugin(tool_name: str) -> dict:
...
These functions receive as argument the name of the tool subtable and should return a JSON schema for the data structure under this table (it should not include the table name itself as a property).
To use a plugin you can pass an extra_plugins
argument to the
Validator
constructor, but you will need to
wrap it with PluginWrapper
to be able to
specify which tool
subtable it would be checking:
from validate_pyproject import api
def your_plugin(tool_name: str) -> dict:
return {
"$id": "https://your-urn-or-url", # $id is mandatory
"type": "object",
"description": "Your tool configuration description",
"properties": {
"your-config-field": {"type": "string", "format": "python-module-name"}
},
}
available_plugins = [
plugins.PluginWrapper("your-tool", your_plugin),
]
validator = api.Validator(extra_plugins=available_plugins)
Please notice that you can also make your plugin “autoloadable” by creating and distributing your own Python package as described in the following section.
If you want to disable the automatic discovery of all “autoloadable” plugins you
can pass plugins=[]
to the constructor; or, for example in the snippet
above, we could have used plugins=...
instead of extra_plugins=...
to ensure only the explicitly given plugins are loaded.
Distributing Plugins¶
To distribute plugins, it is necessary to create a Python package with
a validate_pyproject.tool_schema
entry-point.
For the time being, if using setuptools, this can be achieved by adding the following to your
setup.cfg
file:
# in setup.cfg
[options.entry_points]
validate_pyproject.tool_schema =
your-tool = your_package.your_module:your_plugin
When using a PEP 621-compliant backend, the following can be add to your
pyproject.toml
file:
# in pyproject.toml
[project.entry-points."validate_pyproject.tool_schema"]
your-tool = "your_package.your_module:your_plugin"
The plugin function will be automatically called with the tool_name
argument as same name as given to the entrypoint (e.g. your_plugin("your-tool")
).
Also notice plugins are activated in a specific order, using Python’s built-in
sorted
function.