No more Python dependency pain!
| The win | What it means |
|---|---|
| 🧩 No dependency conflicts | Every package keeps the dependency versions it needs. |
| 🔀 Multiple versions together | Import two versions of the same package in one Python process. |
| ⚡ Install at runtime | Packages are downloaded when your code first needs them, then cached. |
| 🧹 No dependency files | No requirements.txt or dependency list in pyproject.toml is required. |
| 🐍 Normal Python imports | Select a version, then keep writing ordinary import package. |
| 🌱 Start with one line | No environment redesign or separate installation workflow. |
| 🚀 Production ready | Built-in CLI and Python APIs let you preinstall or vendor packages for predictable deployments. |
import depfix
with depfix.using("requests==2.31.0"):
import requests as requests_old
with depfix.using("requests==2.32.3"):
import requests as requests_new
assert requests_old is not requests_new
That is the idea: put the version next to the import and let Depfix handle the rest.
No requirements.txt needed. No dependency list in pyproject.toml. No virtual-environment juggling because two packages
want different versions of the same dependency. Depfix downloads packages when they are first used, keeps them cached, and
makes sure each package continues using the dependencies it was installed with.
Your imports become the source of truth. Dependency conflicts stop being a project-wide problem.
Your imports are the package manager
Install Depfix once:
python -m pip install depfix
Existing dependency files can populate the same shared store without touching the active environment:
depfix pip install requests==2.32.3 PyYAML==6.0.2
depfix pip install -r requirements.txt
This is Depfix installation, not an alias for pip or uv pip. The listed packages are resolved as one group, exact
artifacts are materialized in the Depfix store, and incompatible transitive versions can coexist. Requirement files may
include nested -r files, -c constraints, indexes, hash-pinned direct URLs, and local -e paths. Add --prefer-newest
or -U when newest-first selection matters more than compatible cache reuse.
Then choose whichever import style fits your code.
Use the installed store as an import fallback
Use patch_import() when packages are already in Depfix's shared store and you want unresolved ordinary imports to use
them without a declaration for each distribution:
import depfix
depfix.patch_import()
import requests
import yaml
The opt-in is process-local and never installs from the network. Python's builtins, standard library, project files,
active environment, and existing import hooks resolve first. Depfix consults exact recorded import-module metadata only
after normal resolution cannot find the requested root, then selects the newest compatible installed version with its
recorded dependency graph. It raises StoreImportError instead of guessing when the newest version has competing
artifacts or graphs. Compatible namespace contributors are co-selected only when they belong to one exact recorded graph;
unrelated distributions exposing the same root remain ambiguous. Explicit using() and default() selections still take
priority. When an exact manifest is configured and provides the root, its pinned graph takes priority over unrelated store
records. Call depfix.unpatch_import() to remove the fallback hook; already imported modules retain normal sys.modules
lifetime. Each subprocess must opt in separately.
Applications launched with depfix run application.py or depfix run -m application get this installed-store fallback
automatically; application code does not need to import Depfix or call patch_import() itself.
Set a default version
Use default() when the rest of the file or application should import selected versions normally. You can select one or
several packages together:
import depfix
depfix.default(
"requests==2.32.3",
"PyYAML==6.0.2",
)
import requests
import yaml
response = requests.get("https://raw.githubusercontent.com/pypa/pip/main/.pre-commit-config.yaml")
workflow = yaml.safe_load(response.text)
An existing requirements file can define the same coherent default group:
import depfix
depfix.default_requirements("requirements.txt")
Nested requirements and constraints resolve relative to their containing file. Blank lines, comments, continuations,
PEP 508 requirements, applicable environment markers, local/editable paths, direct URLs, VCS references, and primary or
extra index declarations use the same parser as depfix pip install. Unsupported pip directives fail with file and line
context; use a direct URL #sha256= fragment or a prepared Depfix manifest instead of pip --hash entries.
There is no separate install step. The first default() call prepares the requested packages, and later runs reuse the
cache. When dependency ranges overlap, Depfix prefers the newest compatible version already in that shared cache, so
packages selected together can reuse one copy. Pass prefer_newest=True to default(), using(), import_module(), or
load_package() when you explicitly want newest-first resolution instead.
Use a version temporarily
Use using() when one part of your program needs a specific version:
import depfix
with depfix.using("openai==0.7.0"):
import openai as openai_0_7
with depfix.using("openai==0.28.1"):
import openai as openai_0_28
The imported objects keep working after the block ends:
with depfix.using("requests==2.31.0"):
import requests as legacy_requests
response = legacy_requests.get("https://example.com")
For packages hosted on a dedicated index, select that primary index only for the request. For example, CPU-only PyTorch can be prepared without changing the index used by later or concurrent Depfix calls:
with depfix.using("torch", index_url="https://download.pytorch.org/whl/cpu"):
import torch
index_url= is request-scoped and does not inherit process-wide extra indexes. extra_index_url= is also available for
repositories that intentionally need multiple sources, but all configured indexes can provide any requested project;
use a dedicated primary index when possible to avoid dependency-confusion ambiguity.
using() also works as a function decorator:
import depfix
@depfix.using("requests==2.31.0")
def fetch_with_legacy_requests(url: str):
import requests
return requests.get(url)
The selected version is active every time the function runs. Async functions work too.
Import a package directly
Use import_module() when you want the module returned immediately:
import depfix
requests = depfix.import_module("requests==2.32.3")
This is especially convenient for dynamic code:
version = "2.32.3"
requests = depfix.import_module(f"requests=={version}")
Most packages expose one obvious import. If a package exposes several and you already know which ones you need, select
each with module=:
import depfix
setuptools = depfix.import_module(
"setuptools==75.0.0",
module="setuptools",
)
pkg_resources = depfix.import_module(
"setuptools==75.0.0",
module="pkg_resources",
)
Use load_package() when you want to inspect package metadata or discover its available module names before importing:
package = depfix.load_package("setuptools==75.0.0")
print(package.name, package.version)
print(package.module_names)
print(package.dependencies)
Dependency conflicts just work
Imagine two packages that cannot be installed together conventionally:
awscli==1.32.0 needs botocore==1.34.0
boto3==1.36.0 needs botocore>=1.36,<1.37
With Depfix, import both:
import depfix
with depfix.using("awscli==1.32.0", "boto3==1.36.0"):
import awscli.clidriver
import boto3
Each package receives the Botocore version it needs. You do not have to pin the shared dependency, split the application, or create another environment. See the runnable AWS CLI and Boto3 example.
More than PyPI
The same APIs accept version ranges and common Python package sources:
requests = depfix.import_module("requests>=2.31,<3")
requests_with_socks = depfix.import_module("pypi:requests[socks]~=2.32")
sdk = depfix.import_module("git:https://github.com/acme/sdk.git@v2.4.0")
local_package = depfix.import_module("file:../my-local-package")
helpers = depfix.import_module("file:./helpers.py")
module = depfix.import_module("url:https://packages.example/acme_sdk-2.4.0-py3-none-any.whl#sha256=<digest>")
Standard PEP 508 direct references work as well.
Start simple, lock it later
For local development, just run your Python file. Depfix installs packages into its shared unpacked store as the code reaches them, then removes the downloaded archives. It does not create project files.
When you want a repeatable deployment, Depfix can scan the same imports and prepare everything in advance:
depfix export . -o .depfix/imports.lock
depfix install .depfix/imports.lock --frozen
python application.py
This is optional. You can start with one import and add deployment controls only when you need them. Offline bundles, containers, and generated IDE aliases are also available.
The shared cache cleans itself
Depfix reuses one package store across projects, repositories, and working directories. It records when each exact package artifact was installed and when Depfix last imported it. Once per day, a lightweight background check removes packages that have gone unused for 30 days. The graph being prepared and packages held by active Depfix runtimes are always protected, so returning to an older project does not delete and immediately reinstall its own dependencies. Downloaded wheels and source archives are temporary inputs: successful preparation removes them, while later install and cleanup activity safely reclaims abandoned download parts. Cross-project reuse comes from the complete unpacked store.
Inspect installed packages or clean the store explicitly from the CLI:
depfix list
depfix list --view duplicates
depfix tree
depfix uninstall requests
depfix uninstall 'requests>=2.30,<3'
depfix cache cleanup --days 30
depfix cache resolutions
The package view includes size, installation/last-use dates, artifact identity, and why the package was installed. The
duplicate view ranks distributions with multiple retained artifacts; the tree view starts at each installed root and
indents its dependencies. Python exposes the same snapshot through depfix.inspect_cache(), alongside
depfix.list_cached_packages(), depfix.cleanup_cache(), and depfix.remove_cached_package().
depfix uninstall NAME removes every installed version of that distribution. Add an exact pin or any PEP 440 range to
select only matching versions; quote shell arguments containing <, >, !, or commas. Multiple specifiers are
deduplicated, --dry-run makes no store changes, and active preparation/runtime artifacts are reported as protected.
Uninstall never cascades into dependencies: only explicitly named distributions are selected, and automatic retention
later owns unused shared dependency cleanup. depfix cache remove remains the advanced compatibility interface for an
optional exact artifact hash.
Change the retention window or disable automatic cleanup centrally:
depfix.configure(
cache_retention_days=60,
cache_auto_cleanup=True,
cache_renewal_seconds=3600,
cache_deletion_grace_hours=24,
)
Good to know
- Importing
depfixalone does nothing expensive. Installation starts only when you call a loading function. - Automatic cache cleanup defaults to packages unused for 30 days and runs off the import path in a background sweep.
- Package preparation is shown on stderr, so you can see what is happening. Set
DEPFIX_LOG_LEVEL=WARNINGfor quiet mode. - Depfix supports pure-Python and native wheel packages on CPython 3.11–3.13. Automatic mode isolates pure dependency graphs and loads native graphs through guarded, conventional Python imports.
- Isolated versions keep separate class identities.
Keep library-owned objects within their version, or translate them through an agreed primitive or serialized boundary
before another version consumes them. Use
realm_of(),assert_same_realm(), orenforce_same_realm()to make known crossings fail immediately with producer and consumer versions. - A native package can own one compatible public import version per process. Reusing it is idempotent; requesting an incompatible second owner raises a clear error instead of silently returning the wrong version.
using()works as scoped syntax sugar for the first compatible native version. That version remains loaded as the process owner after the scope exits; use a worker when you need to switch or overlap native versions.- Unsafe package classifications and strict in-process native loading are denied by default. Trusted callers can opt in
per request with
allow_unsafe=Trueor process-wide withdepfix.configure(allow_unsafe=True). - Depfix isolates pure dependency versions; it is not a sandbox for untrusted code.
Documentation
Created by Agent Zero
Depfix is an open-source project by agent0ai, creator of Agent Zero, Space Agent, and DOX.
- Found a bug or compatibility gap? Open an issue.
- Want to contribute? Read CONTRIBUTING.md.
- Want to support agent0ai's work? Sponsor on GitHub.
License
Depfix is released under the MIT License.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file depfix-0.10.2.tar.gz.
File metadata
- Download URL: depfix-0.10.2.tar.gz
- Upload date:
- Size: 192.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
443604260efb3ad8594d880da2015a1a8ce1a2950453cdda22ca2866fa6d2805
|
|
| MD5 |
c0f5592750419d6c2abf0a176968d181
|
|
| BLAKE2b-256 |
5f8b2b9cb68dfeb7cb2353796679587114f05fde3ce0320cd2e5d1d3f0c76247
|
Provenance
The following attestation bundles were made for depfix-0.10.2.tar.gz:
Publisher:
publish-pypi.yml on agent0ai/depfix
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
depfix-0.10.2.tar.gz -
Subject digest:
443604260efb3ad8594d880da2015a1a8ce1a2950453cdda22ca2866fa6d2805 - Sigstore transparency entry: 2465847423
- Sigstore integration time:
-
Permalink:
agent0ai/depfix@5534ddf12ebb2379eb459edc6f02af06ae0037bc -
Branch / Tag:
refs/tags/v0.10.2 - Owner: https://github.com/agent0ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@5534ddf12ebb2379eb459edc6f02af06ae0037bc -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file depfix-0.10.2-py3-none-any.whl.
File metadata
- Download URL: depfix-0.10.2-py3-none-any.whl
- Upload date:
- Size: 139.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d23b5d98467e3cf9729d63a116280b3f72a04c54d57c9a836be59d0fb0a36f66
|
|
| MD5 |
fd9e01eb34ec3251b3e1dfd2268c74eb
|
|
| BLAKE2b-256 |
f91fea3d87d319262522fa58b92e234e4b9a0310435059a5d385e98cf1ea7a7b
|
Provenance
The following attestation bundles were made for depfix-0.10.2-py3-none-any.whl:
Publisher:
publish-pypi.yml on agent0ai/depfix
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
depfix-0.10.2-py3-none-any.whl -
Subject digest:
d23b5d98467e3cf9729d63a116280b3f72a04c54d57c9a836be59d0fb0a36f66 - Sigstore transparency entry: 2465847562
- Sigstore integration time:
-
Permalink:
agent0ai/depfix@5534ddf12ebb2379eb459edc6f02af06ae0037bc -
Branch / Tag:
refs/tags/v0.10.2 - Owner: https://github.com/agent0ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@5534ddf12ebb2379eb459edc6f02af06ae0037bc -
Trigger Event:
workflow_dispatch
-
Statement type: