pymanual
Given a Python module name (import name) or a distribution name (the string you'd type in pip install …), deterministically return the best available documentation URL — or a status token explaining why one isn't available.
Install
End users:
pipx install pymanual
# or
uv tool install pymanual
This puts pymanual on your PATH.
Developing on the project itself instead:
git clone https://github.com/gkarpo/pymanual && cd pymanual
uv sync
CLI
pymanual <module> [--no-network] [--no-cache]
pymanual --clear-cache
Exit code is 0 when a URL is returned, 1 when a status token is returned.
$ pymanual json
https://docs.python.org/3/library/json.html
$ pymanual email.mime.text
https://docs.python.org/3/library/email.html
$ pymanual pytest
https://docs.pytest.org/en/latest/
$ pymanual pytest-cov # distribution name with a dash
https://pytest-cov.readthedocs.io/
$ pymanual python-dateutil # not installed; PyPI lookup
https://dateutil.readthedocs.io/en/stable/
$ pymanual winreg # stdlib, but not importable on Linux
https://docs.python.org/3/library/winreg.html
$ pymanual definitely_no_such_xyz
module_not_found
If you're working inside the project source, prefix every command with uv run (e.g. uv run pymanual json).
Flags
--no-network— skip the PyPI HTTP fallback (step 4.ii below for installed third-party modules, and the second half of step 5 for the distribution-name fallback). Resolution stays fully offline.--no-cache— bypass the local cache for that one call (no read, no write).--clear-cache— delete the cache file and exit.
Library
from pymanual import (
resolve_doc_url,
STATUS_NOT_FOUND,
STATUS_UNRESOLVABLE_PATH,
STATUS_LOCAL,
)
resolve_doc_url("json") # 'https://docs.python.org/3/library/json.html'
resolve_doc_url("pytest") # URL from installed distribution metadata
resolve_doc_url("pytest-cov") # distribution-name fallback
resolve_doc_url("python-dateutil") # not installed → PyPI lookup
resolve_doc_url("nope_xyz") # 'module_not_found'
# Same options as the CLI flags:
resolve_doc_url("pytest", network=False) # offline-only
resolve_doc_url("json", use_cache=False) # bypass the on-disk cache
Resolution strategy
Strict order — the first step that yields a result wins.
The stdlib name check. Steps 1 and 2 both reach points where no file path is available to classify. Both then ask whether top_level is in sys.stdlib_module_names, and if so return https://docs.python.org/3/library/<top-level>.html. That list is generated at build time and covers every stdlib module unconditionally, including ones belonging to other platforms (winreg on Linux) and ones a build omitted (tkinter without Tk). It is deliberately consulted only after a path lookup has come up empty, never before: a local statistics.py shadowing a stdlib name is still your own code and must return local_module_no_external_docs.
- Locate the module with
importlib.util.find_spec— which finds it without executing it (see Modules are never imported below).- On success → continue at step 2.
None,ImportErrororValueError, for a dotted name → retry with just the top-level name, so a missing leaf under a real package (pytest.nosuchthing) still answers from the package.- Still nothing → apply the stdlib name check. This is what resolves
winregon Linux, and what makesjson.nosuchthingreturnjson's page rather than reaching PyPI (which carries projects namedjson,email, and other short stdlib names). If it doesn't apply → try the distribution-name fallback (step 5).
- Take
spec.origin. Builtins (sys,builtins, …) are short-circuited as stdlib before this step. If there's no usable path —Nonefor namespace packages, the sentinel'frozen'for the frozen bootstrap stdlib (os,io,abc,codecs,zipimport), or a source file deleted since import — apply the stdlib name check; if it doesn't apply either, returnunresolvable_module_path. - Classify by file path only (not
sys.path):- inside
sysconfig.get_paths()["stdlib"]→ stdlib - inside
site-packages/dist-packages/ a venv directory → third_party - otherwise → local
- inside
- Resolve URL by classification:
- stdlib →
https://docs.python.org/3/library/<top-level>.html - third_party:
importlib.metadata.metadata(...)→ preferProject-URLkeys matchingdocumentation/docs/doc, thenhomepage, thenHome-page- PyPI JSON (
https://pypi.org/pypi/<name>/json) — same key precedence; skipped whennetwork=False - Heuristic last resort:
https://pypi.org/project/<name>/
- local →
local_module_no_external_docs
- stdlib →
- Distribution-name fallback (when
find_speclocated nothing and the stdlib name check didn't apply):- Try
importlib.metadata.metadata(<input>)— catches cases likepytest-cov,PyYAML,scikit-learnwhose import names don't match their distribution names. If installed → return URL from its metadata (falling back tohttps://pypi.org/project/<input>/). - Else, if
network=True, query PyPI directly for<input>. If the package exists there → return its documentation URL. - Otherwise →
module_not_found.
- Try
Import-name vs distribution-name mismatches (e.g. yaml → PyYAML) from step 4 are handled via importlib.metadata.packages_distributions().
Modules are never imported
Resolving a name does not run that module's code. pymanual locates modules with find_spec rather than importing them, so pymanual foo will not execute whatever foo.py happens to be sitting in your working directory.
One exception survives, by necessity: for a dotted name, find_spec imports the parent packages to read their __path__. pymanual email.mime.text imports email and email.mime; the leaf is never executed. Import-time errors in a parent propagate rather than being swallowed. This is why pymanual pytest is fast while pymanual pytest.something still pays for importing pytest.
Local cache
Successful URL resolutions are cached at $XDG_CACHE_HOME/pymanual/cache.json (defaults to ~/.cache/pymanual/cache.json). Subsequent lookups for the same input string return instantly without hitting find_spec, importlib.metadata, or PyPI.
- Status tokens (
module_not_found, etc.) are not cached, so installing a previously-missing module just works on the next call. - Cache key is the verbatim input string —
pytest-covandpytest_covare independent entries. - LRU-bounded at 100 entries; the oldest entry is evicted when the cap is exceeded.
- No TTL; entries live until eviction, until you run
pymanual --clear-cache, or until you delete the file. - Override location for tests/sandboxing via the
PYMANUAL_CACHE_DIRenv var.
Output contract
A single string — either a URL or one of:
module_not_foundunresolvable_module_pathlocal_module_no_external_docs
Development
uv run pytest -q # run tests
uv run pytest --cov=pymanual # with coverage
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 pymanual-1.1.0.tar.gz.
File metadata
- Download URL: pymanual-1.1.0.tar.gz
- Upload date:
- Size: 11.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21c8a7f9c3286a58225c915100b2683d7ce4bbcf6d3db0296485dcf7268e6387
|
|
| MD5 |
2faaf5a47884079b60dcf80c2bb31355
|
|
| BLAKE2b-256 |
b6186df6943b9566af9a839e132ebfb11ef6d05c468336e74976ca39aaf90132
|
File details
Details for the file pymanual-1.1.0-py3-none-any.whl.
File metadata
- Download URL: pymanual-1.1.0-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab246ffdf1c82c8e0a4e47cf32fc00f466bc1772f29b92f652079e28d2cd383d
|
|
| MD5 |
e6b0776313b63c07eb1603ec59a67a74
|
|
| BLAKE2b-256 |
94dac22c0f1a8ea8f81b5fa426ea9f32d2ec6c675ff4a3c1d387ea031560cf0d
|