devpi-server 6.13.0__tar.gz → 6.15.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.
Files changed (132) hide show
  1. {devpi-server-6.13.0 → devpi-server-6.15.0}/CHANGELOG +31 -0
  2. {devpi-server-6.13.0 → devpi-server-6.15.0}/MANIFEST.in +3 -1
  3. {devpi-server-6.13.0 → devpi-server-6.15.0}/PKG-INFO +32 -43
  4. devpi-server-6.15.0/devpi_server/__init__.py +1 -0
  5. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/config.py +28 -2
  6. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/hookspecs.py +6 -0
  7. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/main.py +5 -2
  8. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/mirror.py +5 -4
  9. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/model.py +2 -1
  10. devpi-server-6.15.0/devpi_server/py.typed +1 -0
  11. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/view_auth.py +15 -3
  12. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/views.py +46 -27
  13. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server.egg-info/PKG-INFO +32 -43
  14. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server.egg-info/SOURCES.txt +2 -0
  15. {devpi-server-6.13.0 → devpi-server-6.15.0}/pyproject.toml +79 -0
  16. {devpi-server-6.13.0 → devpi-server-6.15.0}/setup.py +1 -1
  17. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/plugin.py +7 -0
  18. devpi-server-6.15.0/test_devpi_server/py.typed +1 -0
  19. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_main.py +10 -2
  20. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_model.py +2 -2
  21. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_view_auth.py +18 -8
  22. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_views.py +92 -7
  23. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_views_push_external.py +54 -24
  24. {devpi-server-6.13.0 → devpi-server-6.15.0}/tox.ini +1 -1
  25. devpi-server-6.13.0/devpi_server/__init__.py +0 -1
  26. {devpi-server-6.13.0 → devpi-server-6.15.0}/.flake8 +0 -0
  27. {devpi-server-6.13.0 → devpi-server-6.15.0}/AUTHORS +0 -0
  28. {devpi-server-6.13.0 → devpi-server-6.15.0}/LICENSE +0 -0
  29. {devpi-server-6.13.0 → devpi-server-6.15.0}/README.rst +0 -0
  30. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/__main__.py +0 -0
  31. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/auth.py +0 -0
  32. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/auth_basic.py +0 -0
  33. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/auth_devpi.py +0 -0
  34. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/__init__.py +0 -0
  35. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/crontab.template +0 -0
  36. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/devpi.service.template +0 -0
  37. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/launchd-macos.txt.template +0 -0
  38. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
  39. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
  40. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
  41. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
  42. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
  43. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
  44. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/supervisord.conf.template +0 -0
  45. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/cfg/windows-service.txt.template +0 -0
  46. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/exceptions.py +0 -0
  47. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/filestore.py +0 -0
  48. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/filestore_fs.py +0 -0
  49. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/fileutil.py +0 -0
  50. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/fsck.py +0 -0
  51. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/genconfig.py +0 -0
  52. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/importexport.py +0 -0
  53. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/init.py +0 -0
  54. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/interfaces.py +0 -0
  55. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/keyfs.py +0 -0
  56. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/keyfs_sqlite.py +0 -0
  57. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/keyfs_sqlite_fs.py +0 -0
  58. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/keyfs_types.py +0 -0
  59. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/log.py +0 -0
  60. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/markers.py +0 -0
  61. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/middleware.py +0 -0
  62. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/mythread.py +0 -0
  63. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/passwd.py +0 -0
  64. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/readonly.py +0 -0
  65. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/replica.py +0 -0
  66. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/sizeof.py +0 -0
  67. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/vendor/__init__.py +0 -0
  68. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server/vendor/_pip.py +0 -0
  69. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server.egg-info/dependency_links.txt +0 -0
  70. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server.egg-info/entry_points.txt +0 -0
  71. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server.egg-info/not-zip-safe +0 -0
  72. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server.egg-info/requires.txt +0 -0
  73. {devpi-server-6.13.0 → devpi-server-6.15.0}/devpi_server.egg-info/top_level.txt +0 -0
  74. {devpi-server-6.13.0 → devpi-server-6.15.0}/pytest_devpi_server/__init__.py +0 -0
  75. {devpi-server-6.13.0 → devpi-server-6.15.0}/setup.cfg +0 -0
  76. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/__init__.py +0 -0
  77. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/conftest.py +0 -0
  78. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/example.py +0 -0
  79. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/functional.py +0 -0
  80. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
  81. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
  82. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
  83. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
  84. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
  85. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
  86. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
  87. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
  88. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
  89. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
  90. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
  91. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
  92. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
  93. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
  94. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
  95. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
  96. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
  97. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
  98. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
  99. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +0 -0
  100. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
  101. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
  102. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/reqmock.py +0 -0
  103. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/simpypi.py +0 -0
  104. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_auth.py +0 -0
  105. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_authcheck.py +0 -0
  106. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_config.py +0 -0
  107. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_conftest.py +0 -0
  108. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_filestore.py +0 -0
  109. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_fileutil.py +0 -0
  110. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_fsck.py +0 -0
  111. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_genconfig.py +0 -0
  112. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_importexport.py +0 -0
  113. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_keyfs.py +0 -0
  114. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_keyfs_sqlite_fs.py +0 -0
  115. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_log.py +0 -0
  116. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_mirror.py +0 -0
  117. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_mirror_no_project_list.py +0 -0
  118. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_mythread.py +0 -0
  119. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_nginx.py +0 -0
  120. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_nginx_replica.py +0 -0
  121. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_permissions.py +0 -0
  122. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_readonly.py +0 -0
  123. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_replica.py +0 -0
  124. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_replica_functional.py +0 -0
  125. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_reqmock.py +0 -0
  126. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_stage_customizer.py +0 -0
  127. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_streaming.py +0 -0
  128. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_streaming_nginx.py +0 -0
  129. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_streaming_replica.py +0 -0
  130. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
  131. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_views_patch.py +0 -0
  132. {devpi-server-6.13.0 → devpi-server-6.15.0}/test_devpi_server/test_views_status.py +0 -0
