Why use native uv commands instead of uv pip
uv pip install is the fastest way to start using uv. Replace pip with uv pip in your existing commands, and everything runs 10-100x faster with zero other changes. But uv pip deliberately mimics pip’s behavior, which means it also inherits pip’s limitations. uv’s native project commands offer a different set of guarantees.
Two interfaces, different models
uv ships two distinct interfaces:
| pip-compatible | Native project | |
|---|---|---|
| Install a package | uv pip install requests | uv add requests |
| Lock dependencies | uv pip compile requirements.in | uv lock |
| Sync environment | uv pip sync requirements.txt | uv sync |
| Run code | source .venv/bin/activate && python app.py | uv run python app.py |
These are not interchangeable commands that produce the same result. The pip-compatible interface operates on environments the same way pip does: it installs packages into whatever virtual environment is active and tracks nothing beyond that environment’s site-packages. The native interface manages dependencies through pyproject.toml and a lockfile, treating the virtual environment as a derived artifact that can be recreated at any time.
What native uv provides that uv pip cannot
Cross-platform lockfile
uv pip install resolves dependencies for the current platform and Python version, then installs them. Run the same command on a different OS or Python version and you may get different package versions.
uv lock resolves dependencies across platforms and records the result in uv.lock. The lockfile captures platform-specific forks (a package might resolve to one version on Linux and another on Windows), so when a collaborator runs uv sync, they get the correct versions for their platform without re-resolving. The lockfile is the single source of truth for what should be installed.
# Resolve once
uv lock
# Everyone gets the same result
uv syncSee What is a lock file? for more on why this matters.
Declarative dependency tracking
uv pip install requests puts requests (and its transitive dependencies) into the virtual environment. There is no record of which packages were explicitly requested and which arrived as dependencies of dependencies. If the virtual environment is deleted, the knowledge of what was installed is gone.
uv add requests writes requests to pyproject.toml, updates uv.lock, and installs the package. The intent is captured in version control. Deleting .venv costs nothing because uv sync rebuilds it from the lockfile.
# Intent is recorded in pyproject.toml, resolved in uv.lock
uv add requests
uv add flask
# Environment is disposable — rebuild it anytime
rm -rf .venv
uv syncAutomatic virtual environment management
With uv pip, you create and activate virtual environments yourself:
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt
python app.pyWith native uv, the virtual environment is managed for you:
uv run python app.pyuv run ensures the virtual environment exists, matches the lockfile, and has the correct Python version before executing the command. No activation step. No stale environment from yesterday’s dependency changes.
Integrated dependency groups
uv add --dev pytest adds pytest as a development dependency, separate from production dependencies. Production deploys can exclude dev dependencies with uv sync --no-dev. Adding, removing, and syncing dependency groups is a single workflow: edit pyproject.toml (or use uv add --group), lock, sync.
While uv pip compile and uv pip sync do support a --group flag, the pip-compatible interface requires you to manage the group definitions in pyproject.toml yourself and run separate compile/sync steps. The native interface handles groups as a first-class part of the add/lock/sync cycle.
Reliable CI/CD
In continuous integration, uv sync --locked installs from the lockfile and fails if it doesn’t match pyproject.toml. This catches cases where a developer added a dependency locally but forgot to commit the updated lockfile. (--frozen is a stricter option that skips the freshness check entirely and uses whatever lockfile exists, which is useful when you’ve already validated the lockfile in an earlier CI step.)
uv pip install -r requirements.txt in CI has the same drift problems as regular pip: unpinned dependencies resolve to whatever version is newest at build time, and there’s no mechanism to detect when the requirements file is stale.
When uv pip still makes sense
The pip-compatible interface exists for a reason. Some situations where it’s the right choice:
- Legacy projects that rely on
requirements.txtfiles and can’t adoptpyproject.tomlyet.uv pip install -r requirements.txtgives an immediate speed boost with no structural changes. - Interop with tools that generate or consume
requirements.txt, such as deployment platforms that expect arequirements.txtat the repository root. - Throwaway environments where reproducibility doesn’t matter, like a quick experiment in a temporary virtualenv.
- Gradual migration as a first step. Start with
uv pipfor speed, then move to native uv when the project is ready forpyproject.toml.
Moving from uv pip to native uv
If the project already has a requirements.txt, the migration is a few commands:
uv init --bare
uv add -r requirements.txtThe full process, including handling dev dependencies and cleanup, is covered in How to migrate from requirements.txt to pyproject.toml with uv.
For new projects, uv init sets up the native workflow from the start. See Create your first Python project for a hands-on walkthrough.
Related
- uv: A Complete Guide covers both interfaces in detail
- What’s the difference between pip and uv? compares pip (the tool) with uv at an architectural level
- What is a lock file? explains cross-platform dependency locking
- Why choose pyproject.toml over requirements.txt? makes the broader case for declarative project metadata
Get Python tooling updates
Subscribe to the newsletter