agentsclimarketplace

Python packaging

Skill Paldom/python-skills/skills/python-packaging

Agent Skills and hooks for maintaining high-quality open-source Python packages - an agentic engineering setup covering linting, testing, packaging, releases, and CI quality gates.

Install
npx -y skills add Paldom/python-skills --skill python-packaging

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Configures packaging for a Python library — pyproject.toml metadata, build backend choice, src layout, uv project management, building wheels and sdists, verifying py.typed lands in the wheel. Use for 'package this project', 'set up pyproject.toml', 'build a wheel', 'switch to uv', 'migrate off Poetry'. Not for publishing to PyPI, version bumps, release automation, or typing strategy.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

12.5 KB, as published. Nobody here has run it

python-packaging

Make a Python library installable and buildable with verified artifacts: one pyproject.toml as the single source of truth, a deliberately chosen PEP 517 build backend, src/ layout, uv-managed dependencies with a committed lockfile, and an sdist + wheel whose contents are checked rather than assumed. The failures this skill prevents: declared files (py.typed, data files) silently missing from the wheel, backends that cannot handle the project's layout or extensions, Poetry migrations that switch the backend before resolution is verified, and pip calls that desync a uv-managed environment.

When NOT to use

  • Publishing to PyPI, version bumps, changelogs, git tags, trusted publishing — the python-release skill, if installed. Stop after artifacts are verified.
  • Type-checker choice or configuration, and whether/how to ship py.typed — the python-typing skill. This skill only verifies that an existing py.typed actually lands in the built wheel.
  • [tool.ruff] config — python-lint. pytest/coverage config — python-testing. GitHub Actions — python-ci. Dependency vulnerability audits — python-supply-chain.
  • Single-file scripts that just need dependencies — PEP 723 inline script metadata plus uv run script.py covers that with no packaging at all.

Workflow

1. Assess what exists

ls setup.py setup.cfg MANIFEST.in requirements*.txt poetry.lock Pipfile uv.lock 2>/dev/null
grep -n "build-backend\|tool.poetry\|dependency-groups\|optional-dependencies" pyproject.toml 2>/dev/null
find . -name "*.pyx" -o -name "*.c" -o -name "Cargo.toml" | grep -v .venv | head

Record three facts before changing anything: (a) native extensions or pure Python — this decides the backend; (b) flat or src/ layout; (c) which dev-dependency convention the repo already uses ([dependency-groups], a dev extra under [project.optional-dependencies], or legacy [tool.uv] dev-dependencies) — match it or migrate it wholesale, never mix.

2. Choose the build backend

BackendPick whenAvoid when
uv_buildpure-Python library; zero-config src/ discovery, strict metadata validationany C/Cython/Rust extension modules (unsupported); VCS-tag dynamic versioning needed
hatchlingdynamic versioning from git tags via hatch-vcs matters; widely treated as the general-guidance default; plugin ecosystemyou want uv_build's stricter validation and zero config
setuptoolsextension modules (ext_modules), deep legacy configgreenfield pure-Python — looser validation, auto-discovery surprises

Do not adopt poetry-core for new projects; the ecosystem consolidated on PEP 621 [project] metadata. Note that uv build is a backend-agnostic PEP 517 frontend — it drives hatchling or setuptools just as well, so adopting uv as the project manager does not force a backend migration; treat that as a separate, verified step.

[build-system]
requires = ["uv_build>=0.11,<0.12"]
build-backend = "uv_build"

Keep the upper bound on uv_build: it follows uv's versioning policy, where breaking changes may land in minor releases, so an unbounded requirement can break your build the day a new version ships. Hatchling and setuptools have more conservative compatibility norms; a floor (e.g. hatchling>=1.27) is customary. Risk framing: uv as a whole is pre-1.0, but the uv_build backend specifically is marked Production/Stable on PyPI and has been the uv init default since mid-2025 — do not conflate the two in either direction.

Full trade-offs, [tool.uv.build-backend] keys, and per-backend file-inclusion config: references/build-backends.md.

3. Lay out the package (src/)

my-package/
├── pyproject.toml
├── README.md
├── src/
│   └── my_package/
│       ├── __init__.py
│       └── py.typed        # only if the project already ships one
└── tests/

Use src/ for libraries: tests and tools then import the installed package. With a flat layout, import my_package resolves to the working-directory copy even when the built wheel is broken or missing files — packaging bugs stay invisible until release. For a new project, uv init --lib my-package scaffolds src/, py.typed, and the uv_build backend in one step.

uv_build expects src/<module>/__init__.py with the module named after the normalized project name. If the import name differs, declare it:

[tool.uv.build-backend]
module-name = "actual_import_name"

4. Write [project] metadata

Minimal publishable set — validate-pyproject (step 6) will hold you to it:

[project]
name = "my-package"
version = "0.1.0"
description = "One line, benefit first."
readme = "README.md"
license = "MIT"                      # SPDX expression (PEP 639; needs a current backend)
requires-python = ">=3.10"
authors = [{ name = "Your Name", email = "[email protected]" }]
dependencies = ["httpx>=0.27"]
classifiers = ["Programming Language :: Python :: 3"]

[project.scripts]
my-cli = "my_package.cli:main"       # callable must take no required args; parse sys.argv inside

[dependency-groups]
dev = ["pytest>=8"]

Dev tooling goes in PEP 735 [dependency-groups] (written by uv add --dev), not in a published extra: extras under [project.optional-dependencies] are installable by consumers from PyPI, dependency groups are not. A dev extra leaks your toolchain into downstream resolvers. Optional runtime features (e.g. my-package[cli]) are what extras are for.

5. Manage the project with uv

