DE version is available. Content is displayed in original English for accuracy.
Advertisement
Advertisement
⚡ Community Insights
Discussion Sentiment
62% Positive
Analyzed from 5809 words in the discussion.
Trending Topics
#python#package#pip#version#upgrade#dependencies#don#lock#packages#more

Discussion (137 Comments)Read Original on HackerNews
As a note, you can set the default bounds for `uv add` in persistent configuration — no need to provide it every time. See https://docs.astral.sh/uv/reference/settings/#add-bounds
We prefer not to add upper bounds by default because it causes a lot of unnecessary conflicts in the ecosystem. I previously collected some resources on this back when I used Poetry :) see https://github.com/zanieb/poetry-relax#references
That makes total sense! The article however was written as someone creating websites, not libraries. And when I consume dependencies in my web project, I do want those upper bounds to prevent breaking changes (assuming the dependencies respect SemVer of course).
Thanks for pointing out that config, I’ve updated the article.
> In the eyes of uv, pydantic version 2, 3, and 100 are all perfectly acceptable.
Without semantic versioning, they are.
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.
As someone part of the problem with a couple ancient packages in PyPI that are calver (though arguably I'd be surprised if many depended on them), I wonder if it is too late for more of the Python ecosystem to shift more directly to semver by default/everywhere as some of the other ecosystems now are.
I can't really take the article fully seriously when they are like "uv cant do this. Well actually it can but you gotta use an extra flag." It reads rather PEBKAC.
Maybe when uv knows the project isn’t a library it could default to upper bounds?
> uv add pydantic --bounds major
So not really sure what he's complaining about
His point is that the cli experience of uv is badly designed. And having upper bounds an opt-in is another point he makes in that.
Also, the overuse of the term "clickbait" for anything we don't agree with is ...clickbait.
> So not really sure what he's complaining about
It says it on the bloody title of his post: UV's "package management UX is a mess".
He's complaining that upper bounds by default would the good choice, and that UV potentially nuking your deps by default is bad. And that the whole UX around uv updates is bad in general, for which he gives several examples.
We can argue if he's right or wrong about each of those, but it's pretty clear what he complains about.
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
And then we'd have to run uv again with an argument to not have upper bounds. Oh, the humanity!
As opposed, to it nuking our dependencies with incompatible packages.
Doesn't sound like the unsafe option should be the default.
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.
… an assumption that something happened is not a definitive statement that it did happen, only that we're assuming it did, because it could happen, or perhaps here, that because the major was bumped, that it is legal, according to the contract given, for it to have possibly happened in a way that we depended on. They're not saying that it will/must; "assume a major version is incompatible" is not at odds with what you've written.
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.
uv's default being to always select the latest version seems to be what Clojure's tools.deps does.
We have good test coverage so if anything breaks the tests fail + we have AI assisted review process for this -> when GH action creates the upgrade PR, an AI workflow uses a python script to list major and minor version updates, finds and links changelogs, summarizes them, and makes the risk factor analysis for each package based on how we use it in our codebase.
It's mostly painless and we don't have to deal with upgrading packages one-by-one, checking which packages are outdated, or having outdated packages. Very rarely something breaks in a way it can't be fixed in our code and we need to wait on a fix from the dependency author (maybe once a year?). And in the past 3 months only one such upgrade required some changes to our code, there were 18 major version bumps in that period.
I wish we could do this on the frontend as well (I'm a full stack dev), but we don't have enough tests on frontend for this to work safely. Tests on the backend are easier to write and are more important so I believe every codebase should have them. And if you do, you can just auto-upgrade everything.
It really is far easier than you'd think if you haven't tried.
To take that as good ui is not a good take.
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.
Please feel free to open a reproduction with details and we can look into it.
(I work on uv)
The only thing that you need to be aware of is that if you add another package or change it's version you need to reinstall the tool. I would suggest first removing it with `uv tool uninstall $NAME` and then reinstall it.
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
Dunno if it's your exact use case but it's been amazing for keeping a polyrepo microservice ecosystem in sync.
I personally use "uv pip list --outdated" since it has been introduced.
I agree that this is such an important command that it deserves its own top-level subcommand, though.
Though this makes me wonder why are there 2 ways of viewing outdated packages, with wildly different output? The UX is mess...
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.
[1]: https://news.ycombinator.com/item?id=48230048
Yes, it makes sense that `uv lock` commands only work the lock file, but users have real needs to upgrade direct and transitive dependencies. For transitive dependencies `uv lock --upgrade-package` works, even if a bit wordy. For direct dependencies, `uv lock --upgrade-package` also works, but doesn't touch `pyproject.toml`, which is much more developer visible. As `uv.lock` package versions get ahead of `pyproject.toml`, `pyproject.toml` becomes a less dependable guide to the surface area of dependencies. A friendly `uv upgrade` command would be nice.
The biggest uv ux footgun I have seen by far is `uv pip`. I have seen a lot of projects use uv correctly with pyproject.toml/uv.lock for development, but then use `uv pip install -r pyproject.toml`, which bypasses uv.lock, in their deployment Dockerfiles and ci tooling. Yes, coding agents are to blame for recommending bad `uv pip` patterns because they have so much `pip` in their training sets, but uv should provide some affordances to protect the user.
Sorry for the rant, uv is a great tool, that I think[0] should be used more! Thank you for your contributions to the ecosystem.
[0] https://aleyan.com/blog/2026-why-arent-we-uv-yet
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.
- Our compatibility guarantees mean we can't fundamentally change `cargo update`
- Using the third-party package name of `cargo upgrade` would be confusing in the distinction between the two
- We have to be very careful adding the mode to the existing command
All because of some cargo-culted command!
(Ducks...)
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.
Pixi-diff-to-markdown in particular has made scanning automated CI package updates easier. So for something like viewing outdated packages that would be updated, I'd do something like create a task alias for a project:
pixi task add outdated "pixi update --dry-run --json | pixi exec pixi-diff-to-markdown"
And then run the task in the project via:
pixi run outdated
The output is a readable Markdown table of packages that would be updated, old version, and the new version that would be installed using the pixi update command. Your mileage and tastes, of course, may vary.
The article points out one major issue (bounds) & one minor gripe (outdated cmd).
The bounds issue is very serious & more than worthy of an article in itself. It is a single issue though & hardly constitutes "a mess" when considering the entirety of uv.
I don't think uv's UX is perfect - I could point out plenty more minor gripes other than the outdated one mentioned here - but looking at where we're coming from in the Python package management ecosystem it's a goddamn miracle the UX is as good as it is.
Want to install PyTorch? which one? CUDA? Oh, OK, then you have to get the wheel directly from them because there are 6 different versions for differnet CUDA versions, and the wheels are too large for PyPi anyway.
Conda offers only a partial resolution to this problem. Spack is great at being ultra configurable and having all the C/C++/Fortran dependencies and compiler toolchains you need, and so allowing ekeing out best performance, but doesn't integrate well with uv etc. so it's difficult to take an experimental ML project written by a researcher and take it through to productionisation with it.
From version 1.19 of SciPy there will be no need for fortran compilers (because we translated everything to C https://github.com/scipy/scipy/issues/18566) and then all becomes much easier in all platforms due to the large availability of C compilers in all platforms. Together with the Stable API developments in CPython the wheel clash issues "hopefully" will decrease gradually.
I ran into a frustrating issue today with uv lock. AFAICT there's no way to "unlock" an individual dependency. I either lock everything down or forgo locks entirely. In my case I'm working with two tightly coupled packages - both developed internally to my organization - where package A is dependent on package B and I always want the latest version of package B. But I still want all my other packages to be locked to specific versions.
My thought was to stop using a uv lock file and just go back to pip with all my dependencies pinned with hashes in pyproject.toml. But after some digging I realized there was no way to put dependency hashes in pyproject.toml. So my only solution is to go back to using requirements.txt, at which point I lose out on the primary value-add of uv.
This experience left me feeling like the "new and improved" tools are still half-baked and that I should stick with the old stuff. It's a little slow and clunky sometimes, but I'm familiar with it and once it's setup it just does what I want.
My flow is exact pins always, Renovate/Dependabot to inform me of new versions, or uv tree --outdated.
Possibly the most frustrating hidden command is `uv pip show {package}` - a) why is it hidden inside the pip subcommand, and b) why is it missing the package's homepage that you get from `pip show {package}`? Given that actually upgrading stuff with `uv` often means going off and finding version numbers yourself, removing the homepage URL from the show output just feels spiteful.
I don't expect uv to do anything fancy. I don't run module management (installation, upgrades) through uv commands. I don't much care what uv's syntax is. I let each tool do one thing.
- Tool 1: UV makes a venv for each project, with whatever Py version is suitable for that project, so that each project's dependencies do not collide with one another.
- Tool 2: Pip installs all the requirements for a given project, within the venv for that project. From a requirements.txt file. Which, as far as I am aware, pip commands and requirements fit more of what they author is looking for.
I don't think it's necessary to subject oneself to the things the author (articulately) complains about, e.g.: uv's command syntax for listing packages; uv's emitting unbounded requirements syntax; uv's command to upgrade modules
Then again, it's quite possible the author is managing modules in projects with more complex needs than I am.
Long-winded example:
And in the age of the supply chain attacks, requiring a certain staleness could be useful, too (providing time to catch recent and revoke reasonably major and recently discovered issues, though at the cost of also blocking recent fixes): Am I doing it wrong? Should I be thinking about `uv lock --upgrade`, `uv add`, and `uv tree --outdated` like the author? I'd rather just avoid all that, and have been able to so far.I personally don't really use `uv add` and `uv lock --upgrade`, in the past I'd just hand edit the pyproject to pull forward my dependencies and let `uv lock` figure out the rest.
A good third of my last job was spent chasing after projects that weren't using pyproject. It typically turned multiple steps of "install python, upgrade pip, install this one special library, install requirements" inside of a Dockerfile or bash script into one `uv` command. And was more reliable afterwards, to boot!
Some of the things I love about uv that pip by itself doesn't give me
If you remove a package, it's dependencies get removed too
Caching. Creating a new clone or workspace is almost instant because uv has cached all the packages
Much simpler command-line than your pip examples above.
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.
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.
While true, this is a molehill, not a mountain, of a bar, like "coding once in a while". I'm doing mostly SRE work, and this syntax has no trouble sticking in my head, and I encounter it pretty regularly? (And heck, most of my work these days is in Python, so there I get the >=,< syntax and yearn for the ~mines~ caret, and I still recognize it?)
If you're actively developing a codebase, this definitely isn't going to be arcane trivia.
I have look it up years ago, and I don't remember all combination of `=` vs `^` vs `~` across all languages and package managers
[1]: https://docs.npmjs.com/about-semantic-versioning#using-seman... [2]: https://doc.rust-lang.org/cargo/reference/specifying-depende...
Isd requires uv.
uv? Not installed. Ok, pip install uv. No way, "externally managed" whatever that means.
Ok, install venv. Then install uv. Then install isd. All messages are cryptic, some exceptions were thrown on console for quite vanilla default cases.
Still its a mess
thanks for listening
The "unsafe version" is an unreasonable accusation. Do people really believe in semantic versioning—that not bumping the major version means it's safe? It also creates hard-to-resolve compatibility problems, causing more issues than it solves. Not capping dependency versions has basically become the consensus.