7 ms·
A very well written article! I admire the analysis done by the author regarding the difficulties of Python packaging. With the advent of uv, I'm finally feelin
by barosl 2y ago
A very well written article! I admire the analysis done by the author regarding the difficulties of Python packaging.
With the advent of uv, I'm finally feeling like Python packaging is solved. As mentioned in the article, being able to have inline dependencies in a single-file Python script and running it naturally is just beautiful.
#!/usr/bin/env -S uv run
# /// script
# dependencies = ['requests', 'beautifulsoup4']
# ///
import requests
from bs4 import BeautifulSoup
After being used to this workflow, I have been thinking that a dedicated syntax for inline dependencies would be great, similar to JavaScript's `import ObjectName from 'module-name';` syntax. Python promoted type hints from comment-based to syntax-based, so a similar approach seems feasible.
> It used to be that either you avoided dependencies in small Python script, or you had some cumbersome workaround to make them work for you. Personally, I used to manage a gigantic venv just for my local scripts, which I had to kill and clean every year.
I had the same fear for adding dependencies, and did exactly the same thing.
> This is the kind of thing that changes completely how you work. I used to have one big test venv that I destroyed regularly. I used to avoid testing some stuff because it would be too cumbersome. I used to avoid some tooling or pay the price for using them because they were so big or not useful enough to justify the setup. And so on, and so on.
I 100% sympathize with this.
- shlomo_z 2y agoThis is a nice feature, but I've not found it to be useful, because my IDE wont recognize these dependencies. Or is it a skill issue?
- EdwardDiego 2y agoNo, it's the fact that it's a rather new PEP, and our IDEs don't yet support it, because, rather new.
- zahlman 2y agoWhat exactly do you imagine that such "recognition" would entail? Are you expecting the IDE to provide its own package manager, for example?
- TylerE 2y agoGenerally it means "my inspections and autocomplete works as expected".
- throwup238 2y agoAgreed. I did the exact same thing with that giant script venv and it was a constant source of pain because some scripts would require conflicting dependencies. Now with uv shebang and metadata, it’s trivial. Before uv I avoided writing any scripts that depended on ML altogether, which is now unlocked.
- epistasis 2y agoOne other key part of this is freezing a timestamp with your dependency list, because Python packages are absolutely terrible at maintaining compatibility a year or three or five later as PyPI populates with newer and newer versions. The special toml incantation is [tool.uv] exclude-newer: # /// script # dependencies = [ # "requests", # ] # [tool.uv] # exclude-newer = "2023-10-16T00:00:00Z" # /// https://docs.astral.sh/uv/guides/scripts/#improving-reproducibility https://docs.astral.sh/uv/guides/scripts/#improving-reproduc... This has also let me easily reconstruct some older environments in less than a minute, when I've been version hunting for 30-60 minutes in the past. The speed of uv environment building helps a ton too.
- athrun 2y agoGosh, thanks for sharing! This is the remaining piece I felt I was missing.
- epistasis 2y agoFor completeness, there's also a script.py.lock file that can be checked into version controls but then you have twice as many files to maintain, and potentially lose sync as people forget about it or don't know what to do with it.
- sunshowers 2y agoOooh! Do you end up doing a binary search by hand and/or does uv provide tools for that?
- code_biologist 2y agoWhere would binary search come into it? In the example, the version solver just sees the world as though no versions released after `2023-10-16T00:00:00Z` existed.
- sunshowers 2y agoI mean a binary search or a bisect over dates.
- 8n4vidtmkvmk 2y agoYou know what we need? In both python and JS, and every other scripting language, we should be able to import packages from a url, but with a sha384 integrity check like exists in HTML. Not sure why they didn't adopt this into JS or Deno. Otherwise installing random scripts is a security risk
- woodruffw 2y agoPython has fully-hashed requirements[1], which is what you'd use to assert the integrity of your dependencies. These work with both `pip` and `uv`. You can't use them to directly import the package, but that's more because "packages" aren't really part of Python's import machinery at all. (Note that hashes themselves don't make "random scripts" not a security risk, since asserting the hash of malware doesn't make it not-malware. You still need to establish a trust relationship with the hash itself, which decomposes to the basic problem of trust and identity distribution.) [1]: https://pip.pypa.io/en/stable/topics/secure-installs/ https://pip.pypa.io/en/stable/topics/secure-installs/
- gregmac 2y agoGood point, but it's still a very useful way to ensure it doesn't get swapped out underneath you. Transitive dependencies are still a problem though. You kind of fall back to needing a lock file or specifying everything explicitly.
- 8n4vidtmkvmk 2y agoRight, still a security risk, but at least if I come back to a project after a year or two I can know that even if some malicious group took over a project, they at least didn't backport a crypto-miner or worse into my script.
- zahlman 2y agoThe code that you obtain for a Python "package" does not have any inherent mapping to a "package" that you import in the code. The name overload is recognized as unfortunate; the documentation writing community has been promoting the terms "distribution package" and "import package" as a result. https://packaging.python.org/en/latest/discussions/distribution-package-vs-import-package/ https://packaging.python.org/en/latest/discussions/distribut... https://zahlman.github.io/posts/2024/12/24/python-packaging-1/ https://zahlman.github.io/posts/2024/12/24/python-packaging-... While you could of course put an actual Python code file at a URL, that wouldn't solve the problem for anything involving compiled extensions in C, Fortran etc. You can't feasibly support NumPy this way, for example. That said, there are sufficient hooks in Numpy's `import` machinery that you can make `import foo` programmatically compute a URL (assuming that the name `foo` is enough information to determine the URL), download the code and create and import the necessary `module` object; and you can add this with appropriate priority to the standard set of strategies Python uses for importing modules. A full description of this process is out of scope for a HN comment, but relevant documentation: https://docs.python.org/3/library/importlib.html https://docs.python.org/3/library/importlib.html https://docs.python.org/3/library/sys.html#sys.meta_path https://docs.python.org/3/library/sys.html#sys.meta_path
- nabla9 2y ago> by the author And the author is?
- 0xCMP 2y agoI think this is an awesome feature and will probably a great alternative to my use of nix to do similar things for scripts/python if nothing else because it's way less overhead to get it running and playing with something. Nix for all it's benefits here can be quite slow and make it otherwise pretty annoying to use as a shebang in my experience versus just writing a package/derivation to add to your shell environment (i.e. it's already fully "built" and wrapped. but also requires a lot more ceremony + "switching" either the OS or HM configs).
- zelphirkalt 2y agoWill nix be slow after the first run? I guess it will have to build the deps, but in a second run should be fast, no?
- kokada 2y ago`nix-shell` (that is what the OP seems to be referring) is always slow-ish (not really that slow if you are used with e.g.: Java CLI commands, but definitely slower than I would like) because it doesn't cache evaluations AFAIK. Flakes has caching but support for `nix shell` as shebang is relatively new (nix 2.19) and not widespread.
- EdwardDiego 2y agoIt's not a feature that's exclusive to uv. It's a PEP, and other tools will eventually support it if they don't already.
- aragilar 2y agoMy feeling sadly is because uv is the new thing, it hasn't had to handle anything but the common cases. This kinda gets a mention in the article, but is very much glossed over. There are still some sharp edges, and assumptions which aren't true in general (but are for the easy cases), and this only going to make things worse, because now there's a new set of issues people run into.
- EdwardDiego 2y agoAs an example of an edge case - you have Python dependencies that wrap C libs that come in x86-64 flavour and arm-64. Pipenv, when you create a lockfile, will only specify the architecture specific lib that your machine runs on. So if you're developing on an ARM Macbook, but deploying on an Ubuntu x86-64 box, the Pipenv lockfile will break. Whereas a Poetry lockfile will work fine. And I've not found any documentation about how uv handles this, is it the Pipenv way or the Poetry way?
- zahlman 2y agoPEP 751 is defining a new lockfile standard for the ecosystem, and tools including uv look committed to collaborating on the design and implementing whatever results. From what I've been able to tell of the surrounding discussion, the standard is intended to address this use case - rather, to be powerful enough that tools can express the necessary per-architecture locking. The point of the PEP 723 comment style in the OP is that it's human-writable with relatively little thought. Cases like yours are always going to require actually doing the package resolution ahead of time, which isn't feasible by hand. So a separate lock file is necessary if you want resolved dependencies. If you use this kind of inline script metadata and just specify the Python dependency version, the resolution process is deferred. So you won't have the same kind of control as the script author, but instead the user's tooling can automatically do what's needed for the user's machine. There's inherently a trade-off there.
- EdwardDiego 2y agoYour reply is unrelated to my query - is a uv lockfile able to handle multiple arches like a Poetry lockfile?
- otabdeveloper4 2y ago> I'm finally feeling like Python packaging is solved Not really: https://github.com/astral-sh/uv/issues/5190 https://github.com/astral-sh/uv/issues/5190
- BossingAround 2y agoThis looks horrible for anything but personal scripts/projects. For anything close to production purposes, this seems like a nightmare.
- baq 2y agoDon’t use it in production, problem solved. I find this feature amazing for one-off scripts. It’s removing a cognitive burden I was unconsciously ignoring.
- stavros 2y agoIt's not meant for production.
- dagw 2y agoAnything that makes it easier to make a script that I wrote run on a colleagues machine without having to give them a 45 minute crash course of the current state of python environment setup and package management is a huge win in my book.
- epistasis 2y agoThere's about 50 different versions of "production" for Python, and if this particular tool doesn't appear useful to it, you're probably using Python in a very very different way than those of us who find it useful. One of the great things about Python is that it can be used in such diverse ways by people with very very very different needs and use cases. What does "production" look like in your environment, and why would this be terrible for it?
- zelphirkalt 2y agoAny flow that does not state checksums/hashsums is not ready for production and all but beautiful. But I haven't used uv yet, so maybe it is possible to specify the dependencies with hashsums in the same file too? Actually the order of import statement is one of the things, that Python does better than JS. It makes completions much less costly to calculate when you type the code. An IDE or other tool only has to check one module or package for its contents, rather than whether any module has a binding of the name so and so. If I understand correctly, you are talking about an additional syntax though. When mentioning a gigantic venv ... Why did they do that? Why not have smaller venvs for separate projects? It is really not that hard to do and avoids dependency conflicts between projects, which have nothing to do with each other. Using one giant venv is basically telling me that they either did not understand dependency conflicts, or did not care enough about their dependencies, so that one script can run with one set of dependencies one day, and another set of deps the other day, because a new project's deps have been added to the mix in the meantime. Avoiding deps for small scripts is a good thing! If possible. To me it just reads like a user now having a new tool allowing them to continue the lazy ways of not properly managing dependencies. I mean all deps in one huge venv? Who does that?? No wonder they had issues with that. Can't even keep deps separated, let alone properly having a lock file with checksums. Yeah no surprise they'll run into issues with that workflow. And while we are relating to the JS world: One may complain in many ways about how NPM works, but it has had automatic lock file for aaages. Being the default tool in the ecosystem. And its competitors had it to. At least that part they got right for a long time, compared to pip, which does nothing of the sort eithout extra effort.
- dagw 2y agoWhy did they do that? Why not have smaller venvs for separate projects? What's a 'project'? If you count every throw away data processing script and one off exploratory Jupyter notebook, that can easily be 100 projects. Certainly before uv, having one huge venv or conda environment with 'everything' installed made it much faster and easier to get that sort of work done.
- zelphirkalt 2y agoIn what kind of scope are these data processing scripts? In some kind of pipeline used in production I very much would expect them to have reproducible dependencies. I can understand it for exploratory Jupyter Notebook. But only in the truly exploratory stage. Say for example you are writing a paper. Reproducibility crisis. Exploring is fine, but when it gets to actually writing the paper, one needs to make ones setup reproducible, or lose credibility right away. Most academics are not aware of, or don't know how to, or don't care to, make things reproducible, leading to non-reproducible research. I would be lying, if I claimed, that I personally always set up a lock file with hashsums for every script. Of course there can be scripts and things we care so little about, that we don't make them reproducible.
- zahlman 2y ago> As mentioned in the article, being able to have inline dependencies in a single-file Python script and running it naturally is just beautiful. The syntax for this (https://peps.python.org/pep-0723/ https://peps.python.org/pep-0723/) isn't uv's work, nor are they first to implement it (https://iscinumpy.dev/post/pep723/ https://iscinumpy.dev/post/pep723/). A shebang line like this requires the tool to be installed first, of course; I've repeatedly heard about how people want tooling to be able to bootstrap the Python version, but somehow it's not any more of a problem for users to bootstrap the tooling themselves. And some pessimism: packaging is still not seen as the core team's responsibility, and uv realistically won't enjoy even the level of special support that Pip has any time soon. As such, tutorials will continue to recommend Pip (along with inferior use patterns for it) for quite some time. > I have been thinking that a dedicated syntax for inline dependencies would be great, similar to JavaScript's `import ObjectName from 'module-name';` syntax. Python promoted type hints from comment-based to syntax-based, so a similar approach seems feasible. First off, Python did no such thing. Type annotations are one possible use for an annotation system that was added all the way back in 3.0 (https://peps.python.org/pep-3107/ https://peps.python.org/pep-3107/); the original design explicitly contemplated other uses for annotations besides type-checking. When it worked out that people were really only using them for type-checking, standard library support was added (https://peps.python.org/pep-0484/ https://peps.python.org/pep-0484/) and expanded upon (https://peps.python.org/pep-0526/ https://peps.python.org/pep-0526/ etc.); but this had nothing to do with any specific prior comment-based syntax (which individual tools had up until then had to devise for themselves). Python doesn't have existing syntax to annotate import statements; it would have to be designed specifically for the purpose. It's not possible in general (as your example shows) to infer a PyPI name from the `import` name; but not only that, dependency names don't map one-to-one to imports (anything that you install from PyPI may validly define zero or more importable top-level names, and of course the code might directly use a sub-package or an attribute of some module (which doesn't even have to be a class). So there wouldn't be a clear place to put such names except in a separate block by themselves, which the existing comment syntax already does. Finally, promoting the syntax to an actual part of the language doesn't seem to solve a problem. Using annotations instead of comments for types allows the type information to be discovered at runtime (e.g. through the `__annotations__` attribute of functions). What problem would it solve for packaging? It's already possible for tools to use a PEP 723 comment, and it's also possible (through the standard library - https://docs.python.org/3/library/importlib.metadata.html https://docs.python.org/3/library/importlib.metadata.html) to introspect the metadata of installed packages at runtime.