Symbol-visibility linter for Python
Project description
symvision
symvision is a dependency-free symbol-visibility linter for Python. It finds public functions and classes that no
production code uses, private symbols imported across module boundaries, and private symbols unused in their defining
module.
Python does not enforce public and private APIs. Over time, that can leave a project with dead public surfaces and
underscore-prefixed helpers that are private in name only. symvision turns those conventions into an automated check
while accounting for entry points, non-Python consumers, external repositories, and APIs being built under an active
epic.
Quick start
Install the symvision command with uv:
uv tool install symvision
Or install it with pipx or pip:
pipx install symvision
# or, inside a virtual environment
python -m pip install symvision
Scan the package definitions under src/:
symvision src
symvision treats Python files under the requested directory as the definition tree. When the tree is inside a Git
repository, imports from tracked Python files outside it also count as usage. Definitions and imports in test-support
paths (test/, tests/, testing/, or test_*.py) do not keep production APIs alive. Functions referenced by
[project.scripts], [project.gui-scripts], or [project.entry-points.*] in the nearest pyproject.toml are treated
as used.
Usage
symvision [-h] [--exclude-file PATH] [--epic-symbol EPIC(SYMBOL)]
[--exclude-decorator NAME] [-E PATH]
DIRECTORY
The options that accept values may be repeated:
| Option | Meaning |
|---|---|
DIRECTORY |
Recursively scan Python definitions under this directory |
--exclude-file PATH |
Exclude a file from definition and usage analysis; paths are resolved from the current working directory |
--exclude-decorator NAME |
Ignore top-level functions and classes decorated with this name |
--epic-symbol EPIC(SYMBOL) |
Temporarily allow an otherwise-unused public symbol while its epic is open |
-E PATH, --external-repo-path PATH |
Add a local checkout candidate for resolving an external-repository pragma |
-h, --help |
Show command help |
For example:
symvision \
--exclude-file src/pkg/generated.py \
--exclude-decorator register \
src/pkg
Pragmas for non-Python consumers
A pragma immediately above a top-level public function or class records a real consumer that static Python import analysis cannot see. The target must genuinely reference the symbol; stale or unnecessary pragmas are errors.
Local path form
Use a Git-root-relative path for generated configuration, shell code, or another non-Python consumer:
# symvision: config/application.toml
def build_application() -> Application:
...
The referenced file must exist, must contain the symbol name, and must not be under src/. Test-support paths and
Markdown files are rejected because tests and documentation alone are not production consumers.
External repository form
Use a Git remote URI when another repository consumes the API:
# symvision: https://github.com/example/application.git
class ApplicationPlugin:
...
HTTPS, SSH URLs, scp-style Git remotes such as git@github.com:owner/repo.git, and file:// URLs are supported.
Equivalent HTTPS and SSH origins are matched after normalization. Only Git-tracked, non-test files in the resolved
checkout count; both Python imports and symbol references in non-Python text files are recognized.
External repositories resolve in this order:
- Paths passed with
--external-repo-path, followed by paths fromSYMVISION_EXTERNAL_REPO_PATHS. - The current repository's parent directory and sibling directories. A checkout named exactly for the repository is
preferred over other names, and numbered workspaces such as
repo_2are considered last. - A deterministic shallow clone under
~/.cache/symvision/external-repos.
SYMVISION_EXTERNAL_REPO_PATHS is an os.pathsep-separated list (: on Unix, ; on Windows). Override the fallback
clone root with SYMVISION_EXTERNAL_REPO_CACHE. An existing cache entry must have a matching origin; symvision will
report a mismatch instead of overwriting it.
Multiple consecutive pragmas can document multiple consumers for the same definition:
# symvision: config/plugin.toml
# symvision: git@github.com:example/application.git
class ApplicationPlugin:
...
Symbols tied to an epic
--epic-symbol supports public APIs that are intentionally unused while an active epic is building their consumer:
BD_COMMAND=tools/sase_bead symvision \
--epic-symbol 'sase-123(create_report)' \
src/pkg
The value has the form <bead-id>(<symbol-name>). The command verifies that the bead exists and is not closed, that
the symbol is a public definition, and that it is still otherwise unused. A closed bead, missing symbol, private
symbol, or already-used symbol makes the exclusion stale and fails the scan.
symvision invokes bd show <bead-id> by default. Set BD_COMMAND to a compatible bead-tracker executable when the
project uses a wrapper or another command. Remove the option once the epic's production consumer lands.
Exit codes
| Code | Meaning |
|---|---|
0 |
The scan completed without a symbol-visibility violation, or found no Python definitions to check |
1 |
A visibility rule, pragma, epic-symbol validation, external-repository resolution, or directory check failed |
2 |
Command-line usage error, such as a missing required argument or an unknown option |
Diagnostics are written to stdout or stderr depending on their category; integrations should use the exit code as the stable success signal.
Continuous integration
Run the same command locally and in CI. A small Justfile recipe is enough:
symvision:
uvx --from symvision symvision src/your_package
Pin symvision to the version range appropriate for your project when reproducibility is important.
Development
The repository uses uv for environments and dependency installation, and just as its task runner. From a clone:
just install
just check
just check verifies formatting, runs Ruff and strict mypy, dogfoods both symvision and
toobig, and executes the pytest suite with a 94% branch-coverage gate. The
package supports Python 3.11 through 3.14 and has no runtime dependencies. Set SYMVISION_PYTHON to select the Python
interpreter used by the Justfile, for example SYMVISION_PYTHON=3.14 just test.
Releases
Commits and pull-request titles follow Conventional Commits. On every push to
master, release-please updates or opens a release pull request containing the next version and changelog. Merging
that pull request creates the GitHub release, builds and smoke-tests the wheel in a fresh environment, and publishes
symvision to PyPI using trusted publishing (GitHub Actions OIDC), without a long-lived PyPI token.
License
symvision is available under the MIT License.
Project details
Release history Release notifications | RSS feed
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 symvision-0.1.0.tar.gz.
File metadata
- Download URL: symvision-0.1.0.tar.gz
- Upload date:
- Size: 25.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
775502bc0ad04d90f8f093e9660e602a1557d6dd0c15b1526757a3949619cef7
|
|
| MD5 |
57ad2baba0d979f2a70743aa51ceba5e
|
|
| BLAKE2b-256 |
4c15aeb9496b4b5a617d1ac977ed8c41a0e816c2a920b9b2d55185448495514a
|
Provenance
The following attestation bundles were made for symvision-0.1.0.tar.gz:
Publisher:
publish.yml on bbugyi200/symvision
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
symvision-0.1.0.tar.gz -
Subject digest:
775502bc0ad04d90f8f093e9660e602a1557d6dd0c15b1526757a3949619cef7 - Sigstore transparency entry: 2156683823
- Sigstore integration time:
-
Permalink:
bbugyi200/symvision@e865044f66dc1cc21909b7938d6f002e26cfd9ee -
Branch / Tag:
refs/heads/master - Owner: https://github.com/bbugyi200
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e865044f66dc1cc21909b7938d6f002e26cfd9ee -
Trigger Event:
push
-
Statement type:
File details
Details for the file symvision-0.1.0-py3-none-any.whl.
File metadata
- Download URL: symvision-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0347bfa05cdf88708c99ac990484a90e9fa37235290db4452435875de5327cdc
|
|
| MD5 |
63c1c0711197bcc1096fd79e3887df98
|
|
| BLAKE2b-256 |
6e91552e773854ac2a7951ed560e0593b1892719f6724465218ecd37985496fc
|
Provenance
The following attestation bundles were made for symvision-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on bbugyi200/symvision
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
symvision-0.1.0-py3-none-any.whl -
Subject digest:
0347bfa05cdf88708c99ac990484a90e9fa37235290db4452435875de5327cdc - Sigstore transparency entry: 2156683858
- Sigstore integration time:
-
Permalink:
bbugyi200/symvision@e865044f66dc1cc21909b7938d6f002e26cfd9ee -
Branch / Tag:
refs/heads/master - Owner: https://github.com/bbugyi200
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e865044f66dc1cc21909b7938d6f002e26cfd9ee -
Trigger Event:
push
-
Statement type: