Skip to main content

anti-slop-py

skills.sh

Opinionated lint rules that reject low-evidence, low-signal Python patterns. A port of anti-slop (Oxlint/TypeScript) to the Python ecosystem.

Vendor this project instead of pinning it as a dependency. Copy the rules into your repository, read them, and change them until they say what your team actually believes. The bundled agent skill does the first copy and wiring; after that the files are yours to maintain.

Architecture: three layers

Ruff still has no API for custom rules, so anti-slop-py can't be a single plugin. It's one opinionated configuration spread across three tools, each covering the part it already handles well:

  1. Ruff (configs/ruff-anti-slop.toml), for the rules that already exist: ANN401 (no Any in signatures), PGH003 (no blanket type: ignore), B009/B010 (no constant-name getattr/setattr), and TID251 with a banned-API list that includes unittest.mock.patch.
  2. mypy strict flags (configs/mypy-anti-slop.ini). disallow_any_explicit and friends catch Any in the places Ruff can't see: nested generics, aliases, plain variables.
  3. A Flake8 plugin (src/anti_slop/, vendored as a local plugin) carrying the ten custom ASP rules below. It runs next to Ruff, scoped with select = ASP.

Install with an agent skill

Clone the repository and point your coding agent at the bundled skill:

git clone https://github.com/infoslack/anti-slop-py

Then, from the target repository, ask the agent to install anti-slop-py following <clone>/skills/install-anti-slop-py/SKILL.md. The skill copies the plugin to tools/flake8/anti_slop/, registers it as a Flake8 local plugin, merges the Ruff and mypy layers into whatever configuration already exists, and validates the result. When pydantic or fastapi shows up as a direct dependency, it also enables the matching framework group.

If you have Node available, the skills.sh CLI does the fetch in one step and registers the skill with your agent:

npx skills add infoslack/anti-slop-py --skill install-anti-slop-py

Claude Code users can install it as a plugin instead:

/plugin marketplace add infoslack/anti-slop-py
/plugin install anti-slop-py@anti-slop-py

Scan without installing

The rules also run straight from a clone, reporting findings without adding a single file to the target project. Flake8 resolves the plugin relative to the config file, so from the target repository:

flake8 --config <clone>/configs/flake8-scan.ini src tests

Pair it with CLI-only flags for the other layers (ruff check --extend-select ANN401,PGH003,B009,B010, mypy --disallow-any-explicit) and nothing in the repo changes. With the package published, the clone becomes optional too: uvx --with anti-slop-py flake8 --enable-extensions=ASD,ASF src tests. This is the audit mode; the two installation paths below are for repositories that want the rules enforced in CI for every developer.

Install as a package

The lighter enforcement path:

uv add --dev anti-slop-py

The entry points register the ASP rules with Flake8 automatically; a fresh flake8 src tests already reports them. The framework groups ship off by default and turn on per project:

[flake8]
select = ASP
# with pydantic/fastapi as direct dependencies:
# select = ASP,ASD,ASF
# enable-extensions = ASD,ASF

Then merge configs/ruff-anti-slop.toml into your Ruff configuration and configs/mypy-anti-slop.ini into your mypy configuration (both ship inside the sdist as reference).

Vendored installation

For teams that want to own and edit the rules rather than track releases: copy src/anti_slop/ into the target repository (say, at tools/flake8/anti_slop/), install flake8 as a development dependency, and register the local plugin in .flake8, since Flake8 doesn't read pyproject.toml:

[flake8]
select = ASP

[flake8:local-plugins]
extension =
    ASP = anti_slop.checker:AntiSlopChecker
paths =
    ./tools/flake8

The Ruff and mypy layers merge the same way as in the package path. Avoid running the vendored copy and the installed package at once; each finding gets reported twice.

Rules

Each rule documents which original anti-slop rule it ports and names the ready-made tool covering the complementary half.

Code Ports Rejects
ASP001 require-safety-comment-for-type-assertion typing.cast(...) or # type: ignore with no # SAFETY: comment documenting the checked invariant
ASP002 no-runtime-typeof ad hoc isinstance/hasattr/type(x) is narrowing; parse at the boundary instead. --anti-slop-allow-typeguards permits checks inside TypeGuard/TypeIs functions
ASP003 no-module-mocking mock.patch(...), mocker.patch(...), monkeypatch.setattr(...); inject dependencies through real seams
ASP004 no-chained-type-assertions cast(User, cast(object, value))
ASP005 no-known-value-widening handlers: dict[str, Handler] = {"start": ...}, where the broad annotation throws away known keys; use inference, Final, or a TypedDict
ASP006 no-reflect-get / no-reflect-apply getattr(owner, dynamic_name); Ruff B009/B010 cover the constant-name forms
ASP007 no-shape-in-symbol-names shape in class, function, parameter, or variable names
ASP008 no-object-parameters / no-unknown-* parameters or returns typed bare object, Python's safe top type; dunder methods and cause parameters are exempt
ASP009 no-unsafe-dictionary-type dict[str, Any], Mapping[str, object], and friends; define a TypedDict, dataclass, or model
ASP010 no-unknown-type-aliases type ExternalValue = Any plus the TypeAlias and bare-assignment spellings

