🧩 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 install or pip install -e .): Uses local paths when they exist

  • When 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:

  1. foo stays local (editable for development)

  2. my-bar>=2.0 gets installed from PyPI (not foo’s bundled v1.0)

  3. Propagates: Every nested reference to bar uses your PyPI package

  4. Works 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:

use value

When to use

Installs from

Propagates override?

local (default)

Normal local development

Local path

-

pypi

Force PyPI even when local exists

pypi: spec

Yes

skip

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: local behaves as use: pypi (if a pypi spec exists) or use: skip

  • Explicit use: pypi and use: skip remain unchanged

  • Dependencies 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

pyproject.toml

requirements.yaml

setup.py

setup_py_project

setuptools

βœ…

βœ…

βœ…

setuptools_project

setuptools

βœ…

βœ…

❌

pyproject_toml_project

setuptools

βœ…

❌

❌

hatch_project

hatch

βœ…

βœ…

❌

hatch2_project

hatch

βœ…

❌

❌

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.toml only: The [project.dependencies] field in pyproject.toml gets automatically populated from requirements.yaml or from the [tool.unidep] section in pyproject.toml.

  • Using setup.py: The install_requires field in setup.py automatically reflects dependencies specified in requirements.yaml or pyproject.toml.

  • Wheel metadata: UniDep also writes unidep.json into wheel metadata (.dist-info) so unidep 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