@@ -2,6 +2,37 @@
2
2
 
3
3
  .. towncrier release notes start
4
4
 
5
+ 6.15.0 (2025-05-18)
6
+ ===================
7
+
8
+ Features
9
+ --------
10
+
11
+ - Add ``--connection-limit`` option to devpi-server passed on to waitress.
12
+
13
+
14
+
15
+ 6.14.0 (2024-10-16)
16
+ ===================
17
+
18
+ Features
19
+ --------
20
+
21
+ - Allow pushing of versions which only have documentation and no releases.
22
+
23
+ - Allow pushing of release files only with no documentation. Requires devpi-client 7.2.0.
24
+
25
+ - Allow pushing of documentation only with no release files. Requires devpi-client 7.2.0.
26
+
27
+
28
+
29
+ Bug Fixes
30
+ ---------
31
+
32
+ - No longer automatically "register" a project when pushing releases to PyPI. The reply changed from HTTP status 410 to 400 breaking the upload. With devpi-client 7.2.0 there is a ``--register-project`` option if it is still required for some other package registry.
33
+
34
+
35
+
5
36
  6.13.0 (2024-09-19)
6
37
  ===================
7
38
 
@@ -1,4 +1,6 @@
1
1
  include .flake8 CHANGELOG *.ini *.rst LICENSE
2
- recursive-include devpi_server *.template
2
+ recursive-include devpi_server py.typed
3
+ recursive-include devpi_server *.template
4
+ recursive-include test_devpi_server py.typed
3
5
  recursive-include test_devpi_server *.py
4
6
  recursive-include test_devpi_server/importexportdata *.gz* *.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devpi-server
3
- Version: 6.13.0
3
+ Version: 6.15.0
4
4
  Summary: devpi-server: reliable private and pypi.org caching server
5
5
  Home-page: https://devpi.net
6
6
  Maintainer: Florian Schulze
@@ -105,6 +105,37 @@ Changelog
105
105
 
106
106
  .. towncrier release notes start
107
107
 
108
+ 6.15.0 (2025-05-18)
109
+ ===================
110
+
111
+ Features
112
+ --------
113
+
114
+ - Add ``--connection-limit`` option to devpi-server passed on to waitress.
115
+
116
+
117
+
118
+ 6.14.0 (2024-10-16)
119
+ ===================
120
+
121
+ Features
122
+ --------
123
+
124
+ - Allow pushing of versions which only have documentation and no releases.
125
+
126
+ - Allow pushing of release files only with no documentation. Requires devpi-client 7.2.0.
127
+
128
+ - Allow pushing of documentation only with no release files. Requires devpi-client 7.2.0.
129
+
130
+
131
+
132
+ Bug Fixes
133
+ ---------
134
+
135
+ - No longer automatically "register" a project when pushing releases to PyPI. The reply changed from HTTP status 410 to 400 breaking the upload. With devpi-client 7.2.0 there is a ``--register-project`` option if it is still required for some other package registry.
136
+
137
+
138
+
108
139
  6.13.0 (2024-09-19)
109
140
  ===================
110
141
 
@@ -172,45 +203,3 @@ Bug Fixes
172
203
  - Return json data if toxresult upload is forbidden.
173
204
 
174
205
 
175
-
176
- 6.11.0 (2024-04-20)
177
- ===================
178
-
179
- Features
180
- --------
181
-
182
- - The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
183
-
184
- - Fix #983: Add plugin hook for mirror authentication header.
185
-
186
-
187
-
188
- Bug Fixes
189
- ---------
190
-
191
- - Preserve last modified of docs and toxresults during export/import.
192
-
193
- - Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
194
-
195
-
196
-
197
- 6.10.0 (2023-12-19)
198
- ===================
199
-
200
- Features
201
- --------
202
-
203
- - Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
204
-
205
- - Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
206
-
207
-
208
-
209
- Bug Fixes
210
- ---------
211
-
212
- - Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
213
-
214
- - Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
215
-
216
-
@@ -0,0 +1 @@
1
+ __version__ = '6.15.0'
@@ -56,6 +56,24 @@ def get_pluginmanager(load_entrypoints=True):
56
56
  return pm
57
57
 
58
58
 
59
+ def traced_pluggy_call(hook, **caller_kwargs):
60
+ firstresult = hook.spec.opts.get("firstresult", False) if hook.spec else False
61
+ results = []
62
+ plugin_names = []
63
+ hookimpls = hook._hookimpls if hasattr(hook, '_hookimpls') else hook.get_hookimpls()
64
+ for hook_impl in reversed(hookimpls):
65
+ args = [caller_kwargs[argname] for argname in hook_impl.argnames]
66
+ res = hook_impl.function(*args)
67
+ if res is not None:
68
+ results.append(res)
69
+ plugin_names.append(hook_impl.plugin_name)
70
+ if firstresult:
71
+ break
72
+ if firstresult:
73
+ results = results[0] if results else None
74
+ return (results, plugin_names)
75
+
76
+
59
77
  def add_help_option(parser, pluginmanager):
60
78
  parser.addoption(
61
79
  "-h", "--help",
@@ -155,6 +173,11 @@ def add_web_options(parser, pluginmanager):
155
173
  default=50,
156
174
  help="number of threads to start for serving clients.")
157
175
 
176
+ parser.addoption(
177
+ "--connection-limit", type=int,
178
+ default=100,
179
+ help="maximum number of simultaneous client connections.")
180
+
158
181
  parser.addoption(
159
182
  "--trusted-proxy", type=str,
160
183
  help="IP address of proxy we trust. See waitress documentation.")
@@ -446,8 +469,9 @@ def find_config_file():
446
469
  config_files.append(config_file)
447
470
  if len(config_files) > 1:
448
471
  log.warning("Multiple configuration files found:\n%s", "\n".join(config_files))
449
- if len(config_files):
450
- return config_files[-1]
472
+ if not config_files:
473
+ return None
474
+ return config_files[-1]
451
475
 
452
476
 
453
477
  class InvalidConfigError(ValueError):
@@ -680,6 +704,8 @@ class Config(object):
680
704
  kwargs["trusted_proxy_count"] = self.args.trusted_proxy_count
681
705
  if self.args.trusted_proxy_headers is not None:
682
706
  kwargs["trusted_proxy_headers"] = self.args.trusted_proxy_headers
707
+ if self.args.connection_limit is not None:
708
+ kwargs["connection_limit"] = self.args.connection_limit
683
709
  return dict(kwargs=kwargs, addresses=addresses)
684
710
 
685
711
  @cached_property
@@ -158,6 +158,12 @@ def devpiserver_get_identity(request, credentials):
158
158
  """
159
159
 
160
160
 
161
+ @hookspec
162
+ def devpiserver_identity_loaded(request, credential_plugin_name, identity_plugin_name, identity):
163
+ """ called when identity is loaded for a request.
164
+ """
165
+
166
+
161
167
  @hookspec(warn_on_impl=DeprecationWarning("Use new devpiserver_auth_request hook instead"))
162
168
  def devpiserver_auth_user(userdict, username, password):
163
169
  """return dict containing authentication validation results.
@@ -365,9 +365,12 @@ class XOM:
365
365
 
366
366
  @cached_property
367
367
  def supported_features(self):
368
- results = set((
368
+ results = {
369
+ 'push-no-docs',
370
+ 'push-only-docs',
371
+ 'push-register-project',
369
372
  'server-keyvalue-parsing',
370
- ))
373
+ }
371
374
  for features in self.config.hook.devpiserver_get_features():
372
375
  results.update(features)
373
376
  return tuple(sorted(results))
@@ -120,9 +120,9 @@ class IndexParser:
120
120
  appear later are ignored.
