Metadata-Version: 2.4
Name: cpm-pyoverview
Version: 0.1.0
Summary: Static analyzer that generates a single deterministic Markdown overview of a Python project
Project-URL: Homepage, https://github.com/cprima/pyoverview
Project-URL: Repository, https://github.com/cprima/pyoverview
Project-URL: Issues, https://github.com/cprima/pyoverview/issues
Author-email: Christian Prior-Mamulyan <cprior@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ast,cli,code-documentation,documentation,markdown,static-analysis
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Documentation
Classifier: Topic :: Software Development :: Documentation
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# pyoverview

Static analyzer that turns a Python project into a single, deterministic
Markdown overview: metadata, directory tree, entry points, per-file
docstrings/imports/exports/signatures, a project-wide symbol table, resolved
"used by" back-references (with confidence levels), a module dependency
graph, a flattened public API index, and an unresolved-reference report.

Built entirely on the `ast` module. It never imports or executes the
project it analyzes.

## Installation

Distributed on PyPI as `cpm-pyoverview` (the `pyoverview` name was already
taken by an unrelated package); the installed command is `cpm-pyoverview`.

```bash
uv tool install cpm-pyoverview
# or
pipx install cpm-pyoverview
# or
pip install cpm-pyoverview
```

For local development instead of installing from PyPI, see
[CONTRIBUTING.md](CONTRIBUTING.md).

## Usage

```bash
cpm-pyoverview ./path/to/project --output overview.md
```

Options:

```text
--output PATH                 Write to PATH instead of stdout
--include PATTERN             Glob pattern to include (repeatable)
--exclude PATTERN             Glob pattern to exclude (repeatable)
--python-version VERSION      Declared target version (metadata only, see limitations)
--include-private             Include underscore-prefixed definitions in the Files section
--include-tests               Include tests/ files in the rendered Files section
--show-line-numbers            Append :line to reference locations
--include-local-references     Include "used by" references originating from the same file
--strict                      Exit non-zero if any file has a syntax error
--fail-on-unresolved           Exit non-zero if any reference is unresolved
--version                     Print the installed version and exit
```

## Architecture

Three passes, each backed by a focused module:

1. **Parse** (`parser.py`, syntax-only, per file, via `ast_visitors.py`) →
   `ParsedModule` (internal only -- holds the retained AST tree).
2. **Resolve** (`project_resolver.py` orchestrating `module_resolver.py`,
   `import_resolver.py`, `export_resolver.py`, `binding.py`,
   `symbol_table.py`, `entry_point_resolver.py`) → `ResolvedModule` +
   project-wide symbol table + dependency edges.
3. **Reference** (`reference_resolver.py`, bounded type inference) →
   graded `resolved` / `probable` / `unresolved` usage references.

`markdown_renderer.py` is a pure function (`ProjectInfo -> str`); `cli.py`
wires the pipeline together.

## Known limitations

- **Method-call type inference is bounded**, not full type checking:
  single-function scope, one hop of attribute/alias propagation, and a
  binding is invalidated (never guessed) the moment it sees two different
  constructor assignments. No interprocedural or control-flow-sensitive
  analysis beyond that.
- **Export inference is conservative**: imported names are never treated as
  re-exports unless they also appear in a statically resolved `__all__` --
  even in `__init__.py`. This avoids silently promoting ordinary imports to
  public API, at the cost of missing some intentional re-export patterns.
- **`--python-version` is metadata-plus-parsing only**: stdlib-module
  classification always uses the *running* interpreter's
  `sys.stdlib_module_names`, since Python does not ship a way to query an
  arbitrary historical version's stdlib module list.
- **Entry-point detection is conservative**: only `if __name__ ==
  "__main__":` guards and `pyproject.toml` `[project.scripts]` /
  `[project.gui-scripts]`. Framework-specific route/command discovery
  (Flask, Click, FastAPI, etc.) is not attempted.
- **Module-name resolution is heuristic**: a top-level `src/` directory, if
  present, is treated as the import root; otherwise the project root is.
  This covers conventional layouts but is not a full `sys.path`/package
  configuration resolution.
- **Dynamic Python is always `unresolved`, never guessed at**:
  `getattr`/`setattr`, `importlib.import_module`/`__import__`, wildcard
  imports, monkeypatching, and framework-based discovery all surface in the
  "Unresolved References" section with a reason, rather than being silently
  omitted or confidently mis-resolved.
- **Name-binding lookup is module-flat**, not precisely scope-nested: a name
  shadowed only inside one function can occasionally over-resolve against
  an unrelated module-level binding of the same name.

## Testing

```bash
uv run pytest
```

Includes unit tests per component and an integration test running the full
pipeline against `tests/fixtures/demo_project` (multi-package, re-exports,
tests, a circular dependency, aliased imports, async methods, a dynamic
import, and an ambiguous method call), snapshot-compared against
`tests/integration/__snapshots__/demo_project.md`. Regenerate the snapshot
deliberately after a reviewed output-format change with:

```bash
UPDATE_SNAPSHOTS=1 uv run pytest tests/integration
```
