Skip to main content

sarj-python-lint

Custom Python lint rules via stdlib ast. Designed for pre-commit. For SQL rules see sarj-sql-lint.

uv tool install sarj-python-lint

Pre-commit

- repo: https://github.com/sarj-ai/standards
  rev: python-v0.50.0
  hooks:
    - id: sarj-no-sequential-await
    - id: sarj-inefficient-string-concat-in-loop
    - id: sarj-prefer-str-enum
    - id: sarj-no-fat-try-blocks
    - id: sarj-pydantic-at-boundaries
    - id: sarj-fastapi-openapi-contract            # SARJ094
    - id: sarj-no-hidden-constructor-fallback      # SARJ095 (warning)
    - id: sarj-prefer-self-documenting-constant    # SARJ097 (warning)
    - id: sarj-no-duplicate-dunder-all-entry       # SARJ098 (warning)
    - id: sarj-redundant-module-docstring          # SARJ099 (warning)
    - id: sarj-prefer-class-row
    - id: sarj-prefer-timedelta-for-durations
    - id: sarj-prefer-struct-over-namedtuple
    - id: sarj-no-comment-cruft
    - id: sarj-no-fstring-in-log
    - id: sarj-prefer-non-nullable-collection      # SARJ074

FastAPI OpenAPI contracts (0.44.0)

SARJ094 complements Ruff's ANN* and FAST001-FAST003 checks. It requires schema-visible operations to declare their summary, description and status; uses described Annotated request markers; rejects schema-erasing response shapes and response projections; requires explicit content schemas for direct response objects; and keeps direct errors, custom responses, bodyless statuses, GET/HEAD inputs and local route ordering honest in OpenAPI. Missing Python annotations remain owned by Ruff's ANN* rules, so enable both policies.

The rule resolves module-level FastAPI imports and locally constructed or aliased routers without guessing from names. Dynamic decorator mappings, function-local framework imports, imported router instances and the assembled app.openapi() document remain application-level integration-test concerns. Existing projects can adopt the default-enabled rule with --update-baseline and then shrink that baseline as endpoint contracts are repaired.

Hidden constructor settings fallback (0.45.0)

SARJ095 warns when a keyword-only constructor parameter defaults to None and the constructor silently replaces it with a proven pydantic-settings value. The effective dependency is invisible at the call site, and value or settings.VALUE also treats an explicit falsey value as omitted. Make the argument required and resolve the setting at the application composition root; the annotation may remain nullable when None is still a valid explicit value.

The rule resolves same-module settings objects, imports, aliases and re-exports back to an instance of a pydantic_settings.BaseSettings subclass. Literal and enum defaults, mutable-container initialization, arbitrary factories and clients, module constants, environment-variable APIs, other parameters and instance state are deliberately outside v1. No autofix is offered because changing constructor optionality requires coordinated call-site edits.

Measured over 4,638 tracked Python files in 33 first-party repositories: three constructor warnings across two repositories, all three actionable. A pinned 15-repository OSS sweep covered 29,203 files and produced zero reports, which is compatibility evidence rather than a precision claim. An environment-variable arm was rejected before shipping: it reported a public first-party library and two intentional LiteLLM integration constructors, all non-actionable.

Self-documenting constants

SARJ097 warns when an immediately attached, same-indent comment is the only place a numeric module or class constant names its unit. It also replaces bare HTTP integers in commented *STATUS_CODES collections with a recommendation to use http.HTTPStatus members. A unit already present in the constant name and unit-bearing values such as timedelta(seconds=10) are accepted.

The first release intentionally avoids judging boolean-policy comments or trying to delete rationale. It does not autofix: diagnostics ask callers to encode the proven fact in the name or value while preserving compatibility, security and vendor-contract reasoning that code cannot express.

Export and module-docstring noise

SARJ098 reports an exact duplicate string inside one static package __all__ declaration. It deliberately accepts explicit re-export facades: imports bind the package namespace, while __all__ defines its supported export surface. Dynamic or subsequently mutated export lists are skipped, and there is no autofix because export order and comments can be observable.

