devpi-server 6.19.2__tar.gz → 6.20.0__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.20.0}/.flake8 +0 -1
- {devpi_server-6.19.2 → devpi_server-6.20.0}/CHANGELOG +27 -0
- devpi_server-6.20.0/CHANGELOG.short.rst +97 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/PKG-INFO +28 -80
- devpi_server-6.20.0/devpi_server/__init__.py +1 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/config.py +13 -11
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/filestore.py +1 -1
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/filestore_db.py +5 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/filestore_hash_hl.py +12 -3
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/importexport.py +84 -26
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/keyfs.py +7 -1
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/keyfs_sqlite.py +6 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/mirror.py +57 -17
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/model.py +30 -5
- devpi_server-6.20.0/devpi_server/proxy.py +46 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/replica.py +6 -39
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/views.py +86 -13
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server.egg-info/PKG-INFO +28 -80
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server.egg-info/SOURCES.txt +5 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/functional.py +0 -20
- devpi_server-6.20.0/test_devpi_server/importexportdata/dashes_v1/dataindex.json +69 -0
- devpi_server-6.20.0/test_devpi_server/importexportdata/no_history_log/dataindex.json +83 -0
- devpi_server-6.20.0/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +1 -0
- devpi_server-6.20.0/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.20.0}/test_devpi_server/plugin.py +80 -34
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_importexport.py +112 -137
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_keyfs.py +17 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_mirror.py +40 -2
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_model.py +19 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_streaming.py +24 -8
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_streaming_nginx.py +4 -2
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_streaming_replica.py +4 -2
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_streaming_replica_nginx.py +4 -2
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_views.py +35 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/tox.ini +1 -1
- devpi_server-6.19.2/CHANGELOG.short.rst +0 -149
- devpi_server-6.19.2/devpi_server/__init__.py +0 -1
- {devpi_server-6.19.2 → devpi_server-6.20.0}/LICENSE +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/MANIFEST.in +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/README.rst +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/__main__.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/auth.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/auth_basic.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/auth_devpi.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/__init__.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/crontab.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/compat.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/exceptions.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/filestore_fs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/filestore_fs_base.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/fileutil.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/fsck.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/genconfig.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/hookspecs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/htmlpage.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/httpclient.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/init.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/interfaces.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/keyfs_sqlite_fs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/keyfs_types.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/log.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/main.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/markers.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/middleware.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/mythread.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/normalized.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/passwd.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/py.typed +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/readonly.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/sizeof.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server/view_auth.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server.egg-info/requires.txt +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/mypy.ini +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/pyproject.toml +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/pytest_devpi_server/__init__.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/setup.cfg +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/__init__.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/conftest.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/example.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/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.20.0/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.20.0}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/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.20.0/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.20.0}/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.20.0/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.20.0}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/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.20.0/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.20.0}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/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.20.0}/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.20.0}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/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.20.0}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/py.typed +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/reqmock.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/simpypi.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_auth.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_authcheck.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_config.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_conftest.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_filestore.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_filestore_fs.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_fileutil.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_fsck.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_genconfig.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_log.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_main.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_mythread.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_nginx.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_nginx_replica.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_permissions.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_readonly.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_replica.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_replica_functional.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_stage_customizer.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_view_auth.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_views_patch.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_views_push_external.py +0 -0
- {devpi_server-6.19.2 → devpi_server-6.20.0}/test_devpi_server/test_views_status.py +0 -0
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.20.0 (2026-04-30)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Features
|
|
9
|
+
--------
|
|
10
|
+
|
|
11
|
+
- Add experimental bare bones core-metadata ([PEP 658](https://peps.python.org/pep-0658/), [PEP 714](https://peps.python.org/pep-0714/)) support with ``--enable-core-metadata`` command line option and ``mirror_provides_core_metadata`` mirror index option. Refs #1018
|
|
12
|
+
|
|
13
|
+
Bug Fixes
|
|
14
|
+
---------
|
|
15
|
+
|
|
16
|
+
- Update replica status when the replica is waiting for new serials using the streaming changelog endpoint.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
6.19.3 (2026-04-13)
|
|
20
|
+
===================
|
|
21
|
+
|
|
22
|
+
Bug Fixes
|
|
23
|
+
---------
|
|
24
|
+
|
|
25
|
+
- Fix #1112: Parse simple JSON reply even with wrong content-type in reply if the body seems to contain JSON.
|
|
26
|
+
|
|
27
|
+
- Return stale project list for mirrors when the lock can't be acquired within the timeout.
|
|
28
|
+
|
|
29
|
+
- Fix importing of toxresults from devpi-server 6.5.0 to 6.9.0 where the wrong hash was stored.
|
|
30
|
+
|
|
31
|
+
|
|
5
32
|
6.19.2 (2026-03-17)
|
|
6
33
|
===================
|
|
7
34
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
=========
|
|
4
|
+
Changelog
|
|
5
|
+
=========
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
.. towncrier release notes start
|
|
11
|
+
|
|
12
|
+
6.20.0 (2026-04-30)
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Features
|
|
16
|
+
--------
|
|
17
|
+
|
|
18
|
+
- Add experimental bare bones core-metadata ([PEP 658](https://peps.python.org/pep-0658/), [PEP 714](https://peps.python.org/pep-0714/)) support with ``--enable-core-metadata`` command line option and ``mirror_provides_core_metadata`` mirror index option. Refs #1018
|
|
19
|
+
|
|
20
|
+
Bug Fixes
|
|
21
|
+
---------
|
|
22
|
+
|
|
23
|
+
- Update replica status when the replica is waiting for new serials using the streaming changelog endpoint.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
6.19.3 (2026-04-13)
|
|
27
|
+
===================
|
|
28
|
+
|
|
29
|
+
Bug Fixes
|
|
30
|
+
---------
|
|
31
|
+
|
|
32
|
+
- Fix #1112: Parse simple JSON reply even with wrong content-type in reply if the body seems to contain JSON.
|
|
33
|
+
|
|
34
|
+
- Return stale project list for mirrors when the lock can't be acquired within the timeout.
|
|
35
|
+
|
|
36
|
+
- Fix importing of toxresults from devpi-server 6.5.0 to 6.9.0 where the wrong hash was stored.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
6.19.2 (2026-03-17)
|
|
40
|
+
===================
|
|
41
|
+
|
|
42
|
+
Bug Fixes
|
|
43
|
+
---------
|
|
44
|
+
|
|
45
|
+
- Preserve log for documentation uploads in export.
|
|
46
|
+
|
|
47
|
+
- Any missing file on mirrors will be ignored during event processing as is already the case in other places.
|
|
48
|
+
|
|
49
|
+
- Use short timeout when project list is requested for ``has_project`` call on mirrors instead of the long one used for ``list_projects``. This prevents installers from timing out and retrying several times.
|
|
50
|
+
|
|
51
|
+
- Fix error handling for proxy requests from replica to primary.
|
|
52
|
+
|
|
53
|
+
Other Changes
|
|
54
|
+
-------------
|
|
55
|
+
|
|
56
|
+
- Removed limit of reported missing files for devpi-fsck.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
6.19.1 (2026-02-09)
|
|
60
|
+
===================
|
|
61
|
+
|
|
62
|
+
Bug Fixes
|
|
63
|
+
---------
|
|
64
|
+
|
|
65
|
+
- Pin setuptools as pyramid still requires pkg_resources.
|
|
66
|
+
|
|
67
|
+
- Always allow replicas to access deleted releases to get the proper ``410 Gone`` instead of ``403 Forbidden`` when ``devpi-lockdown`` is in use.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
6.19.0 (2026-02-06)
|
|
71
|
+
===================
|
|
72
|
+
|
|
73
|
+
Features
|
|
74
|
+
--------
|
|
75
|
+
|
|
76
|
+
- Add ``--autocreate-users`` server option.
|
|
77
|
+
Automatically creates users that don't exist in devpi, but have successfully authenticated via an authentication plugin.
|
|
78
|
+
A typical example of when to enable this would be when authenticating via an LDAP directory.
|
|
79
|
+
Automatically created users do not have passwords, and have no password hash to prevent local authentication.
|
|
80
|
+
|
|
81
|
+
- Add ``replica-files-in-sync-at``, ``replica-init-queue-finished-at`` and ``replica-metadata-in-sync-at`` to status view, the existing ``replica-in-sync-at`` is now a combination of all three instead of just metadata.
|
|
82
|
+
|
|
83
|
+
- Warn when an unknown option is found in config file to detect typos. Be aware that some commands don't use all the options, that is why this only warns instead of exiting.
|
|
84
|
+
|
|
85
|
+
- Add new ``devpiserver_user_created`` hook which can be used to create default indexes or other setup for newly created users.
|
|
86
|
+
|
|
87
|
+
Bug Fixes
|
|
88
|
+
---------
|
|
89
|
+
|
|
90
|
+
- Fix ``+status`` json encoding errors by making sure the ``FatalResponse.url`` attribute is a string.
|
|
91
|
+
|
|
92
|
+
- Ignore existing unknown index options from uninstalled plugins when patching other options with ``+=`` and ``-=``.
|
|
93
|
+
|
|
94
|
+
- Fix removal with ``-=`` of index options with default values from ``devpiserver_indexconfig_defaults`` hooks.
|
|
95
|
+
|
|
96
|
+
- Fix #1110: a list for the ``listen`` option in a config file stopped working in 6.18.0.
|
|
97
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.20.0
|
|
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,33 @@ Changelog
|
|
|
121
121
|
|
|
122
122
|
.. towncrier release notes start
|
|
123
123
|
|
|
124
|
+
6.20.0 (2026-04-30)
|
|
125
|
+
===================
|
|
126
|
+
|
|
127
|
+
Features
|
|
128
|
+
--------
|
|
129
|
+
|
|
130
|
+
- Add experimental bare bones core-metadata ([PEP 658](https://peps.python.org/pep-0658/), [PEP 714](https://peps.python.org/pep-0714/)) support with ``--enable-core-metadata`` command line option and ``mirror_provides_core_metadata`` mirror index option. Refs #1018
|
|
131
|
+
|
|
132
|
+
Bug Fixes
|
|
133
|
+
---------
|
|
134
|
+
|
|
135
|
+
- Update replica status when the replica is waiting for new serials using the streaming changelog endpoint.
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
6.19.3 (2026-04-13)
|
|
139
|
+
===================
|
|
140
|
+
|
|
141
|
+
Bug Fixes
|
|
142
|
+
---------
|
|
143
|
+
|
|
144
|
+
- Fix #1112: Parse simple JSON reply even with wrong content-type in reply if the body seems to contain JSON.
|
|
145
|
+
|
|
146
|
+
- Return stale project list for mirrors when the lock can't be acquired within the timeout.
|
|
147
|
+
|
|
148
|
+
- Fix importing of toxresults from devpi-server 6.5.0 to 6.9.0 where the wrong hash was stored.
|
|
149
|
+
|
|
150
|
+
|
|
124
151
|
6.19.2 (2026-03-17)
|
|
125
152
|
===================
|
|
126
153
|
|
|
@@ -180,82 +207,3 @@ Bug Fixes
|
|
|
180
207
|
|
|
181
208
|
- Fix #1110: a list for the ``listen`` option in a config file stopped working in 6.18.0.
|
|
182
209
|
|
|
183
|
-
|
|
184
|
-
6.18.0 (2026-01-27)
|
|
185
|
-
===================
|
|
186
|
-
|
|
187
|
-
Features
|
|
188
|
-
--------
|
|
189
|
-
|
|
190
|
-
- Store all available hashes of files.
|
|
191
|
-
|
|
192
|
-
- Validate hashes of all files during devpi-import, not only releases.
|
|
193
|
-
|
|
194
|
-
Bug Fixes
|
|
195
|
-
---------
|
|
196
|
-
|
|
197
|
-
- Apply argparse transformations on values read from config file or environment.
|
|
198
|
-
|
|
199
|
-
- Restore Python and platform info in user agent string after switch to httpx.
|
|
200
|
-
|
|
201
|
-
- Remove all database entries on project deletion instead of only emptying them.
|
|
202
|
-
|
|
203
|
-
- Fix error at end of replica streaming caused by changed behavior from switch to httpx.
|
|
204
|
-
|
|
205
|
-
- Fix #1102: The data stream was cut off after 64k when proxying from replica to primary after switching to httpx.
|
|
206
|
-
|
|
207
|
-
- Fix #1107: retry file downloads if there has been an error during download.
|
|
208
|
-
|
|
209
|
-
Other Changes
|
|
210
|
-
-------------
|
|
211
|
-
|
|
212
|
-
- 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
|
-
|
|
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.20.0"
|
|
@@ -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
|
|
@@ -244,6 +245,12 @@ def add_web_options(
|
|
|
244
245
|
help="use absolute URLs everywhere. "
|
|
245
246
|
"This will become the default at some point.")
|
|
246
247
|
|
|
248
|
+
parser.addoption(
|
|
249
|
+
"--enable-core-metadata",
|
|
250
|
+
action="store_true",
|
|
251
|
+
help="(experimental) Enable minimal core-metadata support in simple API.",
|
|
252
|
+
)
|
|
253
|
+
|
|
247
254
|
parser.addoption(
|
|
248
255
|
"--profile-requests", type=int, metavar="NUM", default=0,
|
|
249
256
|
help="profile NUM requests and print out cumulative stats. "
|
|
@@ -776,16 +783,11 @@ def get_io_file_factory(storage_info: dict) -> IIOFileFactory:
|
|
|
776
783
|
_io_file_factory: Callable
|
|
777
784
|
db_filestore = storage_info.setdefault("db_filestore", True)
|
|
778
785
|
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
|
-
|
|
786
|
+
fsbackend = settings.setdefault("fsbackend", "db" if db_filestore else "fs")
|
|
787
|
+
_io_file_factory = __import__(
|
|
788
|
+
f"filestore_{fsbackend}", globals=globals(), level=1
|
|
789
|
+
).fsiofile_factory
|
|
790
|
+
if not db_filestore:
|
|
789
791
|
storage_info.setdefault("_test_markers", []).append("storage_with_filesystem")
|
|
790
792
|
verifyObject(IIOFileFactory, _io_file_factory)
|
|
791
793
|
|
|
@@ -1147,7 +1149,7 @@ class Config:
|
|
|
1147
1149
|
def _storage_info(self):
|
|
1148
1150
|
name = self.storage_info["name"]
|
|
1149
1151
|
settings = self.storage_info["settings"]
|
|
1150
|
-
return self._storage_info_from_name(name, settings)
|
|
1152
|
+
return deepcopy(self._storage_info_from_name(name, settings))
|
|
1151
1153
|
|
|
1152
1154
|
@property
|
|
1153
1155
|
def io_file_factory(self) -> IIOFileFactory:
|
|
@@ -700,7 +700,7 @@ class BaseFileEntry:
|
|
|
700
700
|
headers = {}
|
|
701
701
|
headers["last-modified"] = str(self.last_modified)
|
|
702
702
|
m = mimetypes.guess_type(self.basename)[0]
|
|
703
|
-
headers["content-type"] =
|
|
703
|
+
headers["content-type"] = "application/octet-stream" if m is None else m
|
|
704
704
|
headers["content-length"] = str(self.file_size())
|
|
705
705
|
headers["cache-control"] = "max-age=365000000, immutable, public"
|
|
706
706
|
return headers
|
|
@@ -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)
|
|
@@ -386,7 +386,10 @@ class KeyFS:
|
|
|
386
386
|
relpaths: Iterable[RelPath],
|
|
387
387
|
) -> Iterable[FilePathInfo]:
|
|
388
388
|
for relpath in relpaths:
|
|
389
|
-
(_,
|
|
389
|
+
(_, back_serial, val) = conn.get_relpath_at(relpath, serial)
|
|
390
|
+
if val is None:
|
|
391
|
+
# the file was deleted, get the data from before
|
|
392
|
+
(_, _, val) = conn.get_relpath_at(relpath, back_serial)
|
|
390
393
|
if (
|
|
391
394
|
isinstance(val, (dict, DictViewReadonly))
|
|
392
395
|
and "hash_spec" in val
|
|
@@ -742,6 +745,9 @@ class TransactionRootModel(RootModel):
|
|
|
742
745
|
del self.model_cache[key]
|
|
743
746
|
super().delete_stage(username, index)
|
|
744
747
|
|
|
748
|
+
def get_index(self, user: str, index: str | None = None) -> BaseStage | None:
|
|
749
|
+
return self.getstage(user, index)
|
|
750
|
+
|
|
745
751
|
def get_user(self, name):
|
|
746
752
|
if name not in self.model_cache:
|
|
747
753
|
self.model_cache[name] = super().get_user(name)
|
|
@@ -464,6 +464,7 @@ class BaseStorage(object):
|
|
|
464
464
|
self._execute_conn_pragmas(sqlconn)
|
|
465
465
|
if write:
|
|
466
466
|
start_time = time.monotonic()
|
|
467
|
+
log_delay = 2
|
|
467
468
|
thread = current_thread()
|
|
468
469
|
while 1:
|
|
469
470
|
try:
|
|
@@ -475,6 +476,11 @@ class BaseStorage(object):
|
|
|
475
476
|
if hasattr(thread, "exit_if_shutdown"):
|
|
476
477
|
thread.exit_if_shutdown()
|
|
477
478
|
elapsed = time.monotonic() - start_time
|
|
479
|
+
if elapsed >= log_delay:
|
|
480
|
+
threadlog.warn(
|
|
481
|
+
"Waiting on database connection for %s seconds", log_delay
|
|
482
|
+
)
|
|
483
|
+
log_delay = log_delay * 1.5
|
|
478
484
|
if elapsed > timeout:
|
|
479
485
|
# if it takes this long, something is wrong
|
|
480
486
|
raise KeyfsTimeoutError(
|