Skip to main content

PyFixer

The auto-fix button for Python — conservative mechanical fixes by default, LLMs only behind behavioral gates.

Upload a broken file. Get a fixed file. That's it.

Upload → Scan → Fix → Verify → Contracts → Done

What It Does

PyFixer finds and fixes Python problems automatically.

Problem Fix
Security holes Replaces eval, exec, hardcoded passwords, weak crypto
Unused imports Removes them
Wrong types Adds type annotations
Bad style Formats code, fixes line length
Missing docs Adds docstrings
Old Python Modernizes to current syntax
Bare excepts Changes to specific exceptions
Print statements Converts to logging
Mutable defaults Fixes def foo(x=[])
Builtin shadowing Fixes list = x
Type comparison Fixes type(x)==type(y)
Performance Fixes O(n²) loops, string concat
Weak random Flags non-crypto random in security contexts (no blind swap: secrets returns a different type)
Weak hash Replaces MD5 with SHA256
Logic bugs Behavioral tests catch wrong operators, crash-on-empty, stubs

17+ types of issues. One click to fix.


How It Works (v0.4.0)

1. Upload .py file
   ↓
2. Scan with ruff + bandit + mypy + custom/semantic checks
   ↓
3. Generate a behavioral test for EVERY function (coverage guarantee)
   ↓
4. AI proposes a fix (13 specialist workers)
      - clean functions are never sent to the LLM
   ↓
5. Post-processing layer applies guaranteed fixes
      - hardcoded secrets are scrubbed from the output
   ↓
6. CONTRACT GATE: probe original vs fixed behavior
      - untouched functions must return identical results
      - perf/logging-only fixes must be value-identical
      - violations -> corrective retry -> verbatim splice
   ↓
7. Verify: syntax check + ruff + mypy + findings count
   ↓
8. You approve or reject
   ↓
9. Done. File is fixed.

Features

13 Specialist Workers

Each worker is an expert in one thing:

Worker What It Fixes
Syntax Surgeon Syntax errors, indentation
Security Guard SQL injection, hardcoded passwords, weak crypto
Type Tamer Missing type annotations, wrong types
Style Formatter Line length, imports, formatting
Code Cleaner Dead code, unused variables
Docstring Writer Missing docstrings, D101-D107
Annotation Expert Missing annotations, ANN001-ANN204
Modernizer Old Python syntax, PTH/SIM/RET
Performance Optimizer O(n²) loops, string concatenation
Logger print() → logging conversion
Error Handler Bare excepts, broad exceptions
Generalist Complex issues, multiple codes
MyPy Fixer Type ignore comments, mypy-specific

BYOK (Bring Your Own Key)

You bring your own API key. We never store it.

Provider Key prefix Model
Google Gemini AQ. gemini-3.7-flash
DeepSeek sk- (probed) deepseek-chat
OpenRouter sk-or- deepseek/deepseek-v4-flash
OpenAI sk- gpt-4o-mini

Server-side key pool (optional)

Tired of pasting a key into the UI? Configure keys on the server and every client can fix without one:

# Option A: environment variable (comma or newline separated)
export PYFIXER_KEYS="AQ.Ab8RN6...,AQ.Ab8RN7..."

# Option B: key file, one key per line
$EDITOR data/gemini.keys   # gitignored — never commit this file

Keys rotate automatically (N keys = N × 15 requests/min). The pool is only used when a request carries no X-Api-Key header / body key; client keys always take priority and are never stored, logged, or written to disk. GET /api/server-keys reports { "available": true } without exposing them.

LLM control

Every model call goes through a task profile (temperature / token budget / JSON-mode) so behaviour is tunable in one place — PROFILES in pyfixer/byok.py:

Profile Used for Temperature Max tokens
fix code fixes 0.0 4096
fix_escalate contract-violation retries 0.0 4096
testgen behavioral test generation 0.0 3072
classify finding classification 0.0 512 + JSON mode
probe cheap probes 0.0 256

Model resolution order: per-call override → PYFIXER_MODEL env var → provider default. The classifier requests provider-side JSON mode (Gemini response_mime_type, OpenAI-compatible response_format).

Verify & Rollback

  • Verify: Proves the fix works (syntax + ruff + mypy + findings)
  • Rollback: Undo if you don't like it

Pattern Learning

PyFixer remembers what works. The more you use it, the better it gets.


Quickstart

The fastest way — install from PyPI (no repo clone needed):

# Recommended: isolated install via pipx
pipx install pyfixer-ai
pyfixer scan app.py

# Or with pipx unavailable, a virtualenv:
python3 -m venv ~/.venvs/fixer
~/.venvs/fixer/bin/pip install pyfixer-ai
~/.venvs/fixer/bin/pyfixer scan app.py

On Debian/Ubuntu, plain pip install pyfixer-ai refuses with "externally-managed-environment" — that's your system Python protecting itself. Use pipx or a venv as above, never --break-system-packages.

From source (for development):

# Clone
git clone <your-repo>
cd pyfixer

# Setup
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

