nginx-lint-plugin (Python SDK)
Python SDK for writing and testing nginx-lint WASM plugins. The Python
counterpart of the TypeScript SDK at plugins/typescript/nginx-lint-plugin,
built as a single maturin mixed Rust/Python project: one wheel ships the
pure-Python SDK, the componentize-py bindings, and the Rust parser compiled
as a native module.
Layout
python/nginx_lint_plugin/— the SDK package. Everything a plugin needs is re-exported from its root (Plugin,Config,LintError,Fix,Severity, …), so plugin code never imports from the generated bindings directly. It ships a PEP 561py.typedmarker, so mypy and pyright use the annotations.builders—plugin_spec()anderror_builder(), mirroring the Rust SDK'sPluginSpec::new()andspec().error_builder(). The generated dataclasses have no defaults, so without these a spec means spelling out all eleven fields and every error repeats the plugin's rule and category.testing—parse_config()andPluginTestRunnerfor plain-pytest unit tests against the real Rust parser, plusapply_fixes()andPluginTestRunner.assert_fixed()to check what a rule's fixes actually produce, through the same appliernginx-lint --fixusesconfig_builder— reconstructs method-basedConfig/Directiveobjects (matching the componentize-py binding surface, e.g.directive.is_(...)) from parser output or a host snapshot_native— the Rust parser bridge (built by maturin fromsrc/lib.rs; onlytestingimports it, so the rest of the SDK stays bundleable into a WASM component). Built against the stable ABI (abi3-py311), so one wheel per platform covers every Python ≥ 3.11.API_VERSION— the plugin API version, kept in sync withcrates/nginx-lint-plugin
python/wit_world/,python/componentize_py_types.py— componentize-py bindings generated fromwit/nginx-lint-plugin.witbymake bindings, placed as top-level modules so the same imports resolve inside a componentized plugin (the Python analog of the TS SDK'sdist/generated). Like the TS SDK's, they are not committed — regenerated before every install, so they cannot drift from the WIT. They still ship in the wheel and sdist viatool.maturin.include, which applies regardless of.gitignore.Cargo.toml/src/lib.rs— the native parser module (its own cargo workspace, excluded from the repository's root workspace; the crate version is the wheel version, kept in sync with the repository version). It depends onnginx-lint-parserandnginx-lint-commonby exact version rather than by path so that the sdist stands alone; inside this repository../.cargo/config.tomlpatches both back to the working tree, and that file explains why.
Install
Both targets regenerate the bindings and copy the WIT in first, which needs
the componentize-py CLI; make develop also needs maturin. Both are in
the dev dependency group:
cd plugins/python/nginx-lint-plugin
pip install --group dev
make install # pip install .
make develop # editable install for SDK work (maturin develop)
make test # pytest
Outside this repository the SDK is a plain dependency — pip install nginx-lint-plugin is all a plugin author needs, for both testing and
building. The WIT ships inside the package, so componentize-py has an
interface definition to build against:
componentize-py -d "$(python -c 'import nginx_lint_plugin as p; print(p.wit_dir())')" \
-w plugin componentize app -o plugin.wasm --stub-wasi \
-p . -p "$(python -c 'import nginx_lint_plugin as p, pathlib; print(pathlib.Path(p.__file__).parent.parent)')"
The second -p is only needed for editable installs, whose .pth link
componentize-py does not follow; it is harmless otherwise. See
../server-tokens-enabled-py/Makefile for the same commands in a form you
can copy.
Writing a plugin
from nginx_lint_plugin import Config, LintError, Plugin, plugin_spec, error_builder
class WitWorld(Plugin): # the class must keep this name
def spec(self):
return plugin_spec("my-rule", "style", "What it checks",
severity="warning")
def check(self, cfg: Config, path: str) -> list[LintError]:
err = error_builder(self.spec())
return [
err.warning_at("autoindex should be off", ctx.directive,
fixes=[ctx.directive.replace_with("autoindex off;")])
for ctx in cfg.all_directives_with_context()
if ctx.directive.is_("autoindex") and ctx.directive.first_arg_is("on")
]
Testing a plugin
Tests are ordinary pytest. Parsing goes through the same Rust parser the production linter uses:
from app import WitWorld
from nginx_lint_plugin.testing import PluginTestRunner, parse_config
plugin = WitWorld()
runner = PluginTestRunner(plugin.spec, plugin.check)
def test_detects_server_tokens_on():
runner.assert_errors("http {\n server_tokens on;\n}", 1)
def test_include_context():
cfg = parse_config("server_tokens on;", include_context=["http"])
assert len(plugin.check(cfg, "test.conf")) == 1
def test_fix():
# Asserting on the applied output, not just the reported findings: a fix
# the linter normalizes into a different operation than intended shows up
# here rather than in a user's config.
runner.assert_fixed(
"http {\n server_tokens on;\n}\n",
"http {\n server_tokens off;\n}\n",
)
The same WitWorld class runs unmodified under pytest and inside the WASM
component — the test Config/Directive objects reproduce the exact method
surface of the componentize-py bindings.
Why a native module instead of the parser WASM component?
The TS SDK runs the parser as a WASM component inside Node (via jco).
Python currently has no maintained component-model runtime — wasmtime-py
removed its bindgen support — so this SDK compiles the parser natively
via pyo3 instead. Same parser code, same output shape. If wasmtime-py
regrows component support, the WASM path can return without changing the
test-writing API.
Checking the built plugin
The CLI can check a built plugin end to end from the examples in its spec — the bad one has to be reported, the good one clean, and the fixes have to resolve the bad one:
nginx-lint test-plugins --plugins <dir>
Metadata
Release files for nginx-lint-plugin 0.21.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nginx_lint_plugin-0.21.0.tar.gz | 29.8 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| nginx_lint_plugin-0.21.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| nginx_lint_plugin-0.21.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| nginx_lint_plugin-0.21.0-cp311-abi3-macosx_11_0_arm64.whl | CPython 3.11 | abi3 | macOS 11.0+ ARM64 | Details |
| nginx_lint_plugin-0.21.0-cp311-abi3-macosx_10_12_x86_64.whl | CPython 3.11 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 1.6 MB
Release files / nginx_lint_plugin-0.21.0.tar.gz
| Download URL | nginx_lint_plugin-0.21.0.tar.gz |
|---|---|
| Size | 29.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7536d271331ad762a3927168d775870af8d2135ce6e0ac5df01859cd943730bf
|
|
BLAKE2b-256 checksum How to use checksums |
d18f30ec2492e81c691c34b3eb8ce766744b9d3fc1baee018695522599fd642a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / nginx_lint_plugin-0.21.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | nginx_lint_plugin-0.21.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 431.3 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
008dfcaa39afa55cc98a6c418d97186bbab301ecee743764d496be3ed93958a9
|
|
BLAKE2b-256 checksum How to use checksums |
5372b079ae337870b9430c315f16fd384c215a30f0ed0153b249b9e2511c807e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / nginx_lint_plugin-0.21.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | nginx_lint_plugin-0.21.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 391.9 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
836669180c3d98ae3612265e479ea8f04ee51ae17743723c8c534e649b812e27
|
|
BLAKE2b-256 checksum How to use checksums |
8e2f8e631d9f8d46e108bd471d6b28d1f18490a28c7e4140e0b58cd92d8c686d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / nginx_lint_plugin-0.21.0-cp311-abi3-macosx_11_0_arm64.whl
| Download URL | nginx_lint_plugin-0.21.0-cp311-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 352.5 kB |
| Tags | CPython 3.11 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
aed4a77bdd065599bdf637909dca45c30c7c158ef14ff9a63eb08e75ef6ef480
|
|
BLAKE2b-256 checksum How to use checksums |
e3bc090bf449d0aec87c8e523a95743a3899aca96307cdbacb8e236c5daa0d41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / nginx_lint_plugin-0.21.0-cp311-abi3-macosx_10_12_x86_64.whl
| Download URL | nginx_lint_plugin-0.21.0-cp311-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 357.7 kB |
| Tags | CPython 3.11 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
0e89341b9bb33f02745b268d4463b9a6d635de3c396b7576195478fc24929f55
|
|
BLAKE2b-256 checksum How to use checksums |
04064d7508f802b2fb9ae743dfd2293480e426cb8fc399543a86008875b08047
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency log