Skip to main content

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

Project description

Project Doctor

CI PyPI 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

Install from PyPI:

pip install project-doctor
uv tool install project-doctor

Then run:

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

For local development from this checkout:

python3 -m pip install -e .
PYTHONPATH=src python3 -m project_doctor analyze /path/to/your/project

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 analyze examples/sample_project --scope all
project-doctor check examples/sample_project --max-unused 0
project-doctor check examples/sample_project --scope runtime --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. Runtime dependencies, optional extras, and development groups are tagged with a scope. analyze and doctor default to --scope all so the report is complete; check defaults to --scope runtime so CI gates do not fail just because an optional or development dependency is unused by the main runtime path. 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
  --scope runtime|all             Dependency scope to evaluate, default all
  --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
  --scope runtime|all             Dependency scope to evaluate, default runtime
  --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

Full workflow:

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.1.tar.gz (40.8 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.1-py3-none-any.whl (33.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: project_doctor-0.6.1.tar.gz
  • Upload date:
  • Size: 40.8 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.1.tar.gz
Algorithm Hash digest
SHA256 19bff0cc0b5935911fab572af89911665742b9b3cb85512c8dea1c70e314bccc
MD5 36634bb8c06229c2b097dd15c993ca76
BLAKE2b-256 b4c3c3198c1be36b98e9e396d920e26877ddfaadd3773ceb8d59fa184136dc26

See more details on using hashes here.

Provenance

The following attestation bundles were made for project_doctor-0.6.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: project_doctor-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 33.5 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 30a0fc6465b18aa6fd56f79519fc411ac24317c6c87fbe409b92fd03ff4eb59b
MD5 ac49b492fe30cb9d69da1b3abf83f583
BLAKE2b-256 0c06597bec965014d1de1eab7eb3cadb30c894b6b0384c6d9d72f4b158187fd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for project_doctor-0.6.1-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