SARJ099 warns only when a one-sentence module docstring re-spells its filename and immediate package path, such as element.py saying “Element class for element operations.” Package initializers, tests, generated files, structured or multiline docs, and prose carrying an invariant, consumer, compatibility constraint, value or external reference are excluded. In particular, the rule keeps architecture statements about canonical JSON/SARIF diagnostics. It does not overlap SARJ085, which remains the owner of class-name restatements.

Test-quality rules (0.15.0)

Mined from an AST audit of ~7,500 test functions across two production repos. Every one is scoped to test files and carries the false-positive guard that made it shippable; sarj-python-lint explain links each rule to its behavioral tests.

    - id: sarj-mock-without-spec                   # SARJ040
    - id: sarj-test-loops-over-literal-cases       # SARJ041
    - id: sarj-parametrize-case-needs-id           # SARJ042
    - id: sarj-zero-assertion-test                 # SARJ043
    - id: sarj-fixture-returns-bare-tuple          # SARJ044
    - id: sarj-kwarg-heavy-construction-in-test    # SARJ045
    - id: sarj-xfail-requires-strict               # SARJ046
    - id: sarj-sleep-with-computed-arg-in-test     # SARJ047

Private access, first-party only (0.19.0)

    - id: sarj-no-first-party-private-import       # SARJ048

Reaching past a module's public surface is a design finding when the module is ours and an unavoidable fact of life when it is not: a dependency that moves an API private in a minor release leaves no edit that satisfies the lint.

SARJ048 fires only when the module declaring the private name resolves to a package inside your own project. Third-party privates are never flagged.

It replaces ruff's PLC2701 import-private-name, whose only exemption is same top-level package — a different question, and one that cannot separate from app.stores.order_store import _row_to_order (real; export it) from from livekit.agents.inference_runner import _InferenceRunner (no fix exists). sarj-lint-configs ≥ 0.8.0 ships PLC2701 in its ignore list for exactly this reason; if you take that config, turn this hook on, or you lose the check entirely.

Attribute access (session._stt) is out of scope and stays with ruff's SLF001, which cannot make the distinction either — see the rationale in ruff.strict.toml.

Comment-hygiene rules (0.20.0)

From a 37,918-comment, nine-repo measurement study. All three are deletion-class, so each was validated against pydantic / trio / attrs as well as the maintained repos before shipping, with each false-positive guard encoded in the behavioral tests linked by sarj-python-lint explain.

    - id: sarj-no-restated-comment                 # SARJ049
    - id: sarj-redundant-docstring                 # SARJ050
    - id: sarj-trailing-value-narration            # SARJ051

redundant-docstring finds real volume on a codebase that has never had it (105 in one first-party repo), so the same baseline ratchet applies.

Docstring-ceremony rules (0.31.0)

SARJ050 tests a function docstring against its own signature. That leaves three shapes it cannot reach, each now its own code so a consumer can baseline them separately:

    - id: sarj-duplicated-override-docstring       # SARJ084
    - id: sarj-redundant-class-docstring           # SARJ085
    - id: sarj-docstring-args-restate-signature    # SARJ086

SARJ084 flags an override whose docstring is byte-identical to the base method's, with the base resolved by undotted name inside the same file. There is no judgement call — the test is byte equality — and inspect.getdoc, help(), Sphinx and editor hovers all walk the MRO, so deleting the copy changes nothing a reader sees. 49 first-party findings, 49 true positives; 137 across 14 OSS repos, 18 sampled and read, 0 false positives.

SARJ085 flags a class docstring that only re-spells the class name — the case SARJ050's walker structurally never inspects. Its largest guard is that anything whose docstring becomes a published schema description (pydantic models, enums, TypedDicts, @strawberry.type) is exempt: that string is emitted as the JSON-Schema description and reaches OpenAPI documents and LLM tool schemas. The exemption costs 28 of 34 first-party findings and is not negotiable.

SARJ086 flags an Args: block whose every entry only re-spells its own parameter. It fires where SARJ050 cannot: the header word "args" is a content word no signature contains, so any Args: block makes a docstring permanently unflaggable by SARJ050 — 126 first-party functions carry one and SARJ050 flags none of them. The remedy deletes the section and keeps the summary, which was checked against the shipped strict config: ruff's D417 does not fire on a docstring with no parameter section.

Two more shapes were rejected on volume — property docstrings restating the property name and reST/epydoc type duplication (:type x: int, :rtype:) both measure 0 first-party findings outside generated code.

