π§© Build System IntegrationΒΆ
Tip
See example/ for working examples of using unidep with different build systems.
unidep seamlessly integrates with popular Python build systems to simplify dependency management in your projects.
Local Dependencies in MonoreposΒΆ
Local dependencies are essential for monorepos and multi-package projects, allowing you to:
Share code between packages during development
Maintain separate releases for each package
Test changes across multiple packages simultaneously
However, when building wheels for distribution, local paths create non-portable packages that only work on the original system.
PyPI Alternatives for Local DependenciesΒΆ
UniDep solves this problem by letting you specify both local paths (for development) and PyPI packages (for distribution):
# requirements.yaml
dependencies:
- numpy
- pandas
local_dependencies:
# Standard string format for local dependencies
- ../shared-lib
# Dictionary format with optional PyPI alternative for build-time
- local: ../auth-lib
pypi: company-auth-lib>=1.0
- local: ../utils
pypi: company-utils~=2.0
use: pypi # see [Overriding Nested Vendor Copies](build-system-integration.md#overriding-nested-vendor-copies-with-use)
Or in pyproject.toml:
[tool.unidep]
dependencies = ["numpy", "pandas"]
local_dependencies = [
# Standard string format for local dependencies
"../shared-lib",
# Dictionary format with optional PyPI alternative for build-time
{local = "../auth-lib", pypi = "company-auth-lib>=1.0"},
{local = "../utils", pypi = "company-utils~=2.0", use = "pypi"},
]
How it works:
During development (e.g.,
unidep installorpip install -e .): Uses local paths when they existWhen building wheels: PyPI alternatives (if specified) are used to create portable packages
The standard string format continues to work as always for local dependencies
Tip
PyPI alternatives ensure your wheels are portable and can be installed anywhere, not just on the build system. Use the use field (see Overriding Nested Vendor Copies) to control whether UniDep installs the local path, forces PyPI, or skips the entry entirely.
Overriding Nested Vendor Copies with useΒΆ
The Problem: When vendoring dependencies as git submodules, you often encounter conflicts where a submodule bundles its own copy of a dependency you also use, but at a different version.
The Solution: Use use: pypi to force your PyPI package instead of the vendored copy, with automatic propagation to all nested references.
Example: Override fooβs bundled bar with your PyPI buildΒΆ
Your project vendors foo as a submodule. Foo bundles bar@1.0, but you need bar@2.0:
project/
third_party/
foo/ # git submodule you don't control
third_party/
bar/ # foo bundles bar@1.0
Solution with use: pypi:
local_dependencies:
- ./third_party/foo # Keep foo editable for development
# Override: force YOUR PyPI build of bar
- local: ./third_party/foo/third_party/bar
pypi: my-bar>=2.0
use: pypi # Install from PyPI, skip local path
What happens:
foostays local (editable for development)my-bar>=2.0gets installed from PyPI (not fooβs bundled v1.0)Propagates: Every nested reference to
baruses your PyPI packageWorks with
unidep install,unidep conda-lock, all CLI commands
This is the key difference from just using pypi: as a build-time fallback - use: pypi forces the PyPI package during development while keeping other local dependencies editable.
All use valuesΒΆ
Tell UniDep what to use for each entry in local_dependencies:
|
When to use |
Installs from |
Propagates override? |
|---|---|---|---|
|
Normal local development |
Local path |
- |
|
Force PyPI even when local exists |
|
Yes |
|
Ignore this path entirely |
Nothing |
Yes |
Common patterns:
local_dependencies:
# Standard local development (default)
- ../shared-lib
# Force PyPI to override nested vendor copy
- local: ./vendor/foo/nested/bar
pypi: my-bar>=2.0
use: pypi
# Skip a path without installing anything
- local: ./deprecated-module
use: skip
Note
Precedence: The use flag on the entry itself always wins. When UniDep encounters the same path in nested local_dependencies, it uses your override. Setting UNIDEP_SKIP_LOCAL_DEPS=1 forces any effective use: local to behave like pypi (if specified) or skip, but does not override explicit use: pypi or use: skip.
Caution
If use: pypi is set but no pypi: requirement is provided, UniDep exits with a clear error so you can supply the missing spec.
Build System BehaviorΒΆ
Important differences between build backends:
Setuptools: Builds wheels containing
file://URLs with absolute paths. These wheels only work on the original system.Hatchling: Rejects
file://URLs by default, preventing non-portable wheels.
To ensure portable wheels, you can use the UNIDEP_SKIP_LOCAL_DEPS environment variable:
# Force use of PyPI alternatives even when local paths exist
UNIDEP_SKIP_LOCAL_DEPS=1 python -m build
# For hatch projects
UNIDEP_SKIP_LOCAL_DEPS=1 hatch build
# For uv build
UNIDEP_SKIP_LOCAL_DEPS=1 uv build
Note
When UNIDEP_SKIP_LOCAL_DEPS=1 is set:
Any effective
use: localbehaves asuse: pypi(if apypispec exists) oruse: skipExplicit
use: pypianduse: skipremain unchangedDependencies from local packages are still included (from their
requirements.yaml/pyproject.toml)
Example packagesΒΆ
Explore these installable example packages to understand how unidep integrates with different build tools and configurations:
Project |
Build Tool |
|
|
|
|---|---|---|---|---|
|
β |
β |
β |
|
|
β |
β |
β |
|
|
β |
β |
β |
|
|
β |
β |
β |
|
|
β |
β |
β |
Setuptools IntegrationΒΆ
For projects using setuptools, configure unidep in pyproject.toml and either specify dependencies in a requirements.yaml file or include them in pyproject.toml too.
Using
pyproject.tomlonly: The[project.dependencies]field inpyproject.tomlgets automatically populated fromrequirements.yamlor from the[tool.unidep]section inpyproject.toml.Using
setup.py: Theinstall_requiresfield insetup.pyautomatically reflects dependencies specified inrequirements.yamlorpyproject.toml.Wheel metadata: UniDep also writes
unidep.jsoninto wheel metadata (.dist-info) sounidep install "pkg==x.y"can install Conda + pip dependencies directly from the published artifact.
Example pyproject.toml Configuration:
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools", "unidep"]
[project]
dynamic = ["dependencies"]
Hatchling IntegrationΒΆ
For projects managed with Hatch, unidep can be configured in pyproject.toml to automatically process the dependencies from requirements.yaml or from the [tool.unidep] section in pyproject.toml.
For wheel builds, the UniDep build hook stores unidep.json in .dist-info/extra_metadata.
Example Configuration for Hatch:
[build-system]
requires = ["hatchling", "unidep"]
build-backend = "hatchling.build"
[project]
dynamic = ["dependencies"]
# Additional project configurations
[tool.hatch.metadata.hooks.unidep]
# Enable the unidep plugin
[tool.hatch.build.hooks.unidep]
# Embed unidep.json in wheel metadata
[tool.hatch.metadata]
allow-direct-references = true
[tool.unidep]
# Your dependencies configuration