Hatch v1.10.0 - UV support, new test command and built-in script runner

Please share any feedback here or as a discussion on the repo!

8 Likes

Sorry if this is obvious, but I don’t know where to check quickly - is it possible to run hatch in a directory that’s not part of a Python project, or do I need to set up a project to use it? Last time I checked, I think hatch insisted on only being run from within a project. (I feel sure I’ve asked this before, but it was something that was “future plans” and I don’t know if anything’s changed yet).

Basically, can I do hatch run somescript.py from a random directory and it’ll “just work”?

There’s a bunch of followup questions I have about using hatch run as a script runner, but they are only relevant if the answer to the above question is “yes”, so I’ll hold off on them for now…

Yes that is completely possible to just run a script.

Cool. In which case, while I have your attention :wink: can I ask the following questions (which aren’t clear from the linked document).

  1. “Hatch will create a dedicated environment for that script” - where will that environment be stored? Does the fact that I would want to configure hatch to store project environments in the project directory affect the answer to that question? (I.e., how do I say to hatch “put project environments in .venv and script environments somewhere central”?)
  2. How are script environments managed (out of date ones deleted, specifically)? I would expect to use this for a lot of one-off scripts, and I don’t want my disk filling up with environments for long-deleted scripts. If I’m expected to manually delete environments (which I’d consider a fairly bad UX, to be honest) how do I identify which environment is linked to which script? Can I still identify this if the script is no longer present? If the environment is named based on a hash of the script, for example, mapping is one way only (from script to environment, but not from environment to script).

To be clear here, I don’t want to think about any of the above. My ideal is something like pipx does, where environments are created on the fly in a temporary/cache location, and deleted if they haven’t been used for a period of time.

(Maybe that suggests that I should just use pipx run. And in reality I likely will in the short term. My interest here is in whether hatch is evolving towards something I could adopt, or if its fundamental philosophy remains at odds with my workflow. There’s a lot to like about hatch, but if I’m continually fighting against its design principles, that will be an issue for me).

2 Likes
  • There is currently no way to select the location of script environments; they are always isolated in a special directory.

  • Currently there is no automatic deletion of stale environments. That is a difficult challenge both technically (how do we fire off jobs when the CLI is not running) and for UX (how do we minimize response time).

    I think the situation is not so bad however because script environments use UV by default so the environments that are lying around are quite small because the packages are linked from UV’s centralized cache.

  • As you conjectured, the name is indeed based on a hash (of the absolute path however, not contents) so the mapping is one way but I could add a metadata file if that would help.

Overall, I am very interested in supporting what you want, especially because you were the main impetus for that entire PEP! At the same time please understand that, because my time is limited, I personally can’t guarantee prioritization of better disk space utilization/insights. I consider that something a power user such as yourself would benefit from most whereas the masses sorta just want “new features”.

Can you please once more briefly explain precisely what you would want to see?

1 Like

No worries.

  1. Top priority, hatch run some_script.py runs anywhere, and works the same everywhere. That might imply that per-project settings need to be ignored for hatch run, although I don’t know what project settings might make a difference here.
  2. High priority, the fact that there’s an environment being created and used behind the scenes should be completely transparent. The user simply shouldn’t know or care about it.
  3. Medium priority, any environment or other files created to support hatch run working, should not gradually fill up the user’s disk. Not everyone has implausibly large disks, and some environments are quite limited. Having said that, “uv handles that problem for us” might be enough.

Actually, my first question is why persist the environments at all? If it’s for performance, uv handles a lot of that for us just like it handles disk space issues. I did a quick benchmark, and creating, populating and deleting a venv with uv takes about 160ms for click and requests. For bigger packages the overhead is greater (for example a venv with numpy, scipy, sympy, pandas and matplotlib together takes about 2s), but honestly it’s still pretty usable IMO.

Maybe default to ephemeral environments, but have a tool.hatch option to request a persistent one? That way you could avoid creating a bunch of orphaned environments while developing a script, and then when the script is “final”, a persistent environment could be created if performance warrants it. Yes, this makes the existence of environments visible to the user, but only as a performance optimisation, which seems like a good compromise.

Going back to disk space management, maybe all that’s needed is a hatch cache command, with options to delete any “orphaned” script environments (where the associated script is gone or has been updated so the environment will no longer be used), or to delete all environments (so that when scripts get run they will recreate the environment). That should be good enough for most needs.

One other thing that might be worth considering, when I specify that a script depends on requests, and don’t give a Python version, what I mean is “my current default Python interpreter and the latest version of requests”. Yes, that’s not what I said precisely, but it’s the natural interpretation, used by default in all tools. But a persistent environment changes that - it makes the meaning “whatever Python and requests were picked when the environment was created”. Conceded, it’s difficult to come up with a situation where the distinction matters (assuming a new environment gets created on any change to the script), but it’s a subtlety that could potentially result in hard-to-diagnose problems. Which is another reason (IMO) to prefer “ephemeral by default, persistent on request” semantics.

Hope this helps. Sorry, it wasn’t as brief as I’d originally intended :slightly_frowning_face:

2 Likes

Thanks!

As I mentioned in the first comment, this definitely works. Have you tried?

Are you saying you wish to see zero output before the script executes? If so, you can do that already with hatch -q ... but I would never make that the default because in my view exposing the user to what looks like a hanged process for potentially multiple seconds (I’ve seen this even with UV) is poor UX.

I’m actually surprised that you desire that behavior, so perhaps I’m misunderstanding?

That sounds quite desirable, please open a feature request!

I’d be okay with the inverse of that option but the default will never be ephemeral because I consider lower overall response time as far more important than less disk space utilization.

2 Likes

I don’t think it’s about silence, more that the way hatch configures venvs should be of no concern to the user. Like, if Paul has to think about the details of making venvs or their location (…in his day-to-day, outside of this thread), then the tool is not saving enough effort.

2 Likes

There are no details that the user has to think about which is where my confusion is coming from. You simply point hatch run at the script and that’s it, the only thing that is exposed is brief purple text indicating what’s happening.

2 Likes

Sorry, I think we’re talking past each other. This was intended as a list of “how I’d like a script runner to work”. I hadn’t tried the new feature in hatch, as that would require installing hatch and getting things set up first and I didn’t have time to do that earlier.

I’ve done some basic tests now, and as you said (and I wasn’t trying to claim otherwise) this works as I’d like. I’ll take your word for it that nothing weird in a pyproject.toml in a project directory could affect the behaviour - I’ve no easy way of checking that for myself.

I’m saying that the existence of an environment should be something that the user can safely ignore. The transient “progress reporting” hatch displays, that an environment is being set up and populated, which is only shown when the output is a terminal, is fine - it’s essentially just a more detailed “working…” status message.

That’s fair, but to be clear it’s not about disk space, it’s about management. People used to hate the Windows registry for being full of clutter from old, no longer installed, programs. But nowadays $env:LOCALAPPDATA is far worse, containing gigabytes of random junk. I prefer tools that allow me to know what’s going on in there (but yes, this is absolutely “advanced” details that the average user should be able to safely ignore unless they care).

I will admit had a somewhat exaggerated concern about the problem, because it wasn’t at all clear from the docs how hatch manages these environments. Obviously, based on what I said above, this isn’t something that should be part of the user interface documentation, but IMO it should be covered in a “technical details” section. Specifically, it would be good if it were documented (and hence guaranteed) that environments are per script, and the rules on what triggers an environment to be rebuilt were made clear. I suspect it’s trivial (environment name is a hash of the absolute pathname of the script, and rebuilds only occur when the deserialised data from the “script” section changes) but still, being explicit avoids confusion.

I still dislike pretty significantly, the idea that every throwaway script I write (and I write many of them!) will result in a new orphaned environment somewhere on my disk. Being able to do hatch cache prune on a regular basis would go a long way to mitigating that, though.

Yes, sorry, that’s my bad for not being clearer that my comments weren’t intended to describe “problems with hatch’s implementation”, just “features I feel are important for a runner to have”. Apologies for not being clearer - I think I completely misunderstood what you wanted when you said “Can you please once more briefly explain precisely what you would want to see?”

3 Likes

I agree about the importance, but I would nevertheless like to consent to that choice. A thing taking longer is immediately obvious, a thing slowly filling up your machine (or worse, a critical remote machine somewhere) is often not.

2 Likes

This comment on the hatch issue tracker also flags up a case (docker builds) where disk space is not a free resource.

Does hatch have any kind of global config that the user can modify? One way to split the difference here is to have a configurable cache size, and emit a warning if the cache starts to fill up. There could be a soft (warn) and hard (error) limit, for cases where warning messages might get missed for a while.

1 Like

Yes there is user-level configuration Hatch configuration - Hatch

1 Like

I swear I looked through the docs but somehow failed to find this, thanks.

So it would be possible to add soft and hard limits to the cache section. I suppose you’d want to cache the size of the cache itself to keep the check speedy.

Naïve question which I can’t research today: how can you get the full size of a given directory, including all subdirectories, on each platform? I feel like on Windows that would require Hatch to start shipping pywin32.

I think you could do it in python with os.walk and os.path.getsize but I’ve no idea how long that might take on windows–hence my comment about caching/memoizing it.

On my macbook, getting the size of a conda env can take up to a few seconds (for 10s of thousands of files). So it might not be a reasonable idea after all, unless you can update a cache in the background or something. It seemed like a potential solution to the conflict between speedy response times and cache management.

1 Like

Yes, like this. As you say, it can be costly.

I’ll reiterate, personally the overall space usage isn’t the key issue. What matters to me is the clutter of orphaned environments, the difficulty of “seeing the wood for the trees” when trying to find something in the filesystem, etc. All of which is mitigated by the fact that you’re not supposed to be looking in those directories, but sometimes you need to anyway.

For example, maybe there’s some huge binary in the cache somewhere, and you want to know what is using it so that you can decide if it’s important to keep.

An actual use case I had today was triggered by me wanting to review my global settings file (which is in the same directory as the caches, etc), and getting distracted by “what’s all the other stuff in here?” I’m pretty sure that the following are “orphans”, where I’ve deleted the code that depends on them:

  • hatch\env\.metadata\_JkBmXBB\virtual\foo.json
  • hatch\env\.internal\hatch-static-analysis\xxyyzz
  • hatch\env\virtual\b--0rwWMov

There’s 20 or so like that. And there’s some that look like they might be real, but I don’t know how to check.

Ideally, I could trash the lot, but given that the user-editable system settings file config.toml is in there too, I don’t know if that’s safe. (As a side note, I really dislike tools that put user editable config in the same location as transient or “internal” data - but it’s common because of how Windows does the whole APPDATA/LOCALAPPDATA thing…).

1 Like