uv init --lib my-package            # new projects only
uv add httpx                        # runtime dep -> [project.dependencies] + uv.lock
uv add --dev pytest                 # dev dep -> [dependency-groups].dev
uv sync                             # build .venv from uv.lock (--no-dev in production)
uv lock --upgrade-package httpx     # targeted upgrade; rest of the lockfile intact
uv run pytest                       # run tools in the env, no activation

Commit uv.lock; never hand-edit it. uv verifies lockfile freshness against pyproject.toml and environment freshness against the lockfile before every uv run, which is what makes results reproducible. Never call pip inside a uv project — it mutates .venv behind the lockfile's back.

Migrations:

  • From setup.py/setup.cfg — move metadata into [project], set [build-system], delete setup.py, setup.cfg, and MANIFEST.in, then run steps 6–8. Exception: setuptools with C extensions still needs a setup.py for ext_modules — keep only that part.
  • From Poetry — run uvx 'migrate-to-uv==0.12.0' (pin the version: unpinned uvx executes whatever was published to PyPI most recently, which is both irreproducible and a supply-chain hole; bump pins deliberately). It maps [tool.poetry.*] to PEP 621 and poetry.lock to uv.lock. Switch [build-system] away from poetry-core last, after uv lock && uv sync resolve cleanly — flipping the backend first is the classic way to debug two migrations at once.
  • From requirements.txtuv add -r requirements.txt (plus -c constraints.txt if present), then delete the file. If downstream tooling still needs one, generate it with uv export.

6. Validate the metadata

uvx --from 'validate-pyproject[all]==0.25' validate-pyproject pyproject.toml

A pyproject.toml that parses as valid TOML can still violate the packaging PEP schemas (bad license shape, misplaced key, malformed entry point) — this catches what formatters and TOML parsers do not. Run it after every metadata edit; it is cheap.

7. Build

uv build        # sdist (.tar.gz) + wheel (.whl) into dist/

To debug uv_build under any frontend, set RUST_LOG=uv=debug. No-uv fallback (once, for environments where uv is unavailable): python -m pip install build==1.5.0 && python -m build produces the same artifacts, and pip install -e '.[dev]' replaces uv sync for dev installs — at the cost of lockfile reproducibility.

8. Verify the artifacts — never skip

Run the bundled checker (path relative to this skill's folder):

python3 "${CLAUDE_SKILL_DIR}/scripts/check_wheel.py" --project-root /path/to/project /path/to/project/dist

It is read-only and exits non-zero with ERROR lines if: the wheel lacks METADATA/WHEEL/RECORD; any py.typed present in the source tree is missing from the wheel; tests/, docs/, or similar directories leaked into the wheel; or the sdist lacks pyproject.toml. Manual equivalents: unzip -l dist/*.whl and tar -tzf dist/*.tar.gz.

Then smoke-test the wheel itself (not the source tree):

uv run --isolated --no-project --with dist/my_package-0.1.0-py3-none-any.whl \
  python -c "import my_package; print(my_package.__name__)"

Output spec — what done looks like

  • pyproject.toml is the only packaging config; setup.py/setup.cfg/ MANIFEST.in/requirements.txt are gone (except setup.py kept solely for ext_modules).
  • validate-pyproject exits 0.
  • uv build produces both dist/*.tar.gz and dist/*.whl.
  • scripts/check_wheel.py exits 0 — expected files (including py.typed when the source ships one) verified inside the wheel; no test/doc leakage.
  • The built wheel imports in an isolated environment.
  • uv.lock exists and is committed.

Failure modes & gotchas

  • py.typed in the repo, missing from the wheel. No backend can be assumed to ship arbitrary non-.py files: setuptools needs [tool.setuptools.package-data], hatchling ships files inside the package dir only if the wheel target points at it, uv_build includes files under the module root. Consumers of a wheel without the marker silently get Any for your whole API — verify the artifact, not the source tree. Per-backend snippets in references/build-backends.md.
  • uv_build "module not found" / metadata validation failure. It wants exactly one top-level module, named after the normalized project name, at src/<module>/ or ./<module>/. Fix with [tool.uv.build-backend] module-name/module-root; multi-top-level wheels need setuptools/hatchling.
  • Build breaks after a uv release. Unbounded requires = ["uv_build"] — restore the >=X,<X+1 bound (step 2).
  • Half-finished Poetry migration. [build-system] flipped while the real dependency list still sits in [tool.poetry.dependencies] — artifacts build with wrong or missing metadata. Metadata first, resolution verified, backend last.
  • Heisenbug environments. Someone ran pip install in the uv project; the next uv run silently reverts it. Both directions look like flakiness. Only uv add/uv sync mutate the env.
  • Tests pass, installed package broken. Flat layout imported the working-directory copy, masking files missing from the wheel. src/ layout plus the step 8 wheel smoke test catches it.
  • tests/ ships inside the wheel. setuptools auto-discovery picks up any importable top-level directory. Constrain it ([tool.setuptools.packages.find] where = ["src"]) or move to src/ layout; check_wheel.py flags the leak.
  • Dev extra published to PyPI. Dev tooling declared as [project.optional-dependencies] dev becomes an installable extra for every consumer. Use [dependency-groups] for tooling; extras for optional runtime features only.
  • Valid TOML, invalid metadata. Formatters and TOML parsers accept files that violate packaging schemas — that is what step 6 exists for.
  • Performance folklore. Large build-speed multipliers claimed for uv_build trace to practitioner blog posts, not controlled benchmarks. Treat as directional; do not repeat specific numbers as fact.

Bundled files

  • references/build-backends.md — backend trade-offs in depth, [tool.uv.build-backend] reference, per-backend file inclusion (py.typed, data files), migration paths, legacy-tool→uv command map.
  • scripts/check_wheel.py — read-only sdist/wheel verification; run after every build; non-zero exit with machine-readable ERROR lines on failure.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.