# Run
.venv/bin/uvicorn pyfixer.main:app --host 127.0.0.1 --port 8501

Open http://127.0.0.1:8501

Note: python-multipart is required (pulled in by requirements.txt) so the upload endpoints that use Form(...) work. ruff and bandit must be installed and on PATH (the scanner shells out to python -m ruff / python -m bandit). mypy is optional — only used when you enable the mypy scanner.


CLI

Fix files straight from the terminal — same pipeline, same gates, no server:

# module form (works from a checkout)
python -m pyfixer fix app.py

# after `pip install -e .` you also get the console script
pyfixer scan app.py            # findings only, no AI call   (exit 1 on bugs; style hidden unless --all)
pyfixer fix app.py             # writes app.fixed.py next to it
pyfixer fix app.py --apply     # overwrite in place
pyfixer fix app.py --diff      # print the patch, write nothing
pyfixer fix src/               # every .py under a folder (summary at end)
pyfixer fix app.py --format json       # machine-readable output
pyfixer fix app.py --worker security   # force a specialist
pyfixer fix app.py --llm              # + LLM fixes (needs API key)
pyfixer fix app.py --llm --fast       # fewer model calls, same gates
pyfixer fix app.py --llm --verify tests/test_app.py --apply   # real-test gate (recommended)
pyfixer fix app.py --llm --apply --unsafe   # write without any gate (you accept regressions)
pyfixer fix app.py --aggressive       # + mechanical ruff upgrades (no key)
pyfixer fix app.py --check            # preview only, write nothing
pyfixer workers                # list all specialists

# key: --key beats $PYFIXER_KEYS beats data/gemini.keys (rotating pool)

# LLM-gated flags: --comprehensive and --reference work ONLY with --llm;
# --verify needs --llm (pytest gate) or --llm-targeted (mypy gate).
# Without them the CLI exits 2 instead of silently ignoring the flag.
# --llm --apply refuses to write without --verify (real-test gate) unless
# --unsafe is passed explicitly.

Deterministic Fixes (no API key needed)

Deterministic mode (the default — no API key needed) applies 50+ pattern-based fixes instantly:

Code Fix
C408 dict(name="test"){'name': 'test'}
C416 [x for x in y]list(y)
E711 x == Nonex is None
E712 x == Truex
E722 except:except Exception:
F401 Remove unused imports
F841 Remove unused variables
LOGIC005/009 Fix inverted comparison signs
LOGIC010 Fix insertion sort off-by-one
LOGIC014 def f(x=[])def f(x=None)
LOGIC015 type(x) == intisinstance(x, int)
LOGIC016 for i in range(n)for _ in range(n)
PLR1714 x==1 or x==2x in (1, 2)
RET505 Remove unnecessary else after return
SIM102 if x: if y:if x and y:
SIM105 try: passcontextlib.suppress()
SIM115 open()with open()
SIM116 if k in d: return d[k]d.get(k)
SIM118 x in d.keys()x in d
SIM210 True if x else Falsebool(x)
SIM212 if x: return True else: return Falsereturn x
UP030 "{}".format(x)f"{x}"

Example:

# Quick fix without API key (deterministic is the default)
pyfixer fix buggy.py

# See what would change
pyfixer fix buggy.py --diff

# Apply in place
pyfixer fix buggy.py --apply

Exit codes: 0 OK · 1 findings survived the fix · 2 usage/no key.


API

Method Endpoint Description
GET /healthz Health check
POST /api/uploads Upload + auto-scan
GET /api/uploads List all files
GET /api/uploads/{id} Get file details
POST /api/uploads/{id}/fix Propose AI fix
POST /api/uploads/{id}/verify Verify fix works
POST /api/uploads/{id}/rollback Undo fix
GET /api/fixes/{id}/approve Approve fix
GET /api/fixes/{id}/reject Reject fix
GET /api/uploads/{id}/report Download report
GET /api/workers List all workers
GET /api/audit Audit trail

Scanner

PyFixer uses multiple scanners:

  • ruff — Fast Python linter (16 rule sets)
  • bandit — Security scanner
  • mypy — Type checker
  • AST detector — 16 LOGIC codes + 10 STYLE codes + 5+ ALGO/PERF codes
  • Custom checks — 8 additional checks:
    • ERR001: Missing error handling
    • ERR002: Broad exception catching
    • LOG001: print() instead of logging
    • PERF001: Inefficient string concatenation
    • MUT001: Mutable default arguments
    • SHADOW001: Builtin name shadowing
    • TYPE001: type() comparison instead of isinstance()

Total: 50+ types of issues detected.


Testing

PYTHONPATH=. .venv/bin/pytest tests/ -v

862 tests passing (incl. 9 opt-in corpus parity suites).

Regression benchmarks (LLM-in-the-loop)

benchmarks/ contains bug files with human-verified correct.py references and check.py auto-checkers. The harness runs each through the REAL fix pipeline and scores it — including clean canary functions that must keep identical behavior (contract-regression guard).

