Restricting "open ended" releases on PyPI?

Okay, I have some data from @miketheman (thanks Mike!) on how often this behavior is used:

warehouse=> SELECT
    COUNT(DISTINCT p.id) FILTER (WHERE f.upload_time - r.created >= INTERVAL '14 days') AS days_14,
    COUNT(DISTINCT p.id) FILTER (WHERE f.upload_time - r.created >= INTERVAL '7 days')  AS days_7,
    COUNT(DISTINCT p.id) FILTER (WHERE f.upload_time - r.created >= INTERVAL '3 days')  AS days_3,
    COUNT(DISTINCT p.id) FILTER (WHERE f.upload_time - r.created >= INTERVAL '2 days')  AS days_2,
    COUNT(DISTINCT p.id) FILTER (WHERE f.upload_time - r.created >= INTERVAL '1 day')   AS days_1
FROM
    projects p
    INNER JOIN releases r ON r.project_id = p.id
    INNER JOIN release_files f ON f.release_id = r.id
WHERE
    DATE_PART('year', r.created) >= 2025;

 days_14 | days_7 | days_3 | days_2 | days_1
---------+--------+--------+--------+--------
    1295 |   1611 |   2008 |   2188 |   2478

So in the past ~year, there have been 1295 projects that have published an artifact 14+ days after the initial release and many more happening in the 1-3 day span. So if we were to implement the 14 day policy there would be 2-3 projects per day that would see the new error until the community “routed around” the error by creating new releases, instead.

Mike also provided a few example projects where this happened over the last ~5 years:

warehouse=> WITH classified AS (
    SELECT
        p.normalized_name,
        r.version,
        f.upload_time - r.created AS lag,
        CASE
            WHEN f.upload_time - r.created >= INTERVAL '14 days' THEN '14+'
            WHEN f.upload_time - r.created >= INTERVAL '7 days'  THEN '7-13'
            WHEN f.upload_time - r.created >= INTERVAL '3 days'  THEN '3-6'
            WHEN f.upload_time - r.created >= INTERVAL '2 days'  THEN '2'
            WHEN f.upload_time - r.created >= INTERVAL '1 day'   THEN '1'
        END AS bucket
    FROM
        projects p
        INNER JOIN releases r ON r.project_id = p.id
        INNER JOIN release_files f ON f.release_id = r.id
    WHERE
        DATE_PART('year', r.created) >= 2021
        AND f.upload_time - r.created >= INTERVAL '1 day'
),
ranked AS (
    SELECT
        *,
        ROW_NUMBER() OVER (PARTITION BY bucket ORDER BY random()) AS rn
    FROM classified
)
SELECT bucket, normalized_name, version, lag
FROM ranked
WHERE rn <= 10
ORDER BY bucket, rn;
 bucket |             normalized_name              |   version   |           lag
