🧩 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

Raw local paths would create non-portable packages that only work on the original system, so UniDep treats them as development-only unless you provide a publishable pypi: fallback.

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 generating build metadata (setuptools or hatchling): Local paths are never published as file:// requirements

  • If pypi: is specified: The PyPI requirement is published instead

  • If no pypi: is specified: The local entry is omitted from build metadata

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

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

When UniDep generates build metadata for Setuptools or Hatchling, it always emits portable requirements:

  • Local path entries are never written as file:// dependencies

  • If a local dependency has a pypi: fallback, that requirement is published

  • If a local dependency has no pypi: fallback, it is omitted from build metadata

  • Optional sections that become empty after dropping local-only entries are still preserved as empty extras

The same publish-or-omit rule applies whether the local path is declared in local_dependencies or nested inside optional_dependencies.

This keeps development workflows local-first while ensuring published metadata does not depend on the build machine’s filesystem.

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.

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.

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.metadata]
allow-direct-references = true

[tool.unidep]
# Your dependencies configuration