uv connects project metadata to the environment that runs a command
uv expands from pip-compatible operations into project and interpreter management. Its project lockfile and inline script support make dependency declarations part of command execution.
A dependency declaration does not, by itself, establish what is installed in the environment running a program. Someone still has to resolve the declaration, install the result, and select that environment when invoking the command. uv's August 20 expansion brings those operations under a project interface. uv run connects the command to the project's managed environment, while uv lock and uv sync expose the supporting dependency operations.
This changes uv's scope from its February launch. Its original pip-compatible interface remains supported, but the new commands can manage a project and its interpreter requirements. Tool execution and single-file scripts also receive dedicated interfaces. Those uses share an implementation while retaining different definitions of what dependencies belong in the environment.
I welcome fewer manual steps in Python packaging, although the need for such an extensive tool still reflects how much coordination the ecosystem demands. The interesting improvement here is a declared relationship between inputs and execution. A developer should be able to explain which metadata determined an environment and why a command used it. A fast install is useful, but that explanation addresses a different source of confusion.
Project requirements and resolved versions remain separate
The project documentation shipped with uv 0.3.0 places project metadata in pyproject.toml. It creates the project environment in a neighboring .venv directory. uv run creates that environment when missing and updates it when necessary. uv sync performs explicit synchronization. The documentation recommends changing project dependencies through the project interface instead of manually mutating the environment with installer commands.
That recommendation follows from which object carries the intended state. Adding a package directly to an environment changes an installation. Adding it to the project's declaration changes what later synchronization should produce. If these are treated as interchangeable, a package can appear to work until the environment is recreated. A project manager needs a consistent answer to which of those states is authoritative.
The metadata convention comes from PEP 621. It defines project metadata in the [project] table, including dependency declarations. Using the standard table keeps publishable metadata distinct from a tool's private configuration. Another tool can read the project's declared requirements without having to interpret every setting uv uses for local development. A minimal illustration of that distinction is:
[project]
name = "example-service"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["httpx"]
This declares a Python requirement and a direct package dependency. It does not select one exact version of every transitive dependency. That is the resolver's work. Keeping the broad declaration separate from the concrete resolution allows a project to express compatibility while recording the dependency choices used for a particular application environment.
One lockfile can contain conditional choices
uv stores the concrete resolution in uv.lock. Its launch documentation describes a cross-platform result, including resolutions for operating systems other than the one where the command runs. The file can therefore account for conditional dependencies instead of assuming that every target needs an identical list of distributions. A shared lockfile means a shared description of the choices, with the applicable environment determining which choices are installed.
This is a useful distinction from copying one environment's installed package list to another machine. Python requirements can depend on interpreter version and operating system, as defined by PEP 508's environment markers. A marker tells tooling when a requirement applies. Resolving across those conditions requires preserving their meaning, including conditions that are false on the machine performing the resolution.
Consequently, cross-platform consistency should not be described as every machine receiving byte-for-byte identical native packages. Different targets can need different artifacts or dependencies. The useful guarantee is that their choices belong to one recorded dependency solution. That gives maintainers a common file to review while allowing the environment-specific differences the package metadata requires.
The uv documentation also makes an interoperability limit explicit. uv.lock is a uv-specific format because Python has no standard lockfile format at this point. Standard project metadata does not make every file produced from it standardized. A project can retain portable dependency declarations while depending on uv to interpret its recorded resolution. That is a concrete tooling dependency to acknowledge when adopting the project workflow.
Execution has a policy for updating the lock
Project commands normally update the lockfile and synchronize the environment as needed. The versioned documentation offers two different controls over that behavior. --locked requires the lockfile to be current and fails if it needs updating. --frozen avoids updating it. These controls answer different questions, so a build process should choose the behavior it intends to require. For example, a project with an existing lockfile can request execution while rejecting a required lock update:
uv run --locked python -c "print('project environment ready')"
The command makes the lock policy visible at invocation. Its output is only an illustrative message. The meaningful part is that --locked turns a stale dependency resolution into an error instead of allowing execution to revise it. An interactive development command and a release command can use the same project definition while applying different update policies.
This is a better-defined workflow than relying on a previously activated environment. Activation determines which executable a shell finds. Project execution can additionally relate the environment to its metadata and resolved dependencies. That relationship is the relevant improvement when explaining why a command succeeded on one machine but used unexpected packages on another.
A script can carry its own dependency declaration
PEP 723 defines inline metadata for single-file scripts. A comment block starts with # /// script and ends with # ///. Its TOML content can declare dependencies and requires-python. This lets a script retain its requirements when shared as one file, without depending on a separately distributed requirements document. An illustrative script using the format is:
# /// script
# requires-python = ">=3.12"
# dependencies = ["rich"]
# ///
from rich import print
print("Script dependencies are declared in this file.")
uv's versioned script guide describes running scripts with managed dependencies and modifying inline declarations through uv add --script. The important addition is the explicit input. An import alone does not state which distribution version is required or which Python versions the script supports. Metadata provides information before the runner begins executing the program.
The script declaration is still a requirement expression, so its presence does not imply that all future executions select identical dependency versions. That is the same distinction between requirements and resolved choices seen in projects. Inline metadata solves the problem of carrying a declaration with the file. It should be assessed on that specific role.
Tools and interpreters have their own managed state
Local development can also select a different source for a declared dependency. The announcement demonstrates tool.uv.sources pointing to an editable package in a neighboring directory. Publishable metadata still names the dependency, while uv's local configuration supplies the alternate source. This is useful when changing an application and a library together without publishing every intermediate library revision.
That separation makes the local source choice visible in configuration. It also explains why inspecting only the public dependency name is insufficient to reconstruct a development environment. The environment may contain a checkout whose content differs from the published distribution. A source override expresses that intention directly, while the standard declaration continues to describe the dependency that consumers of a published package should obtain. The two records serve different readers and different stages of the package's use.
The tool documentation distinguishes uvx from uv tool install. One-off execution uses an isolated, cached environment treated as disposable. Installed tools receive persistent environments and exposed executables. Neither mode requires installing the tool's dependencies into the application project merely to make a command available.
Interpreter installation is separate again. The Python version documentation distinguishes installations uv manages from installations it discovers elsewhere. It can obtain a requested interpreter when necessary. Its term "system" includes installations managed by other tools, so the label does not necessarily mean an operating-system package.
I prefer an interface that keeps these responsibilities visible even when one binary implements them. A formatter used as a tool and a library imported by an application do not need to share an environment. A project also needs an interpreter that satisfies its declared requirement. uv now coordinates those operations through explicit project, tool, and Python commands, while retaining enough separation to explain which state each command manages.