devpi-server 6.11.0__tar.gz → 6.12.1__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.11.0 → devpi-server-6.12.1}/CHANGELOG +33 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/PKG-INFO +34 -52
- devpi-server-6.12.1/devpi_server/__init__.py +1 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/config.py +4 -16
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/hookspecs.py +31 -1
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/keyfs.py +3 -7
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/mirror.py +2 -2
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/model.py +6 -7
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/replica.py +15 -8
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/vendor/_pip.py +6 -6
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/views.py +58 -23
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server.egg-info/PKG-INFO +34 -52
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server.egg-info/SOURCES.txt +0 -1
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server.egg-info/requires.txt +4 -1
- {devpi_server-6.11.0 → devpi-server-6.12.1}/setup.py +4 -3
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/functional.py +35 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/plugin.py +23 -41
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_fileutil.py +2 -2
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_keyfs.py +44 -4
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_mirror.py +6 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_nginx.py +2 -2
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_views.py +59 -1
- devpi_server-6.11.0/CHANGELOG.short.rst +0 -95
- devpi_server-6.11.0/devpi_server/__init__.py +0 -1
- {devpi_server-6.11.0 → devpi-server-6.12.1}/.flake8 +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/AUTHORS +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/LICENSE +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/MANIFEST.in +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/README.rst +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/__main__.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/auth.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/auth_basic.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/auth_devpi.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/__init__.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/crontab.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/exceptions.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/filestore.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/filestore_fs.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/fileutil.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/fsck.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/genconfig.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/importexport.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/init.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/interfaces.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/keyfs_sqlite.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/keyfs_sqlite_fs.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/keyfs_types.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/log.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/main.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/markers.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/middleware.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/mythread.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/passwd.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/readonly.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/sizeof.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/vendor/__init__.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server/view_auth.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server.egg-info/not-zip-safe +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/pyproject.toml +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/pytest_devpi_server/__init__.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/setup.cfg +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/__init__.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/conftest.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/example.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/reqmock.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/simpypi.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_auth.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_authcheck.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_config.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_conftest.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_filestore.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_fsck.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_genconfig.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_importexport.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_keyfs_sqlite_fs.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_log.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_main.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_model.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_mythread.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_nginx_replica.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_permissions.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_readonly.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_replica.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_replica_functional.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_reqmock.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_stage_customizer.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_streaming.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_streaming_nginx.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_streaming_replica.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_view_auth.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_views_patch.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_views_push_external.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/test_devpi_server/test_views_status.py +0 -0
- {devpi_server-6.11.0 → devpi-server-6.12.1}/tox.ini +0 -0
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.12.1 (2024-07-24)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Bug Fixes
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- Support Python 3.13 by depending on legacy-cgi.
|
|
12
|
+
|
|
13
|
+
- Preserve query string when proxying requests from replica to primary. This fixes force removal on non-volatile indexes and probably other bugs.
|
|
14
|
+
|
|
15
|
+
- Fix #1044: Correctly update cache expiry time when mirrored server returns 304 Not Modified.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
6.12.0 (2024-06-25)
|
|
20
|
+
===================
|
|
21
|
+
|
|
22
|
+
Features
|
|
23
|
+
--------
|
|
24
|
+
|
|
25
|
+
- Added ``devpiserver_on_toxresult_store`` hook to allow blocking or skipping a toxresult upload on more specific conditions as ``acl_toxresult_upload`` would allow.
|
|
26
|
+
|
|
27
|
+
- Added ``devpiserver_on_toxresult_upload_forbidden`` hook to allow returning a custom message and result (403 or 200).
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Bug Fixes
|
|
32
|
+
---------
|
|
33
|
+
|
|
34
|
+
- Return json data if toxresult upload is forbidden.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
5
38
|
6.11.0 (2024-04-20)
|
|
6
39
|
===================
|
|
7
40
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.12.1
|
|
4
4
|
Summary: devpi-server: reliable private and pypi.org caching server
|
|
5
5
|
Home-page: https://devpi.net
|
|
6
6
|
Maintainer: Florian Schulze
|
|
@@ -27,25 +27,10 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
27
27
|
Classifier: Programming Language :: Python :: 3.10
|
|
28
28
|
Classifier: Programming Language :: Python :: 3.11
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
30
31
|
Requires-Python: >=3.7
|
|
31
32
|
License-File: LICENSE
|
|
32
33
|
License-File: AUTHORS
|
|
33
|
-
Requires-Dist: py>=1.4.23
|
|
34
|
-
Requires-Dist: httpx
|
|
35
|
-
Requires-Dist: argon2-cffi
|
|
36
|
-
Requires-Dist: attrs>=21.3.0
|
|
37
|
-
Requires-Dist: defusedxml
|
|
38
|
-
Requires-Dist: devpi_common<5,>3.6.0
|
|
39
|
-
Requires-Dist: itsdangerous>=0.24
|
|
40
|
-
Requires-Dist: platformdirs
|
|
41
|
-
Requires-Dist: pyramid>=2
|
|
42
|
-
Requires-Dist: waitress>=1.0.1
|
|
43
|
-
Requires-Dist: repoze.lru>=0.6
|
|
44
|
-
Requires-Dist: passlib[argon2]
|
|
45
|
-
Requires-Dist: pluggy<2.0,>=0.6.0
|
|
46
|
-
Requires-Dist: ruamel.yaml
|
|
47
|
-
Requires-Dist: strictyaml
|
|
48
|
-
Requires-Dist: lazy
|
|
49
34
|
|
|
50
35
|
=============================================================================
|
|
51
36
|
devpi-server: server for private package indexes and PyPI caching
|
|
@@ -119,89 +104,86 @@ Changelog
|
|
|
119
104
|
|
|
120
105
|
.. towncrier release notes start
|
|
121
106
|
|
|
122
|
-
6.
|
|
107
|
+
6.12.1 (2024-07-24)
|
|
123
108
|
===================
|
|
124
109
|
|
|
125
|
-
Features
|
|
126
|
-
--------
|
|
127
|
-
|
|
128
|
-
- The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
|
|
129
|
-
|
|
130
|
-
- Fix #983: Add plugin hook for mirror authentication header.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
110
|
Bug Fixes
|
|
135
111
|
---------
|
|
136
112
|
|
|
137
|
-
-
|
|
113
|
+
- Support Python 3.13 by depending on legacy-cgi.
|
|
138
114
|
|
|
139
|
-
-
|
|
115
|
+
- Preserve query string when proxying requests from replica to primary. This fixes force removal on non-volatile indexes and probably other bugs.
|
|
140
116
|
|
|
117
|
+
- Fix #1044: Correctly update cache expiry time when mirrored server returns 304 Not Modified.
|
|
141
118
|
|
|
142
119
|
|
|
143
|
-
|
|
120
|
+
|
|
121
|
+
6.12.0 (2024-06-25)
|
|
144
122
|
===================
|
|
145
123
|
|
|
146
124
|
Features
|
|
147
125
|
--------
|
|
148
126
|
|
|
149
|
-
-
|
|
127
|
+
- Added ``devpiserver_on_toxresult_store`` hook to allow blocking or skipping a toxresult upload on more specific conditions as ``acl_toxresult_upload`` would allow.
|
|
150
128
|
|
|
151
|
-
-
|
|
129
|
+
- Added ``devpiserver_on_toxresult_upload_forbidden`` hook to allow returning a custom message and result (403 or 200).
|
|
152
130
|
|
|
153
131
|
|
|
154
132
|
|
|
155
133
|
Bug Fixes
|
|
156
134
|
---------
|
|
157
135
|
|
|
158
|
-
-
|
|
136
|
+
- Return json data if toxresult upload is forbidden.
|
|
159
137
|
|
|
160
|
-
- Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
|
|
161
138
|
|
|
162
139
|
|
|
140
|
+
6.11.0 (2024-04-20)
|
|
141
|
+
===================
|
|
163
142
|
|
|
164
|
-
|
|
165
|
-
|
|
143
|
+
Features
|
|
144
|
+
--------
|
|
166
145
|
|
|
167
|
-
|
|
168
|
-
---------
|
|
146
|
+
- The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
|
|
169
147
|
|
|
170
|
-
-
|
|
148
|
+
- Fix #983: Add plugin hook for mirror authentication header.
|
|
171
149
|
|
|
172
150
|
|
|
173
|
-
6.9.1 (2023-07-02)
|
|
174
|
-
==================
|
|
175
151
|
|
|
176
152
|
Bug Fixes
|
|
177
153
|
---------
|
|
178
154
|
|
|
179
|
-
-
|
|
155
|
+
- Preserve last modified of docs and toxresults during export/import.
|
|
180
156
|
|
|
181
|
-
- Fix #
|
|
157
|
+
- Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
|
|
182
158
|
|
|
183
159
|
|
|
184
|
-
|
|
185
|
-
|
|
160
|
+
|
|
161
|
+
6.10.0 (2023-12-19)
|
|
162
|
+
===================
|
|
186
163
|
|
|
187
164
|
Features
|
|
188
165
|
--------
|
|
189
166
|
|
|
190
|
-
-
|
|
167
|
+
- Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
|
|
168
|
+
|
|
169
|
+
- Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
|
|
191
170
|
|
|
192
|
-
- Fix #931: Add ``mirror_no_project_list`` setting for mirror indexes that have no full project list like google cloud artifacts or if you want to prevent downloading the full list for huge indexes like PyPI.
|
|
193
171
|
|
|
194
172
|
|
|
195
173
|
Bug Fixes
|
|
196
174
|
---------
|
|
197
175
|
|
|
198
|
-
-
|
|
176
|
+
- Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
|
|
177
|
+
|
|
178
|
+
- Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
|
|
179
|
+
|
|
199
180
|
|
|
200
|
-
- Support changed default of ``enforce_content_length`` in urllib3 >= 2.
|
|
201
181
|
|
|
202
|
-
|
|
182
|
+
6.9.2 (2023-08-06)
|
|
183
|
+
==================
|
|
203
184
|
|
|
204
|
-
|
|
185
|
+
Bug Fixes
|
|
186
|
+
---------
|
|
205
187
|
|
|
206
|
-
-
|
|
188
|
+
- Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
|
|
207
189
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '6.12.1'
|
|
@@ -321,27 +321,15 @@ def add_deploy_options(parser, pluginmanager):
|
|
|
321
321
|
|
|
322
322
|
parser.addoption(
|
|
323
323
|
"--argon2-memory-cost", type=int, default=DEFAULT_ARGON2_MEMORY_COST,
|
|
324
|
-
help=
|
|
325
|
-
"This is *not* for the user password hashes. "
|
|
326
|
-
"There should be no need to touch this setting, "
|
|
327
|
-
"except you really know what this is about! "
|
|
328
|
-
"Replicas need to use the same parameters as the master.")
|
|
324
|
+
help=argparse.SUPPRESS)
|
|
329
325
|
|
|
330
326
|
parser.addoption(
|
|
331
327
|
"--argon2-parallelism", type=int, default=DEFAULT_ARGON2_PARALLELISM,
|
|
332
|
-
help=
|
|
333
|
-
"This is *not* for the user password hashes. "
|
|
334
|
-
"There should be no need to touch this setting, "
|
|
335
|
-
"except you really know what this is about! "
|
|
336
|
-
"Replicas need to use the same parameters as the master.")
|
|
328
|
+
help=argparse.SUPPRESS)
|
|
337
329
|
|
|
338
330
|
parser.addoption(
|
|
339
331
|
"--argon2-time-cost", type=int, default=DEFAULT_ARGON2_TIME_COST,
|
|
340
|
-
help=
|
|
341
|
-
"This is *not* for the user password hashes. "
|
|
342
|
-
"There should be no need to touch this setting, "
|
|
343
|
-
"except you really know what this is about! "
|
|
344
|
-
"Replicas need to use the same parameters as the master.")
|
|
332
|
+
help=argparse.SUPPRESS)
|
|
345
333
|
|
|
346
334
|
parser.addoption(
|
|
347
335
|
"--requests-only", action="store_true",
|
|
@@ -567,7 +555,7 @@ class MyArgumentParser(argparse.ArgumentParser):
|
|
|
567
555
|
default = action.default
|
|
568
556
|
if isinstance(action, argparse._StoreFalseAction):
|
|
569
557
|
default = not default
|
|
570
|
-
if action.help and action.default
|
|
558
|
+
if action.help and action.help is not argparse.SUPPRESS and action.default is not argparse.SUPPRESS:
|
|
571
559
|
action.help += " [%s]" % default
|
|
572
560
|
|
|
573
561
|
def addgroup(self, *args, **kwargs):
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
from pluggy import HookspecMarker
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from devpi_server.views import ToxResultHandling
|
|
9
|
+
from pyramid.request import Request
|
|
10
|
+
|
|
3
11
|
|
|
4
12
|
hookspec = HookspecMarker("devpiserver")
|
|
5
13
|
|
|
@@ -255,6 +263,28 @@ def devpiserver_on_replicated_file(stage, project, version, link, serial, back_s
|
|
|
255
263
|
"""Called when a file was downloaded from master on replica."""
|
|
256
264
|
|
|
257
265
|
|
|
266
|
+
@hookspec(firstresult=True)
|
|
267
|
+
def devpiserver_on_toxresult_store(request: Request, tox_result_handling: ToxResultHandling) -> Optional[ToxResultHandling]:
|
|
268
|
+
"""Called when a toxresult is about to be stored.
|
|
269
|
+
|
|
270
|
+
Stops at first non-None result.
|
|
271
|
+
|
|
272
|
+
:returns: A ToxResultHandling object which determines how the upload is processed.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@hookspec(firstresult=True)
|
|
277
|
+
def devpiserver_on_toxresult_upload_forbidden(request: Request, tox_result_handling: ToxResultHandling) -> Optional[str]:
|
|
278
|
+
"""Called when the permission check for toxresult upload failed.
|
|
279
|
+
|
|
280
|
+
Stops at first non-None result.
|
|
281
|
+
|
|
282
|
+
:returns: A ToxResultHandling object which determines whether an error
|
|
283
|
+
with message (default using ``block``) or a success with a message
|
|
284
|
+
(using ``skip``) is returned.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
|
|
258
288
|
@hookspec
|
|
259
289
|
def devpiserver_metrics(request):
|
|
260
290
|
""" called for status view.
|
|
@@ -673,13 +673,9 @@ class Transaction(object):
|
|
|
673
673
|
def exists(self, typedkey):
|
|
674
674
|
if typedkey in self.cache:
|
|
675
675
|
val = self.cache[typedkey]
|
|
676
|
-
|
|
677
|
-
return False
|
|
678
|
-
return True
|
|
676
|
+
return val not in (absent, deleted)
|
|
679
677
|
(serial, val) = self.get_original(typedkey)
|
|
680
|
-
|
|
681
|
-
return False
|
|
682
|
-
return True
|
|
678
|
+
return val not in (absent, deleted)
|
|
683
679
|
|
|
684
680
|
def delete(self, typedkey):
|
|
685
681
|
if not self.write:
|
|
@@ -692,7 +688,7 @@ class Transaction(object):
|
|
|
692
688
|
raise self.keyfs.ReadOnly()
|
|
693
689
|
# sanity check for dictionaries: we always want to have unicode
|
|
694
690
|
# keys, not bytes
|
|
695
|
-
if typedkey.type
|
|
691
|
+
if typedkey.type is dict:
|
|
696
692
|
check_unicode_keys(val)
|
|
697
693
|
assert val is not None
|
|
698
694
|
self.cache[typedkey] = val
|
|
@@ -794,8 +794,8 @@ class MirrorStage(BaseStage):
|
|
|
794
794
|
f"timeout after {self.timeout} seconds while getting data for {project!r}")
|
|
795
795
|
except self.UpstreamNotModified as e:
|
|
796
796
|
if links is not None:
|
|
797
|
-
|
|
798
|
-
|
|
797
|
+
# immediately update the cache
|
|
798
|
+
self.cache_retrieve_times.refresh(project, e.etag)
|
|
799
799
|
return self.SimpleLinks(links)
|
|
800
800
|
if e.etag is None:
|
|
801
801
|
threadlog.error(
|
|
@@ -42,9 +42,9 @@ def join_links_data(links, requires_python, yanked):
|
|
|
42
42
|
# build list of (key, href, require_python, yanked) tuples
|
|
43
43
|
result = []
|
|
44
44
|
links = zip_longest(links, requires_python, yanked, fillvalue=None)
|
|
45
|
-
for link, require_python,
|
|
45
|
+
for link, require_python, link_yanked in links:
|
|
46
46
|
key, href = link
|
|
47
|
-
result.append((key, href, require_python,
|
|
47
|
+
result.append((key, href, require_python, link_yanked))
|
|
48
48
|
return result
|
|
49
49
|
|
|
50
50
|
|
|
@@ -1615,11 +1615,10 @@ class LinkStore:
|
|
|
1615
1615
|
|
|
1616
1616
|
@property
|
|
1617
1617
|
def metadata(self):
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
return metadata
|
|
1618
|
+
return {
|
|
1619
|
+
k: v
|
|
1620
|
+
for k, v in get_mutable_deepcopy(self.verdata).items()
|
|
1621
|
+
if not k.startswith("+")}
|
|
1623
1622
|
|
|
1624
1623
|
def get_file_entry(self, relpath):
|
|
1625
1624
|
return self.filestore.get_file_entry(relpath)
|
|
@@ -6,6 +6,7 @@ import secrets
|
|
|
6
6
|
import threading
|
|
7
7
|
import time
|
|
8
8
|
import traceback
|
|
9
|
+
from contextlib import suppress
|
|
9
10
|
from functools import partial
|
|
10
11
|
from pluggy import HookimplMarker
|
|
11
12
|
from pyramid.httpexceptions import HTTPNotFound, HTTPAccepted, HTTPBadRequest
|
|
@@ -1174,18 +1175,22 @@ class BodyFileWrapper:
|
|
|
1174
1175
|
self.len = length
|
|
1175
1176
|
|
|
1176
1177
|
|
|
1177
|
-
def proxy_request_to_master(xom, request, stream=False):
|
|
1178
|
+
def proxy_request_to_master(xom, request, *, stream=False):
|
|
1178
1179
|
master_url = xom.config.master_url
|
|
1179
|
-
|
|
1180
|
+
request_url = URL(request.url)
|
|
1181
|
+
url = (
|
|
1182
|
+
master_url
|
|
1183
|
+
.joinpath(request_url.path)
|
|
1184
|
+
.replace(query=request_url.query)
|
|
1185
|
+
.url)
|
|
1180
1186
|
assert url.startswith(master_url.url)
|
|
1181
1187
|
http = xom._httpsession
|
|
1182
1188
|
with threadlog.around("info", "relaying: %s %s", request.method, url):
|
|
1183
1189
|
try:
|
|
1184
1190
|
headers = clean_request_headers(request)
|
|
1185
|
-
|
|
1191
|
+
length = None
|
|
1192
|
+
with suppress(ValueError, TypeError):
|
|
1186
1193
|
length = int(headers.get('Content-Length'))
|
|
1187
|
-
except (ValueError, TypeError):
|
|
1188
|
-
length = None
|
|
1189
1194
|
if length:
|
|
1190
1195
|
body = BodyFileWrapper(request.body_file, length)
|
|
1191
1196
|
else:
|
|
@@ -1197,7 +1202,8 @@ def proxy_request_to_master(xom, request, stream=False):
|
|
|
1197
1202
|
allow_redirects=False,
|
|
1198
1203
|
timeout=xom.config.args.proxy_timeout)
|
|
1199
1204
|
except http.Errors as e:
|
|
1200
|
-
|
|
1205
|
+
msg = f"proxy-write-to-master {url}: {e}"
|
|
1206
|
+
raise UpstreamError(msg) from e
|
|
1201
1207
|
|
|
1202
1208
|
|
|
1203
1209
|
def proxy_write_to_master(xom, request):
|
|
@@ -1231,10 +1237,11 @@ def proxy_write_to_master(xom, request):
|
|
|
1231
1237
|
headers=headers)
|
|
1232
1238
|
|
|
1233
1239
|
|
|
1234
|
-
def proxy_view_to_master(
|
|
1240
|
+
def proxy_view_to_master(_context, request):
|
|
1235
1241
|
xom = request.registry["xom"]
|
|
1236
1242
|
tx = getattr(xom.keyfs, "tx", None)
|
|
1237
|
-
|
|
1243
|
+
if getattr(tx, "write", False):
|
|
1244
|
+
raise RuntimeError("there should be no write transaction")
|
|
1238
1245
|
return proxy_write_to_master(xom, request)
|
|
1239
1246
|
|
|
1240
1247
|
|
|
@@ -14,12 +14,12 @@ class HTMLPage(object):
|
|
|
14
14
|
"""Represents one page, along with its URL"""
|
|
15
15
|
|
|
16
16
|
# FIXME: these regexes are horrible hacks:
|
|
17
|
-
_homepage_re = re.compile(r'<th>\s*home\s*page', re.
|
|
18
|
-
_download_re = re.compile(r'<th>\s*download\s+url', re.
|
|
17
|
+
_homepage_re = re.compile(r'<th>\s*home\s*page', re.IGNORECASE)
|
|
18
|
+
_download_re = re.compile(r'<th>\s*download\s+url', re.IGNORECASE)
|
|
19
19
|
# These aren't so awful:
|
|
20
|
-
_rel_re = re.compile(r"""<[^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*>""", re.
|
|
21
|
-
_href_re = re.compile('href=(?:"([^"]*)"|\'([^\']*)\'|([^>\\s\\n]*))', re.
|
|
22
|
-
_base_re = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.
|
|
20
|
+
_rel_re = re.compile(r"""<[^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*>""", re.IGNORECASE)
|
|
21
|
+
_href_re = re.compile('href=(?:"([^"]*)"|\'([^\']*)\'|([^>\\s\\n]*))', re.IGNORECASE | re.DOTALL)
|
|
22
|
+
_base_re = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.IGNORECASE)
|
|
23
23
|
|
|
24
24
|
def __init__(self, content, url, headers=None):
|
|
25
25
|
self.content = content
|
|
@@ -113,7 +113,7 @@ class HTMLPage(object):
|
|
|
113
113
|
url = self.clean_link(urljoin(self.base_url, url))
|
|
114
114
|
yield Link(url, self)
|
|
115
115
|
|
|
116
|
-
_clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.
|
|
116
|
+
_clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.IGNORECASE)
|
|
117
117
|
|
|
118
118
|
def clean_link(self, url):
|
|
119
119
|
"""Makes sure a link is fully encoded. That is, if a ' ' shows up in
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import contextlib
|
|
2
3
|
import os
|
|
3
4
|
import re
|
|
@@ -50,6 +51,7 @@ from .readonly import get_mutable_deepcopy
|
|
|
50
51
|
from .log import thread_push_log, thread_pop_log, threadlog
|
|
51
52
|
|
|
52
53
|
from .auth import Auth
|
|
54
|
+
import attrs
|
|
53
55
|
|
|
54
56
|
devpiweb_hookimpl = HookimplMarker("devpiweb")
|
|
55
57
|
server_version = devpi_server.__version__
|
|
@@ -79,9 +81,7 @@ def _select_simple_content_type(request):
|
|
|
79
81
|
|
|
80
82
|
|
|
81
83
|
def is_simple_json(request):
|
|
82
|
-
|
|
83
|
-
return True
|
|
84
|
-
return False
|
|
84
|
+
return _select_simple_content_type(request) == SIMPLE_API_V1_JSON
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def is_requested_by_installer(request):
|
|
@@ -412,6 +412,34 @@ def devpiserver_authcheck_forbidden(request):
|
|
|
412
412
|
return True
|
|
413
413
|
|
|
414
414
|
|
|
415
|
+
TOXRESULT_UPLOAD_FORBIDDEN = (
|
|
416
|
+
"No permission to upload tox results. "
|
|
417
|
+
"You can use the devpi test --no-upload option to skip the upload.")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@attrs.define(frozen=True)
|
|
421
|
+
class ToxResultHandling:
|
|
422
|
+
_block: bool = attrs.field(default=False, alias="_block")
|
|
423
|
+
_skip: bool = attrs.field(default=False, alias="_skip")
|
|
424
|
+
msg: None | str = None
|
|
425
|
+
|
|
426
|
+
def block(self, msg=TOXRESULT_UPLOAD_FORBIDDEN):
|
|
427
|
+
return ToxResultHandling(_block=True, msg=msg)
|
|
428
|
+
|
|
429
|
+
def skip(self, msg=None):
|
|
430
|
+
return ToxResultHandling(_skip=True, msg=msg)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@hookimpl(trylast=True)
|
|
434
|
+
def devpiserver_on_toxresult_store(request, tox_result_handling):
|
|
435
|
+
return tox_result_handling
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@hookimpl(trylast=True)
|
|
439
|
+
def devpiserver_on_toxresult_upload_forbidden(request, tox_result_handling):
|
|
440
|
+
return tox_result_handling
|
|
441
|
+
|
|
442
|
+
|
|
415
443
|
def version_in_filename(version, filename):
|
|
416
444
|
if version is None:
|
|
417
445
|
# no version set, so skip check
|
|
@@ -419,9 +447,7 @@ def version_in_filename(version, filename):
|
|
|
419
447
|
if version in filename:
|
|
420
448
|
return True
|
|
421
449
|
# PEP 427 escaped wheels
|
|
422
|
-
|
|
423
|
-
return True
|
|
424
|
-
return False
|
|
450
|
+
return re.sub(r"[^\w\d.]+", "_", version, flags=re.UNICODE) in filename
|
|
425
451
|
|
|
426
452
|
|
|
427
453
|
class PyPIView:
|
|
@@ -530,14 +556,25 @@ class PyPIView:
|
|
|
530
556
|
|
|
531
557
|
@view_config(
|
|
532
558
|
route_name="/{user}/{index}/+f/{relpath:.*}",
|
|
533
|
-
request_method="POST"
|
|
534
|
-
permission="toxresult_upload")
|
|
559
|
+
request_method="POST")
|
|
535
560
|
def post_toxresult(self):
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
561
|
+
if not self.request.has_permission("toxresult_upload"):
|
|
562
|
+
default_tox_result_handling = ToxResultHandling().block(TOXRESULT_UPLOAD_FORBIDDEN)
|
|
563
|
+
tox_result_handling = self.xom.config.hook.devpiserver_on_toxresult_upload_forbidden(
|
|
564
|
+
request=self.request, tox_result_handling=default_tox_result_handling)
|
|
565
|
+
else:
|
|
566
|
+
stage = self.context.stage
|
|
567
|
+
relpath = self.request.path_info.strip("/")
|
|
568
|
+
link = stage.get_link_from_entrypath(relpath)
|
|
569
|
+
if link is None or link.rel != "releasefile":
|
|
570
|
+
apireturn(404, message="no release file found at %s" % relpath)
|
|
571
|
+
default_tox_result_handling = ToxResultHandling()
|
|
572
|
+
tox_result_handling = self.xom.config.hook.devpiserver_on_toxresult_store(
|
|
573
|
+
request=self.request, tox_result_handling=default_tox_result_handling)
|
|
574
|
+
if tox_result_handling._block:
|
|
575
|
+
apireturn(403, tox_result_handling.msg)
|
|
576
|
+
if tox_result_handling._skip:
|
|
577
|
+
apireturn(200, tox_result_handling.msg)
|
|
541
578
|
# the getjson call validates that we got valid json
|
|
542
579
|
getjson(self.request)
|
|
543
580
|
# but we store the original body
|
|
@@ -555,9 +592,7 @@ class PyPIView:
|
|
|
555
592
|
def _use_absolute_urls(self):
|
|
556
593
|
if self.xom.config.args.absolute_urls:
|
|
557
594
|
return True
|
|
558
|
-
|
|
559
|
-
return True
|
|
560
|
-
return False
|
|
595
|
+
return 'HTTP_X_DEVPI_ABSOLUTE_URLS' in self.request.environ
|
|
561
596
|
|
|
562
597
|
@view_config(route_name="/{user}/{index}/+simple/{project}")
|
|
563
598
|
def simple_list_project_redirect(self):
|
|
@@ -745,18 +780,18 @@ class PyPIView:
|
|
|
745
780
|
# depending on remote networks
|
|
746
781
|
content_type = _select_simple_content_type(self.request)
|
|
747
782
|
if content_type == SIMPLE_API_V1_JSON:
|
|
748
|
-
app_iter = self._simple_list_all_json_v1(
|
|
783
|
+
app_iter = self._simple_list_all_json_v1(stage_results)
|
|
749
784
|
elif is_requested_by_installer(self.request):
|
|
750
|
-
app_iter = self._simple_list_all_installer(
|
|
785
|
+
app_iter = self._simple_list_all_installer(stage_results)
|
|
751
786
|
else:
|
|
752
|
-
app_iter = self._simple_list_all(stage, stage_results)
|
|
787
|
+
app_iter = self._simple_list_all(stage.name, stage_results)
|
|
753
788
|
return Response(
|
|
754
789
|
app_iter=buffered_iterator(app_iter),
|
|
755
790
|
content_type=content_type,
|
|
756
791
|
vary=set(["Accept", "User-Agent"]))
|
|
757
792
|
|
|
758
|
-
def _simple_list_all(self,
|
|
759
|
-
title = "
|
|
793
|
+
def _simple_list_all(self, stage_name, stage_results):
|
|
794
|
+
title = f"{stage_name}: simple list (including inherited indices)"
|
|
760
795
|
yield f"<!DOCTYPE html><html><head><title>{title}</title></head><body><h1>{title}</h1>".encode("utf-8")
|
|
761
796
|
last_index = len(stage_results) - 1
|
|
762
797
|
seen = set()
|
|
@@ -773,7 +808,7 @@ class PyPIView:
|
|
|
773
808
|
seen.add(name)
|
|
774
809
|
yield "</body></html>".encode("utf-8")
|
|
775
810
|
|
|
776
|
-
def _simple_list_all_installer(self,
|
|
811
|
+
def _simple_list_all_installer(self, stage_results):
|
|
777
812
|
yield "<!DOCTYPE html><html><body>".encode("utf-8")
|
|
778
813
|
last_index = len(stage_results) - 1
|
|
779
814
|
seen = set()
|
|
@@ -785,7 +820,7 @@ class PyPIView:
|
|
|
785
820
|
seen.add(name)
|
|
786
821
|
yield "</body></html>".encode("utf-8")
|
|
787
822
|
|
|
788
|
-
def _simple_list_all_json_v1(self,
|
|
823
|
+
def _simple_list_all_json_v1(self, stage_results):
|
|
789
824
|
yield '{"meta":{"api-version":"1.0"},"projects":['.encode("utf-8")
|
|
790
825
|
last_index = len(stage_results) - 1
|
|
791
826
|
seen = set()
|