--------+------------------------------------------+-------------+--------------------------
 1      | ensmallen                                | 0.8.76      | 1 day 00:10:45.922215
 1      | cmarkgfm                                 | 2025.10.20  | 1 day 01:27:05.343397
 1      | libfsapfs-python                         | 20220501    | 1 day 14:05:15.12519
 1      | passagemath-graphs                       | 10.6.42     | 1 day 07:17:19.996306
 1      | fast-bencode                             | 1.1.6       | 1 day 14:58:30.727354
 1      | ros-sensor-msgs                          | 5.3.6       | 1 day 20:32:41.263156
 1      | sccache                                  | 0.8.2       | 1 day 10:40:01.020694
 1      | thirdai                                  | 0.9.33      | 1 day 09:56:05.610839
 1      | rscheduler                               | 0.1.0       | 1 day 07:11:43.045616
 1      | signalflow                               | 0.5.3       | 1 day 02:42:15.365097
 2      | g2o-python                               | 0.0.3       | 2 days 01:12:39.599578
 2      | libfvde-python                           | 20240502    | 2 days 12:57:30.880789
 2      | passagemath-rankwidth                    | 10.6.30     | 2 days 06:26:43.426063
 2      | netgen-mesher                            | 6.2.2306    | 2 days 02:08:19.66846
 2      | poselib                                  | 2.0.0       | 2 days 00:07:12.73351
 2      | passagemath-meataxe                      | 10.6.31rc3  | 2 days 22:00:14.091101
 2      | llama-cpp-cffi                           | 0.1.2       | 2 days 16:37:21.36003
 2      | pymatio                                  | 0.1.0       | 2 days 12:32:45.487768
 2      | kratosgeomechanicsapplication            | 9.4.3       | 2 days 20:02:06.617887
 2      | note-scoring                             | 0.1.0       | 2 days 09:54:10.648191
 3-6    | libevtx-python                           | 20240504    | 3 days 02:38:41.990573
 3-6    | bosa-connectors-binary                   | 0.0.10      | 4 days 07:02:42.279023
 3-6    | nessie-py                                | 0.1.7       | 3 days 06:05:31.521771
 3-6    | graph4nlp-cu110                          | 0.2a4       | 3 days 22:24:18.7649
 3-6    | mandelbrot-implementations-cython-direct | 0.1.0       | 6 days 20:47:03.090528
 3-6    | pillow                                   | 8.3.1       | 4 days 22:10:28.756944
 3-6    | gamsapi                                  | 49.6.1      | 3 days 06:32:15.66552
 3-6    | rocksdict                                | 0.3.24      | 3 days 03:44:11.150153
 3-6    | fast-dep                                 | 0.0.1       | 4 days 12:16:08.354373
 3-6    | trust-free                               | 2.1.4       | 3 days 20:34:26.858133
 7-13   | python-sat                               | 0.1.8.dev13 | 10 days 05:04:46.601852
 7-13   | ros-unique-identifier-msgs               | 2.5.0       | 8 days 19:42:51.64209
 7-13   | mlpack                                   | 4.7.0       | 9 days 03:24:38.198048
 7-13   | ytsaurus-yson                            | 0.4.5       | 7 days 18:37:11.562206
 7-13   | google-re2                               | 1.0         | 12 days 00:05:13.312822
 7-13   | instanttensor                            | 0.1.6       | 12 days 20:42:33.076557
 7-13   | opencv-contrib-python-headless           | 3.4.14.51   | 12 days 19:21:55.810543
 7-13   | google-re2                               | 1.0         | 12 days 00:05:19.726237
 7-13   | dlblas                                   | 0.0.2       | 7 days 08:48:55.218678
 7-13   | pyhyperscan                              | 0.1.9       | 10 days 22:54:47.973345
 14+    | xmlib-to-git                             | 1.0.0       | 474 days 18:57:09.411265
 14+    | tensorflow-metal                         | 0.8.0       | 45 days 14:21:08.78746
 14+    | rs-audio-stats                           | 1.3.9       | 134 days 17:27:47.292922
 14+    | dlib-bin                                 | 19.24.6     | 147 days 10:42:54.269826
 14+    | abstract-utilities                       | 0.2.0.58    | 40 days 20:39:22.917332
 14+    | pyzstd                                   | 0.15.0      | 106 days 17:25:03.286604
 14+    | opening-hours-py                         | 0.9.1       | 29 days 19:33:15.477529
 14+    | sbank                                    | 1.0.2       | 726 days 14:19:53.907989
 14+    | pr2codon                                 | 1.1.18      | 722 days 10:33:47.026539
 14+    | hmpty                                    | 1.5.10      | 571 days 23:20:35.227465
(50 rows)

I do see a few names I recognize in this list: Pillow, google-re2, pyzstd. Looking at a few of the examples, I see many of the longer durations being “adding wheels after only having sdists” and the medium length durations being “adding wheels with different platforms”.

2 Likes