Publishing¶
Two artifacts are published from this repository: the documentation site on GitHub Pages, and the package on PyPI.
Documentation¶
The Material site is published to GitHub Pages at open-byte.github.io/django-modern-schemas.
One-Time Repository Setup¶
An organization owner or repository administrator must enable GitHub Pages once:
- Open the repository Settings.
- Open Pages in the sidebar.
- Under Build and deployment, choose GitHub Actions as the source.
The deployment workflow then has the permissions it needs to publish the built site.
Automatic Deployment¶
deploy-docs.yml
deploys after a push to main that changes documentation, executable examples,
the Material configuration, or documentation dependencies.
The workflow:
- Installs the locked
docsdependency group withuv. - Runs
mkdocs build --strict. - Uploads the generated
site/directory to GitHub Pages.
The deployment URL appears in the Deploy Documentation workflow summary.
Manual Deployment¶
Open the repository Actions tab, choose Deploy Documentation, and select Run workflow. This is useful when Pages has just been enabled or when a deployment must be repeated without another documentation commit.
Local Verification¶
Build the same artifact before pushing:
Preview it locally with:
Package¶
The package is published to
pypi.org/project/django-modern-schemas
and built with the uv_build backend. The version lives in exactly one place,
pyproject.toml; django_modern_schemas.__version__ reads it back from the
installed distribution metadata.
One-Time PyPI Setup¶
Releases upload through Trusted Publishing, so no API token is stored in the repository. An owner configures the publisher once with these values:
| Field | Value |
|---|---|
| PyPI Project Name | django-modern-schemas |
| Owner | open-byte |
| Repository | django-modern-schemas |
| Workflow | publish.yml |
| Environment | pypi |
Where that form lives depends on whether the project already exists on PyPI:
- Before the first release, the project has no settings page yet, so it is registered as a pending publisher at Account settings → Publishing. PyPI creates the project on the first successful upload and converts the pending publisher into a normal one.
- Afterwards, at Manage project → Publishing.
The GitHub pypi environment must also exist, under Settings → Environments
in the repository. It can be empty; the workflow only needs the name to match.
Cutting a Release¶
uv version --bump minor # or --bump patch / --bump major
uv lock # the lock records the project version too
Commit the result, merge it to main, then tag and publish a GitHub Release
whose tag is the version prefixed with v (v0.1.0 for 0.1.0):
Publishing the release triggers
publish.yml,
which refuses to continue when the tag and the packaged version disagree, then
builds, validates the metadata with twine check, and uploads.
Local Verification¶
Build and inspect the artifacts without uploading anything:
To rehearse the upload end to end, publish to TestPyPI first — a separate site with its own account and token:
Then install from there into a throwaway environment. TestPyPI has no usable
Django or Pydantic, so PyPI stays the primary index and TestPyPI is added as an
extra. --index-strategy unsafe-best-match is required: under the default
first-index strategy uv stops at the first index it consults and never falls
through to the one holding the release candidate.
uv venv /tmp/dms-smoke
VIRTUAL_ENV=/tmp/dms-smoke uv pip install \
--index-strategy unsafe-best-match \
--index-url https://pypi.org/simple/ \
--extra-index-url https://test.pypi.org/simple/ \
django-modern-schemas
Supported Versions¶
Every version the package advertises is executed by the compat job in
test_full.yml,
which pins Django and Pydantic instead of resolving from the lock. When a floor
in pyproject.toml changes, add or move a cell in that matrix so the claim stays
tested.
requires-python is capped below 3.14: PEP 649
defers the evaluation of class annotations, so the schema metaclass reads an
empty __annotations__ and silently drops Source and MethodSource fields.
The cap is lifted once the metaclass reads annotations through annotationlib.