Skip to content

chore: fill in the PyPI project metadata and fix the sdist - #7

Merged
jamesbeedy merged 1 commit into
mainfrom
chore/pypi-metadata
Sep 6, 2026
Merged

jamesbeedy merged 1 commit into
mainfrom
chore/pypi-metadata

Conversation

@jamesbeedy

Copy link
Copy Markdown
Contributor

What

PyPI's project page renders its sidebar from the metadata inside the uploaded
distribution, not from the repository. armasec-lite shipped the bare minimum, so the
page had an empty Meta section and a single GitHub link. This fills in the fields PyPI
actually renders, and fixes a source distribution that would have failed at upload.

Metadata

  • project.urls gains Documentation, Issues and Changelog; Homepage now points
    at the docs site instead of duplicating Repository. PyPI recognises these five names
    specifically and gives each its own icon, so a sixth under some other label would
    render as a plain bullet.
  • license moves to the PEP 639 SPDX expression plus license-files. Metadata 2.4
    forbids a distribution carrying both a License-Expression and a License ::
    classifier, so the MIT classifier is removed as part of this change. Keeping both
    makes PyPI reject the upload.
  • Classifiers gain Operating System :: OS Independent, Programming Language :: Python :: 3 :: Only, Framework :: Pytest (the package ships a pytest11 entry point),
    Topic :: Software Development :: Libraries :: Python Modules, Topic :: System :: Systems Administration :: Authentication/Directory and Typing :: Typed.
  • Keywords gain the provider names people search for (keycloak, auth0,
    openid-connect, jwks, authentication, authorization).
  • maintainers added.

armasec_lite/py.typed

New, and it is what earns the Typing :: Typed classifier. Every signature in the
package is annotated and mypy runs in strict mode here, but without the PEP 561 marker
mypy and pyright both skip an installed package silently and treat every symbol it
exports as Any. Downstream projects were getting no types from a fully typed library.

The sdist was a release blocker

Hatchling's default sdist is "everything the root .gitignore does not exclude", and
it does not read nested ignore files. docusaurus/node_modules is ignored by
docusaurus/.gitignore, so it went into the tarball: 574MB of input, a 115.7MB
artifact, over PyPI's 100MB per-file limit. release.yml publishes on a tag via trusted
publishing, so this would have failed at the upload step, after the tag was pushed.

[tool.hatch.build.targets.sdist] now names its contents. include alone was not
sufficient: hatchling matches README* and LICEN[CS]E* recursively, which still pulled
1888 of them out of node_modules even with only the two root files named. exclude
names docusaurus and legacy_comparison_compose outright.

Verification

uv build            → armasec_lite-0.1.3.tar.gz  105KB, 49 members
                      roots: armasec_lite, tests, examples, README.md, LICENSE, pyproject.toml
                      (was 115.7MB, 32095 members)
                    → armasec_lite-0.1.3-py3-none-any.whl  77KB, contains
                      armasec_lite/py.typed and dist-info/licenses/LICENSE
twine check         → PASSED on both the wheel and the sdist
METADATA            → Metadata-Version 2.5, License-Expression: MIT,
                      no `License ::` classifier, all five Project-URLs present
uv run pytest       → 371 passed
ruff check / format → clean
mypy --strict       → no issues in 15 source files

Tests

Three added to tests/unit/test_packaging.py: the project-page metadata fields, the
py.typed marker, and the sdist allow-list. Each was confirmed failing against the old
configuration before the fix, per the project's TDD rule.

The sdist test checks the declaration rather than building a tarball, so it stays a
millisecond-scale unit test. It cannot prove the output is small; it catches the
regression that would make it large again. The size claims above come from an actual
build.

Not changed

Development Status :: 3 - Alpha and the description are left as they are. Say the
word if the next release should advertise Beta.

🤖 Generated with Claude Code

PyPI's project page for armasec-lite showed an empty Meta section and only a
GitHub link, because the distribution carried the bare minimum of metadata.

- `project.urls` gains Documentation, Issues and Changelog, and Homepage now
  points at the docs site rather than duplicating Repository. PyPI recognises
  these five names specifically and gives each its own icon.
- `license` moves to the PEP 639 SPDX expression with `license-files`. Metadata
  2.4 forbids carrying both a License-Expression and a `License ::` classifier,
  so the MIT classifier is removed with it; keeping both makes PyPI reject the
  upload.
- Classifiers gain OS Independent, Python 3 Only, Framework :: Pytest (the
  package ships a `pytest11` entry point), two Topic entries and Typing :: Typed.
- Keywords gain the provider names people actually search for.
- `maintainers` added.

`armasec_lite/py.typed` is new, which is what earns the Typing :: Typed
classifier. Every signature is annotated and mypy runs in strict mode, but
without the PEP 561 marker a consumer's type checker skips the installed
package silently and treats everything it exports as `Any`.

The sdist was also a release blocker. Hatchling's default is to ship everything
the ROOT .gitignore does not exclude, and it does not read nested ignore files,
so `docusaurus/node_modules` (ignored by docusaurus/.gitignore) went into the
tarball: 574MB of input, a 115.7MB artifact, over PyPI's 100MB per-file limit.
A tagged release would have failed at upload. The sdist target now names its
contents. `include` alone was not enough: hatchling matches README* and
LICEN[CS]E* recursively, which still pulled 1888 of them out of node_modules,
so `exclude` names the two directories outright.

Verified: `uv build` produces a 105KB sdist of 49 members rooted at
armasec_lite, tests and examples only, and `twine check` PASSES on both the
wheel and the sdist. The wheel contains py.typed and the LICENSE. Metadata
renders as version 2.5 with License-Expression: MIT and no License ::
classifier. Full suite 371 passed; ruff and mypy --strict clean.

Three new tests in tests/unit/test_packaging.py cover the project-page fields,
the py.typed marker and the sdist allow-list. All three were confirmed failing
against the old configuration first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jamesbeedy
jamesbeedy merged commit c798731 into main Sep 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant