Skip to main content

A local-first health checker for Python imports, dependencies, startup time, and package bloat.

Project description

Project Doctor

CI Python 3.11+ License: MIT Project Doctor

A local-first health checker for Python imports, dependencies, startup time, and package bloat.

Project Doctor is a zero-dependency analyzer for finding project optimization work that usually hides in plain sight:

  • entrypoint startup drag
  • slow third-party imports
  • likely unused dependencies
  • possible undeclared imports
  • opt-in large installed package checks
  • top-level imports that look safe to move into deferred code paths
  • CI thresholds for dependency and import hygiene
  • uv.lock sync status and package explanations

Project Doctor never edits your code. It produces reviewable reports and CI-friendly checks so teams can make deliberate changes.

Security and privacy

Project Doctor is local-first and safe by default. Static scans parse source files with Python's ast module and do not import your project code. Optional import timing must be enabled with --import-time; it imports third-party modules in child processes, so leave it off when reviewing sensitive projects or code with import-time side effects. Installed package size checks are also opt-in with --package-sizes because they walk installed distribution metadata.

Install

Current reliable install path:

From this folder:

python3 -m pip install -e .

Then run:

project-doctor analyze /path/to/your/project

Or without installing:

PYTHONPATH=src python3 -m project_doctor analyze /path/to/your/project

After the PyPI release is live:

pip install project-doctor
uv tool install project-doctor

Quick examples

project-doctor doctor
project-doctor analyze examples/sample_project
project-doctor analyze examples/sample_project --json -o project-doctor-report.json
project-doctor analyze examples/sample_project --jobs auto --package-sizes
project-doctor analyze examples/sample_project --entrypoint "python app.py"
project-doctor analyze examples/sample_project --uv
project-doctor check examples/sample_project --max-unused 0
project-doctor check examples/sample_project --import-time --json --max-import-ms 150
project-doctor sync-check examples/sample_project/uv.lock
project-doctor explain-package pandas examples/sample_project --uv

analyze writes a shareable "wow" report by default. Use --report detailed for the longer audit report. check prints a compact status report and exits nonzero when a configured threshold is exceeded.

Example report

Project Doctor Report

Startup time: 1.42s
Potential avoidable import cost: 630ms

Top startup contributors:
- pandas: 312ms
- boto3: 205ms
- matplotlib: 113ms

Likely unused dependencies:
- openpyxl
- beautifulsoup4
- python-dotenv

Possible undeclared imports:
- requests imported but not declared

Largest installed packages:
- torch: 742MB
- pandas: 78MB
- botocore: 71MB

Suggested quick wins:
1. Move `pandas` import at `reports.py:3` inside the deferred function that uses it.
2. Remove `openpyxl` if it is no longer used by active code paths.
3. Add `requests` to `pyproject.toml`.

Python API

from project_doctor import AnalysisContext, analyze_project

context = AnalysisContext.from_environment()
report = analyze_project(
    "examples/sample_project",
    context=context,
    jobs="auto",
    run_import_timing=False,
    collect_package_sizes=False,
    entrypoint="python app.py",
)
print(report.unused_dependencies)

The returned AnalysisReport and nested report objects are dataclasses and can be converted to dictionaries with report.to_dict(). Reuse AnalysisContext when analyzing multiple projects in one process; it keeps installed package metadata and package size estimates cached.

What Project Doctor checks

Static import scan

Project Doctor parses Python source with ast, so it can read imports without importing your project code. Syntax errors are reported as warnings instead of aborting the full scan.

Dependency usage

Project Doctor reads dependencies from:

  • pyproject.toml [project.dependencies]
  • pyproject.toml [project.optional-dependencies]
  • pyproject.toml [dependency-groups], including { include-group = "..." }
  • common Poetry dependency sections
  • requirements*.txt, including nested -r and --requirement includes

It compares declared dependencies against static imports. Results marked unused should be treated as a review queue, not an automatic delete list.

Import timings

When --import-time is enabled, Project Doctor runs a subprocess like this for each likely third-party top-level import:

python -X importtime -c "import pandas"

This keeps imports out of the analyzer process, but the imported library can still run import-time side effects in the child process. Leave import timing disabled when you want a purely static scan.

Entrypoint startup timing

Entrypoint mode measures the command users actually wait on:

project-doctor analyze . --entrypoint "python app.py"
project-doctor analyze . --entrypoint "uvicorn app:app"
project-doctor analyze . --entrypoint "python -m my_cli"

Project Doctor runs the command with Python import profiling enabled and shell=False, then folds the parsed startup import costs into the report. Server-style commands may time out by design; Project Doctor still reports import data captured before the timeout.

Lazy-import candidates

Project Doctor looks for imports that are defined at module load but only used inside deferred function or method bodies.

For example:

import pandas as pd


def make_report(rows):
    return pd.DataFrame(rows)

Project Doctor may suggest:

def make_report(rows):
    import pandas as pd
    return pd.DataFrame(rows)

That kind of change can reduce startup time for CLIs, Lambdas, Flask/FastAPI apps, and agent scripts when a heavy dependency is only needed on a less-common path.

CLI

project-doctor doctor [path] [options]

Runs the default project health report. It is an alias for `project-doctor analyze .` with the same analysis options.
project-doctor analyze [path] [options]

Options:
  --json                         Emit JSON instead of Markdown
  --report wow|detailed           Human report style, default wow
  --output FILE, -o FILE          Write report to a file
  --import-time                   Run subprocess import timing checks
  --no-import-time                Skip subprocess import timing checks, default
  --import-time-limit N           Max third-party modules to time, default 20
  --import-time-timeout SECONDS   Timeout per import, default 10
  --package-sizes                 Collect installed package sizes
  --no-package-sizes              Skip installed package size checks, default
  --jobs N|auto                   Static scan workers, default auto
  --entrypoint COMMAND            Measure startup for a real entrypoint command
  --entrypoint-timeout SECONDS    Timeout for entrypoint measurement, default 10
  --uv                            Include uv.lock status
  --max-files N                   Max Python files to scan, default 5000
  --exclude DIR                   Extra directory name to exclude; repeatable
project-doctor check [path] [options]

Options:
  --json                         Emit machine-readable check results
  --max-unused N                  Max likely unused dependencies, default 0
  --max-undeclared N              Max possible undeclared imports, default 0
  --max-lazy-imports N            Max lazy-import candidates
  --max-import-ms N               Max cumulative import time for any measured module
  --max-package-mb N              Max installed package size; enables package size checks
  --import-time                   Run subprocess import timing checks
  --no-import-time                Skip subprocess import timing checks, default
  --import-time-limit N           Max third-party modules to time, default 20
  --import-time-timeout SECONDS   Timeout per import, default 10
  --package-sizes                 Collect installed package sizes
  --no-package-sizes              Skip installed package size checks, default
  --jobs N|auto                   Static scan workers, default auto
  --entrypoint COMMAND            Measure startup for a real entrypoint command
  --entrypoint-timeout SECONDS    Timeout for entrypoint measurement, default 10
  --uv                            Include uv.lock status
  --max-files N                   Max Python files to scan, default 5000
  --exclude DIR                   Extra directory name to exclude; repeatable
project-doctor sync-check [uv.lock] [options]

Options:
  --json                         Emit machine-readable sync results
project-doctor explain-package PACKAGE [path] [options]

Options:
  --uv                           Include uv.lock status
  --json                         Emit machine-readable package explanation

CI

Add Project Doctor to GitHub Actions as a dependency hygiene gate:

- name: Check Python dependency health
  run: project-doctor check . --max-unused 0 --max-undeclared 0 --max-package-mb 100

Intended full workflow after first package release and name/package ownership confirmation:

name: project-doctor

on: [push, pull_request]

jobs:
  project-doctor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install project-doctor
      - run: project-doctor check . --max-unused 0 --max-undeclared 0

For projects that use uv:

- name: Check uv lock sync
  run: project-doctor sync-check uv.lock

