Sanopy
Sanopy is a CLI tool for improving Python code quality. It runs multiple linters concurrently and emits findings as JSON to stdout.
Requirements
- Python 3.12+
- uv
Installation
pip install sanopy
# Optional with uv
uv venv .venv
uv pip install sanopy
Quick Start
1. Configure
Sanopy always uses a local .sanopy.toml in the current project.
- If
.sanopy.tomldoes not exist, Sanopy creates it automatically with defaults. - Use
initto customize settings manually or in automation.
# Interactive (manual)
sanopy init
# Non-interactive (CI/automation)
sanopy init --only ruff,mypy --skip bandit
2. Scan a Codebase
sanopy scan src/
By default, machine-readable JSON is printed to stdout in a versioned envelope:
{
"schema_version": "1.0.0",
"run": {
"target": "src",
"generated_at": "2026-08-09T00:00:00+00:00",
"active_linters": ["ruff", "pylint", "mypy"],
"finding_count": 0
},
"findings": []
}
Use human output mode for terminal-friendly progress and summaries:
sanopy scan src/ --output-mode human
You can scan multiple directories or files at once:
sanopy scan src/ tests/
When scanning multiple targets, the results are merged into a single JSON
document, and run.target becomes an array of the scanned paths.
Generate a human-readable Markdown report:
sanopy scan src/ --human-readable
The report is saved as linting-report-<target>.md (e.g.,
linting-report-src.md).
Save results to a custom file:
sanopy scan src/ -o my-scan.json
Linter Filtering
Run only selected linters:
sanopy scan . --only ruff,mypy
Skip selected linters:
sanopy scan . --skip safety
You can also set default only_linters and skip_linters values in
.sanopy.toml via sanopy init.
Supported Linters
| Linter | Category | Detects |
|---|---|---|
| Ruff | Style | PEP 8, imports, code smells |
| Pylint | Style | Code quality, conventions |
| Bandit | Security | Common security vulnerabilities |
| MyPy | Typing | Static type checking |
| Pyright | Typing | Advanced type inference |
| Semgrep | Semantic | Pattern-based analysis |
| Vulture | Dead code | Unused variables, functions |
| Radon | Complexity | Cyclomatic complexity |
| Safety | Dependencies | Known vulnerabilities |
| pip-audit | Dependencies | Known vulnerabilities in dependency tree |
Configuration File
The .sanopy.toml file controls linter defaults for the current project.
- Manual workflow: run
sanopy initand answer prompts. - CI/AI workflow: run
sanopy init --only ... --skip ...in scripts. - If the file is missing, Sanopy creates
.sanopy.tomlwith defaults.
[linters]
only_linters = []
skip_linters = []
[linters.pylint]
disable = ["duplicate-code", "too-many-locals"]
[linters.bandit]
skips = []
[linters.ruff]
select = ["E", "F", "W", "I", "N", "UP", "B", "A", "C4", "SIM", "PTH"]
ignore = []
[safety]
ignore_cves = ["CVE-2026-0994"]
[pip-audit]
ignore_vulns = ["PYSEC-2026-3482"]
The [safety] section lists CVE IDs that the Safety linter should
suppress. By default a small set of known-unresolvable CVEs is ignored;
set ignore_cves = [] to disable all suppressions.
The [pip-audit] section lists vulnerability IDs (or aliases) that the
pip-audit linter should suppress, matched by primary ID or alias.
The optional [linters.<name>] sections provide the configuration that
Sanopy passes to linters shipping bundled defaults (pylint, bandit,
and ruff). A nested [linters.<name>.test] table overrides the
settings used for test code. Sections and keys you omit fall back to the
bundled defaults; a freshly generated .sanopy.toml materializes all of
them so they are visible and editable.
Example CI step:
steps:
- name: Configure Sanopy
run: sanopy init --only ruff,mypy --skip bandit
- name: Run scan
run: sanopy scan src/ tests/
Development
Clone the repo and install dependencies:
git clone https://github.com/lpozo/sanopy.git
cd sanopy
uv sync
Run tests:
uv run pytest
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sanopy-0.1.0.tar.gz.
File metadata
- Download URL: sanopy-0.1.0.tar.gz
- Upload date:
- Size: 160.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0255c1bf702ff71267a5b002a56427387d27b13ceaeae7d8dd513da96d3d36a5
|
|
| MD5 |
886833dea2427a18d83b71dba71c7271
|
|
| BLAKE2b-256 |
0b6482a9bd1246884df4805de2ae2c4687025b46ed9dd018b0c451cc6504eef3
|
Provenance
The following attestation bundles were made for sanopy-0.1.0.tar.gz:
Publisher:
publish.yml on lpozo/sanopy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sanopy-0.1.0.tar.gz -
Subject digest:
0255c1bf702ff71267a5b002a56427387d27b13ceaeae7d8dd513da96d3d36a5 - Sigstore transparency entry: 2587998856
- Sigstore integration time:
-
Permalink:
lpozo/sanopy@4090db5126a81fafbc7f6bde3728ac7cd058cc22 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/lpozo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4090db5126a81fafbc7f6bde3728ac7cd058cc22 -
Trigger Event:
pull_request
-
Statement type:
File details
Details for the file sanopy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sanopy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 38.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5f668be77ae19424f15c8a23950c5c63141272516a471bbdcce14164784971f
|
|
| MD5 |
b34f16b5d37354335d343ba6567739e9
|
|
| BLAKE2b-256 |
92e9b5f318a091305ed0a8875f85eaf052c20c15ef33a7bbad0f6655a988c322
|
Provenance
The following attestation bundles were made for sanopy-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on lpozo/sanopy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sanopy-0.1.0-py3-none-any.whl -
Subject digest:
d5f668be77ae19424f15c8a23950c5c63141272516a471bbdcce14164784971f - Sigstore transparency entry: 2587999060
- Sigstore integration time:
-
Permalink:
lpozo/sanopy@4090db5126a81fafbc7f6bde3728ac7cd058cc22 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/lpozo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4090db5126a81fafbc7f6bde3728ac7cd058cc22 -
Trigger Event:
pull_request
-
Statement type: