Architecture overview¶
This page describes how the modules in pypi_profile/pypi_profile/ relate to each other and what
each one is responsible for.
Module map¶
pypi_profile/
├── cli.py ← argparse entry point; dispatches to every command
├── server.py ← FastAPI app factory; all HTML and JSON routes
├── builder.py ← static site generator (calls server via TestClient)
├── loader.py ← reads pypi_profile.toml; discovers installed plugins
├── models.py ← Pydantic data models for the TOML schema
├── claims.py ← builds and encodes signed proof-of-control claims
├── signing.py ← minisign keypair management; signs claims
├── verifier.py ← fetches external URLs; validates claim signatures
├── fetcher.py ← live data from PyPI, GitHub, GitLab, Mastodon (cached)
├── importers.py ← import from JSON Resume; merge live data into profile
├── finder.py ← file-system scanner that locates pypi_profile.toml files
├── key_management.py ← key-rotate, key-recover, key-export, key-import
├── plugin_spec.py ← pluggy hook spec (defines what plugins can implement)
├── plugin_manager.py ← discovers and registers installed plugins via entry points
├── wizard.py ← interactive terminal onboarding (prompt-toolkit)
├── gui.py ← Tkinter desktop GUI
├── log.py ← logging setup (one logger, one handler, one call site)
└── ds/
├── paths.py ← locates pypi_ds templates and static assets on disk
└── __init__.py
How data flows through a typical command¶
pypi-profile serve ./my_profile¶
cli.py cmd_serve()
│
├─ loader.find_profile("./my_profile") → Path to pypi_profile.toml
├─ loader.load_profile(path) → ProfileData (Pydantic object)
│
├─ server.build_app(profile, ...) → FastAPI app
│ │
│ ├─ Jinja2 FileSystemLoader
│ │ 1. pypi_profile/templates/
│ │ 2. pypi_ds/templates/
│ │
│ ├─ StaticFiles at /static/pypi_ds ← CSS + images from pypi_ds package
│ │
│ └─ Route handlers (/, /packages, /verification, /api/profile.json, …)
│
└─ uvicorn.run(app)
pypi-profile build ./my_profile --output dist¶
cli.py cmd_build()
│
├─ builder.build_static_site("./my_profile", output=Path("dist"))
│ │
│ ├─ loader.find_profile() → toml_path
│ ├─ loader.load_profile() → ProfileData
│ ├─ server.build_app(profile, static_mode=True)
│ │
│ ├─ starlette.testclient.TestClient(app)
│ │ for each route in STATIC_ROUTES:
│ │ resp = client.get(route)
│ │ write resp.text → dist/path/index.html
│ │
│ ├─ for each JSON route in JSON_ROUTES:
│ │ write resp.text → dist/api/*.json
│ │
│ ├─ copy resume.json → dist/api/resume.json (if found)
│ └─ copy static assets → dist/static/pypi_ds/
└─ done
pypi-profile sign controls-url . --url https://github.com/you¶
cli.py cmd_sign()
│
├─ loader.find_profile(".") → toml_path
├─ loader.load_profile(toml_path) → ProfileData
│
├─ signing.sign_controls_url(
│ profile_package, pypi_username, subject_url
│ )
│ │
│ ├─ claims.build_claim(...) → unsigned claim dict
│ ├─ json.dumps(claim) → canonical bytes
│ ├─ minisign.sign(bytes, secret_key) → 64-byte signature
│ ├─ embed signature in claim dict
│ └─ claims.encode_claim(claim) → "pypi-profile-proof: <base64>"
│
└─ print proof token
pypi-profile verify .¶
cli.py cmd_verify()
│
├─ loader.load_profile(...) → ProfileData
│
├─ verifier.diagnose_all_profiles(profile)
│ │
│ for each [[profiles]] link:
│ ├─ verifier.fetch_page(link.url) → raw HTML text
│ ├─ verifier.find_proof_tokens(html) → list of token strings
│ ├─ claims.decode_claim(token) → dict
│ ├─ claims.is_expired(claim) → bool
│ └─ verifier.verify_claim_signature(claim, public_key_b64) → bool
│
└─ print table of results
Key design decisions¶
Pydantic for the TOML schema¶
Every field in pypi_profile.toml maps to a Pydantic model in models.py. Pydantic does three
things here:
- Validates that required fields are present and have the right types.
- Fills in sensible defaults (most fields are optional with empty defaults).
- Provides
model_dump()so the API endpoints can serialize the whole profile to JSON with one call.
If you add a field to the TOML schema you need to add it to the matching Pydantic class. The
validator on ProfileData.profile shows how to add a custom validator if the field needs coercion
beyond what Pydantic does automatically.
FastAPI + TestClient for static generation¶
The static builder does not re-implement page rendering. It calls the live FastAPI app through
Starlette's built-in TestClient, which runs the ASGI app in-process without starting a real HTTP
server. Every response is written to disk as a file. This means the static output is always
byte-for-byte identical to what the live server would return, and any bug fixed in a route is
automatically fixed in the static build too.
The static_mode=True flag tells the app to use stored_proof values from the TOML instead of
making live HTTP requests to external URLs, which would require network access and a private key
during CI builds.
pluggy for plugin discovery¶
pluggy is a plugin framework originally built for pytest. It lets third-party packages register
themselves as providers of certain functionality by declaring an entry point in their
pyproject.toml. pypi-profile uses it so that installing a profile package (e.g. pip install matthewdeanmartin) automatically makes that profile discoverable without any manual configuration.
The discovery chain is:
importlib.metadata.entry_points(group="pypi_profile.plugins")— finds all installed packages that registered themselves.- For each,
resources.files(module_name).joinpath("pypi_profile.toml")— locates the bundled TOML file inside the installed package. load_profile(toml_path)— reads and validates it.
tomllib / tomli for TOML parsing¶
Python 3.11 added tomllib to the standard library. On older versions the code falls back to the
tomli third-party package, which is API-compatible. The import at the top of loader.py handles
this:
if sys.version_info >= (3, 11):
import tomllib
else:
try:
import tomllib
except ImportError:
import tomli as tomllib
This pattern is common in projects that need to support Python 3.10 without dropping TOML support.
Dual Jinja2 template loader¶
The server sets up a FileSystemLoader with two directories in priority order:
pypi_profile/templates/— app-specific templates (can override anything)pypi_ds/templates/— design system base templates
When a template is referenced, Jinja2 searches directory 1 first. This allows a future custom profile package to ship its own templates in directory 1 and have them take precedence over the defaults.