Skip to content

TST: decoupling stable CI and incompatibilities discovery (let's use lock files) #17596

Open
0 of 2 issues completed

Description

@neutrinoceros

Disclaimer

As the length of this issue may suggest, I've put a lot of thoughts into this proposal, which comes as a culmination of one year serving as our first line of defense against environment incompatibilities (also known as dependency hell). Maybe this should be an APE ?

TL;DR

The one-liner version of this proposal is: let's start using lock files.

Now for a slightly longer summary:
We already have a distinction between regular, required CI and jobs that are allowed to fail (allowed-failure). I propose to consolidate this distinction into a stable/unstable paradigm, using a lock file in stable jobs to get exact reproducibility of the testing environment and improve our trust that failures are always related to the patch under test.

Since lock files are currently foreign to our shared maintenance culture, and I was myself completely new to them a couple months back, I'll take a long detour to explain the motivation as well as the solution: if you don't know what a lock file is, hang in there ! I'll get to it.

Here are some external resources describing the core concepts and stakes for cargo, the rust package manager, and uv, the Python package manager inspired by cargo:

Problem: required CI isn't stable

Our regular CI comprises jobs that are required for merging, and some that are explicitly allowed to fail. The former are meant to reveal failures caused by the patch under test, while the latter are aimed to continuously reveal incoming incompatibilities with a selection of important dependencies (first and foremost, numpy).
Incoming incompatibilities indeed shouldn't block most patches, as they are usually caused by the outside world, so I think this distinction between required and allowed-failures is a great thing to have. However, I see a problem with how it is currently implemented:

Even in required jobs, the Python environment that is installed isn't stable or easily reproducible; whatever is the latest version of one of our dependencies, is what gets installed and tested against. This regularly leads to situations where required CI is failing on every single PR at once because of some upstream change we didn't happen to test early against (again, we only run such early tests against a selection of our direct dependencies, leaving lots of room for failure). Such a situation, because it is so inconvenient to the team as a whole, is often treated as an emergency, and rightfully so. Regardless how much dopamine I get by serving as our first line (and I am grateful to be of help as often as I can), I need to point that solutions found while in crisis mode may not always be the best or most robust ones.

It's also not always obvious what (if anything) changed in the environment between two runs, and a defensive reflex when ones encounter a cryptic, seemingly unrelated error, is to diff the outputs of pip freeze found in CI logs.
This is bad for two reasons:

  • it shows that we do not really trust required CI to reveal only issues with the patch under test
  • it is a somewhat tedious and error-prone task

Proposed solution (spoilers: it's lock files)

What are lock files

Simply put, a lock file is a record of a resolved environment with metadata including exact versions of all dependencies (direct, indirect, mandatory, and optional ones).
A requirements.txt file obtained via pip freeze is conceptually similar (albeit generally less portable1, or secure2), in that it allows to recreate an environment exactly.

Practically, lock files allow pinning all dependencies in a way that's not visible to end users.

Expected gains

Locking CI means that we never get surprise updates on weekends or in indirect dependencies we don't pay attention to. Updates happen on our schedule rather than being imposed by the outside world. This proposed strategy is actually not that foreign to us: we already use exact pins (paired with automated updates) for GitHub Action workflows and for pre-commit hooks.

Having a version-controlled lock files also means that we can revisit previous states of the repo and run tests within the exact same environment

The current state of standards

I want to point out that lock files are currently not standardized in the Python packaging ecosystem, which means all workflow tools that support a version of this implement their own flavor (poetry.lock, pdm.lock, uv.lock ...) but there is a PEP under discussion.

Namely PEP 751 is being discussed on discourse., note that stake holders (poetry, pdm, uv ...) are all included in the discussion, which is a good sign that this one might succeed3.

Having a standard file format for lock files would greatly improve interoperability between tools that consume them, and would allow us to migrate from one tool to another as we see fit without converting the lock file.

Proposed plan

I propose we start using a uv lock file (uv.lock) in stable CI as well as documentation (#17052) and wheel builds. I choose uv over other options because:

  • it integrates nicely with our existing toolbox
  • astral-sh/uv as a very good record on supporting newly accepted PEPs (e.g. PEP 723 or PEP 735, supported within 2 weeks after acceptance), so I'm confident that they would support converting to a standardized lock file format if there is one in the future.
  • I think I have a good understanding of how this tool work: I've been using uv more and more over the past year that it's been available.
  • I don't have experience with the other options (pdm, poetry and pixi are the ones I know about), but I hear some of them have non-ideal records in terms of standard adoption:
    • pdm support at least one PEP that was ultimately rejected
    • poetry has been very slow/reluctant to support standards, effectively locking its users in)
  • maybe it's worth mentioning pixi is already bound to uv in a sense (it uses uv internally for dependency resolution)

Deployment

There are 3 easy steps needed to adopt to this testing strategy, which I believe can be performed in a single PR:

  • generate and commit the file itself (trivial)
  • use uv in CI (this can easily be achieved as a follow up to #16963)
  • add uv-lock to pre-commit (ensure uv.lock stays in sync when dependencies are added, updated or removed in pyproject.toml)

For the record, I have been doing exactly this on smaller packages that I control.

Long term maintenance

The core idea is to decouple environment updates, and the ensuing risk of surprise incompatibility, from regular testing. However, running regular CI in an up-to-date environment remains very much needed, so keeping the lock file up to date is crucial to this strategy.
There are two questions to discuss here:

  • how frequently should it be updated ? Given how large astropy's testing stack is, I would recommend at least weekly updates, maybe even daily (5 days a week) ?
  • what service (if any) can we use ?

My personal recommendation for now would be to use renovate exclusively for this task (as opposed to replacing dependabot completely) until dependabot gains support for uv.lock or an equivalent, standardized format (which might take a while).

Expected drawbacks, side effects

In short: none that I'm aware of.

How does this affect end users and downstream packages ?

It doesn't. Lock files are not meant to be distributed and using them does not change how we specify our dependencies in pyproject.toml .

How does this affect individual maintainers ?

This does not force anyone to use uv locally4 If you manage your own local dev environment(s) with pip, conda, pyenv (you name it) and you don't care for uv, nothing changes for you.

Maintainers who do want to use uv will benefit from having a centralized and version-controlled lock file.

In general, lock file upgrades should not invalidate CI jobs that already ran on pending PRs, with the possible exception of someone working specifically around dependency issues.

How does this affect release managers ?

This removes the (admittedly small) risk of discovering incompatibilities with our dependency stack between the moment a release is tagged and when it's actually published (wheels are tested in a guaranteed-stable environment).
Lock file updates should probably be backported as much as possible.

Does this lock us into current major versions of our dependencies ?

It's been pointed out by @eerovaher that cargo, the inspiration for uv, assumes that every package in the rust ecosystem strictly follows semantic versioning, so cargo update (the command to upgrade Cargo.lock) will not perform major upgrades by default (one needs to specify --breaking to get major upgrades). uv lock --upgrade doesn't do this and treats all upgrades (major, minor, patch) the same, to the best of my knowledge, and as of uv 0.5.14.

How does this affect oldestdeps and devdeps jobs ?

It doesn't. uv simply ignore existing lock files if a different resolution strategy is requested, which is exactly what's done for oldestdeps (via --resolution=lowest-direct, see #16963), and devdeps (via --prerelease=allow, equivalent to pip's --pre flag).

Cultural impact

This proposal is a cultural shift in the testing strategy. While it is by no means required that packages downstream of astropy follow the same path, I believe that it would still be helpful to encourage it: the problem I want to resolve isn't specific to astropy. I would be happy to help coordinated and affiliated packages migrate to this new paradigm if it is adopted by astropy itself.

Footnotes

  1. Actual lock files are more portable: they can lock on different versions of a package depending, e.g., on the target platform (Linux VS Windows), or the version of Python, depending on what's available. See https://github.com/astral-sh/uv/pull/9827, for instance. ↩

  2. lock files typically contain a lot more metadata than package names and versions, and will also record sources (usually pypi.org) and sha256 hashes of artifacts (wheels and source distributions) to be downloaded. ↩

  3. as opposed to the previous PEP 665 it replaced ↩

  4. except in the context of running tox, but one doesn't need to know about uv to use uv-in-tox, just like one doesn't need to know about pip to run pip-in-tox (current status quo). ↩

Activity

  1. neutrinoceros commented on Jan 3, 2025

    @neutrinoceros
    ContributorAuthor

    We discussed this issue at length with @eerovaher over yesterday's telecon. I've incorporated most his suggestions in the original post.
    One other important idea he had was that instead of trying to guess an optimal updating frequency, we could make an informed decision by manually maintaining a lock file (outside of VCS) for a little while and get a sense of how many upgrades we typically go through in a month or so.
    The uv lock command actually has a --exclude-newer option that allows me to simulate what would have happened if I had this idea a couple month back, so I'll do this instead and hopefully get some data today !

  2. neutrinoceros commented on Jan 3, 2025

    @neutrinoceros
    ContributorAuthor

    I ran three small simulations, varying the update strategy:

    • every week day (weekdays)
    • only on Mondays and Thurdays (mondays-and-thursdays)
    • only on Mondays (mondays)

    My simulations are spanned over the last two months (from 2024-11-01 to 2024-12-27 to be exact). The span is completely arbitrary; I just took a recent period that spans an integer number of weeks. In all my simulations, I froze astropy to commit 7d08082 (2024-11-01) for simplicity, created a lock file at this date and ran uv lock --upgrade for each selected following update days.

    raw data (logging the output of `uv lock --upgrade`)

    weekdays

    // 2024-11-04
    Updated astropy-iers-data v0.2024.10.28.0.34.7 -> v0.2024.11.4.0.33.34
    Updated numpy v2.1.2 -> v2.1.3
    // 2024-11-07
    Updated array-api-strict v2.1 -> v2.1.1
    Updated hypothesis v6.116.0 -> v6.117.0
    // 2024-11-08
    Updated array-api-strict v2.1.1 -> v2.1.2
    Updated debugpy v1.8.7 -> v1.8.8
    Updated hypothesis v6.117.0 -> v6.118.0
    Updated packaging v24.1 -> v24.2
    // 2024-11-11
    Updated array-api-strict v2.1.2 -> v2.2
    Updated astropy-iers-data v0.2024.11.4.0.33.34 -> v0.2024.11.11.0.32.38
    Updated dask v2024.10.0 -> v2024.11.1
    Updated hypothesis v6.118.0 -> v6.118.7
    Updated identify v2.6.1 -> v2.6.2
    Updated jedi v0.19.1 -> v0.19.2
    Updated pyerfa v2.0.1.4 -> v2.0.1.5
    Updated setuptools v75.3.0 -> v75.4.0
    Updated tomli v2.0.2 -> v2.1.0
    Updated zipp v3.20.2 -> v3.21.0
    // 2024-11-12
    Updated contourpy v1.3.0 -> v1.3.1
    Updated hypothesis v6.118.7 -> v6.118.8
    // 2024-11-13
    Updated aiohttp v3.10.10 -> v3.11.0
    Updated asdf-astropy v0.6.1 -> v0.7.0
    Updated dask v2024.11.1 -> v2024.11.2
    Updated setuptools v75.4.0 -> v75.5.0
    // 2024-11-14
    Updated aiohttp v3.11.0 -> v3.11.1
    Updated coverage v7.6.4 -> v7.6.5
    Updated fonttools v4.54.1 -> v4.55.0
    // 2024-11-15
    Updated aiohttp v3.11.1 -> v3.11.2
    Updated coverage v7.6.5 -> v7.6.7
    Updated hypothesis v6.118.8 -> v6.119.1
    // 2024-11-18
    Updated astropy-iers-data v0.2024.11.11.0.32.38 -> v0.2024.11.18.0.35.2
    Updated hypothesis v6.119.1 -> v6.119.3
    Updated yarl v1.17.1 -> v1.17.2
    // 2024-11-19
    Updated aiohttp v3.11.2 -> v3.11.6
    Updated asdf v3.5.0 -> v4.0.0
    // 2024-11-20
    Updated setuptools v75.5.0 -> v75.6.0
    // 2024-11-21
    Updated aiohttp v3.11.6 -> v3.11.7
    Updated debugpy v1.8.8 -> v1.8.9
    Updated yarl v1.17.2 -> v1.18.0
    // 2024-11-22
    Updated hypothesis v6.119.3 -> v6.119.4
    Updated tornado v6.4.1 -> v6.4.2
    Updated wrapt v1.16.0 -> v1.17.0
    // 2024-11-25
    Updated astropy-iers-data v0.2024.11.18.0.35.2 -> v0.2024.11.25.0.34.48
    Updated coverage v7.6.7 -> v7.6.8
    Updated pytest-doctestplus v1.2.1 -> v1.3.0
    // 2024-11-26
    Updated identify v2.6.2 -> v2.6.3
    Updated pandas-stubs v2.2.3.241009 -> v2.2.3.241126
    Updated pyarrow v18.0.0 -> v18.1.0
    Updated virtualenv v20.27.1 -> v20.28.0
    // 2024-11-27
    Updated aiohttp v3.11.7 -> v3.11.8
    Updated tomli v2.1.0 -> v2.2.1
    // 2024-11-28
    Updated hypothesis v6.119.4 -> v6.121.0
    // 2024-11-29
    Updated hypothesis v6.121.0 -> v6.121.2
    Updated ipython v8.29.0 -> v8.30.0
    // 2024-12-02
    Updated aiohappyeyeballs v2.4.3 -> v2.4.4
    Updated aiohttp v3.11.8 -> v3.11.9
    Updated astropy-iers-data v0.2024.11.25.0.34.48 -> v0.2024.12.2.0.35.34
    Updated asttokens v2.4.1 -> v3.0.0
    Updated hypothesis v6.121.2 -> v6.122.1
    Updated matplotlib v3.9.2 -> v3.9.3
    Updated propcache v0.2.0 -> v0.2.1
    Updated pytest v8.3.3 -> v8.3.4
    Updated yarl v1.18.0 -> v1.18.3
    // 2024-12-03
    Updated dask v2024.11.2 -> v2024.12.0
    Updated fonttools v4.55.0 -> v4.55.1
    // 2024-12-04
    Updated six v1.16.0 -> v1.17.0
    // 2024-12-05
    Updated fonttools v4.55.1 -> v4.55.2
    // 2024-12-06
    Updated aiohttp v3.11.9 -> v3.11.10
    Updated coverage v7.6.8 -> v7.6.9
    // 2024-12-09
    Updated astropy-iers-data v0.2024.12.2.0.35.34 -> v0.2024.12.9.0.36.21
    Updated hypothesis v6.122.1 -> v6.122.3
    Updated numpy v2.1.3 -> v2.2.0
    // 2024-12-10
    Updated fonttools v4.55.2 -> v4.55.3
    // 2024-12-13
    Updated aiosignal v1.3.1 -> v1.3.2
    Updated debugpy v1.8.9 -> v1.8.11
    Updated matplotlib v3.9.3 -> v3.9.4
    // 2024-12-16
    Updated astropy-iers-data v0.2024.12.9.0.36.21 -> v0.2024.12.16.0.35.48
    Updated attrs v24.2.0 -> v24.3.0
    Updated certifi v2024.8.30 -> v2024.12.14
    Updated matplotlib v3.9.4 -> v3.10.0
    // 2024-12-17
    Updated aiobotocore v2.15.2 -> v2.16.0
    Updated botocore v1.35.36 -> v1.35.81
    Updated dask v2024.12.0 -> v2024.12.1
    Updated ipydatagrid v1.3.2 -> v1.4.0
    Updated pydata-sphinx-theme v0.16.0 -> v0.16.1
    // 2024-12-18
    Updated aiohttp v3.11.10 -> v3.11.11
    // 2024-12-19
    Updated fsspec v2024.10.0 -> v2024.12.0
    Updated hypothesis v6.122.3 -> v6.122.4
    Updated psutil v6.1.0 -> v6.1.1
    Updated s3fs v2024.10.0 -> v2024.12.0
    // 2024-12-20
    Updated hypothesis v6.122.4 -> v6.122.5
    Updated ipython v8.30.0 -> v8.31.0
    // 2024-12-23
    Updated astropy-iers-data v0.2024.12.16.0.35.48 -> v0.2024.12.23.0.33.24
    Updated click v8.1.7 -> v8.1.8
    Updated hypothesis v6.122.5 -> v6.123.0
    Updated jinja2 v3.1.4 -> v3.1.5
    Updated numpy v2.2.0 -> v2.2.1
    Updated types-pytz v2024.2.0.20241003 -> v2024.2.0.20241221
    Updated urllib3 v2.2.3 -> v2.3.0
    // 2024-12-24
    Updated bqplot v0.12.43 -> v0.12.44
    Updated charset-normalizer v3.4.0 -> v3.4.1
    Updated hypothesis v6.123.0 -> v6.123.1
    Updated kiwisolver v1.4.7 -> v1.4.8
    // 2024-12-26
    Updated coverage v7.6.9 -> v7.6.10
    // 2024-12-27
    Updated aiobotocore v2.16.0 -> v2.16.1
    Updated botocore v1.35.81 -> v1.35.88
    Updated hypothesis v6.123.1 -> v6.123.2
    // 2024-12-30
    Updated astropy-iers-data v0.2024.12.23.0.33.24 -> v0.2024.12.30.0.33.36
    Updated identify v2.6.3 -> v2.6.4
    // 2024-12-31
    Updated pyparsing v3.2.0 -> v3.2.1
    

    mondays-and-thursdays

    // 2024-11-07
    Updated array-api-strict v2.1 -> v2.1.1
    Updated hypothesis v6.116.0 -> v6.117.0
    // 2024-11-11
    Updated array-api-strict v2.1.1 -> v2.2
    Updated astropy-iers-data v0.2024.11.4.0.33.34 -> v0.2024.11.11.0.32.38
    Updated dask v2024.10.0 -> v2024.11.1
    Updated debugpy v1.8.7 -> v1.8.8
    Updated hypothesis v6.117.0 -> v6.118.7
    Updated identify v2.6.1 -> v2.6.2
    Updated jedi v0.19.1 -> v0.19.2
    Updated packaging v24.1 -> v24.2
    Updated pyerfa v2.0.1.4 -> v2.0.1.5
    Updated setuptools v75.3.0 -> v75.4.0
    Updated tomli v2.0.2 -> v2.1.0
    Updated zipp v3.20.2 -> v3.21.0
    // 2024-11-14
    Updated aiohttp v3.10.10 -> v3.11.1
    Updated asdf-astropy v0.6.1 -> v0.7.0
    Updated contourpy v1.3.0 -> v1.3.1
    Updated coverage v7.6.4 -> v7.6.5
    Updated dask v2024.11.1 -> v2024.11.2
    Updated fonttools v4.54.1 -> v4.55.0
    Updated hypothesis v6.118.7 -> v6.118.8
    Updated setuptools v75.4.0 -> v75.5.0
    // 2024-11-18
    Updated aiohttp v3.11.1 -> v3.11.2
    Updated astropy-iers-data v0.2024.11.11.0.32.38 -> v0.2024.11.18.0.35.2
    Updated coverage v7.6.5 -> v7.6.7
    Updated hypothesis v6.118.8 -> v6.119.3
    Updated yarl v1.17.1 -> v1.17.2
    // 2024-11-21
    Updated aiohttp v3.11.2 -> v3.11.7
    Updated asdf v3.5.0 -> v4.0.0
    Updated debugpy v1.8.8 -> v1.8.9
    Updated setuptools v75.5.0 -> v75.6.0
    Updated yarl v1.17.2 -> v1.18.0
    // 2024-11-25
    Updated astropy-iers-data v0.2024.11.18.0.35.2 -> v0.2024.11.25.0.34.48
    Updated coverage v7.6.7 -> v7.6.8
    Updated hypothesis v6.119.3 -> v6.119.4
    Updated pytest-doctestplus v1.2.1 -> v1.3.0
    Updated tornado v6.4.1 -> v6.4.2
    Updated wrapt v1.16.0 -> v1.17.0
    // 2024-11-28
    Updated aiohttp v3.11.7 -> v3.11.8
    Updated hypothesis v6.119.4 -> v6.121.0
    Updated identify v2.6.2 -> v2.6.3
    Updated pandas-stubs v2.2.3.241009 -> v2.2.3.241126
    Updated pyarrow v18.0.0 -> v18.1.0
    Updated tomli v2.1.0 -> v2.2.1
    Updated virtualenv v20.27.1 -> v20.28.0
    // 2024-12-02
    Updated aiohappyeyeballs v2.4.3 -> v2.4.4
    Updated aiohttp v3.11.8 -> v3.11.9
    Updated astropy-iers-data v0.2024.11.25.0.34.48 -> v0.2024.12.2.0.35.34
    Updated asttokens v2.4.1 -> v3.0.0
    Updated hypothesis v6.121.0 -> v6.122.1
    Updated ipython v8.29.0 -> v8.30.0
    Updated matplotlib v3.9.2 -> v3.9.3
    Updated propcache v0.2.0 -> v0.2.1
    Updated pytest v8.3.3 -> v8.3.4
    Updated yarl v1.18.0 -> v1.18.3
    // 2024-12-05
    Updated dask v2024.11.2 -> v2024.12.0
    Updated fonttools v4.55.0 -> v4.55.2
    Updated six v1.16.0 -> v1.17.0
    // 2024-12-09
    Updated aiohttp v3.11.9 -> v3.11.10
    Updated astropy-iers-data v0.2024.12.2.0.35.34 -> v0.2024.12.9.0.36.21
    Updated coverage v7.6.8 -> v7.6.9
    Updated hypothesis v6.122.1 -> v6.122.3
    Updated numpy v2.1.3 -> v2.2.0
    // 2024-12-12
    Updated fonttools v4.55.2 -> v4.55.3
    // 2024-12-16
    Updated aiosignal v1.3.1 -> v1.3.2
    Updated astropy-iers-data v0.2024.12.9.0.36.21 -> v0.2024.12.16.0.35.48
    Updated attrs v24.2.0 -> v24.3.0
    Updated certifi v2024.8.30 -> v2024.12.14
    Updated debugpy v1.8.9 -> v1.8.11
    Updated matplotlib v3.9.3 -> v3.10.0
    // 2024-12-19
    Updated aiobotocore v2.15.2 -> v2.16.0
    Updated aiohttp v3.11.10 -> v3.11.11
    Updated botocore v1.35.36 -> v1.35.81
    Updated dask v2024.12.0 -> v2024.12.1
    Updated fsspec v2024.10.0 -> v2024.12.0
    Updated hypothesis v6.122.3 -> v6.122.4
    Updated ipydatagrid v1.3.2 -> v1.4.0
    Updated psutil v6.1.0 -> v6.1.1
    Updated pydata-sphinx-theme v0.16.0 -> v0.16.1
    Updated s3fs v2024.10.0 -> v2024.12.0
    // 2024-12-23
    Updated astropy-iers-data v0.2024.12.16.0.35.48 -> v0.2024.12.23.0.33.24
    Updated click v8.1.7 -> v8.1.8
    Updated hypothesis v6.122.4 -> v6.123.0
    Updated ipython v8.30.0 -> v8.31.0
    Updated jinja2 v3.1.4 -> v3.1.5
    Updated numpy v2.2.0 -> v2.2.1
    Updated types-pytz v2024.2.0.20241003 -> v2024.2.0.20241221
    Updated urllib3 v2.2.3 -> v2.3.0
    // 2024-12-26
    Updated bqplot v0.12.43 -> v0.12.44
    Updated charset-normalizer v3.4.0 -> v3.4.1
    Updated coverage v7.6.9 -> v7.6.10
    Updated hypothesis v6.123.0 -> v6.123.1
    Updated kiwisolver v1.4.7 -> v1.4.8
    // 2024-12-30
    Updated aiobotocore v2.16.0 -> v2.16.1
    Updated astropy-iers-data v0.2024.12.23.0.33.24 -> v0.2024.12.30.0.33.36
    Updated botocore v1.35.81 -> v1.35.88
    Updated hypothesis v6.123.1 -> v6.123.2
    Updated identify v2.6.3 -> v2.6.4
    

    mondays

    // 2024-11-11
    Updated array-api-strict v2.1 -> v2.2
    Updated astropy-iers-data v0.2024.11.4.0.33.34 -> v0.2024.11.11.0.32.38
    Updated dask v2024.10.0 -> v2024.11.1
    Updated debugpy v1.8.7 -> v1.8.8
    Updated hypothesis v6.116.0 -> v6.118.7
    Updated identify v2.6.1 -> v2.6.2
    Updated jedi v0.19.1 -> v0.19.2
    Updated packaging v24.1 -> v24.2
    Updated pyerfa v2.0.1.4 -> v2.0.1.5
    Updated setuptools v75.3.0 -> v75.4.0
    Updated tomli v2.0.2 -> v2.1.0
    Updated zipp v3.20.2 -> v3.21.0
    // 2024-11-18
    Updated aiohttp v3.10.10 -> v3.11.2
    Updated asdf-astropy v0.6.1 -> v0.7.0
    Updated astropy-iers-data v0.2024.11.11.0.32.38 -> v0.2024.11.18.0.35.2
    Updated contourpy v1.3.0 -> v1.3.1
    Updated coverage v7.6.4 -> v7.6.7
    Updated dask v2024.11.1 -> v2024.11.2
    Updated fonttools v4.54.1 -> v4.55.0
    Updated hypothesis v6.118.7 -> v6.119.3
    Updated setuptools v75.4.0 -> v75.5.0
    Updated yarl v1.17.1 -> v1.17.2
    // 2024-11-25
    Updated aiohttp v3.11.2 -> v3.11.7
    Updated asdf v3.5.0 -> v4.0.0
    Updated astropy-iers-data v0.2024.11.18.0.35.2 -> v0.2024.11.25.0.34.48
    Updated coverage v7.6.7 -> v7.6.8
    Updated debugpy v1.8.8 -> v1.8.9
    Updated hypothesis v6.119.3 -> v6.119.4
    Updated pytest-doctestplus v1.2.1 -> v1.3.0
    Updated setuptools v75.5.0 -> v75.6.0
    Updated tornado v6.4.1 -> v6.4.2
    Updated wrapt v1.16.0 -> v1.17.0
    Updated yarl v1.17.2 -> v1.18.0
    // 2024-12-02
    Updated aiohappyeyeballs v2.4.3 -> v2.4.4
    Updated aiohttp v3.11.7 -> v3.11.9
    Updated astropy-iers-data v0.2024.11.25.0.34.48 -> v0.2024.12.2.0.35.34
    Updated asttokens v2.4.1 -> v3.0.0
    Updated hypothesis v6.119.4 -> v6.122.1
    Updated identify v2.6.2 -> v2.6.3
    Updated ipython v8.29.0 -> v8.30.0
    Updated matplotlib v3.9.2 -> v3.9.3
    Updated pandas-stubs v2.2.3.241009 -> v2.2.3.241126
    Updated propcache v0.2.0 -> v0.2.1
    Updated pyarrow v18.0.0 -> v18.1.0
    Updated pytest v8.3.3 -> v8.3.4
    Updated tomli v2.1.0 -> v2.2.1
    Updated virtualenv v20.27.1 -> v20.28.0
    Updated yarl v1.18.0 -> v1.18.3
    // 2024-12-09
    Updated aiohttp v3.11.9 -> v3.11.10
    Updated astropy-iers-data v0.2024.12.2.0.35.34 -> v0.2024.12.9.0.36.21
    Updated coverage v7.6.8 -> v7.6.9
    Updated dask v2024.11.2 -> v2024.12.0
    Updated fonttools v4.55.0 -> v4.55.2
    Updated hypothesis v6.122.1 -> v6.122.3
    Updated numpy v2.1.3 -> v2.2.0
    Updated six v1.16.0 -> v1.17.0
    // 2024-12-16
    Updated aiosignal v1.3.1 -> v1.3.2
    Updated astropy-iers-data v0.2024.12.9.0.36.21 -> v0.2024.12.16.0.35.48
    Updated attrs v24.2.0 -> v24.3.0
    Updated certifi v2024.8.30 -> v2024.12.14
    Updated debugpy v1.8.9 -> v1.8.11
    Updated fonttools v4.55.2 -> v4.55.3
    Updated matplotlib v3.9.3 -> v3.10.0
    // 2024-12-23
    Updated aiobotocore v2.15.2 -> v2.16.0
    Updated aiohttp v3.11.10 -> v3.11.11
    Updated astropy-iers-data v0.2024.12.16.0.35.48 -> v0.2024.12.23.0.33.24
    Updated botocore v1.35.36 -> v1.35.81
    Updated click v8.1.7 -> v8.1.8
    Updated dask v2024.12.0 -> v2024.12.1
    Updated fsspec v2024.10.0 -> v2024.12.0
    Updated hypothesis v6.122.3 -> v6.123.0
    Updated ipydatagrid v1.3.2 -> v1.4.0
    Updated ipython v8.30.0 -> v8.31.0
    Updated jinja2 v3.1.4 -> v3.1.5
    Updated numpy v2.2.0 -> v2.2.1
    Updated psutil v6.1.0 -> v6.1.1
    Updated pydata-sphinx-theme v0.16.0 -> v0.16.1
    Updated s3fs v2024.10.0 -> v2024.12.0
    Updated types-pytz v2024.2.0.20241003 -> v2024.2.0.20241221
    Updated urllib3 v2.2.3 -> v2.3.0
    // 2024-12-30
    Updated aiobotocore v2.16.0 -> v2.16.1
    Updated astropy-iers-data v0.2024.12.23.0.33.24 -> v0.2024.12.30.0.33.36
    Updated botocore v1.35.81 -> v1.35.88
    Updated bqplot v0.12.43 -> v0.12.44
    Updated charset-normalizer v3.4.0 -> v3.4.1
    Updated coverage v7.6.9 -> v7.6.10
    Updated hypothesis v6.123.0 -> v6.123.2
    Updated identify v2.6.3 -> v2.6.4
    Updated kiwisolver v1.4.7 -> v1.4.8
    

    A simple viz (update size is simply computed as the number of packages that get updated)
    out

    Finally, some reduced metrics

    metric weekdays twice weekly weekly
    Average PR count per week 4.25 1.87 0.875
    Average update size (0s excluded) 3.06 6.2 11.4
    Cumulative update size 104 93 80

    There is some tension between minimizing the number of PRs that are generated and minimizing the size of each update. I resisted the temptation to weight updates by assumed importance (assuming version numbers are semantic) because I'm not sure it would yield anything meaningful and I also don't think the scoring system can easily be made free of my biases.

  3. mhvk commented on Jan 3, 2025

    @mhvk
    Contributor

    I like this idea overall, as long as the effort associated with updating the lock files becomes truly minimal. Obviously, it should at the very least be less than what we currently spend dealing with flames every half year or so, but I think the updates probably should just be done without intervention, i.e., something like a weekly test run with unlocked dependencies, and if those work, just update the lock file without a PR (or one that auto-merges; i.e., effectively mimic what happens currently, except that the updated packages have been tested).

    Of course, the lack of oversight is also slightly worrying, since we then rely on that update mechanism to not inject something malicious (but a human would, after doing a check every week for a year, not notice anything either...). So, I think we'd perhaps need some separate check that the update only changes the lockfile and only changes version numbers in it.

    On the developer side, an advantage of using tox is that it gives an environment as similar as possible as what is run in CI. Would this still be the case? And could one turn the lockfile off? E.g., would it be as easy as, say, invoking tox -e test-alldeps-locked or tox -e test-alldeps-nolock?

    On the frequency of updates: I think weekly should be fine. Maybe this even helps with CI caching of packages? (I think that is done but am actually not sure...)

  4. neutrinoceros commented on Jan 3, 2025

    @neutrinoceros
    ContributorAuthor

    as long as the effort associated with updating the lock files becomes truly minimal.

    Well, we need to define "truly minimal" first because there are two metrics we would like to minimize that can't possibly be minimized at the same time: frequency and size of updates.

    I think the updates probably should just be done without intervention

    good news: renovate (the only service that currently supports managing uv.lock) can be configured for auto-mergers.

    I think we'd perhaps need some separate check that the update only changes the lockfile and only changes version numbers in it.

    That's a technicality, but upgrades are do not just change version numbers because lock files also record exact source urls and sha256 hashes.
    That said, I don't know how to automate merges but still somehow validate patches.

    On the developer side, an advantage of using tox is that it gives an environment as similar as possible as what is run in CI. Would this still be the case?

    yes.

    And could one turn the lockfile off? E.g., would it be as easy as, say, invoking tox -e test-alldeps-locked or tox -e test-alldeps-nolock?

    hum, I guess the simplest way to "ignore" the lock file would be to run uv lock --upgrade right before tests are run. I haven't studied question this too closely but I think it should be doable. In the meantime, as I mentioned, the lock will already be ignored for devdeps and oldestdeps.

    On the frequency of updates: I think weekly should be fine. Maybe this even helps with CI caching of packages? (I think that is done but am actually not sure...)

    • I don't think we're caching anything at the moment
    • tangent, but using a lockfile would by itself save a lot of time on dependency resolution
    • astral-sh/setup-uv supports caching (and enables it by default since v5.0)
    • given how entrenched we are with tox + OpenAstronomy workflows, I do not know how feasible it would be to leverage caching. Certainly something to think about though.
  5. pllim commented on Jan 3, 2025

    @pllim
    Member

    Expected drawbacks, side effects

    This idea is great if we are maintaining a pipeline, but this is a fundamental library. Since as you pointed out, most users don't go to great lengths to use uv (some barely uses Python because we are a diverse community), the CI will stop testing a realistic env.

    Which as you also pointed out, moves the risk of incompatibility from PR time to release time or lock file update time. Fires are unpleasant but they do not happen very often. But if we lock everything down, then they pile up and add to the stress for core maintainers. One fire at a time, we can pinpoint more easily what happened and when, but if you pile them up, then more time need to be spent to do code archeology. Which could be minimized with higher frequency update but the risk will never be zero. Also moves the work load from a subpackage maintainer that might know exactly what is going on during unlucky PR review, to only a few (2-3) of us who actually checks infrastructure stuff.

    We also inherit any potential bugs from whatever lockfile system we choose to go with. If their bot stops working for a while, small fire turns into a very big fire.

    And what about non-traditional env like pyinstaller?

    Lock file does not fix the problem, it delays the problem and move it to another plate. So I am not super enthusiastic about it, but I am open to what other core maintainers think.

  6. mhvk commented on Jan 3, 2025

    @mhvk
    Contributor

    Hmm, I do share @pllim's worry about "yet more magic that too few people understand". The more painful issues we have had are in fact with updates to our infrastructure packages (like tox...), mostly because we do not really have expertise in those.

    That said, if all the cron jobs run without the lockfiles, we do gain the separation of concerns that drives this idea, that PRs are always tested in a known-good environment.

    @neutrinoceros - I think a more minimal verifier for any auto-merge feature would simply check that the PR only actually touches the lockfiles.

  7. bsipocz commented on Jan 3, 2025

    @bsipocz
    Member

    I also do not support this. The lock file just delays and enlarges pain, similar to when projects used to or still do exact versions in their CI files and keep receiving user complaints from actual real-life scenarios. So this adds an extra layer of complexity to @pllim infrastructure chart without much added benefit.

    And besides, this would add a lot of noise of PRs for the automated lockfile updates. When a project already has a lot of non-content related PRs (it has been complained about here a lot already, then this just adds more into that same pile.

  8. mhvk commented on Jan 3, 2025

    @mhvk
    Contributor

    @bsipocz - the idea is that the lockfiles get updated very regularly, and that the PRs would be merged automatically. But the extra complexity definitely is a worry; it has to be weighed against regularly (if infrequently) getting to a state where every PR is failing because of a change in a dependency.

  9. bsipocz commented on Jan 3, 2025

    @bsipocz
    Member

    @mhvk - 1) That would still generate notification noise. 2) I do maintain projects where we use a lock file, they have their absolute usefulness for projects, website build, etc, but not for a library as if there is indeed a real problem with the dependencies the lock file update can be painful. (and if all can be done automatically, then there is no need for the lockfile to begin with as there is nothing breaking and changing). I also maintain a couple of libraries where, for historical reason, there are pinned versions in the CI config. And that is an actual hindrance and pain and we are slowly but surely removing those pins as they hide actual issues the users do run into.

    And likelihood that a PR has to be rebased and CI restarted if an update is done to the pinned numpy, or some dependency that actually matters, or would you trust a passing status and then rather fix up broken main? That scenario is even more a no on a lock file in my book.

  10. mhvk commented on Jan 3, 2025

    @mhvk
    Contributor

    I'm definitely feeling less convinced now that this is a good idea... I'd love to reduce the complexity of our infrastructure...

  11. neutrinoceros commented on Jan 3, 2025

    @neutrinoceros
    ContributorAuthor

    I feel that maybe I didn't stress this enough but problems from realistic (i.e. not locked) environments would not be made invisible in between updates: they would continue to pop up in jobs that are allowed to fail (which I pay a lot of attention to).

  12. neutrinoceros commented on Jan 3, 2025

    @neutrinoceros
    ContributorAuthor

    I'd love to reduce the complexity of our infrastructure

    Me too. But somehow I feel that proposing, e.g., dropping tox would be a lot more controversial still.

  13. mhvk commented on Jan 3, 2025

    @mhvk
    Contributor

    The advantage of tox is that it unifies things -- and we've probably all gotten used to it now... But it is a pain that numpy has its own spin on things.

  14. pllim commented on Jan 3, 2025

    @pllim
    Member

    I still don't use tox locally... 😝

  15. 20 remaining items

  16. neutrinoceros commented on Apr 28, 2025

    @neutrinoceros
    ContributorAuthor

    tooling update: pip 25.1 ships a new (and experimental) pip lock command

  17. namurphy commented on Apr 29, 2025

    @namurphy
    Contributor

    That's good news about pip lock supporting pylock.toml so quickly! 🎉🎂🎆🪩🌌🥦

    One thing to note is that the pip lock documentation says:

    The generated lock file is only guaranteed to be valid for the current python version and platform.

    I think that's a limitation of pylock.toml. In contrast, uv.lock defines environments for multiple Python versions, multiple platforms, and all dependency groups in a single file. 🌈

    For PlasmaPy, I've had a great experience using uv lock to pin requirements for CI and doc builds, since doing so greatly reduces the likelihood of other PRs having spontaneous unrelated failures. Having weekly automated PRs to update uv.lock lets us run tests with new versions of dependencies (and fix any new failures) before we start using them in CI.

    Note

    A downside of pinning requirements with uv.lock is that the diffs end up including a lot of hashes, so it was hard to tell what was changed solely from the diffs. For PlasmaPy, I made it so that the weekly automated PRs to update uv.lock include a table of the changes, like in PlasmaPy/PlasmaPy#2996. (This was before dependabot started supporting uv, so I probably should have waited a month or two before setting up my own thing. 😅)

    Important

    "Pylock" would be a great name for a cartoonish supervillain from the 1980s.

  18. eli-schwartz commented on Apr 29, 2025

    @eli-schwartz

    A downside of pinning requirements with uv.lock is that the diffs end up including a lot of hashes, so it was hard to tell what was changed solely from the diffs.

    That's why I said that I elsewhere pin requirements via weekly rolling docker containers, no need to review hashes then. :)

    In fact though, why do you care what changed? If the only thing that changes is a lockfile, and the purpose of the lockfile is to define the CI environment, all you really need to do is assume that it upgrades "stuff" and gets "stuff" from PyPI. You can treat it as an opaque binary file. The lockfile is a means to an end, and the end is not about security (which is what hashes do), just about recreating the same additional installed software on top of runs-on: ubuntu-latest, in a way that avoids "some dependency released a new version and we automatically started using it in CI, but weren't ready yet". Even committing a lockfile to git is just the simplest place to store that environment seed.

  19. namurphy commented on Apr 29, 2025

    @namurphy
    Contributor

    In fact though, why do you care what changed?

    That is an important question!

    New releases of dependencies occasionally cause CI to break due to backward incompatible changes. Tracking down the cause of the problem is a lot easier when we know exactly which dependencies were updated.

    We had a problem recently where one of PlasmaPy's Sphinx extensions got updated, and our documentation build broke. We found this out in one of the automated PRs to update the lock file, so we were able to quarantine the new version of the extension before we started using it in other PRs. Since I was able to quickly find out which packages were updated from that PR, I was able to go to the changelog for the extension and then raise an issue detailing the problem, which then got fixed upstream. In this case, knowing which packages got updated probably saved me ∼20 minutes.

  20. eli-schwartz commented on Apr 29, 2025

    @eli-schwartz

    This doesn't require a hashing lockfile, though, only a list of versions. And even that only matters when the build breaks, it doesn't matter if the update is a success.

    (It also does not strictly require the list of versions to be stored in the same repository as the project being tested, though as I said above it's certainly the simplest approach to git commit the list of versions.)

  21. namurphy commented on Apr 30, 2025

    @namurphy
    Contributor

    This doesn't require a hashing lockfile, though, only a list of versions.

    Agreed; for my use case the hashes didn't provide me with any useful information. This turned out to be what motivated me to add tables like this to the automated PRs that update uv.lock:

    package old version new version
    argcomplete 3.5.3 3.6.0

    We used to pin requirements for CI with requirements.txt-style files in the past. While the diffs were actually helpful, we had to create multiple requirements files for different versions of Python and different dependency sets. That was a mess to maintain...and then we discovered that a requirements file created for macOS does not necessarily work on Windows. 😭 😭 😭 😭 😭

    It was a big relief when we went from ∼18 requirements.txt files to a single uv.lock file that uniquely defines environments for multiple versions of Python, multiple dependency sets, and multiple operating systems with a single command (uv lock --upgrade) that typically takes ∼1–3 seconds to run.

  22. neutrinoceros commented on Jun 23, 2025

    @neutrinoceros
    ContributorAuthor

    anocdoctally, we presently have a situation with CI that I believe illustrates why all dependencies are important, and potentially blocking, with our current (non locked) CI strategy: scipy (major, recommended-but-optional dep) and jplephem (very niche, completely optional and barely tested dep) both released versions that are breaking a required test job at the same time

    This means we cannot merge anything until both problem are resolved, but they are also mutually blocking, so we need to either force-merge one fix, or resolve both (unrelated) issues in the same PR. Either way, it's pretty inconvenient, especially because I only have a solution to one problem at the moment.

    We were lucky enough that breaking updates happened in direct dependencies, which made them much easier to guess, but it could have been much worse with indirect dependencies.

  23. neutrinoceros commented on Jun 24, 2025

    @neutrinoceros
    ContributorAuthor

    Another data point: I just hit an error on readthedocs that reads

    Unknown problem
    
    We encountered a problem with a command while building your project. To resolve this error, double check your project configuration and installed dependencies are correct and have not changed recently.
    

    Now imagine having to check 1701 dependencies for "recent"2 changes.

    Footnotes

    1. yes, that's the actual number of dependencies in astropy (including dev-only and indirect ones) ↩

    2. defining this word is left as an exercise to the reader ↩

  24. mhvk commented on Jun 24, 2025

    @mhvk
    Contributor

    The current astropy meeting seems like a perfect opportunity to make a decision here -- my 2¢ is to give it a shot. I like the separation of concerns of testing PRs with a known-good environment and using cron to identify problems with new packages (and update the lock file if there are none). The real worry is added complication -- so a PR introducing it should have good descriptions (maybe simply in the workflow file -- or at least a reference there).

  25. neutrinoceros commented on Jun 24, 2025

    @neutrinoceros
    ContributorAuthor

    I'll propose this as a breakout session

  26. neutrinoceros commented on Sep 29, 2025

    @neutrinoceros
    ContributorAuthor

    Follow up: we had discussion about this at the CM, but it was inconclusive as far as I recall.

    A new data point: #18646

  27. neutrinoceros commented on Dec 12, 2025

    @neutrinoceros
    ContributorAuthor

    another data point: scipy has been using pixi.lock in CI for at least a couple weeks as far as I can tell.
    xref: scipy/scipy#21489
    xref: scipy/scipy#23637

  28. neutrinoceros commented on Jun 9, 2026

    @neutrinoceros
    ContributorAuthor

    For visibility: security issues that'd be solved by locking CI envs:
    #19879

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions