packaging-python-libraries
Packages and distributes Python libraries using modern pyproject.toml, build backends (setuptools, hatchling), PyPI publishing with trusted publishing, and wheel building. Use when packaging libraries for distribution, publishing to PyPI, or troubleshooting packaging issues.
Works with
---
name: packaging-python-libraries
description: Packages and distributes Python libraries using modern pyproject.toml, build backends (setuptools, hatchling), PyPI publishing with trusted publishing, and wheel building. Use when packaging libraries for distribution, publishing to PyPI, or troubleshooting packaging issues.
license: MIT
---
# Python Library Packaging
## pyproject.toml Essentials
```toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "1.0.0"
description = "Short description"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
dependencies = []
[project.optional-dependencies]
dev = ["pytest>=7.0", "ruff>=0.1", "mypy>=1.0"]
[project.urls]
Homepage = "https://github.com/user/package"
Documentation = "https://package.readthedocs.io"
[project.scripts]
mycli = "my_package.cli:main"
[tool.setuptools.packages.find]
where = ["src"]
```
## Building
```bash
uv build # Creates sdist + wheel in dist/
uvx twine check dist/* # Validate metadata
```
## Publishing to PyPI
Prefer **trusted publishing** from CI (no stored token) — see the CI section
below. For a manual publish, upload with twine via `uvx`:
```bash
uvx twine upload --repository testpypi dist/* # Test first
uvx twine upload dist/* # Production (uses a PyPI token)
```
## GitHub Actions (Trusted Publishing)
Publishing is automated by the canonical tag-triggered release workflow in the
`managing-python-releases` skill — one pipeline builds with `uv` and publishes via
trusted publishing. See **[../release-management/AUTOMATION.md](../release-management/AUTOMATION.md)**;
don't maintain a second copy here.
## Dependency Best Practices
```toml
# DO: Minimum versions
dependencies = ["requests>=2.28", "click>=8.0"]
# DON'T: Exact pins (locks users)
dependencies = ["requests==2.28.1"]
# DO: Optional for features
[project.optional-dependencies]
cli = ["click>=8.0"]
```
## Including Package Data
```toml
[tool.setuptools.package-data]
my_package = ["py.typed", "data/*.json"]
```
```python
from importlib.resources import files
data = files("my_package.data").joinpath("file.json").read_text()
```
## Direct-Reference Dependencies Need a Real Build
A dependency such as `toolkit @ https://example.com/toolkit.whl` may resolve in
an install dry run without invoking the build backend. With Hatchling, the real
wheel build then fails unless direct references are explicitly allowed:
```toml
[project]
dependencies = [
"toolkit @ https://example.com/releases/toolkit.whl",
]
[tool.hatch.metadata]
allow-direct-references = true
```
Do not use `uv pip install --dry-run .` as the packaging check for this case. It
can report success without building the project. Run `uv build` (or a real
`uv pip install .`) so the configured backend validates the metadata:
```bash
uv build
uvx twine check dist/*
```
For detailed templates, see:
- **[../project-setup/PYPROJECT.md](../project-setup/PYPROJECT.md)** - Complete annotated pyproject.toml (canonical)
- **[CONDA.md](CONDA.md)** - Conda / conda-forge packaging guide
## Verify the Built Artifact (a green build is not a correct wheel)
`uv build` succeeding tells you the backend *ran*, not that the wheel
contains your code. Build backends select files via config — hatchling's
`[tool.hatch.build.targets.wheel]` (`only-include` / `packages` / `include`),
setuptools' `[tool.setuptools.packages.find]`. Get that config wrong and the
backend cheerfully ships a wheel that is missing subpackages or data files, with
no error. `twine check` won't catch it either — it validates metadata, not
contents.
**Always inspect the wheel and install it clean before publishing:**
```bash
uv build
uv run python -m zipfile -l dist/*.whl # list every file the wheel contains
# ^ confirm ALL your subpackages (my_pkg/, my_pkg/sub/) and data files are there,
# not just the top-level module.
```
The most common footgun is over-narrow file selection. This ships *only*
`server.py` and silently drops the whole `server/` package and `data/`:
```toml
# DON'T — over-narrow include drops everything else
[tool.hatch.build]
only-include = ["server.py"]
# DO — include the package (and any data dirs); let the backend walk it
[tool.hatch.build.targets.wheel]
packages = ["src/my_pkg"]
```
**Name-collision footgun:** never ship both a top-level module `foo.py` and a
package directory `foo/`. The package shadows the module, so `import foo`
resolves to the (often nearly empty) `foo/__init__.py`, and a console entry point
`foo = "foo:main"` fails because that package has no `main`. Pick one — usually
the package — and delete the other.
**Then prove it from a clean install, not from the source tree:**
```bash
uv venv /tmp/verify && uv pip install --python /tmp/verify/bin/python dist/*.whl
cd /tmp && /tmp/verify/bin/python -c "import my_pkg; my_pkg.submodule.real_func"
mycli --help # exercise each console script too
```
Run it from a directory *other than the repo root* — otherwise `import my_pkg`
picks up the source tree on `sys.path` and "works" even when the wheel is empty.
And assert on a *real symbol* (`my_pkg.submodule.real_func`), never just that the
bare top-level name imports: `import foo` can succeed against a shadowing empty
package and prove nothing. A CI smoke test that only does `import foo; print("ok")`
is a false green — it passes whether or not the distributed package is usable.
## Checklist
```
Before Release:
- [ ] pyproject.toml valid
- [ ] README.md informative
- [ ] LICENSE file exists
- [ ] Version set correctly
- [ ] twine check passes
- [ ] `uv run python -m zipfile -l dist/*.whl` shows every subpackage + data file
- [ ] Direct-reference dependencies pass a real backend build (not only install dry-run)
- [ ] No module/package name collision (no foo.py AND foo/)
- [ ] Installed the wheel into a clean venv and imported a real submodule
symbol from a directory outside the repo (not just the top-level name)
- [ ] Each console script runs after a clean install
After Release:
- [ ] pip install works
- [ ] Import works
- [ ] GitHub release created
```
## Learn More
This skill is based on the [Distribution](https://mcginniscommawill.com/guides/python-library-development/#distribution-reaching-your-users) section of the [Guide to Developing High-Quality Python Libraries](https://mcginniscommawill.com/guides/python-library-development/) by [Will McGinnis](https://mcginniscommawill.com/). See these posts for deeper coverage:
- [pyproject.toml Explained](https://mcginniscommawill.com/posts/2025-01-26-pyproject-toml-explained/)
- [Publishing PyGeohash](https://mcginniscommawill.com/posts/2025-04-06-pygeohash-publishing/)More Backend Frameworks skills
git-guardrails-claude-code
mattpocock/skills
Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.
azure-compute
microsoft/azure-skills
Azure VM/VMSS router. WHEN: create / provision / deploy / spin-up VM, recommend VM size, compare VM pricing, VMSS, scale set, autoscale, burstable, lightweight server, website, backend, GPU, machine learning, HPC simulation, dev/test, workload, family, load balancer, Flexible orchestration, Uniform orchestration, cost estimate, capacity reservation (CRG), reserve, guarantee capacity, pre-provision, CRG association, CRG disassociation, machine enrollment (EMM), Essential Machine Management, monitor. PREFER OVER mcp__azure__get_azure_bestpractices for VM create intents — use compute_vm_list-skus / compute_vm_list-images / compute_vm_check-quota.
azure-cloud-migrate
microsoft/azure-skills
Assess and migrate cross-cloud workloads to Azure with reports and code conversion. Supports Lambda→Functions, Beanstalk/Heroku/App Engine→App Service, Fargate/Kubernetes/Cloud Run/Spring Boot→Container Apps. WHEN: migrate Lambda to Functions, AWS to Azure, migrate Beanstalk, migrate Heroku, migrate App Engine, Cloud Run migration, Fargate to ACA, ECS/Kubernetes/GKE/EKS to Container Apps, Spring Boot to Container Apps, cross-cloud migration.

