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.
Files changed (139) hide show
  1. {devpi_server-6.19.2 → devpi_server-6.19.3}/.flake8 +0 -1
  2. {devpi_server-6.19.2 → devpi_server-6.19.3}/CHANGELOG +13 -0
  3. {devpi_server-6.19.2 → devpi_server-6.19.3}/CHANGELOG.short.rst +13 -48
  4. {devpi_server-6.19.2 → devpi_server-6.19.3}/PKG-INFO +14 -49
  5. devpi_server-6.19.3/devpi_server/__init__.py +1 -0
  6. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/config.py +7 -11
  7. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore_db.py +5 -0
  8. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore_hash_hl.py +12 -3
  9. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/importexport.py +84 -26
  10. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/keyfs.py +3 -0
  11. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/mirror.py +19 -8
  12. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/model.py +6 -0
  13. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/replica.py +2 -2
  14. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/PKG-INFO +14 -49
  15. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/SOURCES.txt +4 -0
  16. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/functional.py +0 -20
  17. devpi_server-6.19.3/test_devpi_server/importexportdata/dashes_v1/dataindex.json +69 -0
  18. devpi_server-6.19.3/test_devpi_server/importexportdata/no_history_log/dataindex.json +83 -0
  19. devpi_server-6.19.3/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +1 -0
  20. devpi_server-6.19.3/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +1 -0
  21. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/plugin.py +61 -25
  22. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_importexport.py +112 -137
  23. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_mirror.py +6 -2
  24. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_model.py +19 -0
  25. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_streaming.py +24 -8
  26. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_streaming_nginx.py +4 -2
  27. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_streaming_replica.py +4 -2
  28. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_streaming_replica_nginx.py +4 -2
  29. devpi_server-6.19.2/devpi_server/__init__.py +0 -1
  30. {devpi_server-6.19.2 → devpi_server-6.19.3}/LICENSE +0 -0
  31. {devpi_server-6.19.2 → devpi_server-6.19.3}/MANIFEST.in +0 -0
  32. {devpi_server-6.19.2 → devpi_server-6.19.3}/README.rst +0 -0
  33. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/__main__.py +0 -0
  34. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/auth.py +0 -0
  35. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/auth_basic.py +0 -0
  36. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/auth_devpi.py +0 -0
  37. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/__init__.py +0 -0
  38. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/crontab.template +0 -0
  39. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/devpi.service.template +0 -0
  40. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/launchd-macos.txt.template +0 -0
  41. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
  42. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
  43. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
  44. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
  45. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
  46. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
  47. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/supervisord.conf.template +0 -0
  48. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/cfg/windows-service.txt.template +0 -0
  49. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/compat.py +0 -0
  50. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/exceptions.py +0 -0
  51. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore.py +0 -0
  52. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore_fs.py +0 -0
  53. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/filestore_fs_base.py +0 -0
  54. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/fileutil.py +0 -0
  55. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/fsck.py +0 -0
  56. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/genconfig.py +0 -0
  57. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/hookspecs.py +0 -0
  58. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/htmlpage.py +0 -0
  59. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/httpclient.py +0 -0
  60. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/init.py +0 -0
  61. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/interfaces.py +0 -0
  62. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/keyfs_sqlite.py +0 -0
  63. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/keyfs_sqlite_fs.py +0 -0
  64. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/keyfs_types.py +0 -0
  65. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/log.py +0 -0
  66. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/main.py +0 -0
  67. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/markers.py +0 -0
  68. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/middleware.py +0 -0
  69. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/mythread.py +0 -0
  70. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/normalized.py +0 -0
  71. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/passwd.py +0 -0
  72. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/py.typed +0 -0
  73. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/readonly.py +0 -0
  74. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/sizeof.py +0 -0
  75. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/view_auth.py +0 -0
  76. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server/views.py +0 -0
  77. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/dependency_links.txt +0 -0
  78. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/entry_points.txt +0 -0
  79. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/requires.txt +0 -0
  80. {devpi_server-6.19.2 → devpi_server-6.19.3}/devpi_server.egg-info/top_level.txt +0 -0
  81. {devpi_server-6.19.2 → devpi_server-6.19.3}/mypy.ini +0 -0
  82. {devpi_server-6.19.2 → devpi_server-6.19.3}/pyproject.toml +0 -0
  83. {devpi_server-6.19.2 → devpi_server-6.19.3}/pytest_devpi_server/__init__.py +0 -0
  84. {devpi_server-6.19.2 → devpi_server-6.19.3}/setup.cfg +0 -0
  85. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/__init__.py +0 -0
  86. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/conftest.py +0 -0
  87. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/example.py +0 -0
  88. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
  89. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
  90. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
  91. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
  92. /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
  93. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
  94. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
  95. /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
  96. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
  97. /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
  98. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
  99. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
  100. /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
  101. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
  102. {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
  103. {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
  104. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
  105. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
  106. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
  107. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
  108. {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
  109. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
  110. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/py.typed +0 -0
  111. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/reqmock.py +0 -0
  112. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/simpypi.py +0 -0
  113. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_auth.py +0 -0
  114. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_authcheck.py +0 -0
  115. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_config.py +0 -0
  116. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_conftest.py +0 -0
  117. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_filestore.py +0 -0
  118. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_filestore_fs.py +0 -0
  119. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_fileutil.py +0 -0
  120. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_fsck.py +0 -0
  121. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_genconfig.py +0 -0
  122. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_keyfs.py +0 -0
  123. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_log.py +0 -0
  124. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_main.py +0 -0
  125. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_mirror_no_project_list.py +0 -0
  126. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_mythread.py +0 -0
  127. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_nginx.py +0 -0
  128. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_nginx_replica.py +0 -0
  129. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_permissions.py +0 -0
  130. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_readonly.py +0 -0
  131. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_replica.py +0 -0
  132. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_replica_functional.py +0 -0
  133. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_stage_customizer.py +0 -0
  134. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_view_auth.py +0 -0
  135. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_views.py +0 -0
  136. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_views_patch.py +0 -0
  137. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_views_push_external.py +0 -0
  138. {devpi_server-6.19.2 → devpi_server-6.19.3}/test_devpi_server/test_views_status.py +0 -0
  139. {devpi_server-6.19.2 → devpi_server-6.19.3}/tox.ini +0 -0
@@ -4,5 +4,4 @@ ignore = E203,E501,E704,E741,W503
4
4
  per-file-ignores =
5
5
  plugin.py:E127,E128,E231
6
6
  model.py:E128,E231
7
- test_importexport.py:E121
8
7
  test*.py:E126,E127,E128,E225,E226,E231,E251
@@ -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.2
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
- from .filestore_db import DBIOFile
781
-
782
- _io_file_factory = DBIOFile
783
- else:
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:
@@ -80,3 +80,8 @@ class DBIOFile:
80
80
 
81
81
  def _rollback(self) -> None:
82
82
  pass
83
+
84
+
85
+ @provider(IIOFileFactory)
86
+ def fsiofile_factory(conn: Any, settings: dict) -> DBIOFile:
87
+ return DBIOFile(conn, settings)
@@ -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(cls, basedir: Path, file_path_info: FilePathInfo) -> Path:
116
- assert file_path_info.hash_digest is not None
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
- self.import_data = self.read_json(json_path)
444
- self.dumpversion = self.import_data["dumpversion"]
445
- if self.dumpversion not in ("1", "2"):
446
- msg = f"incompatible dumpversion: {self.dumpversion!r}"
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
- if self.dumpversion == "1":
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 response.headers.get('content-type') == SIMPLE_API_V1_JSON:
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.projects_timeout if timeout is None else timeout
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
- with self._list_projects_perstage_lock:
744
- # retry in case it was updated in another thread
745
- if not self.cache_projectnames.is_expired(self.cache_expiry):
746
- return (self.cache_projectnames.get(), False)
747
- # no fresh projects or None at all, let's go remote
748
- return self._update_projects(timeout=timeout)
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.get_current_serial()
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.get_current_serial()
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.2
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