Packaging & Build Systems¶
A build system transforms high-level source code into machine-executable binary artifacts. These artifacts can then be indexed and distributed as modular components for larger projects. In the case of Python, these artifacts are called "wheels".
There are many benefits in this approach to software delivery, however, it is a broad topic and the above is not the complete truth ("source" distributions are commonly available).
Integration with Private Package Indices¶
We can point uv to a private index through relevant config blocks in pyproject.toml:
[[tool.uv.index]]
name = "some-alias"
url = "https://${INDEX_HOSTNAME}/${INDEX_PYPI_SIMPLE_PATH}"
publish-url = "https://${INDEX_HOSTNAME}/${INDEX_PYPI_PATH}"
explicit = true
Dependency Groups¶
Dependencies can be separated by "group" i.e. include documentation generators like zensical under a docs group and test frameworks such as pytest under a test group:
[project]
name = "example"
version = "0.1.0"
requires-python = ">=3.11"
[tool.uv]
default-groups = ["dev", "docs", "test"]
[dependency-groups]
dev = [
"twine >= 6.2.0, < 7",
]
docs = [
"markdown-callouts >= 0.4.0, < 2",
"markdown-exec[ansi] >= 1.12.1, < 2",
"mkdocstrings[python] >= 0.30.1, < 2",
"zensical >= 0.0.30, < 2",
]
test = [
"coverage >= 7.3.3, < 8",
"hypothesis >= 6.138.6, < 7",
"mutmut >= 3.2.3, < 4",
"pytest >= 9.0.2, < 10",
"pytest-asyncio >= 0.26.0, < 2",
"pytest-env >= 1.1.5, < 2",
"pytest-html >= 4.1.1, < 5",
"pytest-randomly >= 3.16, < 4",
"tox >= 4.25, < 5",
"tox-uv >= 1.33.4, < 2",
]
Tip
Modern build systems efficiently resolve the "graph" of package dependencies helping to prevent "dependency hell".
Test Environments¶
You can control pytest and its test environment through relevant config blocks in pyproject.toml:
[tool.pytest.ini_options]
log_cli = true
log_cli_level = "INFO"
env = [
'ENV_VAR_1=mock-value-1',
'ENV_VAR_2=mock-value-2',
# ...
'ENV_VAR_N=mock-value-n',
]
[tool.coverage.run]
source = ["src"]
branch = true
omit = ["tests/**/*"]
[tool.coverage.report]
omit = ["tests/**/*"]
Idempotence¶
Each package build is made reproducible with any dependencies being "locked" to specific versions in a "lockfile".
The "lockfile" can then be used to rebuild a stable virtual environment with the exact same dependency versions.
Below is an example of a uv.lock file (the "lock file" format used by uv):
# This file is automatically @generated by `uv` and should not be changed by hand.
version = 1
requires-python = ">=3.12"
resolution-markers = [
"python_full_version < '3.13'",
"python_full_version >= '3.13'",
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
]
[[package]]
name = "anyio"
version = "4.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/e3/c4c8d473d6780ef1853d630d581f70d655b4f8d7553c6997958c283039a2/anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94", size = 163930 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/a2/10639a79341f6c019dedc95bd48a4928eed9f1d1197f4c04f546fc7ae0ff/anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7", size = 86780 },
]
# ...
The lockfile is automatically updated upon any change to package dependencies.
Implications for CI/CD¶
Tip
The use of a modern "build system" such as uv underpins CI/CD.
This is because the "CI" pipeline can be reduced to a sequence of commands against the build system.
| Stage | Command |
|---|---|
| Format | uv run black |
| Lint | uv run ruff |
| Test | uv run pytest |
| Coverage (Reporting) | uv run coverage run -m pytest |
| Coverage (HTML) | uv run coverage html |
| Build | uv build |
| Publish | uv publish |
Alternative Build Systems¶
This template uses the uv build system.
As of writing, uv is widely regarded to be state of the art for Python.