python-packaging
Automatically applies when configuring Python project packaging. Ensures proper pyproject.toml setup, project layout, build configuration, metadata, and distribution best practices.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install benchflow-ai-skillsbench-python-packaging
Repository
Skill path: registry/terminal_bench_2.0/full_batch_reviewed/terminal_bench_2_0_pypi-server/environment/skills/python-packaging
Automatically applies when configuring Python project packaging. Ensures proper pyproject.toml setup, project layout, build configuration, metadata, and distribution best practices.
Open repositoryBest for
Primary workflow: Grow & Distribute.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: benchflow-ai.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install python-packaging into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/benchflow-ai/SkillsBench before adding python-packaging to shared team environments
- Use python-packaging for python workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: python-packaging
description: Automatically applies when configuring Python project packaging. Ensures proper pyproject.toml setup, project layout, build configuration, metadata, and distribution best practices.
category: python
---
# Python Packaging Patterns
When packaging Python projects, follow these patterns for modern, maintainable package configuration.
**Trigger Keywords**: pyproject.toml, packaging, setup.py, build, distribution, package, publish, PyPI, wheel, sdist, project structure
**Agent Integration**: Used by `backend-architect`, `devops-engineer`, `python-engineer`
## ✅ Correct Pattern: pyproject.toml Setup
```toml
# pyproject.toml - Modern Python packaging
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mypackage"
version = "0.1.0"
description = "A short description of your package"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "Your Name", email = "[email protected]"}
]
maintainers = [
{name = "Maintainer Name", email = "[email protected]"}
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries",
]
keywords = ["example", "package", "keywords"]
requires-python = ">=3.11"
# Core dependencies
dependencies = [
"fastapi>=0.109.0",
"pydantic>=2.5.0",
"sqlalchemy>=2.0.0",
"httpx>=0.26.0",
]
# Optional dependencies (extras)
[project.optional-dependencies]
dev = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-asyncio>=0.23.0",
"black>=24.0.0",
"ruff>=0.1.0",
"mypy>=1.8.0",
]
docs = [
"sphinx>=7.2.0",
"sphinx-rtd-theme>=2.0.0",
]
all = [
"mypackage[dev,docs]",
]
[project.urls]
Homepage = "https://github.com/username/mypackage"
Documentation = "https://mypackage.readthedocs.io"
Repository = "https://github.com/username/mypackage.git"
Issues = "https://github.com/username/mypackage/issues"
Changelog = "https://github.com/username/mypackage/blob/main/CHANGELOG.md"
[project.scripts]
# Entry points for CLI commands
mypackage-cli = "mypackage.cli:main"
# Tool configurations
[tool.setuptools]
# Package discovery
packages = ["mypackage", "mypackage.submodule"]
[tool.setuptools.package-data]
mypackage = ["py.typed", "*.json", "templates/*.html"]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--cov=mypackage",
"--cov-report=term-missing",
"--cov-report=html",
]
markers = [
"slow: marks tests as slow",
"integration: marks tests as integration tests",
]
[tool.black]
line-length = 100
target-version = ["py311", "py312"]
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.venv
| build
| dist
)/
'''
[tool.ruff]
line-length = 100
target-version = "py311"
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
ignore = []
exclude = [
".git",
".venv",
"build",
"dist",
]
[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_any_generics = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
check_untyped_defs = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
```
## Project Structure
```
mypackage/
├── pyproject.toml # Project configuration
├── README.md # Project documentation
├── LICENSE # License file
├── CHANGELOG.md # Version history
├── .gitignore # Git ignore patterns
│
├── src/ # Source code (src layout)
│ └── mypackage/
│ ├── __init__.py # Package init
│ ├── __main__.py # Entry point for -m
│ ├── py.typed # PEP 561 marker
│ ├── core.py
│ ├── models.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
│
├── tests/ # Test suite
│ ├── __init__.py
│ ├── conftest.py # Pytest fixtures
│ ├── test_core.py
│ └── test_models.py
│
├── docs/ # Documentation
│ ├── conf.py
│ ├── index.rst
│ └── api.rst
│
└── scripts/ # Utility scripts
└── setup_dev.sh
```
## Package Init
```python
# src/mypackage/__init__.py
"""
MyPackage - A Python package for doing things.
This package provides tools for X, Y, and Z.
"""
from mypackage.core import MainClass, main_function
from mypackage.models import Model1, Model2
# Version
__version__ = "0.1.0"
# Public API
__all__ = [
"MainClass",
"main_function",
"Model1",
"Model2",
]
# Convenience imports for common use cases
def quick_start():
"""Quick start helper for new users."""
return MainClass()
```
## CLI Entry Point
```python
# src/mypackage/__main__.py
"""
Entry point for python -m mypackage.
"""
from mypackage.cli import main
if __name__ == "__main__":
main()
# src/mypackage/cli.py
"""Command-line interface."""
import argparse
import sys
from typing import List, Optional
def main(argv: Optional[List[str]] = None) -> int:
"""
Main CLI entry point.
Args:
argv: Command-line arguments (defaults to sys.argv)
Returns:
Exit code (0 for success, non-zero for error)
"""
parser = argparse.ArgumentParser(
prog="mypackage",
description="MyPackage CLI tool",
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {__version__}"
)
parser.add_argument(
"--verbose",
"-v",
action="store_true",
help="Enable verbose output"
)
subparsers = parser.add_subparsers(dest="command", help="Available commands")
# Add subcommands
init_parser = subparsers.add_parser("init", help="Initialize project")
init_parser.add_argument("path", help="Project path")
run_parser = subparsers.add_parser("run", help="Run the application")
run_parser.add_argument("--config", help="Config file path")
# Parse arguments
args = parser.parse_args(argv)
# Execute command
if args.command == "init":
return init_command(args)
elif args.command == "run":
return run_command(args)
else:
parser.print_help()
return 1
def init_command(args) -> int:
"""Handle init command."""
print(f"Initializing project at {args.path}")
return 0
def run_command(args) -> int:
"""Handle run command."""
print("Running application")
return 0
if __name__ == "__main__":
sys.exit(main())
```
## Type Hints for Distribution
```python
# src/mypackage/py.typed
# This file signals that the package supports type hints (PEP 561)
# Leave empty or add configuration if needed
```
## Version Management
```python
# src/mypackage/_version.py
"""Version information."""
__version__ = "0.1.0"
__version_info__ = tuple(int(i) for i in __version__.split("."))
# src/mypackage/__init__.py
from mypackage._version import __version__, __version_info__
__all__ = ["__version__", "__version_info__"]
```
## Build and Distribution
```bash
# Build package
python -m build
# This creates:
# dist/mypackage-0.1.0-py3-none-any.whl (wheel)
# dist/mypackage-0.1.0.tar.gz (source distribution)
# Test installation locally
pip install dist/mypackage-0.1.0-py3-none-any.whl
# Upload to PyPI
python -m twine upload dist/*
# Upload to Test PyPI first
python -m twine upload --repository testpypi dist/*
```
## Manifest for Non-Python Files
```
# MANIFEST.in - Include additional files in source distribution
include README.md
include LICENSE
include CHANGELOG.md
include pyproject.toml
recursive-include src/mypackage *.json
recursive-include src/mypackage *.yaml
recursive-include src/mypackage/templates *.html
recursive-include tests *.py
global-exclude __pycache__
global-exclude *.py[co]
```
## Development Installation
```bash
# Install in editable mode with dev dependencies
pip install -e ".[dev]"
# Or with all optional dependencies
pip install -e ".[all]"
# This allows you to edit code without reinstalling
```
## GitHub Actions for Publishing
```yaml
# .github/workflows/publish.yml
name: Publish to PyPI
on:
release:
types: [published]
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install build twine
- name: Build package
run: python -m build
- name: Check distribution
run: twine check dist/*
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*
```
## ❌ Anti-Patterns
```python
# ❌ Using setup.py instead of pyproject.toml
# setup.py is legacy, use pyproject.toml
# ❌ No version pinning in dependencies
dependencies = ["fastapi"] # Which version?
# ✅ Better: Pin compatible versions
dependencies = ["fastapi>=0.109.0,<1.0.0"]
# ❌ Flat package structure
mypackage.py # Single file, hard to scale
# ✅ Better: Proper package structure
src/mypackage/__init__.py
# ❌ No py.typed marker
# Users can't benefit from type hints
# ✅ Better: Include py.typed
src/mypackage/py.typed
# ❌ Not specifying python_requires
# Package might be installed on incompatible Python
# ✅ Better: Specify Python version
requires-python = ">=3.11"
# ❌ No optional dependencies
dependencies = ["pytest", "sphinx"] # Forces install!
# ✅ Better: Use optional-dependencies
[project.optional-dependencies]
dev = ["pytest"]
docs = ["sphinx"]
```
## Best Practices Checklist
- ✅ Use pyproject.toml for configuration
- ✅ Use src/ layout for packages
- ✅ Include py.typed for type hints
- ✅ Specify Python version requirement
- ✅ Pin dependency versions
- ✅ Use optional-dependencies for extras
- ✅ Include README, LICENSE, CHANGELOG
- ✅ Define entry points for CLI tools
- ✅ Configure tools in pyproject.toml
- ✅ Test package installation locally
- ✅ Use build and twine for publishing
- ✅ Automate publishing with CI/CD
## Auto-Apply
When creating Python packages:
1. Use pyproject.toml for all configuration
2. Use src/package_name/ layout
3. Include py.typed marker
4. Define optional-dependencies for dev/docs
5. Pin dependency versions
6. Include README and LICENSE
7. Define CLI entry points if needed
8. Configure testing and linting tools
## Related Skills
- `dependency-management` - For managing dependencies
- `type-safety` - For type hints
- `pytest-patterns` - For testing
- `git-workflow-standards` - For releases
- `docs-style` - For documentation