Chapter 15 of 24 | Python
Multi-file programs, packages, and libraries
A project with several modules needs a predictable structure, a clear entry point, and repeatable dependency setup. A package lets users import reusable functionality while a command-line entry point provides an executable interface.
weather_app/
pyproject.toml
README.md
src/
weather_app/
__init__.py
api.py
cli.py
tests/
test_api.py
The src layout helps ensure tests import the installed project instead of accidentally importing files from the repository root. Small projects can use a simpler layout, but the import behavior should remain explicit.
Virtual environments and dependencies
A virtual environment isolates an interpreter environment for a project. Create one with python -m venv .venv, activate it according to your shell, and install only the dependencies the project needs. A virtual environment is generally generated locally and should not be committed; record dependency metadata and reproducible setup instructions instead.
python -m venv .venv
python -m pip install --upgrade pip
python -m pip install -e .
Use python -m pip so the package installer is associated with the selected interpreter. Dependency constraints, lock files, and hashes serve different goals; choose a policy that fits the project's reproducibility and update needs.
Project metadata and entry points
[project]
name = "weather-app"
version = "0.1.0"
requires-python = ">=3.10"
[project.scripts]
weather = "weather_app.cli:main"
Modern Python packaging uses pyproject.toml to declare build-system and project metadata. The exact build backend and supported fields depend on the packaging tools. A console entry point calls a function in your package so users do not need to know the internal file layout.
Library interfaces and versioning
Keep public APIs small and document behavior, error conditions, and compatibility. Semantic versioning is a convention, not a guarantee: define what counts as a breaking change and test the promise. Avoid exposing internal modules as stable interfaces unless you intend to support them.
Third-party dependencies are executable code. Review maintenance, license, security history, transitive dependencies, and supported Python versions. Pin versions for reproducible applications where appropriate and update them regularly with tests.
Advanced packaging concerns
Editable installs are convenient during development but are not the same as testing a built wheel or source distribution. Build release artifacts, install them into a clean environment, and verify that package data and entry points are present. Reproducible releases require controlling build tools, dependency inputs, and generated artifacts.
Beginner-to-master progression
- Beginner: split code into modules and create a virtual environment.
- Practitioner: package an application, test imports from a clean environment, and manage dependencies deliberately.
- Advanced: reason about build backends, wheel compatibility tags, supply-chain risk, reproducible artifacts, and API deprecation strategy.
Exercises
- Turn a script into a package with a
main()function and console entry point. - Install the project in a fresh virtual environment and run its tests.
- Build a wheel and verify it works after installation outside the source directory.
- Write a dependency update policy that balances reproducibility and security maintenance.
Build systems and artifact types
A build backend turns project sources into distributable artifacts. A wheel is an installable archive; a source distribution contains files used to build a wheel. Test both if the project publishes them. Package data, native extensions, and generated files can behave differently in a built artifact than in an editable source checkout.
Version constraints communicate compatibility but do not freeze an environment. Applications commonly use a lock file to reproduce a tested dependency graph. Libraries usually declare a range so they can coexist with an application's other dependencies. Keep the distinction clear and regenerate lock files through the chosen tool rather than editing opaque hashes by hand.
Namespace, package, and distribution names
The import package name, project distribution name, and repository name need not be identical. Distribution names are used by package installers; import names appear in Python code. A project may install a top-level package with a different spelling. Avoid names that collide with the standard library or popular packages, and test imports in a clean environment.
Supply-chain and release security
Dependency metadata is part of the software supply chain. Review package ownership and release history, minimize unnecessary transitive dependencies, and pin or verify artifacts according to the deployment risk. Build credentials belong in secret storage, not in project files or logs. For critical releases, consider artifact provenance and signed release processes.
Releasing a library also requires a deprecation policy. Warn before removing public behavior, document migration steps, and test supported Python versions. A version number alone does not make compatibility promises clear; state which APIs are stable and which are experimental.
Mastery checkpoint
- Build a wheel and source distribution, install each in a clean environment, and verify included package data.
- Compare dependency ranges for a library with a lock file for an application.
- Write a release checklist covering compatibility, artifact testing, provenance, and rollback.
- Inspect a dependency tree and identify which direct dependency introduces each transitive package.