# all cases (uses server key pool or PYFIXER_BENCH_KEY)
.venv/bin/python benchmarks/run_bench.py

# one case
.venv/bin/python benchmarks/run_bench.py --only logic_signs

Cases: algorithms, strings, mixed, security_basics, crash_edges, logic_signs, perf_logging. A manual GitHub Actions workflow (.github/workflows/regression.yml) runs them on demand with a PYFIXER_KEYS secret.

Corpus regression (downstream suite parity)

tests/test_corpus_regression.py runs nine real projects' test suites against both the pristine checkout and the deterministic rewrite, asserting identical pass/fail signatures — the harness that caught the RET505 click corruption, the ERR003 logging-import breakage, and the F401 # noqa side-effect import deletion. Opt-in via PYFIXER_CORPUS_DIR:

bash scripts/provision_corpus.sh /path/to/pyfixer-corpus   # checkouts + venvs
PYFIXER_CORPUS_DIR=/path/to/pyfixer-corpus PYTHONPATH=. pytest tests/test_corpus_regression.py -m corpus -q

Repos: click, httpie, requests, rich, flask, fastapi, attrs (2022-era), httpx 1.0b0 (2025-era), pandas 2.2.3. Pandas is special: its compiled _libs can't be PYTHONPATH-shadowed, so it runs as a dedicated two-venv parity test (venv-pandas vs venv-pandas-fixed, both on numpy==1.26.4). A manual + weekly workflow (.github/workflows/corpus.yml) runs the same in CI.

tests/test_semantic_rules.py locks the semantic tier (SEM003/SEM004/SEM006) with a detector mutation oracle plus differential-execution coverage of the SEM004 fixer; tests/test_mock_llm_contract.py locks the SEM003 detector -> per-function-LLM wiring with a stubbed model (no network).

Code quality

# Lint + auto-format
.venv/bin/ruff check pyfixer
.venv/bin/ruff format pyfixer

# Static type checking
.venv/bin/mypy pyfixer

A GitHub Actions workflow (.github/workflows/ci.yml) runs ruff check, ruff format --check, mypy, and the full pytest suite on every push and pull request, so regressions are caught before merge.


Tech Stack

  • Backend: FastAPI + SQLite + Python
  • Scanner: ruff + bandit + mypy + custom AST checks
  • AI: OpenAI, OpenRouter, Google, DeepSeek (BYOK)
  • Testing: pytest + coverage

Tier Semantics

PyFixer promotes fixes through four tier levels. The goal is honest tier assignment — a function's tier reflects the evidence that validates its rewrite.

Tier When it's assigned Promotion path
UNVERIFIED Default for any fixed function that has no oracle (no func_test). LOGIC‑only fixes (ast‑detector findings) always land here, regardless of verify_mode. After the real project suite passes (--verify in cli.py), tiers are promoted by _promote_tiers_to_verified in cli.py:1168/1276.
AI_VERIFIED A generated func_test exists and the rewrite passes _valid_fix. The rewrite was validated against the AI‑generated test. Same real‑suite promotion; this is the interim tier after propose.
VERIFIED Earned only after the real project suite (--verify) runs successfully via _promote_tiers_to_verified. No automatic path mint VERIFIED at propose time. Manual / CI gate after suite runs.
RETRIEVED Deterministic fixes that match an existing reference (e.g. RETRIEVED from known‑good repo). Stays RETRIEVED; no further promotion.

Honesty invariant: verify_mode=True does not mint VERIFIED. It only means the user passed --verify; a real suite still needs to run afterwards. Promotion to VERIFIED belongs to the real‑suite gate in cli.py, never propose().


License

Copyright (c) 2026 Aziz. All rights reserved.


Author

Aziz — Built PyFixer to fix Python code automatically.

Release files for pyfixer-ai 0.4.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyfixer-ai 0.4.6
File Size Uploaded
pyfixer_ai-0.4.6.tar.gz 515.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyfixer-ai 0.4.6
File Interpreter ABI Platform
pyfixer_ai-0.4.6-py3-none-any.whl Python 3 none any Details

Total release size: 909.1 kB

Release files / pyfixer_ai-0.4.6.tar.gz

Download URL pyfixer_ai-0.4.6.tar.gz
Size 515.1 kB
Tags Source
SHA-256 checksum
How to use checksums
88fe55a18c63ad9132e69371f7e5ee98245232d3359509630d56e41578165524
BLAKE2b-256 checksum
How to use checksums
53003db5b16f4d298800eaa371760b50feaa856dc80650c57c6d782db4ed9f02
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 19, 2026.

Transparency log

Release files / pyfixer_ai-0.4.6-py3-none-any.whl

Download URL pyfixer_ai-0.4.6-py3-none-any.whl
Size 394.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9b02b73f955fdd697214076435943ed63509058f3522cd73c1731fcced68611d
BLAKE2b-256 checksum
How to use checksums
ea289c27ee47541e421e64ff8068b14ea33a9d06c2a287fb268ff17632578bc0
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 19, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

This release

0.4.6 This release

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release 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