The Returns: half (0.36.0)

    - id: sarj-docstring-returns-restate-signature  # SARJ087

SARJ087 is the Returns: sibling of SARJ086, and it was rejected once: deleting a Returns: section used to make ruff's DOC201 fire, so the only compliant remedy was deleting the whole docstring. #164 then removed DOC201 from ruff.strict.toml as a rule that DEMANDS prose, and the premise expired — under the shipped config the section goes and the summary stays.

756 findings over 33 OSS repos / 35,254 files; two seeded samples of 40 and 20 read against source gave ~2% false positives, the whole of which was one family (Returns: A new X — whether the value is a copy is the one thing -> Self cannot say) now guarded. Three findings on this repo's own source, all true, all deleted. The guarded copy-return case is covered by the paired rule tests.

Test ceremony, and the census it was chosen from (0.38.0)

    - id: sarj-restated-test-docstring              # SARJ088
    - id: sarj-test-phase-label-comment             # SARJ089

Every comment GROUP in 19 repositories / 45,900 Python files was collected with its adjacent code and classified: 451,482 groups, 1,293,022 lines, of which the seven comment/docstring rules that predate this release reached 4.9%. The shipped predicate and its boundaries are recorded in the paired rule tests.

The largest precisely-detectable class left in it is the test docstring: 52,894 of them, 10.1% of every comment group, and SARJ050 reached 4.7%. It reached so few because SARJ050 measures a docstring against its signature, and a test's specification is its BODY. SARJ088 measures it against the signature, the identifiers in the test's own body, and the vocabulary a test docstring spends on being a test. 5,382 findings; 98 read at source, 0 false positives on the shipped predicate.

SARJ089 deletes the bare # given / # when / # then / # Arrange / # Act / # Assert phase label. 27,714 findings, 36 read, 0 false positives — but 94.5% come from one OSS suite, and none from any first-party repo. It is a fence against the convention arriving, not a cleanup; adopt it behind the baseline ratchet.

Shipped with them, _docstrings.STOPWORDS gained FILLER_QUALIFIERS: 31 qualifiers that narrow nothing (specific, appropriate, entire, overall). One of these was the commonest single reason a pure restatement survived the whole family — """Get a specific account by ID.""" over get_account(self, account_id: str). +683 findings across SARJ050/085/086/087, -0; 58 of the delta read, ~3.4% false positives. main was tried and rejected: as filler it makes """Main function.""" content-free, hence unflaggable.

Three shapes measured on the same census and rejected, each on a seeded 12-finding read at source:

shape findings true why it fails
comment restates the if / for / with header below it 225 3/12 the population is BRANCH LABELS naming a case (# PIL.Image over if isinstance(item, PILImage.Image)), not narration
comment restates a plain assignment (no call on the RHS) 308 2/12 the population heads a multi-line literal or a 3-statement block — a section label, which is SARJ016's subject
multi-line comment run restating the block it heads 119 1-2/12 banners, Sphinx #: attribute docs, and worked calculations dominate

Those three are why SARJ049 still excludes block openers, plain assignments and multi-line runs. The exclusions are load-bearing, not unfinished work.

House conventions moved out of consumer repos (0.21.0)

    - id: sarj-no-stdlib-logging                   # SARJ052
    - id: sarj-no-gen-random-uuid-in-sql           # SARJ053
    - id: sarj-no-file-level-escape-hatch-noqa     # SARJ054

SARJ052 bans importing stdlib logging in application code, because the house logger is loguru and two logger hierarchies mean two handler chains: the records written to the one nobody configured skip the JSON formatter, the redaction patcher and the error reporter, and — since the stdlib root defaults to WARNING — usually vanish in production while looking fine locally.

The one legitimate reason to touch stdlib logging in a loguru house is to bridge it, and the bridge cannot be written without naming both loggers, so a module importing loguru is exempt. Measured across two production repos that exemption is exact: all four sites that import stdlib logging (three in the first repo — its package __init__.py, a dedicated configure_logging.py, and a service entrypoint main.py — plus a common/logging.py in the second) are bridges, all four import loguru, and no other module in either repo imports stdlib logging at all. Tests, scripts/, notebooks/, generated files and if TYPE_CHECKING: imports are also exempt.

This is a house-convention rule, not a universal one. A library should log through stdlib logging precisely so it does not impose a sink on its callers — trio's three sites are correct for trio. Enable it in applications only.

SARJ053 flags gen_random_uuid() in SQL embedded in a Python string literal: UUIDv4 keys scatter B-tree inserts across every leaf page, where uuidv7() (Postgres 18) is time-ordered and appends. It is the embedded-SQL third of a policy the stack already states twice — ruff.strict.toml bans uuid.uuid4, and sarj-sql-lint's SARJ109 prefer-uuidv7-default covers .sql migration files (41 sites in one first-party repo, 14 in another, all of them a primary-key DEFAULT). A literal only counts when it is SQL-shaped, so prose naming the function is not a finding.

SARJ054 is SARJ038's scoped sibling. SARJ038 bans the unscoped blanket (# ruff: noqa); this bans a scoped file-level exemption that names an escape-hatch code — a code whose remediation ruff.strict.toml spells as an inline # noqa: CODE — <reason>, which today is TID251 alone, ruff's only banned-API code. Hoisting that to the top of a file turns N reviewed per-site decisions into one unreviewable one and pre-authorizes every mock added later. Scoped exemptions for mechanical codes (E501, F401, UP035) are never flagged — measured across five repos those are the entire population.

Mock-quality and real-dependency rules (0.24.0)

The second test-quality wave. Where the 0.15.0 family asks "does this test assert anything?", this one asks "does it exercise anything real?" — it pushes suites off hand-rolled doubles and onto the real store, the real database and a maintained fake library. Measured against seven first-party repos and fourteen OSS corpora; two candidates were dropped outright when the corpus showed they only duplicated ruff.

    - id: sarj-prefer-real-store-in-tests          # SARJ058
    - id: sarj-prefer-library-fake                 # SARJ059
    - id: sarj-tautological-mock-assertion         # SARJ060
    - id: sarj-over-mocked-test                    # SARJ062
    - id: sarj-interaction-only-test               # SARJ063
    - id: sarj-trivially-true-assertion            # SARJ064
    - id: sarj-conditional-assertion-in-test       # SARJ065
    - id: sarj-duplicate-test-body                 # SARJ066
    - id: sarj-unused-mock-setup                   # SARJ067

Expressiveness rules (0.24.0)

    - id: sarj-prefer-fstring-over-concat          # SARJ068
    - id: sarj-prefer-or-pattern                   # SARJ070
    - id: sarj-require-port-for-service            # SARJ071

require-port-for-service treats a local concrete superclass differently from an abstract/Protocol port and follows local port inheritance transitively. Class-size enforcement stays with Ruff PLR0904 (enabled by the strict ALL selection); a second custom size rule would duplicate that owner.

Tuple boundaries are deliberately stricter than Ruff: SARJ026 rejects fixed multi-field tuple annotations on public production functions, including local aliases and abstract/NotImplemented contracts, while SARJ044 applies the same named-result requirement to pytest fixtures. Variadic tuple[T, ...] remains a sequence rather than a positional record. Prefer typing.NamedTuple, a frozen dataclass, or a frozen validation model at schema boundaries.

SARJ093 recognizes str, int, UUID and supported containers as non-nominal ID carriers, while preserving NewType aliases as nominal identities. SARJ006 keeps wire-derived values open, respects Literal/enum annotations across closures and comprehensions, and recognizes explicit and Django-style choice collections.

Suppression ratchet (sarj-ratchet, 0.21.0)

    - id: sarj-suppression-ratchet

One tool replacing the per-repo ratchet scripts. It counts every escape hatch in the tree and enforces three ceilings that may only shrink:

  • per codenoqa:TID251 going 40 → 41 is a regression even if the total falls
  • per package — one package's headroom must not finance another's debt
  • per file — a global cap so new suppressions cannot pile into one hot spot; pre-existing hot spots are grandfathered at their then-current counts

All four dialects are counted under distinct key prefixes, so moving a suppression between spellings can never hide it: noqa:CODE, sarj-noqa:CODE, pyright:CODE, type-ignore:CODE / bare type-ignore, plus the file-level file-noqa:CODE / file-noqa:<blanket> and file-pyright:RULE.

sarj-ratchet --update python/          # seed (or lock in a drop)
sarj-ratchet python/                   # gate
sarj-ratchet --update --allow-increase python/   # a reviewed ceiling raise

--update refuses to raise a ceiling unless --allow-increase says the raise was reviewed, and it drops a per-file grandfather clause as soon as the file falls back under the global cap, so an allowance cannot outlive its debt.

Two conventions that stayed pygrep

sarj-fakes-in-shared-location and sarj-no-raw-connection-in-tests ship as pygrep hooks, not SARJ rules, and both need a files:/exclude: from the consumer. An AST port of each was built and measured, and the boundary each encodes turned out to be repo-specific rather than shared: "shared fake" flagged 9/9 single-use test doubles in one first-party repo that are idiomatic where they sit, and "raw connection in a test" flagged 46 sites in another of which every one is already an intentional exemption (store tests asserting DB state, pool-lifecycle tests, retention tests where physical deletion is the subject). SARJ036 no-raw-sql-in-tests remains the corpus-validated shared rule for raw SQL in tests.

Adopting these against an existing suite is easier through the baseline ratchet than as a big-bang fix — snapshot the current counts, then let them only shrink:

sarj-python-lint check --rule mock-without-spec --update-baseline test-quality-baseline.json python/
sarj-python-lint check --rule mock-without-spec --baseline test-quality-baseline.json python/

Multi-tenant scoping (0.24.0)

    - id: sarj-no-optional-tenant-predicate        # SARJ056

SARJ056 fires when every WHERE-fragment mentioning a tenant column (organization_id and friends) sits inside a conditional, so the predicate disappears — and the query still runs — whenever the filter is empty:

where_conditions = []
if args.organization_ids:  # ← optional
    where_conditions.append(SQL("organization_id = ANY(%s::uuid[])"))
...
where_clause = SQL(" AND ").join(where_conditions) if where_conditions else SQL("1=1")

The safe idiom seeds the list, so scoping always applies and the rule stays quiet:

conditions: list[Composable] = [SQL("organization_id = %s")]

A function with no tenant predicate at all never fires — an intentionally cross-tenant admin query is out of scope; only attempted-but-optional scoping is a finding. Where a caller genuinely wants the all-tenant query, that intent belongs in an explicit method (or an inline sarj-noqa) rather than in an omitted filter.

Measured before shipping: 0 findings across 26,345 files of pydantic, trio, attrs, Airflow and Home Assistant — single-tenant codebases have no tenant column, so the rule is silent by construction — and 0 across four other first-party repos. In the fifth it finds 10 sites, all genuine fail-open compositions, two of which were reachable cross-tenant reads at the time of writing (two paginated list endpoints, both of which composed WHERE 1=1 for a user whose organization_id was NULL).

Assertions that can never fail (0.23.0)

    - id: sarj-no-tautological-expect              # SARJ057

SARJ057 fires when an assertion's operands are all literals, so its outcome is fixed before the code runs. SARJ043 already catches the test with no assertion; this is the test whose assertion is decorative.

The placeholder spelling (assert True) is the obvious half. The expensive half is the assertion whose real condition slid out of the condition slot, because it was a working assertion when it was typed:

assert {  # ← braces, not parentheses
    "referencing a non existing `via_device` " in caplog.text
}  # one-element SET, always truthy

assert [f"No logs found on hdfs for ti={ti}"]  # the `== messages` was lost
assert True, cover_result_json[0]["success"][...]  # slid into the MESSAGE slot

The narrowness is the rule. The obvious generalisation — "flag a comparison of a thing with itself" — measures ~95% false positives: assert i == i, assert x is x, expect(hash([o])).toEqual(hash([o])) are reflexivity, determinism and memoization tests, and for a type with custom __eq__/__hash__ they can genuinely fail. So an identifier, attribute or call operand is never enough; both sides must be literals, and textually identical ones. assert True as the sole statement of an except handler is exempt — it asserts which branch ran — as is anything inside a pytest-benchmark test.

Measured before shipping: 4 findings across 28,608 files — 26,346 of pydantic, trio, attrs, Airflow and Home Assistant plus 2,262 first-party files across five first-party repos. All 4 are true positives (Home Assistant tests/helpers/test_device_registry.py:3711 and :3777, tests/components/emulated_hue/test_hue_api.py:1078, Airflow providers/apache/hdfs/.../log/test_hdfs_task_handler.py:170); 0 false positives. The two except ...: assert True markers that a carve-out-free version does flag — pydantic-core/tests/benchmarks/test_micro_benchmarks.py:716 and core/tests/components/mqtt/test_client.py:1353 — were verified silent.

The TypeScript half of the same rule ships as @sarj/no-tautological-expect in @sarj/eslint-plugin ≥ 2.14.0; until now there was no TS counterpart at all, which is how expect(true).toBe(true); // placeholder survived in a suite named for the behaviour it was supposed to check.

CLI

sarj-python-lint check --rule no-sequential-await path/to/file.py
sarj-python-lint list-rules

Diagnostic format is path:line:col: CODE message — Ruff-compatible.

Suppression

Inline # sarj-noqa: SARJ00X — <reason> on the offending line.

Each rule's source under src/sarj_python_lint/rules/ carries its own description and diagnostic message.

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.

sarj_python_lint-0.50.0-py3-none-any.whl (302.1 kB view details)

Uploaded Python 3

File details

Details for the file sarj_python_lint-0.50.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sarj_python_lint-0.50.0-py3-none-any.whl
Algorithm Hash digest
SHA256 80c294c06619cfdcca56ccc6ea3ebcc018d232cce65e9af7f39162977e48e668
MD5 32e26aa643a3828667a3eff9aa168d28
BLAKE2b-256 c17d6291b40f84c392995949085e47164225e4bf57ce9252ba7b3b591a54b109

See more details on using hashes here.

Provenance

The following attestation bundles were made for sarj_python_lint-0.50.0-py3-none-any.whl:

Publisher: release.yml on sarj-ai/standards

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.77.1

2 files

0.77.0

2 files

0.76.0

2 files

0.75.0

2 files

0.74.0

2 files

0.73.2

2 files

0.73.1

2 files

0.73.0

2 files

0.72.0

2 files

0.71.0

2 files

0.70.1

2 files

0.70.0

2 files

0.69.1

2 files

0.69.0

2 files

0.68.0

2 files

0.67.14

2 files

0.67.13

2 files

0.67.12

2 files

0.67.11

2 files

0.67.10

2 files

0.67.9

2 files

0.67.8

2 files

0.67.7

2 files

0.67.6

2 files

0.67.5

2 files

0.67.4

2 files

0.67.3

2 files

0.67.2

2 files

0.67.0

1 file

0.66.0

1 file

0.65.0

1 file

0.64.0

1 file

0.63.0

1 file

0.62.0

1 file

0.61.0

1 file

0.60.0

1 file

0.59.0

1 file

0.58.0

1 file

0.57.0

1 file

0.56.0

1 file

0.55.0

1 file

0.54.1

1 file

0.54.0

1 file

0.53.0

1 file

0.52.1

1 file

0.52.0

1 file

0.51.3

1 file

0.51.1

1 file

0.51.0

1 file

This release

0.50.0 This release

1 file

0.49.0

1 file

0.48.0

1 file

0.47.1

1 file

0.47.0

1 file

0.46.0

2 files

0.45.0

2 files

0.44.0

2 files

0.43.2

2 files

0.43.0

2 files

0.42.0

2 files

0.41.0

2 files

0.40.0

2 files

0.39.0

2 files

0.38.0

2 files

0.37.0

2 files

0.36.1

2 files

0.36.0

2 files

0.35.0

2 files

0.34.0

2 files

0.33.0

2 files

0.32.0

2 files

0.31.0

2 files

0.30.0

2 files

0.29.0

2 files

0.28.0

2 files

0.27.0

2 files

0.26.0

2 files

0.25.0

2 files

0.24.0

2 files

0.23.0

2 files

0.21.0

2 files

0.20.0

2 files

0.19.0

2 files

0.18.1

2 files

0.18.0

2 files

0.17.0

2 files

0.16.0

2 files

0.15.1

2 files

0.15.0

2 files

0.14.0

2 files

0.13.1

2 files

0.12.2

2 files

0.12.1

2 files

0.12.0

2 files

0.11.1

2 files

0.11.0

2 files

0.10.1

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

Supported by

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