devpi-server 6.19.2__tar.gz → 6.19.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devpi_server-6.19.2 → devpi_server-6.19.3}/.flake8 +0 -1
- {devpi_server-6.19.2 → devpi_server-6.19.3}/CHANGELOG +13 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/CHANGELOG.short.rst +13 -48
- {devpi_server-6.19.2 → devpi_server-6.19.3}/PKG-INFO +14 -49
- devpi_server-6.19.3/devpi_server/__init__.py +1 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/config.py +7 -11
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore_db.py +5 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore_hash_hl.py +12 -3
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/importexport.py +84 -26
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/keyfs.py +3 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/mirror.py +19 -8
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/model.py +6 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/replica.py +2 -2
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/PKG-INFO +14 -49
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/SOURCES.txt +4 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/functional.py +0 -20
- devpi_server-6.19.3/test_devpi_server/importexportdata/dashes_v1/dataindex.json +69 -0
- devpi_server-6.19.3/test_devpi_server/importexportdata/no_history_log/dataindex.json +83 -0
- devpi_server-6.19.3/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +1 -0
- devpi_server-6.19.3/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +1 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/plugin.py +61 -25
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_importexport.py +112 -137
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_mirror.py +6 -2
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_model.py +19 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_streaming.py +24 -8
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_streaming_nginx.py +4 -2
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_streaming_replica.py +4 -2
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_streaming_replica_nginx.py +4 -2
- devpi_server-6.19.2/devpi_server/__init__.py +0 -1
- {devpi_server-6.19.2 → devpi_server-6.19.3}/LICENSE +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/MANIFEST.in +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/README.rst +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/__main__.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/auth.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/auth_basic.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/auth_devpi.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/__init__.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/crontab.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/compat.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/exceptions.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore_fs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore_fs_base.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/fileutil.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/fsck.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/genconfig.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/hookspecs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/htmlpage.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/httpclient.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/init.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/interfaces.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/keyfs_sqlite.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/keyfs_sqlite_fs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/keyfs_types.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/log.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/main.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/markers.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/middleware.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/mythread.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/normalized.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/passwd.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/py.typed +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/readonly.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/sizeof.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/view_auth.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/views.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/requires.txt +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/mypy.ini +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/pyproject.toml +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/pytest_devpi_server/__init__.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/setup.cfg +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/__init__.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/conftest.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/example.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- /devpi_server-6.19.2/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz → /devpi_server-6.19.3/test_devpi_server/importexportdata/dashes_v1/user1/dev/hello/hello-1.2_3.tar.gz +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
- /devpi_server-6.19.2/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz → /devpi_server-6.19.3/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- /devpi_server-6.19.2/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz → /devpi_server-6.19.3/test_devpi_server/importexportdata/no_history_log/user1/dev/hello/hello-1.0.tar.gz +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- /devpi_server-6.19.2/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz → /devpi_server-6.19.3/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/py.typed +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/reqmock.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/simpypi.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_auth.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_authcheck.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_config.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_conftest.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_filestore.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_filestore_fs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_fileutil.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_fsck.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_genconfig.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_keyfs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_log.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_main.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_mythread.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_nginx.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_nginx_replica.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_permissions.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_readonly.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_replica.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_replica_functional.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_stage_customizer.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_view_auth.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_views.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_views_patch.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_views_push_external.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_views_status.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.19.3}/tox.ini +0 -0
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.19.3 (2026-04-13)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Bug Fixes
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- Fix #1112: Parse simple JSON reply even with wrong content-type in reply if the body seems to contain JSON.
|
|
12
|
+
|
|
13
|
+
- Return stale project list for mirrors when the lock can't be acquired within the timeout.
|
|
14
|
+
|
|
15
|
+
- Fix importing of toxresults from devpi-server 6.5.0 to 6.9.0 where the wrong hash was stored.
|
|
16
|
+
|
|
17
|
+
|
|
5
18
|
6.19.2 (2026-03-17)
|
|
6
19
|
===================
|
|
7
20
|
|
|
@@ -9,6 +9,19 @@ Changelog
|
|
|
9
9
|
|
|
10
10
|
.. towncrier release notes start
|
|
11
11
|
|
|
12
|
+
6.19.3 (2026-04-13)
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Bug Fixes
|
|
16
|
+
---------
|
|
17
|
+
|
|
18
|
+
- Fix #1112: Parse simple JSON reply even with wrong content-type in reply if the body seems to contain JSON.
|
|
19
|
+
|
|
20
|
+
- Return stale project list for mirrors when the lock can't be acquired within the timeout.
|
|
21
|
+
|
|
22
|
+
- Fix importing of toxresults from devpi-server 6.5.0 to 6.9.0 where the wrong hash was stored.
|
|
23
|
+
|
|
24
|
+
|
|
12
25
|
6.19.2 (2026-03-17)
|
|
13
26
|
===================
|
|
14
27
|
|
|
@@ -99,51 +112,3 @@ Other Changes
|
|
|
99
112
|
|
|
100
113
|
- The filenames of some exported doczip files change due to normalization of the project name caused by changing the internals during export to allow ``--hard-links`` to work.
|
|
101
114
|
|
|
102
|
-
|
|
103
|
-
6.17.0 (2025-08-27)
|
|
104
|
-
===================
|
|
105
|
-
|
|
106
|
-
Deprecations and Removals
|
|
107
|
-
-------------------------
|
|
108
|
-
|
|
109
|
-
- Dropped support for migrating old password hashes that were replaced in devpi-server 4.2.0.
|
|
110
|
-
|
|
111
|
-
- Removed support for basic authorization in primary URL. The connection is already secured by a bearer token header.
|
|
112
|
-
|
|
113
|
-
- Removed the experimental ``--replica-cert`` option. The replica is already using a token via a shared secret, so this is redundant.
|
|
114
|
-
|
|
115
|
-
- Removed ``--replica-max-retries`` option. It wasn't implemented for async_httpget and didn't work correctly when streaming data.
|
|
116
|
-
|
|
117
|
-
Features
|
|
118
|
-
--------
|
|
119
|
-
|
|
120
|
-
- Use httpx for all data fetching for mirrors and fetch projects list asynchronously to allow update in background even after a timeout.
|
|
121
|
-
|
|
122
|
-
- Use httpx instead of requests when proxying from replicas to primary.
|
|
123
|
-
|
|
124
|
-
- Use httpx for all requests from replicas to primary.
|
|
125
|
-
|
|
126
|
-
- Use httpx when pushing releases to external index.
|
|
127
|
-
|
|
128
|
-
- Added ``mirror_ignore_serial_header`` mirror index option, which allows switching from PyPI to a mirror without serials header when set to ``True``, otherwise only stale links will be served and no updates be stored.
|
|
129
|
-
|
|
130
|
-
- The HTTP cache information for mirrored projects is persisted and re-used on server restarts.
|
|
131
|
-
|
|
132
|
-
- Added ``--file-replication-skip-indexes`` option to skip file replication for ``all``, by index type (i.e. ``mirror``) or index name (i.e. ``root/pypi``).
|
|
133
|
-
|
|
134
|
-
Bug Fixes
|
|
135
|
-
---------
|
|
136
|
-
|
|
137
|
-
- Correctly handle lists for ``Provides-Extra`` and ``License-File`` metadata in database.
|
|
138
|
-
|
|
139
|
-
- Fix traceback by returning 401 error code when using wrong password with a user that was created using an authentication plugin like devpi-ldap which passes authentication through in that case.
|
|
140
|
-
|
|
141
|
-
- Fix #1053: allow users to update their passwords when ``--restrict-modify`` is used.
|
|
142
|
-
|
|
143
|
-
- Fix #1097: return 404 when trying to POST to +simple.
|
|
144
|
-
|
|
145
|
-
Other Changes
|
|
146
|
-
-------------
|
|
147
|
-
|
|
148
|
-
- Changed User-Agent when fetching data for mirrors from just "server" to "devpi-server".
|
|
149
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.19.
|
|
3
|
+
Version: 6.19.3
|
|
4
4
|
Summary: devpi-server: backend for hosting private package indexes and PyPI on-demand mirrors
|
|
5
5
|
Maintainer-email: Florian Schulze <mail@pyfidelity.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -121,6 +121,19 @@ Changelog
|
|
|
121
121
|
|
|
122
122
|
.. towncrier release notes start
|
|
123
123
|
|
|
124
|
+
6.19.3 (2026-04-13)
|
|
125
|
+
===================
|
|
126
|
+
|
|
127
|
+
Bug Fixes
|
|
128
|
+
---------
|
|
129
|
+
|
|
130
|
+
- Fix #1112: Parse simple JSON reply even with wrong content-type in reply if the body seems to contain JSON.
|
|
131
|
+
|
|
132
|
+
- Return stale project list for mirrors when the lock can't be acquired within the timeout.
|
|
133
|
+
|
|
134
|
+
- Fix importing of toxresults from devpi-server 6.5.0 to 6.9.0 where the wrong hash was stored.
|
|
135
|
+
|
|
136
|
+
|
|
124
137
|
6.19.2 (2026-03-17)
|
|
125
138
|
===================
|
|
126
139
|
|
|
@@ -211,51 +224,3 @@ Other Changes
|
|
|
211
224
|
|
|
212
225
|
- The filenames of some exported doczip files change due to normalization of the project name caused by changing the internals during export to allow ``--hard-links`` to work.
|
|
213
226
|
|
|
214
|
-
|
|
215
|
-
6.17.0 (2025-08-27)
|
|
216
|
-
===================
|
|
217
|
-
|
|
218
|
-
Deprecations and Removals
|
|
219
|
-
-------------------------
|
|
220
|
-
|
|
221
|
-
- Dropped support for migrating old password hashes that were replaced in devpi-server 4.2.0.
|
|
222
|
-
|
|
223
|
-
- Removed support for basic authorization in primary URL. The connection is already secured by a bearer token header.
|
|
224
|
-
|
|
225
|
-
- Removed the experimental ``--replica-cert`` option. The replica is already using a token via a shared secret, so this is redundant.
|
|
226
|
-
|
|
227
|
-
- Removed ``--replica-max-retries`` option. It wasn't implemented for async_httpget and didn't work correctly when streaming data.
|
|
228
|
-
|
|
229
|
-
Features
|
|
230
|
-
--------
|
|
231
|
-
|
|
232
|
-
- Use httpx for all data fetching for mirrors and fetch projects list asynchronously to allow update in background even after a timeout.
|
|
233
|
-
|
|
234
|
-
- Use httpx instead of requests when proxying from replicas to primary.
|
|
235
|
-
|
|
236
|
-
- Use httpx for all requests from replicas to primary.
|
|
237
|
-
|
|
238
|
-
- Use httpx when pushing releases to external index.
|
|
239
|
-
|
|
240
|
-
- Added ``mirror_ignore_serial_header`` mirror index option, which allows switching from PyPI to a mirror without serials header when set to ``True``, otherwise only stale links will be served and no updates be stored.
|
|
241
|
-
|
|
242
|
-
- The HTTP cache information for mirrored projects is persisted and re-used on server restarts.
|
|
243
|
-
|
|
244
|
-
- Added ``--file-replication-skip-indexes`` option to skip file replication for ``all``, by index type (i.e. ``mirror``) or index name (i.e. ``root/pypi``).
|
|
245
|
-
|
|
246
|
-
Bug Fixes
|
|
247
|
-
---------
|
|
248
|
-
|
|
249
|
-
- Correctly handle lists for ``Provides-Extra`` and ``License-File`` metadata in database.
|
|
250
|
-
|
|
251
|
-
- Fix traceback by returning 401 error code when using wrong password with a user that was created using an authentication plugin like devpi-ldap which passes authentication through in that case.
|
|
252
|
-
|
|
253
|
-
- Fix #1053: allow users to update their passwords when ``--restrict-modify`` is used.
|
|
254
|
-
|
|
255
|
-
- Fix #1097: return 404 when trying to POST to +simple.
|
|
256
|
-
|
|
257
|
-
Other Changes
|
|
258
|
-
-------------
|
|
259
|
-
|
|
260
|
-
- Changed User-Agent when fetching data for mirrors from just "server" to "devpi-server".
|
|
261
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "6.19.3"
|
|
@@ -4,6 +4,7 @@ from . import fileutil
|
|
|
4
4
|
from . import hookspecs
|
|
5
5
|
from .interfaces import IIOFileFactory
|
|
6
6
|
from .log import threadlog
|
|
7
|
+
from copy import deepcopy
|
|
7
8
|
from devpi_common.types import cached_property
|
|
8
9
|
from devpi_common.url import URL
|
|
9
10
|
from operator import itemgetter
|
|
@@ -776,16 +777,11 @@ def get_io_file_factory(storage_info: dict) -> IIOFileFactory:
|
|
|
776
777
|
_io_file_factory: Callable
|
|
777
778
|
db_filestore = storage_info.setdefault("db_filestore", True)
|
|
778
779
|
settings = storage_info.setdefault("settings", {})
|
|
779
|
-
if db_filestore
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
fsbackend = settings.setdefault("fsbackend", "fs")
|
|
785
|
-
_io_file_factory = __import__(
|
|
786
|
-
f"filestore_{fsbackend}", globals=globals(), level=1
|
|
787
|
-
).fsiofile_factory
|
|
788
|
-
|
|
780
|
+
fsbackend = settings.setdefault("fsbackend", "db" if db_filestore else "fs")
|
|
781
|
+
_io_file_factory = __import__(
|
|
782
|
+
f"filestore_{fsbackend}", globals=globals(), level=1
|
|
783
|
+
).fsiofile_factory
|
|
784
|
+
if not db_filestore:
|
|
789
785
|
storage_info.setdefault("_test_markers", []).append("storage_with_filesystem")
|
|
790
786
|
verifyObject(IIOFileFactory, _io_file_factory)
|
|
791
787
|
|
|
@@ -1147,7 +1143,7 @@ class Config:
|
|
|
1147
1143
|
def _storage_info(self):
|
|
1148
1144
|
name = self.storage_info["name"]
|
|
1149
1145
|
settings = self.storage_info["settings"]
|
|
1150
|
-
return self._storage_info_from_name(name, settings)
|
|
1146
|
+
return deepcopy(self._storage_info_from_name(name, settings))
|
|
1151
1147
|
|
|
1152
1148
|
@property
|
|
1153
1149
|
def io_file_factory(self) -> IIOFileFactory:
|
|
@@ -43,7 +43,7 @@ class File:
|
|
|
43
43
|
basedir: Path = field(kw_only=True)
|
|
44
44
|
file_path_info: FilePathInfo = field(kw_only=True)
|
|
45
45
|
path: Path = field(kw_only=True)
|
|
46
|
-
digest_path: Path = field(kw_only=True)
|
|
46
|
+
digest_path: Path | None = field(kw_only=True)
|
|
47
47
|
|
|
48
48
|
def __repr__(self) -> str:
|
|
49
49
|
return f"<{self.__class__.__name__} {self.path}>"
|
|
@@ -70,6 +70,7 @@ class File:
|
|
|
70
70
|
with suppress(OSError):
|
|
71
71
|
self.path.unlink()
|
|
72
72
|
digest_path = self.digest_path
|
|
73
|
+
assert digest_path is not None
|
|
73
74
|
if digest_path.exists() and digest_path.stat().st_nlink == 1:
|
|
74
75
|
# if nothing else links to the digest file anymore, then remove it
|
|
75
76
|
digest_path.unlink()
|
|
@@ -87,10 +88,13 @@ class DirtyFile(File):
|
|
|
87
88
|
def commit(self) -> list[str]:
|
|
88
89
|
assert tmpsuffix_for_path(self.path) is not None
|
|
89
90
|
digest_path = self.digest_path
|
|
91
|
+
assert digest_path is not None
|
|
90
92
|
if digest_path.exists():
|
|
91
93
|
# additional check besides the content digest
|
|
92
94
|
assert getsize(digest_path) == self.getsize()
|
|
93
95
|
# link to the existing digest file
|
|
96
|
+
if self.dst_path.exists():
|
|
97
|
+
raise RuntimeError(f"{self.dst_path} -> {digest_path}")
|
|
94
98
|
hardlink(digest_path, self.dst_path)
|
|
95
99
|
# drop the temporary file
|
|
96
100
|
self.drop()
|
|
@@ -112,8 +116,11 @@ class DirtyFile(File):
|
|
|
112
116
|
@provider(IDirtyFileFactory, IFileFactory)
|
|
113
117
|
class HashHLFactory:
|
|
114
118
|
@classmethod
|
|
115
|
-
def _make_digest_path(
|
|
116
|
-
|
|
119
|
+
def _make_digest_path(
|
|
120
|
+
cls, basedir: Path, file_path_info: FilePathInfo
|
|
121
|
+
) -> Path | None:
|
|
122
|
+
if file_path_info.hash_digest is None:
|
|
123
|
+
return None
|
|
117
124
|
return basedir.joinpath("+h", *split_digest(file_path_info.hash_digest))
|
|
118
125
|
|
|
119
126
|
@classmethod
|
|
@@ -184,6 +191,7 @@ class HashHLIOFile(FSIOFileBase):
|
|
|
184
191
|
path.unlink()
|
|
185
192
|
threadlog.warn("completed file-del from crashed tx: %s", path)
|
|
186
193
|
digest_path = HashHLFactory._make_digest_path(basedir, file_path_info)
|
|
194
|
+
assert digest_path is not None
|
|
187
195
|
with suppress(OSError):
|
|
188
196
|
digest_path.unlink()
|
|
189
197
|
threadlog.warn(
|
|
@@ -193,6 +201,7 @@ class HashHLIOFile(FSIOFileBase):
|
|
|
193
201
|
dst = HashHLFactory._make_path(basedir, relpath)
|
|
194
202
|
src = basedir / rel_rename
|
|
195
203
|
digest_path = HashHLFactory._make_digest_path(basedir, file_path_info)
|
|
204
|
+
assert digest_path is not None
|
|
196
205
|
if digest_path.exists() and src.exists():
|
|
197
206
|
# additional check besides the content digest
|
|
198
207
|
assert getsize(digest_path) == getsize(src)
|
|
@@ -9,12 +9,16 @@ from .main import Fatal
|
|
|
9
9
|
from .main import init_default_indexes
|
|
10
10
|
from .main import set_state_version
|
|
11
11
|
from .main import xom_from_config
|
|
12
|
+
from .markers import absent
|
|
12
13
|
from .model import Rel
|
|
13
14
|
from .normalized import normalize_name
|
|
14
15
|
from .readonly import ReadonlyView
|
|
15
16
|
from .readonly import get_mutable_deepcopy
|
|
17
|
+
from attrs import define
|
|
18
|
+
from attrs import field
|
|
16
19
|
from collections import defaultdict
|
|
17
20
|
from devpi_common.metadata import BasenameMeta
|
|
21
|
+
from devpi_common.types import parse_hash_spec
|
|
18
22
|
from devpi_common.url import URL
|
|
19
23
|
from devpi_server import __version__ as server_version
|
|
20
24
|
from devpi_server.model import get_stage_customizer_classes
|
|
@@ -350,6 +354,81 @@ class IndexDump:
|
|
|
350
354
|
self.exporter.completed(f"{type}: {relpath} ")
|
|
351
355
|
|
|
352
356
|
|
|
357
|
+
@define(kw_only=True)
|
|
358
|
+
class Migrator:
|
|
359
|
+
dumpversion: int = field(converter=int)
|
|
360
|
+
|
|
361
|
+
@dumpversion.validator
|
|
362
|
+
def _validate_dumpversion(self, _attribute, value):
|
|
363
|
+
if value not in {1, 2}:
|
|
364
|
+
msg = f"incompatible dumpversion: {self.dumpversion}"
|
|
365
|
+
raise Fatal(msg)
|
|
366
|
+
|
|
367
|
+
def migrate(self, data: dict) -> dict:
|
|
368
|
+
data["indexes"] = {
|
|
369
|
+
k: self.migrate_index(v) for k, v in data.pop("indexes").items()
|
|
370
|
+
}
|
|
371
|
+
data["users"] = {k: self.migrate_user(v) for k, v in data.pop("users").items()}
|
|
372
|
+
return data
|
|
373
|
+
|
|
374
|
+
def migrate_file(self, data: dict) -> dict:
|
|
375
|
+
if self.dumpversion < 2:
|
|
376
|
+
# previous versions would not add a version attribute
|
|
377
|
+
data["version"] = BasenameMeta(Path(data["relpath"]).name).version
|
|
378
|
+
if "entrymapping" in data:
|
|
379
|
+
mapping = data["entrymapping"]
|
|
380
|
+
hashes = Digests(mapping.pop("hashes", {}))
|
|
381
|
+
# devpi-server-2.1 exported with md5 checksums
|
|
382
|
+
if "md5" in mapping:
|
|
383
|
+
hashes["md5"] = mapping.pop("md5")
|
|
384
|
+
# docs and toxresults didn't always have hashes stored in export dump
|
|
385
|
+
if "hash_spec" in mapping:
|
|
386
|
+
hashes.add_spec(mapping.pop("hash_spec"))
|
|
387
|
+
mapping["hashes"] = dict(hashes)
|
|
388
|
+
if "for_entrypath" in data:
|
|
389
|
+
self.migrate_toxresult(data)
|
|
390
|
+
return data
|
|
391
|
+
|
|
392
|
+
def migrate_index(self, data: dict) -> dict:
|
|
393
|
+
if "files" in data:
|
|
394
|
+
data["files"] = [self.migrate_file(v) for v in data.pop("files")]
|
|
395
|
+
indexconfig = data["indexconfig"]
|
|
396
|
+
if (
|
|
397
|
+
"uploadtrigger_jenkins" in indexconfig
|
|
398
|
+
and not indexconfig["uploadtrigger_jenkins"]
|
|
399
|
+
):
|
|
400
|
+
# remove if not set, so if the trigger was never
|
|
401
|
+
# used, you don't need to install the plugin
|
|
402
|
+
del indexconfig["uploadtrigger_jenkins"]
|
|
403
|
+
if "pypi_whitelist" in indexconfig:
|
|
404
|
+
# this was renamed in 3.0.0
|
|
405
|
+
whitelist = indexconfig.pop("pypi_whitelist")
|
|
406
|
+
if "mirror_whitelist" not in indexconfig:
|
|
407
|
+
indexconfig["mirror_whitelist"] = whitelist
|
|
408
|
+
return data
|
|
409
|
+
|
|
410
|
+
def migrate_toxresult(self, data: dict) -> dict:
|
|
411
|
+
hash_type = None
|
|
412
|
+
hash_value = None
|
|
413
|
+
parts = Path(data["relpath"]).parts
|
|
414
|
+
if len(parts) == 5:
|
|
415
|
+
hash_spec = parse_hash_spec(parts[3])
|
|
416
|
+
if hash_spec[0] is not None:
|
|
417
|
+
hash_type = hash_spec[0]().name
|
|
418
|
+
hash_value = hash_spec[1]
|
|
419
|
+
if (entrymapping := data.get("entrymapping")) is not None:
|
|
420
|
+
hashes = entrymapping.get("hashes", {})
|
|
421
|
+
if hashes.get(hash_type, absent) == hash_value:
|
|
422
|
+
# from 6.5.0 until it was fixed in 6.9.0 the hash for toxresults
|
|
423
|
+
# was for the linked file, not for the contents, so we remove them
|
|
424
|
+
# here to prevent mismatch errors
|
|
425
|
+
hashes.clear()
|
|
426
|
+
return data
|
|
427
|
+
|
|
428
|
+
def migrate_user(self, data: dict) -> dict:
|
|
429
|
+
return data
|
|
430
|
+
|
|
431
|
+
|
|
353
432
|
class Importer:
|
|
354
433
|
import_indexes: dict[str, Any]
|
|
355
434
|
|
|
@@ -440,11 +519,10 @@ class Importer:
|
|
|
440
519
|
def import_all(self, path: Path) -> None: # noqa: PLR0912
|
|
441
520
|
self.import_rootdir = path
|
|
442
521
|
json_path = path / "dataindex.json"
|
|
443
|
-
|
|
444
|
-
self.
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
raise Fatal(msg)
|
|
522
|
+
import_data = self.read_json(json_path)
|
|
523
|
+
self.import_data = Migrator(dumpversion=import_data["dumpversion"]).migrate(
|
|
524
|
+
import_data
|
|
525
|
+
)
|
|
448
526
|
self.import_users = self.import_data["users"]
|
|
449
527
|
self.import_indexes = self.import_data["indexes"]
|
|
450
528
|
self.display_import_header(path)
|
|
@@ -495,16 +573,6 @@ class Importer:
|
|
|
495
573
|
indexconfig = dict(import_index["indexconfig"])
|
|
496
574
|
if indexconfig['type'] in self.types_to_skip:
|
|
497
575
|
continue
|
|
498
|
-
if 'uploadtrigger_jenkins' in indexconfig:
|
|
499
|
-
if not indexconfig['uploadtrigger_jenkins']:
|
|
500
|
-
# remove if not set, so if the trigger was never
|
|
501
|
-
# used, you don't need to install the plugin
|
|
502
|
-
del indexconfig['uploadtrigger_jenkins']
|
|
503
|
-
if 'pypi_whitelist' in indexconfig:
|
|
504
|
-
# this was renamed in 3.0.0
|
|
505
|
-
whitelist = indexconfig.pop('pypi_whitelist')
|
|
506
|
-
if 'mirror_whitelist' not in indexconfig:
|
|
507
|
-
indexconfig['mirror_whitelist'] = whitelist
|
|
508
576
|
username, index = stagename.split("/")
|
|
509
577
|
user = self.xom.model.get_user(username)
|
|
510
578
|
assert user is not None
|
|
@@ -605,22 +673,12 @@ class Importer:
|
|
|
605
673
|
# docs and toxresults didn't always have entrymapping in export dump
|
|
606
674
|
mapping = filedesc.get("entrymapping", {})
|
|
607
675
|
hashes = Digests(mapping.get("hashes", {}))
|
|
608
|
-
# devpi-server-2.1 exported with md5 checksums
|
|
609
|
-
if "md5" in mapping:
|
|
610
|
-
hashes["md5"] = mapping["md5"]
|
|
611
|
-
# docs and toxresults didn't always have hashes stored in export dump
|
|
612
|
-
if "hash_spec" in mapping:
|
|
613
|
-
hashes.add_spec(mapping['hash_spec'])
|
|
614
676
|
# note that the actual hash_type used within devpi-server is not
|
|
615
677
|
# determined here but in store_releasefile/store_doczip/store_toxresult etc
|
|
616
678
|
hashes.update(get_hashes(f, hash_types=hashes.get_missing_hash_types()))
|
|
617
679
|
|
|
618
680
|
if filedesc["type"] == Rel.ReleaseFile:
|
|
619
|
-
|
|
620
|
-
# previous versions would not add a version attribute
|
|
621
|
-
version = BasenameMeta(p.name).version
|
|
622
|
-
else:
|
|
623
|
-
version = filedesc["version"]
|
|
681
|
+
version = filedesc["version"]
|
|
624
682
|
|
|
625
683
|
if hasattr(stage, 'store_releasefile'):
|
|
626
684
|
stage = cast("PrivateStage", stage)
|
|
@@ -742,6 +742,9 @@ class TransactionRootModel(RootModel):
|
|
|
742
742
|
del self.model_cache[key]
|
|
743
743
|
super().delete_stage(username, index)
|
|
744
744
|
|
|
745
|
+
def get_index(self, user: str, index: str | None = None) -> BaseStage | None:
|
|
746
|
+
return self.getstage(user, index)
|
|
747
|
+
|
|
745
748
|
def get_user(self, name):
|
|
746
749
|
if name not in self.model_cache:
|
|
747
750
|
self.model_cache[name] = super().get_user(name)
|
|
@@ -628,6 +628,9 @@ class MirrorStage(BaseStage):
|
|
|
628
628
|
return self.xom.setdefault_singleton(
|
|
629
629
|
self.name, "project_retrieve_times", factory=ProjectUpdateCache)
|
|
630
630
|
|
|
631
|
+
def get_projects_timeout(self, timeout: float | None) -> float:
|
|
632
|
+
return self.projects_timeout if timeout is None else timeout
|
|
633
|
+
|
|
631
634
|
async def _get_remote_projects(
|
|
632
635
|
self, projects_future: asyncio.Future[ProjectsResult]
|
|
633
636
|
) -> None:
|
|
@@ -656,7 +659,9 @@ class MirrorStage(BaseStage):
|
|
|
656
659
|
)
|
|
657
660
|
assert text is not None
|
|
658
661
|
parser: ProjectHTMLParser | ProjectJSONv1Parser
|
|
659
|
-
if
|
|
662
|
+
if (
|
|
663
|
+
response.headers.get("content-type") == SIMPLE_API_V1_JSON
|
|
664
|
+
) or text.startswith("{"):
|
|
660
665
|
parser = ProjectJSONv1Parser(response.url)
|
|
661
666
|
parser.feed(json.loads(text))
|
|
662
667
|
else:
|
|
@@ -675,7 +680,7 @@ class MirrorStage(BaseStage):
|
|
|
675
680
|
def _update_projects(
|
|
676
681
|
self, timeout: float | None = None
|
|
677
682
|
) -> tuple[dict[NormalizedName, str], bool]:
|
|
678
|
-
projects_timeout = self.
|
|
683
|
+
projects_timeout = self.get_projects_timeout(timeout)
|
|
679
684
|
projects_future = cast(
|
|
680
685
|
"asyncio.Future[ProjectsResult]", self.xom.create_future()
|
|
681
686
|
)
|
|
@@ -740,12 +745,18 @@ class MirrorStage(BaseStage):
|
|
|
740
745
|
# try without lock first
|
|
741
746
|
if not self.cache_projectnames.is_expired(self.cache_expiry):
|
|
742
747
|
return (self.cache_projectnames.get(), False)
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
748
|
+
lock = self._list_projects_perstage_lock
|
|
749
|
+
projects_timeout = self.get_projects_timeout(timeout)
|
|
750
|
+
if lock.acquire(timeout=projects_timeout):
|
|
751
|
+
try:
|
|
752
|
+
# retry in case it was updated in another thread
|
|
753
|
+
if not self.cache_projectnames.is_expired(self.cache_expiry):
|
|
754
|
+
return (self.cache_projectnames.get(), False)
|
|
755
|
+
# no fresh projects or None at all, let's go remote
|
|
756
|
+
return self._update_projects(timeout=timeout)
|
|
757
|
+
finally:
|
|
758
|
+
lock.release()
|
|
759
|
+
return (self._stale_list_projects_perstage(), True)
|
|
749
760
|
|
|
750
761
|
def list_projects_perstage(self) -> dict[str, NormalizedName | str]:
|
|
751
762
|
""" Return the project names. """
|
|
@@ -236,6 +236,9 @@ class RootModel:
|
|
|
236
236
|
del indexes[index]
|
|
237
237
|
self.xom.del_singletons(f"{username}/{index}")
|
|
238
238
|
|
|
239
|
+
def get_index(self, user: str, index: str | None = None) -> BaseStage | None:
|
|
240
|
+
return self.getstage(user, index)
|
|
241
|
+
|
|
239
242
|
def get_user(self, name: str) -> User | None:
|
|
240
243
|
user = User(self, name)
|
|
241
244
|
if user.key.exists():
|
|
@@ -492,6 +495,9 @@ class User:
|
|
|
492
495
|
from .mirror import MirrorStage
|
|
493
496
|
return MirrorStage
|
|
494
497
|
|
|
498
|
+
def get_index(self, indexname: str) -> BaseStage | None:
|
|
499
|
+
return self.getstage(indexname)
|
|
500
|
+
|
|
495
501
|
def _getstage(self, indexname, index_type, ixconfig):
|
|
496
502
|
if index_type == "mirror":
|
|
497
503
|
cls = self.MirrorStage
|
|
@@ -303,7 +303,7 @@ class PrimaryChangelogRequest:
|
|
|
303
303
|
with self.update_replica_status(start_serial):
|
|
304
304
|
keyfs = self.xom.keyfs
|
|
305
305
|
self._wait_for_serial(start_serial)
|
|
306
|
-
devpi_serial = keyfs.
|
|
306
|
+
devpi_serial = keyfs.tx.conn.last_changelog_serial
|
|
307
307
|
all_changes = []
|
|
308
308
|
raw_size = 0
|
|
309
309
|
start_time = time.time()
|
|
@@ -331,7 +331,7 @@ class PrimaryChangelogRequest:
|
|
|
331
331
|
|
|
332
332
|
keyfs = self.xom.keyfs
|
|
333
333
|
self._wait_for_serial(start_serial)
|
|
334
|
-
devpi_serial = keyfs.
|
|
334
|
+
devpi_serial = keyfs.tx.conn.last_changelog_serial
|
|
335
335
|
threadlog.info("Streaming from %s to %s", start_serial, devpi_serial)
|
|
336
336
|
|
|
337
337
|
def iter_changelog_entries():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.19.
|
|
3
|
+
Version: 6.19.3
|
|
4
4
|
Summary: devpi-server: backend for hosting private package indexes and PyPI on-demand mirrors
|
|
5
5
|
Maintainer-email: Florian Schulze <mail@pyfidelity.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -121,6 +121,19 @@ Changelog
|
|
|
121
121
|
|
|
122
122
|
.. towncrier release notes start
|
|
123
123
|
|
|
124
|
+
6.19.3 (2026-04-13)
|
|
125
|
+
===================
|
|
126
|
+
|
|
127
|
+
Bug Fixes
|
|
128
|
+
---------
|
|
129
|
+
|
|
130
|
+
- Fix #1112: Parse simple JSON reply even with wrong content-type in reply if the body seems to contain JSON.
|
|
131
|
+
|
|
132
|
+
- Return stale project list for mirrors when the lock can't be acquired within the timeout.
|
|
133
|
+
|
|
134
|
+
- Fix importing of toxresults from devpi-server 6.5.0 to 6.9.0 where the wrong hash was stored.
|
|
135
|
+
|
|
136
|
+
|
|
124
137
|
6.19.2 (2026-03-17)
|
|
125
138
|
===================
|
|
126
139
|
|
|
@@ -211,51 +224,3 @@ Other Changes
|
|
|
211
224
|
|
|
212
225
|
- The filenames of some exported doczip files change due to normalization of the project name caused by changing the internals during export to allow ``--hard-links`` to work.
|
|
213
226
|
|
|
214
|
-
|
|
215
|
-
6.17.0 (2025-08-27)
|
|
216
|
-
===================
|
|
217
|
-
|
|
218
|
-
Deprecations and Removals
|
|
219
|
-
-------------------------
|
|
220
|
-
|
|
221
|
-
- Dropped support for migrating old password hashes that were replaced in devpi-server 4.2.0.
|
|
222
|
-
|
|
223
|
-
- Removed support for basic authorization in primary URL. The connection is already secured by a bearer token header.
|
|
224
|
-
|
|
225
|
-
- Removed the experimental ``--replica-cert`` option. The replica is already using a token via a shared secret, so this is redundant.
|
|
226
|
-
|
|
227
|
-
- Removed ``--replica-max-retries`` option. It wasn't implemented for async_httpget and didn't work correctly when streaming data.
|
|
228
|
-
|
|
229
|
-
Features
|
|
230
|
-
--------
|
|
231
|
-
|
|
232
|
-
- Use httpx for all data fetching for mirrors and fetch projects list asynchronously to allow update in background even after a timeout.
|
|
233
|
-
|
|
234
|
-
- Use httpx instead of requests when proxying from replicas to primary.
|
|
235
|
-
|
|
236
|
-
- Use httpx for all requests from replicas to primary.
|
|
237
|
-
|
|
238
|
-
- Use httpx when pushing releases to external index.
|
|
239
|
-
|
|
240
|
-
- Added ``mirror_ignore_serial_header`` mirror index option, which allows switching from PyPI to a mirror without serials header when set to ``True``, otherwise only stale links will be served and no updates be stored.
|
|
241
|
-
|
|
242
|
-
- The HTTP cache information for mirrored projects is persisted and re-used on server restarts.
|
|
243
|
-
|
|
244
|
-
- Added ``--file-replication-skip-indexes`` option to skip file replication for ``all``, by index type (i.e. ``mirror``) or index name (i.e. ``root/pypi``).
|
|
245
|
-
|
|
246
|
-
Bug Fixes
|
|
247
|
-
---------
|
|
248
|
-
|
|
249
|
-
- Correctly handle lists for ``Provides-Extra`` and ``License-File`` metadata in database.
|
|
250
|
-
|
|
251
|
-
- Fix traceback by returning 401 error code when using wrong password with a user that was created using an authentication plugin like devpi-ldap which passes authentication through in that case.
|
|
252
|
-
|
|
253
|
-
- Fix #1053: allow users to update their passwords when ``--restrict-modify`` is used.
|
|
254
|
-
|
|
255
|
-
- Fix #1097: return 404 when trying to POST to +simple.
|
|
256
|
-
|
|
257
|
-
Other Changes
|
|
258
|
-
-------------
|
|
259
|
-
|
|
260
|
-
- Changed User-Agent when fetching data for mirrors from just "server" to "devpi-server".
|
|
261
|
-
|
|
@@ -112,10 +112,14 @@ test_devpi_server/importexportdata/badindexname/dataindex.json
|
|
|
112
112
|
test_devpi_server/importexportdata/badusername/dataindex.json
|
|
113
113
|
test_devpi_server/importexportdata/basescycle/dataindex.json
|
|
114
114
|
test_devpi_server/importexportdata/createdmodified/dataindex.json
|
|
115
|
+
test_devpi_server/importexportdata/dashes_v1/dataindex.json
|
|
116
|
+
test_devpi_server/importexportdata/dashes_v1/user1/dev/hello/hello-1.2_3.tar.gz
|
|
115
117
|
test_devpi_server/importexportdata/deletedbase/dataindex.json
|
|
116
118
|
test_devpi_server/importexportdata/mirrordata/dataindex.json
|
|
117
119
|
test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz
|
|
118
120
|
test_devpi_server/importexportdata/modifiedpypi/dataindex.json
|
|
121
|
+
test_devpi_server/importexportdata/no_history_log/dataindex.json
|
|
122
|
+
test_devpi_server/importexportdata/no_history_log/user1/dev/hello/hello-1.0.tar.gz
|
|
119
123
|
test_devpi_server/importexportdata/nocreatedmodified/dataindex.json
|
|
120
124
|
test_devpi_server/importexportdata/normalization/dataindex.json
|
|
121
125
|
test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz
|