121
121
  """
122
122
  entry = self.basename2link.get(newlink.basename)
123
- if entry is None or not entry.hash_spec and (newlink.hash_spec or (
123
+ if entry is None or (not entry.hash_spec and (newlink.hash_spec or (
124
124
  not entry.requires_python and newlink.requires_python
125
- )):
125
+ ))):
126
126
  self.basename2link[newlink.basename] = newlink
127
127
  threadlog.debug("indexparser: adding link %s", newlink)
128
128
  else:
@@ -324,9 +324,9 @@ class MirrorStage(BaseStage):
324
324
  if key == "mirror_cache_expiry":
325
325
  try:
326
326
  value = int(value)
327
- except (TypeError, ValueError):
327
+ except (TypeError, ValueError) as e:
328
328
  raise self.InvalidIndexconfig([
329
- "'mirror_cache_expiry' option must be an integer"])
329
+ "'mirror_cache_expiry' option must be an integer"]) from e
330
330
  return value
331
331
  if key == "mirror_no_project_list":
332
332
  return ensure_boolean(value)
@@ -334,6 +334,7 @@ class MirrorStage(BaseStage):
334
334
  return ensure_boolean(value)
335
335
  if key in ("custom_data", "description", "mirror_web_url_fmt", "title"):
336
336
  return value
337
+ return None
337
338
 
338
339
  def delete(self):
339
340
  # delete all projects on this index
@@ -1770,7 +1770,8 @@ class SimplelinkMeta:
1770
1770
  __slots__ = (
1771
1771
  '__basename', '__cmpval', '__ext', '__hash_spec',
1772
1772
  '__name', '__path', '__url', '__version',
1773
- 'key', 'href', 'require_python', 'yanked')
1773
+ 'href', 'key', 'require_python', 'yanked',
1774
+ )
1774
1775
 
1775
1776
  def __init__(self, link_info):
1776
1777
  self.__basename = notset
@@ -0,0 +1 @@
1
+ partial
@@ -4,6 +4,7 @@ from devpi_common.types import ensure_unicode
4
4
  from devpi_common.validation import normalize_name
5
5
  from devpi_server.auth import Auth
6
6
  from devpi_server.config import hookimpl
7
+ from devpi_server.config import traced_pluggy_call
7
8
  from devpi_server.views import abort
8
9
  from devpi_server.model import BaseStage
9
10
  from devpi_server.model import UpstreamError
@@ -218,12 +219,23 @@ class DevpiSecurityPolicy:
218
219
  return [('WWW-Authenticate', 'Basic realm="%s"' % self.realm)]
219
220
 
220
221
  def _get_credentials(self, request):
221
- return self.hook.devpiserver_get_credentials(request=request)
222
+ return traced_pluggy_call(
223
+ self.hook.devpiserver_get_credentials,
224
+ request=request)
222
225
 
223
226
  def load_identity(self, request):
224
- credentials = self._get_credentials(request)
225
- return self.hook.devpiserver_get_identity(
227
+ (credentials, plugin_names) = self._get_credentials(request)
228
+ credential_plugin_name = plugin_names[0] if plugin_names else None
229
+ (result, plugin_names) = traced_pluggy_call(
230
+ self.hook.devpiserver_get_identity,
226
231
  request=request, credentials=credentials)
232
+ identity_plugin_name = plugin_names[0] if plugin_names else None
233
+ self.hook.devpiserver_identity_loaded(
234
+ request=request,
235
+ credential_plugin_name=credential_plugin_name,
236
+ identity_plugin_name=identity_plugin_name,
237
+ identity=result)
238
+ return result
227
239
 
228
240
  def identity(self, request):
229
241
  return self.identity_cache.get_or_create(request)
@@ -10,6 +10,7 @@ from devpi_common.url import URL
10
10
  from devpi_common.metadata import get_pyversion_filetype
11
11
  import devpi_server
12
12
  from html import escape
13
+ from http import HTTPStatus
13
14
  from lazy import lazy
14
15
  from operator import attrgetter
15
16
  from pluggy import HookimplMarker
@@ -141,7 +142,7 @@ class HTTPResponse(HTTPSuccessful):
141
142
  Exception.__init__(self)
142
143
 
143
144
 
144
- def apireturn(code, message=None, result=None, type=None):
145
+ def apiresult(code, message=None, result=None, type=None): # noqa: A002
145
146
  d = dict()
146
147
  if result is not None:
147
148
  assert type is not None
@@ -151,7 +152,11 @@ def apireturn(code, message=None, result=None, type=None):
151
152
  d["message"] = message
152
153
  data = json.dumps(d, indent=2) + "\n"
153
154
  headers = {"content-type": "application/json"}
154
- raise HTTPResponse(body=data, status=code, headers=headers)
155
+ return HTTPResponse(body=data, status=code, headers=headers)
156
+
157
+
158
+ def apireturn(code, message=None, result=None, type=None): # noqa: A002
159
+ raise apiresult(code, message=message, result=result, type=type)
155
160
 
156
161
 
157
162
  def json_preferred(request):
@@ -1007,8 +1012,8 @@ class PyPIView:
1007
1012
  stage = self.context.stage
1008
1013
  pushdata = getjson(request)
1009
1014
  try:
1010
- name = pushdata["name"]
1011
- version = pushdata["version"]
1015
+ name = pushdata.pop("name")
1016
+ version = pushdata.pop("version")
1012
1017
  except KeyError:
1013
1018
  apireturn(400, message="no name/version specified in json")
1014
1019
 
@@ -1018,14 +1023,21 @@ class PyPIView:
1018
1023
  except stage.MissesRegistration:
1019
1024
  apireturn(400, "there are no files for %s-%s on stage %s" % (
1020
1025
  name, version, stage.name))
1026
+ rels = {'releasefile', 'doczip', 'toxresult'}
1027
+ no_docs = pushdata.pop("no_docs", False)
1028
+ only_docs = pushdata.pop("only_docs", False)
1029
+ if no_docs and only_docs:
1030
+ return apiresult(400, "can't use 'no_docs' and 'only_docs' together")
1031
+ if no_docs:
1032
+ rels.remove('doczip')
1033
+ elif only_docs:
1034
+ rels = {'doczip'}
1021
1035
  links = {
1022
1036
  rel: sorted(linkstore.get_links(rel=rel), key=attrgetter('basename'))
1023
- for rel in ('releasefile', 'doczip', 'toxresult')}
1024
- if not links["releasefile"]:
1025
- self.log.info("%s: no release files for version %s-%s" %
1026
- (stage.name, name, version))
1027
- apireturn(404, message="no release/files found for %s-%s" % (
1028
- name, version))
1037
+ for rel in rels}
1038
+ if not any(links.values()):
1039
+ self.log.info("%s: no files for version %s %s", stage.name, name, version)
1040
+ apireturn(404, f"no release/files found for {name} {version}")
1029
1041
 
1030
1042
  if not request.has_permission("pkg_read"):
1031
1043
  abort(request, 403, "package read forbidden")
@@ -1033,8 +1045,11 @@ class PyPIView:
1033
1045
  metadata = linkstore.metadata
1034
1046
 
1035
1047
  results = []
1036
- targetindex = pushdata.get("targetindex", None)
1048
+ targetindex = pushdata.pop("targetindex", None)
1037
1049
  if targetindex is not None: # in-server push
1050
+ if pushdata:
1051
+ keys = ', '.join(sorted(pushdata.keys()))
1052
+ return apiresult(400, f"unknown additional options: {keys}")
1038
1053
  parts = targetindex.split("/")
1039
1054
  if len(parts) != 2:
1040
1055
  apireturn(400, message="targetindex not in format user/index")
@@ -1057,6 +1072,7 @@ class PyPIView:
1057
1072
  return apireturn(502, e.args[0])
1058
1073
  apireturn(200, result=results, type="actionlog")
1059
1074
  else:
1075
+ register_project = pushdata.pop("register_project", False)
1060
1076
  posturl = pushdata["posturl"]
1061
1077
  username = pushdata["username"]
1062
1078
  password = pushdata["password"]
@@ -1064,22 +1080,25 @@ class PyPIView:
1064
1080
  # prepare metadata for submission
1065
1081
  metadata[":action"] = "submit"
1066
1082
  metadata["metadata_version"] = "2.1"
1067
- self.log.info("registering %s-%s to %s", name, version, posturl)
1068
1083
  session = new_requests_session(agent=("server", server_version))
1069
1084
  with contextlib.closing(session):
1070
- try:
1071
- r = session.post(posturl, data=metadata, auth=pypiauth)
1072
- r.close()
1073
- except Exception as e:
1074
- exc_msg = ''.join(traceback.format_exception_only(e.__class__, e))
1075
- results.append((-1, "exception on register:", exc_msg))
1076
- apireturn(502, result=results, type="actionlog")
1077
- self.log.debug("register returned: %s", r.status_code)
1078
- results.append((r.status_code, "register", name, version))
1079
- ok_codes = (200, 201, 410)
1080
- proceed = (r.status_code in ok_codes)
1085
+ ok_codes = {HTTPStatus.OK, HTTPStatus.CREATED}
1086
+ if register_project:
1087
+ self.log.info("registering %s-%s to %s", name, version, posturl)
1088
+ try:
1089
+ r = session.post(posturl, data=metadata, auth=pypiauth)
1090
+ r.close()
1091
+ except Exception as e: # noqa: BLE001
1092
+ exc_msg = ''.join(traceback.format_exception_only(e.__class__, e))
1093
+ results.append((-1, "exception on register:", exc_msg))
1094
+ apireturn(502, result=results, type="actionlog")
1095
+ self.log.debug("register returned: %s", r.status_code)
1096
+ results.append((r.status_code, "register", name, version))
1097
+ proceed = (r.status_code in ok_codes | {HTTPStatus.GONE})
1098
+ else:
1099
+ proceed = True
1081
1100
  if proceed:
1082
- for link in links["releasefile"]:
1101
+ for link in links.get("releasefile", ()):
1083
1102
  entry = link.entry
1084
1103
  file_metadata = metadata.copy()
1085
1104
  file_metadata[":action"] = "file_upload"
@@ -1106,7 +1125,7 @@ class PyPIView:
1106
1125
  results.append((
1107
1126
  r.status_code, "upload", entry.relpath, text))
1108
1127
  r.close()
1109
- if links["doczip"]:
1128
+ if links.get("doczip", ()):
1110
1129
  doc_metadata = metadata.copy()
1111
1130
  doc_metadata[":action"] = "doc_upload"
1112
1131
  doczip = links["doczip"][0].entry.file_get_content()
@@ -1128,7 +1147,7 @@ class PyPIView:
1128
1147
 
1129
1148
  def _push_links(self, links, target_stage, name, version):
1130
1149
  stage = self.context.stage
1131
- for link in links["releasefile"]:
1150
+ for link in links.get("releasefile", ()):
1132
1151
  entry = link.entry
1133
1152
  logs = link.get_logs()
1134
1153
  del link
@@ -1170,7 +1189,7 @@ class PyPIView:
1170
1189
  src=self.context.stage.name,
1171
1190
  dst=target_stage.name)
1172
1191
  yield (200, "store_toxresult", tlink.entrypath)
1173
- for link in links["doczip"]:
1192
+ for link in links.get("doczip", ()):
1174
1193
  with link.entry.file_open_read() as doczip:
1175
1194
  new_link = target_stage.store_doczip(
1176
1195
  name, version, doczip, hashes=link.entry.hashes)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devpi-server
3
- Version: 6.13.0
3
+ Version: 6.15.0
4
4
  Summary: devpi-server: reliable private and pypi.org caching server
5
5
  Home-page: https://devpi.net
6
6
  Maintainer: Florian Schulze
@@ -105,6 +105,37 @@ Changelog
105
105
 
106
106
  .. towncrier release notes start
107
107
 
108
+ 6.15.0 (2025-05-18)
109
+ ===================
110
+
111
+ Features
112
+ --------
113
+
114
+ - Add ``--connection-limit`` option to devpi-server passed on to waitress.
115
+
116
+
117
+
118
+ 6.14.0 (2024-10-16)
119
+ ===================
120
+
121
+ Features
122
+ --------
123
+
124
+ - Allow pushing of versions which only have documentation and no releases.
125
+
126
+ - Allow pushing of release files only with no documentation. Requires devpi-client 7.2.0.
127
+
128
+ - Allow pushing of documentation only with no release files. Requires devpi-client 7.2.0.
129
+
130
+
131
+
132
+ Bug Fixes
133
+ ---------
134
+
135
+ - No longer automatically "register" a project when pushing releases to PyPI. The reply changed from HTTP status 410 to 400 breaking the upload. With devpi-client 7.2.0 there is a ``--register-project`` option if it is still required for some other package registry.
136
+
137
+
138
+
108
139
  6.13.0 (2024-09-19)
109
140
  ===================
110
141
 
@@ -172,45 +203,3 @@ Bug Fixes
172
203
  - Return json data if toxresult upload is forbidden.
173
204
 
174
205
 
175
-
176
- 6.11.0 (2024-04-20)
177
- ===================
178
-
179
- Features
180
- --------
181
-
182
- - The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
183
-
184
- - Fix #983: Add plugin hook for mirror authentication header.
185
-
186
-
187
-
188
- Bug Fixes
189
- ---------
190
-
191
- - Preserve last modified of docs and toxresults during export/import.
192
-
193
- - Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
194
-
195
-
196
-
197
- 6.10.0 (2023-12-19)
198
- ===================
199
-
200
- Features
201
- --------
202
-
203
- - Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
204
-
205
- - Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
206
-
207
-
208
-
209
- Bug Fixes
210
- ---------
211
-
212
- - Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
213
-
214
- - Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
215
-
216
-
@@ -36,6 +36,7 @@ devpi_server/mirror.py
36
36
  devpi_server/model.py
37
37
  devpi_server/mythread.py
38
38
  devpi_server/passwd.py
39
+ devpi_server/py.typed
39
40
  devpi_server/readonly.py
40
41
  devpi_server/replica.py
41
42
  devpi_server/sizeof.py
@@ -68,6 +69,7 @@ test_devpi_server/conftest.py
68
69
  test_devpi_server/example.py
69
70
  test_devpi_server/functional.py
70
71
  test_devpi_server/plugin.py
72
+ test_devpi_server/py.typed
71
73
  test_devpi_server/reqmock.py
72
74
  test_devpi_server/simpypi.py
73
75
  test_devpi_server/test_auth.py
@@ -2,6 +2,85 @@
2
2
  extend = "../pyproject.toml"
3
3
 
4
4
 
5
+ [tool.ruff.format]
6
+ exclude = [
7
+ "devpi_server/__init__.py",
8
+ "devpi_server/auth.py",
9
+ "devpi_server/auth_basic.py",
10
+ "devpi_server/auth_devpi.py",
11
+ "devpi_server/config.py",
12
+ "devpi_server/exceptions.py",
13
+ "devpi_server/filestore.py",
14
+ "devpi_server/filestore_fs.py",
15
+ "devpi_server/fileutil.py",
16
+ "devpi_server/fsck.py",
17
+ "devpi_server/genconfig.py",
18
+ "devpi_server/hookspecs.py",
19
+ "devpi_server/importexport.py",
20
+ "devpi_server/init.py",
21
+ "devpi_server/interfaces.py",
22
+ "devpi_server/keyfs.py",
23
+ "devpi_server/keyfs_sqlite.py",
24
+ "devpi_server/keyfs_sqlite_fs.py",
25
+ "devpi_server/keyfs_types.py",
26
+ "devpi_server/log.py",
27
+ "devpi_server/main.py",
28
+ "devpi_server/markers.py",
29
+ "devpi_server/middleware.py",
30
+ "devpi_server/mirror.py",
31
+ "devpi_server/model.py",
32
+ "devpi_server/mythread.py",
33
+ "devpi_server/passwd.py",
34
+ "devpi_server/readonly.py",
35
+ "devpi_server/replica.py",
36
+ "devpi_server/sizeof.py",
37
+ "devpi_server/vendor/_pip.py",
38
+ "devpi_server/view_auth.py",
39
+ "devpi_server/views.py",
40
+ "pytest_devpi_server/__init__.py",
41
+ "setup.py",
42
+ "test_devpi_server/conftest.py",
43
+ "test_devpi_server/example.py",
44
+ "test_devpi_server/functional.py",
45
+ "test_devpi_server/plugin.py",
46
+ "test_devpi_server/reqmock.py",
47
+ "test_devpi_server/simpypi.py",
48
+ "test_devpi_server/test_auth.py",
49
+ "test_devpi_server/test_authcheck.py",
50
+ "test_devpi_server/test_config.py",
51
+ "test_devpi_server/test_conftest.py",
52
+ "test_devpi_server/test_filestore.py",
53
+ "test_devpi_server/test_fileutil.py",
54
+ "test_devpi_server/test_fsck.py",
55
+ "test_devpi_server/test_genconfig.py",
56
+ "test_devpi_server/test_importexport.py",
57
+ "test_devpi_server/test_keyfs.py",
58
+ "test_devpi_server/test_keyfs_sqlite_fs.py",
59
+ "test_devpi_server/test_log.py",
60
+ "test_devpi_server/test_main.py",
61
+ "test_devpi_server/test_mirror.py",
62
+ "test_devpi_server/test_mirror_no_project_list.py",
63
+ "test_devpi_server/test_model.py",
64
+ "test_devpi_server/test_mythread.py",
65
+ "test_devpi_server/test_nginx.py",
66
+ "test_devpi_server/test_nginx_replica.py",
67
+ "test_devpi_server/test_permissions.py",
68
+ "test_devpi_server/test_replica.py",
69
+ "test_devpi_server/test_replica_functional.py",
70
+ "test_devpi_server/test_reqmock.py",
71
+ "test_devpi_server/test_stage_customizer.py",
72
+ "test_devpi_server/test_streaming.py",
73
+ "test_devpi_server/test_streaming_nginx.py",
74
+ "test_devpi_server/test_streaming_replica.py",
75
+ "test_devpi_server/test_streaming_replica_nginx.py",
76
+ "test_devpi_server/test_view_auth.py",
77
+ "test_devpi_server/test_views.py",
78
+ "test_devpi_server/test_views_patch.py",
79
+ "test_devpi_server/test_views_push_external.py",
80
+ "test_devpi_server/test_views_status.py",
81
+ ]
82
+
83
+
5
84
  [tool.ruff.lint]
6
85
  extend-ignore = [
7
86
  "A001", # cleanup later - variable name shadows builtin
@@ -59,7 +59,7 @@ if __name__ == "__main__":
59
59
  'Funding': 'https://github.com/sponsors/devpi',
60
60
  'Source Code': 'https://github.com/devpi/devpi'
61
61
  },
62
- version='6.13.0',
62
+ version='6.15.0',
63
63
  maintainer="Florian Schulze",
64
64
  maintainer_email="mail@pyfidelity.com",
65
65
  packages=[