Install
$ agentstack add skill-paldom-python-skills-python-packaging ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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
| Backend | Pick when | Avoid when | | --- | --- | --- | | uv_build | pure-Python library; zero-config src/ discovery, strict metadata validation | any C/Cython/Rust extension modules (unsupported); VCS-tag dynamic versioning needed | | hatchling | dynamic versioning from git tags via hatch-vcs matters; widely treated as the general-guidance default; plugin ecosystem | you want uv_build's stricter validation and zero config | | setuptools | extension modules (ext_modules), deep legacy config | greenfield 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,=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](references/build-backends.md).
### 3. Lay out the package (src/)
my-package/ ├── pyproject.toml ├── README.md ├── src/ │ └── mypackage/ │ ├── _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//__init__.py` with the module named after the
normalized project name. If the import name differs, declare it:
```toml
[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 = "you@example.com" }]
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.txt —
uv 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.tomlis the only packaging config;setup.py/setup.cfg/
MANIFEST.in/requirements.txt are gone (except setup.py kept solely for ext_modules).
validate-pyprojectexits 0.uv buildproduces bothdist/*.tar.gzanddist/*.whl.scripts/check_wheel.pyexits 0 — expected files (includingpy.typedwhen
the source ships one) verified inside the wheel; no test/doc leakage.
- The built wheel imports in an isolated environment.
uv.lockexists and is committed.
Failure modes & gotchas
py.typedin 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](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// or .//. 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 installin 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](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.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Paldom
- Source: Paldom/python-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.