Since uv needs a singular resolution that's entirely intentional. In npm you can install diverging resolutions for different parts of the tree but that is not an option with Python. I had to make the same decision in Rye and there is just no better solution here.
If an upper bound were to be supplied you would end up with trees that can no longer resolve in practice. Some package ecosystems in Python even went as far as publishing overrides for old packages that got published with assumed upper bounds that ended up wrong.
Don't forget that you cannot know today if your package is going to be compatible or incompatible with a not yet released package.
Maybe when uv knows the project isn’t a library it could default to upper bounds?
For libraries, having loose bounds might mean that users upgrade and hit issues due to a lack of an upper bound. But given how lightly maintained most projects are, the risk of upper bounds simply getting in the way are higher IMO.
(Put an upper bound if you know of an issue, of course!)
It's a bit tricky though. Django deps in particular tend to want to explicitly check support for newer versions, but the more I think about it the more I ask myself if this is the right strategy
I mean, it may not actually work, but that's what it's for.
I solved this issue a few months ago. Created a tool that essentially allows the use of multiple envs at once, with their own versions of packages at any level.
I'm not sure I'd agree with that characterization. The point of semver is that you can assume that certain types of bumps won't include certain types of changes, not that you assume that the types of changes that can happen in a type of bump will happen. A major version bump not breaking anything is completely valid semver, and breaking one function (which plenty of users might not use, or might use in a way that doesn't get broken) in an API with thousands is still technically only valid in a major version bump (outside of specific exceptions like the major version being 0).
It's a subtle difference, and I'm optimistic that it's something you understand, but misunderstandings of semver seem so common that I can't help but feel like precision when discussing it is important. I've encountered so many smart people who misunderstand aspects of semver (and not just minutia like "what constraints are there on tags after the version numbers"), and almost all of them seemed to have stemmed from people learning a few of basic tenets of it and inferring how to fill in the large gaps in a way that isn't at all how its specified. The semver specification is pretty clear in my opinion even about where some of the edge cases someone less informed might assume, and if we don't agree on that as the definition, I don't know how we avoid the (completely realistic) scenario where everyone in the room has an idea of what "semver" means that's maybe 80% compatible with the spec, but the 80% is different for each of them, and trying to resolve disagreements when people don't agree about what words mean is really hard.
uv's default being to always select the latest version seems to be what Clojure's tools.deps does.
There's a lot of packages in the Python ecosystem that use time based versioning rather than semver (literally `year.minor`) and closed ranges cause untold problems.
I was trying to centralize the management of a script that appears in a few different repos, and has invariably drifted in its implementation in multiple way over time.
My idea was
uv run --with $package main --help
I was looking for an easy way to automatically
1. Install it if it doesn’t exist and run 2. Don’t install it if it’s running the latest version 3. Update if it’s not on the latest version
All three were surprisingly tricky to accomplish.
By default uv run will reinstall it every time. Which is 6 seconds of venv and installs
uvx or uv tool weren’t much better as that posed new problems where a user wouldn’t get upgrades.
I ended up having the script run a paginated GET on codeartifact and update if there’s a newer non-dev version (and then re-execute).
That seems to work. And 200ms delay is better than 6 seconds. But it wasn’t quite the experience I wanted.
Couldn't the user just run `uv tool upgrade <tool_name>`?
Basically if there’s an upgrade everyone needs to be using the most recent version, I didn’t want to rely on a pr dance to pin versions, and I also didn’t want to rely on everyone running a command when there’s a change
then cites two examples where you have to write a couple extra args..
better title: “QOL changes i wish UV had”
Much of this is useful feedback, even if phrased in a clickbait style. Some thoughts:
- Re: `pnpm outdated`: this is something that hasn't come up very much, even though it seems reasonable to me. I suspect this comes down to cultural differences between Python and JavaScript -- I can't think of a time when I've cared about whether my Python dependencies were outdated, so long as they weren't vulnerable or broken. By contrast, it appears to be somewhat common in the JavaScript ecosystem to upgrade opportunistically. I don't think this is bad per se, but seems to me like a good demonstration of discontinuous intuitions around what's valuable to surface in a CLI between very large programming communities.
- As Armin notes[1], uv's upper bound behavior is intentional (and is a functional necessity of how Python resolution works at large). This is a tradeoff Python makes versus other languages, but I frankly think it's a good one: I like having one copy of each dependency in my tree, and knowing that _all_ of my interdependent requirements resolve to it.
- `uv lock --upgrade` is written like that because it upgrades the lockfile, not the user's own requirements. By contrast, `pnpm update` appears to update the user's own requirements (in package.json). I can see why this is confusing, but I think it's strictly more precise to place under `uv lock`; otherwise, we'd have users with competing intuitions confused about why `uv upgrade` doesn't do their idea of what an upgrade is. Still, it's certainly something we could surface more cleanly, and there's been clear user demand for a uv subcommand that also upgrades the requirements directly.
poetry update also updates the lockfile. I really think the way the uv cli is organized makes it quite annoying to work with. It’s designed for correctness, for machines, not for user-friendliness.
One use for it is to see what would be updated by running "uv sync --update" or "uv lock --update". Although that might be better served by having a confirmation prompt for those commands.
uv pip list --outdated
when I need that information.Having a command runner within your project will mask a lot of the issues the author mentioned. And although, in my experience, having a command runner for mid-sized projects and up is useful for many things, masking the UX issues means there's a problem.
I got on the uv bandwagon relatively recently as most of my work is maintaining older python projects so I've been using it for new builds. Although the speed part is welcome, I couldn't see what the big deal is and mostly keep on using it because it is a popular tool(there are benefits to that in my line of work) and not necessarily because it can do something that couldn't be done before though with a couple of other tools. Whether it is beneficial or detrimental to having all of that functionality within one tool, to me, is a matter of opinion.
The problem to me is that I've seen this cycle many times before. New tool shows up claiming it is far superior to everything else with speed being a major factor and everyone else is doing it wrong. Even though the new tool does a fraction of what the old "bad" tool is doing. With adoption comes increased functionality and demands and the new tool starts morphing into the old tool with the same claimed downsides. The UX issues to me are a symptom of that process.
I still think uv is a fine tool. I've used poetry before and sometimes plain old pip. They're all fine with each tool catering to different use cases, in my opinion. Sometimes you have to add pyenv, sometimes you don't. Sometimes you add direnv, sometimes you don't and so on. And I've cursed at everyone of them at times. However, the fanboyism is very strong with uv which makes me wonder why.
> PEP 658 went live on PyPI in May 2023. uv launched in February 2024. uv could be fast because the ecosystem finally had the infrastructure to support it. A tool like uv couldn’t have shipped in 2020. The standards weren’t there yet.
> Other ecosystems figured this out earlier. Cargo has had static metadata from the start. npm’s package.json is declarative. Python’s packaging standards finally bring it to parity.
Are there any tools written in Python since then that are anywhere as close to as fast as uv when operating on packages that use this newer format? I've yet to hear of one.
> No .egg support. Eggs were the pre-wheel binary format. pip still handles them; uv doesn’t even try. The format has been obsolete for over a decade.
It seems dubious that adding support for egg would prevent uv from being as fast on packages that don't use that format.
> Virtual environments required. pip lets you install into system Python by default. uv inverts this, refusing to touch system Python without explicit flags. This removes a whole category of permission checks and safety code.
Passing `--user` to `pip install` doesn't seem to make things noticeably faster in most cases.
> Parallel downloads. pip downloads packages one at a time. uv downloads many at once. Any language can do this.
Any language with a global interpreter lock certainly can't do that as effectively as a language without one.
> Python-free resolution. pip needs Python running to do anything, and invokes build backends as subprocesses to get metadata from legacy packages. uv parses TOML and wheel metadata natively, only spawning Python when it hits a setup.py-only package that has no other option.
This one is pretty self-explanatory.
The section at the end somewhat overlaps with the parts I called out, and I recognize that the author of that post is almost certainly more familiar with the specifics of uv and Python package management than me, but with a lack of concrete example of a Python package manager that's anywhere close to the level of performance of a Rust one, I can't help but feel like pip would probably be quite noticeably slower than a Rust alternative written with an identical feature set (whether that feature set is "what pip currently supports" or "the minimal set of features described here"). I could imagine it being something like, pip could maybe be optimized from being 50x slower than uv to only 5x, but if that's the case, I think "Rust isn't the main reason it's fast" is a bit of an oversimplification when the discussion is about comparisons to alternatives that are all written in Python.
What???
I understood the first format instantly, but had no idea what the second meant until the author explained it.
Not, in fact, correct. Knowledge only cements itself in the brain when it's regularly referenced. Because `>=` and `<=` borrow well-established concepts well-established, they are both intuitive to people reading them for the first time, and easier to solidify or to re-infer for someone who's forgotten their meaning.
[1]: https://docs.npmjs.com/about-semantic-versioning#using-seman... [2]: https://doc.rust-lang.org/cargo/reference/specifying-depende...