📝 requirements.yaml and pyproject.toml structure¶
unidep allows either using a
requirements.yamlfile with a specific format (similar but not the same as a Condaenvironment.yamlfile) orpyproject.tomlfile with a[tool.unidep]section.
Both files contain the following keys:
name (Optional): For documentation, not used in the output.
channels: List of conda channels for packages, such as
conda-forge.dependencies: Mix of Conda and Pip packages.
local_dependencies (Optional): List of paths to other
requirements.yamlorpyproject.tomlfiles to include.optional_dependencies (Optional): Dictionary with lists of optional dependencies.
platforms (Optional): List of platforms that are supported (used in
conda-lock).
Whether you use a requirements.yaml or pyproject.toml file, the same information can be specified in either.
Choose the format that works best for your project.
Example¶
Example requirements.yaml¶
Example of a requirements.yaml file:
name: example_environment
channels:
- conda-forge
dependencies:
- numpy # same name on conda and pip
- conda: python-graphviz # When names differ between Conda and Pip
pip: graphviz
- pip: slurm-usage >=1.1.0,<2 # pip-only
- conda: mumps # conda-only
# Use platform selectors
- conda: cuda-toolkit =11.8 # [linux64]
local_dependencies:
- ../other-project-using-unidep # include other projects that use unidep
- ../common-requirements.yaml # include other requirements.yaml files
- ../project-not-managed-by-unidep # 🚨 Skips its dependencies!
optional_dependencies:
test:
- pytest
full:
- ../other-local-dep[test] # include its optional 'test' dependencies
platforms: # (Optional) specify platforms that are supported (used in conda-lock)
- linux-64
- osx-arm64
Important
unidep can process this during pip install and create a Conda installable environment.yaml or conda-lock.yml file, and more!
Note
For a more in-depth example containing multiple installable projects, see the example directory.
Example pyproject.toml¶
Alternatively, one can fully configure the dependencies in the pyproject.toml file in the [tool.unidep] section:
[tool.unidep]
channels = ["conda-forge"]
dependencies = [
"numpy", # same name on conda and pip
{ conda = "python-graphviz", pip = "graphviz" }, # When names differ between Conda and Pip
{ pip = "slurm-usage >=1.1.0,<2" }, # pip-only
{ conda = "mumps" }, # conda-only
{ conda = "cuda-toolkit =11.8:linux64" } # Use platform selectors by appending `:linux64`
]
local_dependencies = [
"../other-project-using-unidep", # include other projects that use unidep
"../common-requirements.yaml", # include other requirements.yaml files
"../project-not-managed-by-unidep" # 🚨 Skips its dependencies!
]
optional_dependencies = {
test = ["pytest"],
full = ["../other-local-dep[test]"] # include its optional 'test' dependencies
}
platforms = [ # (Optional) specify platforms that are supported (used in conda-lock)
"linux-64",
"osx-arm64"
]
This data structure is identical to the requirements.yaml format, with the exception of the name field and the platform selectors.
In the requirements.yaml file, one can use e.g., # [linux64], which in the pyproject.toml file is :linux64 at the end of the package name.
See Build System Integration for more information on how to set up unidep with different build systems (Setuptools or Hatchling).
Important
In these docs, we often mention the requirements.yaml format for simplicity, but the same information can be specified in pyproject.toml as well.
Everything that is possible in requirements.yaml is also possible in pyproject.toml!
Key Points¶
Standard names (e.g.,
- numpy) are assumed to be the same for Conda and Pip.Use a dictionary with
conda: <package>andpip: <package>to specify different names across platforms.Use
pip:to specify packages that are only available through Pip.Use
conda:to specify packages that are only available through Conda.Use
# [selector](YAML only) orpackage:selectorto specify platform-specific dependencies.Use
local_dependencies:to include otherrequirements.yamlorpyproject.tomlfiles and merge them into one. Also allows projects that are not managed byunidepto be included, but be aware that this skips their dependencies! Can specify PyPI alternatives for monorepo setups (see PyPI Alternatives for Local Dependencies).Use
optional_dependencies:to specify optional dependencies. Can be installed likeunidep install ".[test]"orpip install ".[test]".Use
platforms:to specify the platforms that are supported. If omitted, all platforms are assumed to be supported.
We use the YAML notation here, but the same information can be specified in
pyproject.tomlas well.
Supported Version Pinnings¶
UniDep supports a range of version pinning operators (the same as Conda):
Standard Version Constraints: Specify exact versions or ranges with standard operators like
=,>,<,>=,<=.Example:
=1.0.0,>1.0.0, <2.0.0.
Version Exclusions: Exclude specific versions using
!=.Example:
!=1.5.0.
Redundant Pinning Resolution: Automatically resolves redundant version specifications.
Example:
>1.0.0, >0.5.0simplifies to>1.0.0.
Contradictory Version Detection: Errors are raised for contradictory pinnings to maintain dependency integrity. See the Conflict Resolution section for more information.
Example: Specifying
>2.0.0, <1.5.0triggers aVersionConflictError.
Invalid Pinning Detection: Detects and raises errors for unrecognized or improperly formatted version specifications.
Conda Build Pinning: UniDep also supports Conda’s build pinning, allowing you to specify builds in your pinning patterns.
Example: Conda supports pinning builds like
qsimcirq * cuda*orvtk * *egl*.Limitation: While UniDep allows such build pinning, it requires that there be a single pin per package. UniDep cannot resolve conflicts where multiple build pinnings are specified for the same package.
Example: UniDep can handle
qsimcirq * cuda*, but it cannot resolve a scenario with bothqsimcirq * cuda*andqsimcirq * cpu*.
Other Special Cases: In addition to Conda build pins, UniDep supports all special pinning formats, such as VCS (Version Control System) URLs or local file paths. This includes formats like
package @ git+https://git/repo/hereorpackage @ file:///path/to/package. However, UniDep has a limitation: it can handle only one special pin per package. These special pins can be combined with an unpinned version specification, but not with multiple special pin formats for the same package.Example: UniDep can manage dependencies specified as
package @ git+https://git/repo/hereandpackagein the samerequirements.yaml. However, it cannot resolve scenarios where bothpackage @ git+https://git/repo/hereandpackage @ file:///path/to/packageare specified for the same package.
Caution
Pinning Validation and Combination: UniDep actively validates and/or combines pinnings only when multiple different pinnings are specified for the same package.
This means if your requirements.yaml files include multiple pinnings for a single package, UniDep will attempt to resolve them into a single, coherent specification.
However, if the pinnings are contradictory or incompatible, UniDep will raise an error to alert you of the conflict.
Conflict Resolution¶
unidep features a conflict resolution mechanism to manage version conflicts and platform-specific dependencies in requirements.yaml or pyproject.toml files.
How It Works¶
Version Pinning Priority:
unidepgives priority to version-pinned packages when the same package is specified multiple times. For instance, if bothfooandfoo <1are listed,foo <1is selected due to its specific version pin.Platform-Specific Version Pinning:
unidepresolves platform-specific dependency conflicts by preferring the version with the narrowest platform scope. For instance, givenfoo <3 # [linux64]andfoo >1, it installsfoo >1,<3exclusively on Linux-64 andfoo >1on all other platforms.Intractable Conflicts: When conflicts are irreconcilable (e.g.,
foo >1vs.foo <1),unidepraises an exception.
Platform Selectors¶
This tool supports a range of platform selectors that allow for specific handling of dependencies based on the user’s operating system and architecture. This feature is particularly useful for managing conditional dependencies in diverse environments.
Supported Selectors¶
The following selectors are supported:
linux: For all Linux-based systems.linux64: Specifically for 64-bit Linux systems.aarch64: For Linux systems on ARM64 architectures.ppc64le: For Linux on PowerPC 64-bit Little Endian architectures.osx: For all macOS systems.osx64: Specifically for 64-bit macOS systems.arm64: For macOS systems on ARM64 architectures (Apple Silicon).macos: An alternative toosxfor macOS systems.unix: A general selector for all UNIX-like systems (includes Linux and macOS).win: For all Windows systems.win64: Specifically for 64-bit Windows systems.
Usage¶
Selectors are used in requirements.yaml files to conditionally include dependencies based on the platform:
dependencies:
- some-package >=1 # [unix]
- another-package # [win]
- special-package # [osx64]
- pip: cirq # [macos win]
conda: cirq # [linux]
Or when using pyproject.toml instead of requirements.yaml:
[tool.unidep]
dependencies = [
"some-package >=1:unix",
"another-package:win",
"special-package:osx64",
{ pip = "cirq:macos win", conda = "cirq:linux" },
]
In this example:
some-packageis included only in UNIX-like environments (Linux and macOS).another-packageis specific to Windows.special-packageis included only for 64-bit macOS systems.cirqis managed bypipon macOS and Windows, and bycondaon Linux. This demonstrates how you can specify different package managers for the same package based on the platform.
Note that the package-name:unix syntax can also be used in the requirements.yaml file, but the package-name # [unix] syntax is not supported in pyproject.toml.
Implementation¶
unidep parses these selectors and filters dependencies according to the platform where it’s being installed.
It is also used for creating environment and lock files that are portable across different platforms, ensuring that each environment has the appropriate dependencies installed.
[project.dependencies] in pyproject.toml handling¶
The project_dependency_handling option in [tool.unidep] (in pyproject.toml) controls how dependencies listed in the standard [project.dependencies] section of pyproject.toml are handled when processed by unidep.
Modes:
ignore(default): Dependencies in[project.dependencies]are ignored byunidep.same-name: Dependencies in[project.dependencies]are treated as dependencies with the same name for both Conda and Pip. They will be added to thedependencieslist in[tool.unidep]under the assumption that the package name is the same for both package managers.pip-only: Dependencies in[project.dependencies]are treated as pip-only dependencies. They will be added to thedependencieslist in[tool.unidep]under thepipkey.
Example pyproject.toml:
[build-system]
requires = ["hatchling", "unidep"]
build-backend = "hatchling.build"
[project]
name = "my-project"
version = "0.1.0"
dependencies = [ # These will be handled according to the `project_dependency_handling` option
"requests",
"pandas",
]
[tool.unidep]
project_dependency_handling = "same-name" # Or "pip-only", "ignore"
dependencies = [
{conda = "python-graphviz", pip = "graphivz"},
]
Notes:
The
project_dependency_handlingoption only affects how dependencies from[project.dependencies]are processed. Dependencies directly listed under[tool.unidep.dependencies]are handled as before.This feature is helpful for projects that are already using the standard
[project.dependencies]field and want to integrateunidepwithout duplicating their dependency list.The
project_dependency_handlingfeature is only available when usingpyproject.tomlfiles. It is not supported inrequirements.yamlfiles.