Not ported: no-conditional-empty-object-spread, because the JS idiom barely exists in Python, and no-widen-then-assert, which needs local flow analysis and stays on the roadmap. The Effect rule no-service-constructor-imports maps to import-linter contracts rather than a lint rule.

Framework groups (opt-in)

Framework policy lives in separate rule groups, the same split the original makes with anti-slop-effect. The groups are off_by_default Flake8 plugins: enable one only when its framework is a direct dependency, with enable-extensions = ASD,ASF next to select (package path) or together with the extension lines in [flake8:local-plugins] (vendored path). Each group repeats the three-layer split, with ready-made rules first and custom ASD/ASF rules only for the gaps.

Pydantic (ASD)

Ready-made layer: flake8-pydantic for PYD hygiene rules, plus the official Pydantic mypy plugin (configs/mypy-anti-slop-pydantic.ini).

Code Rejects
ASD001 model_construct() or construct() without a # SAFETY: comment. Both skip validation; the docs allow them only for data you already trust
ASD002 extra="allow" in ConfigDict or a legacy class Config, which stores unvalidated keys without a contract
ASD003 model fields typed Any, bare or nested (list[Any]), opting the field out of validation
ASD004 f(**model.model_dump()) and x: dict[...] = model.model_dump(): re-widening a validated model into an untyped dict, the Pydantic version of no-widen-then-assert
ASD005 TypeAdapter(...) inside a function; official performance guidance says build it once at module scope

FastAPI (ASF)

Ready-made layer: Ruff FAST (redundant response_model, non-Annotated Depends, unused path params) and ASYNC (blocking I/O inside async routes), via configs/ruff-anti-slop-fastapi.toml.

Code Rejects
ASF001 endpoints with no typed response contract: no return annotation and no response_model, or a return annotated Any/dict
ASF002 await request.json(), .body(), or .form() inside endpoints; declare a body model parameter and let the framework parse at the boundary
ASF003 JSONResponse(content={...}) with a dict literal inside endpoints; return a model. Exception handlers and middleware stay unflagged

Detection is syntactic. Models are found by base-class name (same-module subclasses included), endpoints by @<receiver>.<http-verb>(...) decorators; cross-module inheritance and aliased imports are known misses.

Development

uv run --group dev pytest
uv run flake8 src tests scripts   # the project's own rules, on itself
uv run ruff check src tests scripts
uv run mypy

The repo dogfoods all three layers on its own source through the installed entry points; CI enforces it. One documented deviation lives in .flake8: ASP002 targets application boundaries, and an AST linter discriminating the closed ast.* sum type is that boundary, so the rule does not apply to this codebase.

src/ is canonical. After changing production source, run scripts/sync_skill_assets.py so the skill's bundled copy stays identical; --check verifies without writing. Releases: bump __version__ in src/anti_slop/__init__.py, tag v*, and the publish workflow ships to PyPI via trusted publishing.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

anti_slop_py-0.1.0.tar.gz (21.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

anti_slop_py-0.1.0-py3-none-any.whl (28.7 kB view details)

Uploaded Python 3

File details

Details for the file anti_slop_py-0.1.0.tar.gz.

File metadata

  • Download URL: anti_slop_py-0.1.0.tar.gz
  • Upload date:
  • Size: 21.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for anti_slop_py-0.1.0.tar.gz
Algorithm Hash digest
SHA256 912b71cae2185d26eb9c54e5ee592674b58bfad66b42df05869b8a52aabdff12
MD5 272cc073f2eb359188db9887ce04a40d
BLAKE2b-256 625f05761cf9c7ae37d192b4ee6130c7c0b6bb191c1001cd6ddb81c66d05c629

See more details on using hashes here.

Provenance

The following attestation bundles were made for anti_slop_py-0.1.0.tar.gz:

Publisher: publish.yml on infoslack/anti-slop-py

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

File details

Details for the file anti_slop_py-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: anti_slop_py-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 28.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for anti_slop_py-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 92870885a34b029f4767eb7cd9cdc80c135d8f1ef521e35aa33a48fd3cd01b7e
MD5 697fb8d2d2d38496c9d06af2ce280c72
BLAKE2b-256 cb7b2bcf9d2f5904f51bb3dc93489a53745d3231b7813552b51774a1afd08896

See more details on using hashes here.

Provenance

The following attestation bundles were made for anti_slop_py-0.1.0-py3-none-any.whl:

Publisher: publish.yml on infoslack/anti-slop-py

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.1.1

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page