Badge for project docs:

![Project Doctor](https://img.shields.io/badge/project--doctor-passing-brightgreen)

uv

Project Doctor understands the common pyproject.toml + uv.lock workflow:

project-doctor analyze --uv
project-doctor sync-check uv.lock
project-doctor explain-package pandas --uv

sync-check verifies that direct dependencies declared in pyproject.toml are represented in uv.lock. explain-package shows whether a package is declared, which import names map to it, whether it is installed locally, and whether uv has locked it.

Performance

Project Doctor keeps the default path fast:

  • installed package metadata is indexed once per analysis context
  • package size checks are skipped unless requested or needed by --max-package-mb
  • static scans stream into aggregate results instead of retaining every file scan object
  • --jobs auto uses serial scanning for small projects and bounded parallel scanning for larger projects

Run the benchmark helper against a synthetic project:

PYTHONPATH=src python3 scripts/benchmark.py --files 1000 --runs 5 --jobs auto

Or benchmark a real project:

PYTHONPATH=src python3 scripts/benchmark.py /path/to/project --runs 5 --jobs auto

Development

python3 -m venv .venv
.venv/bin/python -m pip install -e ".[dev,security]"
.venv/bin/python -m pytest
.venv/bin/python -m ruff check .
.venv/bin/python -m mypy src/project_doctor tests
.venv/bin/python -m bandit -r src examples scripts -q
.venv/bin/python -m pip_audit
.venv/bin/python scripts/benchmark.py --files 200 --runs 2
.venv/bin/python -m build

Current limitations

  • Static analysis misses dynamic imports and plugin systems.
  • Dependency names do not always match import names.
  • Optional dependencies may be marked unused if their optional code path is not statically imported.
  • Opt-in import timing imports third-party packages in a subprocess, which can still trigger child-process side effects.
  • Entrypoint mode runs your command in a subprocess; use it for commands that are safe to execute locally.
  • Package size checks are opt-in and only work for dependencies installed in the current environment.

Roadmap

The next serious versions should add:

  1. Deeper entrypoint startup benchmarks with clearer default-path waste attribution.
  2. project-doctor fix --lazy-imports with AST-safe rewrites and backups.
  3. Lockfile awareness for Poetry, PDM, and pip-tools.
  4. Docker/image-size analysis.
  5. Richer package-name/import-name mapping.
  6. Profiler integration for hot-loop acceleration suggestions.

Project details


Download files

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

Source Distribution

project_doctor-0.6.0.tar.gz (37.5 kB view details)

Uploaded Source

Built Distribution

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

project_doctor-0.6.0-py3-none-any.whl (31.3 kB view details)

Uploaded Python 3

File details

Details for the file project_doctor-0.6.0.tar.gz.

File metadata

  • Download URL: project_doctor-0.6.0.tar.gz
  • Upload date:
  • Size: 37.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for project_doctor-0.6.0.tar.gz
Algorithm Hash digest
SHA256 22f14f81a336e028e8136394d536911dbe61c5646be353404b7d1aa3028e65cc
MD5 87b1736022856dff1cabc460c01d0cf3
BLAKE2b-256 4854fdb2aa48cd011ac8b55a9239c692ddd813074c77248e5810ac952da88726

See more details on using hashes here.

Provenance

The following attestation bundles were made for project_doctor-0.6.0.tar.gz:

Publisher: publish.yml on billjamesevans/project-doctor

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

File details

Details for the file project_doctor-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: project_doctor-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 31.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for project_doctor-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 841ae348509651da95050b6f9838609bc1475058977456005d2a12408495397d
MD5 3b1957842b704ebc305afa5acc132427
BLAKE2b-256 c551638a390602ae852bc2fa794eaa2ba0bd6a08693c086f53f65d2e09589b97

See more details on using hashes here.

Provenance

The following attestation bundles were made for project_doctor-0.6.0-py3-none-any.whl:

Publisher: publish.yml on billjamesevans/project-doctor

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

Supported by

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