Python Project Development
Comprehensive guide for building production-ready Python CLI tools and distributable packages.
When to Use This Skill
- Building command-line tools with argparse or click
- Creating Python libraries for distribution
- Publishing packages to PyPI
- Setting up modern pyproject.toml configuration
- Creating installable packages with dependencies
- Building wheels and source distributions
Quick Start: CLI Tool
#!/usr/bin/env python3
"""Brief description of what this script does."""
import sys
from pathlib import Path
from dataclasses import dataclass
from typing import Final
@dataclass(frozen=True, slots=True)
class Config:
"""Immutable configuration."""
input_dir: Path
max_size: int = 1000
verbose: bool = False
def main() -> int:
"""Entry point. Returns exit code."""
try:
# Parse args, validate input, process
return 0
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
Quick Start: Package Structure
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── cli.py
│ └── core.py
└── tests/
└── test_core.py
Minimal pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "0.1.0"
description = "A short description"
authors = [{name = "Your Name", email = "you@example.com"}]
readme = "README.md"
requires-python = ">=3.8"
dependencies = ["click>=8.0"]
[project.optional-dependencies]
dev = ["pytest>=7.0", "ruff>=0.1"]
[project.scripts]
my-cli = "my_package.cli:main"
[tool.setuptools.packages.find]
where = ["src"]
CLI Standards
- Types: All functions typed (
-> ReturnType),dataclass(slots=True)for data - Performance: O(n) algorithms, frozenset for lookups, generators for large data
- Stdlib-first: Zero external deps unless justified
- Exit codes: 0=success, 1=error, 2+=specific failures
- Error handling: Catch specific exceptions, fail fast, clear messages
CLI with Click
# src/my_package/cli.py
import click
@click.group()
@click.version_option()
def cli():
"""My awesome CLI tool."""
pass
@cli.command()
@click.argument("name")
@click.option("--greeting", default="Hello", help="Greeting to use")
def greet(name: str, greeting: str):
"""Greet someone."""
click.echo(f"{greeting}, {name}!")
def main():
cli()
if __name__ == "__main__":
main()
CLI with argparse
import argparse
import sys
def main():
parser = argparse.ArgumentParser(description="My tool", prog="my-tool")
parser.add_argument("--version", action="version", version="%(prog)s 1.0.0")
subparsers = parser.add_subparsers(dest="command", help="Commands")
process_parser = subparsers.add_parser("process", help="Process data")
process_parser.add_argument("input_file", help="Input file")
process_parser.add_argument("-o", "--output", default="output.txt")
args = parser.parse_args()
if args.command == "process":
return process_data(args.input_file, args.output)
parser.print_help()
return 1
if __name__ == "__main__":
sys.exit(main())
Full pyproject.toml Template
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "1.0.0"
description = "Package description"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [{name = "Name", email = "email@example.com"}]
keywords = ["example", "package"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"click>=8.0.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = ["pytest>=7.0", "ruff>=0.1", "mypy>=1.0"]
[project.urls]
Homepage = "https://github.com/user/my-package"
Documentation = "https://my-package.readthedocs.io"
Repository = "https://github.com/user/my-package"
[project.scripts]
my-cli = "my_package.cli:main"
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
my_package = ["py.typed", "data/*.json"]
[tool.ruff]
line-length = 100
target-version = "py38"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
[tool.mypy]
python_version = "3.8"
warn_return_any = true
disallow_untyped_defs = true
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --cov=my_package"
Building and Publishing
# Install build tools
uv pip install build twine
# Build distribution
python -m build
# Check distribution
twine check dist/*
# Test on TestPyPI first
twine upload --repository testpypi dist/*
# Publish to PyPI
twine upload dist/*
Editable Install for Development
# Install in development mode
uv pip install -e .
# With optional dependencies
uv pip install -e ".[dev]"
Dynamic Versioning
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
dynamic = ["version"]
[tool.setuptools_scm]
write_to = "src/my_package/_version.py"
Including Data Files
[tool.setuptools.package-data]
my_package = ["data/*.json", "templates/*.html", "py.typed"]
# Accessing data files
from importlib.resources import files
data = files("my_package").joinpath("data/config.json").read_text()
Security Checklist
- [ ] No hardcoded secrets/paths
- [ ] Validate all user input (paths, patterns)
- [ ] Use
Path.resolve()to prevent traversal - [ ] Catch
PermissionError,FileNotFoundError - [ ] Timeout on subprocess calls
- [ ] No
eval(),exec(),__import__()
Publishing Checklist
- [ ] Code is tested (pytest passing)
- [ ] Documentation complete (README, docstrings)
- [ ] Version number updated
- [ ] CHANGELOG.md updated
- [ ] License file included
- [ ] pyproject.toml complete
- [ ] Package builds without errors
- [ ] Installation tested in clean environment
- [ ] Tested on TestPyPI first
- [ ] Git tag created for release
Reusable Components
Use battle-tested components from scripts/:
cli_template.py- Production CLI scaffold with argparselog_component.py- ANSI colored loggingsubprocess_helpers.py- Safe subprocess wrappers with retry/timeoutcommon_utils.py- has(), find_files(), safe file ops
See references/patterns.md for common CLI patterns and references/stdlib_perf.md for performance tips.
Resources
- Python Packaging Guide: https://packaging.python.org/
- PyPI: https://pypi.org/
- setuptools: https://setuptools.pypa.io/
- Click: https://click.palletsprojects.com/
微信扫一扫