devpi-server 6.8.0__tar.gz → 6.9.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.8.0 → devpi-server-6.9.1}/CHANGELOG +36 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/PKG-INFO +37 -41
- devpi-server-6.9.1/devpi_server/__init__.py +1 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/config.py +2 -5
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/filestore.py +1 -4
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/fileutil.py +2 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/importexport.py +4 -2
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/init.py +2 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/keyfs.py +10 -3
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/keyfs_sqlite.py +12 -4
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/keyfs_sqlite_fs.py +2 -3
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/main.py +15 -3
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/middleware.py +2 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/mirror.py +32 -9
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/model.py +60 -25
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/readonly.py +1 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/replica.py +11 -3
- devpi-server-6.9.1/devpi_server/vendor/_pip.py +152 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/views.py +26 -12
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server.egg-info/PKG-INFO +37 -41
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server.egg-info/SOURCES.txt +3 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server.egg-info/requires.txt +1 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/setup.py +3 -2
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/conftest.py +21 -18
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/functional.py +27 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/reqmock.py +1 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/simpypi.py +3 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_config.py +2 -4
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_importexport.py +9 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_keyfs.py +2 -2
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_log.py +0 -2
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_main.py +1 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_mirror.py +28 -1
- devpi-server-6.9.1/test_devpi_server/test_mirror_no_project_list.py +247 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_model.py +14 -8
- devpi-server-6.9.1/test_devpi_server/test_nginx.py +90 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_nginx_replica.py +6 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_readonly.py +1 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_replica.py +23 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_replica_functional.py +6 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_streaming.py +6 -2
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_views.py +25 -23
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_views_push_external.py +1 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/tox.ini +4 -1
- devpi-server-6.8.0/devpi_server/__init__.py +0 -1
- {devpi-server-6.8.0 → devpi-server-6.9.1}/AUTHORS +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/LICENSE +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/MANIFEST.in +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/README.rst +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/__main__.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/auth.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/auth_basic.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/auth_devpi.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/__init__.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/crontab.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/exceptions.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/fsck.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/genconfig.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/hookspecs.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/interfaces.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/keyfs_types.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/log.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/mythread.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/passwd.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/sizeof.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server/view_auth.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server.egg-info/not-zip-safe +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/pyproject.toml +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/pytest_devpi_server/__init__.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/setup.cfg +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/__init__.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/example.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_auth.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_authcheck.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_conftest.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_filestore.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_fileutil.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_genconfig.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_keyfs_sqlite_fs.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_mythread.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_permissions.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_reqmock.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_stage_customizer.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_streaming_nginx.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_streaming_replica.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_view_auth.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_views_patch.py +0 -0
- {devpi-server-6.8.0 → devpi-server-6.9.1}/test_devpi_server/test_views_status.py +0 -0
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.9.1 (2023-07-02)
|
|
6
|
+
==================
|
|
7
|
+
|
|
8
|
+
Bug Fixes
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- Prevent error in find_pre_existing_file in case of incomplete metadata.
|
|
12
|
+
|
|
13
|
+
- Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
6.9.0 (2023-05-23)
|
|
17
|
+
==================
|
|
18
|
+
|
|
19
|
+
Features
|
|
20
|
+
--------
|
|
21
|
+
|
|
22
|
+
- Support export directory layout for ``--replica-file-search-path`` option.
|
|
23
|
+
|
|
24
|
+
- 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.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Bug Fixes
|
|
28
|
+
---------
|
|
29
|
+
|
|
30
|
+
- Keep a reference to async tasks to avoid their removal mid execution.
|
|
31
|
+
|
|
32
|
+
- Support changed default of ``enforce_content_length`` in urllib3 >= 2.
|
|
33
|
+
|
|
34
|
+
- Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
|
|
35
|
+
|
|
36
|
+
- Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
|
|
37
|
+
|
|
38
|
+
- Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
|
|
39
|
+
|
|
40
|
+
|
|
5
41
|
6.8.0 (2022-12-05)
|
|
6
42
|
==================
|
|
7
43
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.9.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
|
|
@@ -102,6 +102,42 @@ Changelog
|
|
|
102
102
|
|
|
103
103
|
.. towncrier release notes start
|
|
104
104
|
|
|
105
|
+
6.9.1 (2023-07-02)
|
|
106
|
+
==================
|
|
107
|
+
|
|
108
|
+
Bug Fixes
|
|
109
|
+
---------
|
|
110
|
+
|
|
111
|
+
- Prevent error in find_pre_existing_file in case of incomplete metadata.
|
|
112
|
+
|
|
113
|
+
- Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
6.9.0 (2023-05-23)
|
|
117
|
+
==================
|
|
118
|
+
|
|
119
|
+
Features
|
|
120
|
+
--------
|
|
121
|
+
|
|
122
|
+
- Support export directory layout for ``--replica-file-search-path`` option.
|
|
123
|
+
|
|
124
|
+
- 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.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Bug Fixes
|
|
128
|
+
---------
|
|
129
|
+
|
|
130
|
+
- Keep a reference to async tasks to avoid their removal mid execution.
|
|
131
|
+
|
|
132
|
+
- Support changed default of ``enforce_content_length`` in urllib3 >= 2.
|
|
133
|
+
|
|
134
|
+
- Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
|
|
135
|
+
|
|
136
|
+
- Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
|
|
137
|
+
|
|
138
|
+
- Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
|
|
139
|
+
|
|
140
|
+
|
|
105
141
|
6.8.0 (2022-12-05)
|
|
106
142
|
==================
|
|
107
143
|
|
|
@@ -154,43 +190,3 @@ Bug Fixes
|
|
|
154
190
|
|
|
155
191
|
- Preserve toxresult filenames during import to keep them being accessible on the same URLs after the fix for #686 in 5.2.0.
|
|
156
192
|
|
|
157
|
-
|
|
158
|
-
6.6.0 (2022-08-16)
|
|
159
|
-
==================
|
|
160
|
-
|
|
161
|
-
Features
|
|
162
|
-
--------
|
|
163
|
-
|
|
164
|
-
- Fix #592: return dict from ``list_projects_perstage`` of mirrors where the values contain the un-normalized project name. This allows support in devpi-web 4.1.0 to index them correctly.
|
|
165
|
-
|
|
166
|
-
- Check name in project list instead of fetching project page for mirrors. This improves response times and avoids leaking typos of private package names upstream.
|
|
167
|
-
|
|
168
|
-
- Use ETag header if provided by mirror to reduce bandwidth usage and latency.
|
|
169
|
-
|
|
170
|
-
- Prevent concurrent updates of simple links on mirrors with a short lived lock.
|
|
171
|
-
|
|
172
|
-
- Support `PEP-691 <https://peps.python.org/pep-0691/>`_ conformant fetching for mirrors, and requests with JSON result for installers. Proxy servers should add compression support for the ``application/vnd.pypi.simple.v1+json`` content type (``gzip_types`` for nginx).
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
Bug Fixes
|
|
176
|
-
---------
|
|
177
|
-
|
|
178
|
-
- Fix #743: support PEP427 escaped wheels with local version, where the + is replaced by _.
|
|
179
|
-
|
|
180
|
-
- Fix #895: store and return content of data-yanked.
|
|
181
|
-
|
|
182
|
-
- Fix #908: include basic auth from ``mirror_url`` when fetching packages.
|
|
183
|
-
|
|
184
|
-
- Fix #914: switch to write transaction as late as possible when streaming a file from a mirror.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
6.5.1 (2022-04-25)
|
|
188
|
-
==================
|
|
189
|
-
|
|
190
|
-
Bug Fixes
|
|
191
|
-
---------
|
|
192
|
-
|
|
193
|
-
- Fix traceback when trying to delete already deleted release or toxresult.
|
|
194
|
-
|
|
195
|
-
- Preserve index config settings of plugins during import instead of aborting, even if the plugin isn't installed during import.
|
|
196
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '6.9.1'
|
|
@@ -45,8 +45,6 @@ def strtobool(val):
|
|
|
45
45
|
|
|
46
46
|
def get_pluginmanager(load_entrypoints=True):
|
|
47
47
|
pm = PluginManager("devpiserver")
|
|
48
|
-
# support old plugins, but emit deprecation warnings
|
|
49
|
-
pm._implprefix = "devpiserver_"
|
|
50
48
|
pm.add_hookspecs(hookspecs)
|
|
51
49
|
# XXX load internal plugins here
|
|
52
50
|
if load_entrypoints:
|
|
@@ -508,7 +506,7 @@ def parseoptions(pluginmanager, argv, parser=None):
|
|
|
508
506
|
try:
|
|
509
507
|
config_options = load_config_file(config_file)
|
|
510
508
|
except InvalidConfigError as e:
|
|
511
|
-
log.error("Error in config file '%s':\n %s" % (
|
|
509
|
+
log.error("Error in config file '%s':\n %s" % ( # noqa: TRY400
|
|
512
510
|
config_file, e))
|
|
513
511
|
sys.exit(4)
|
|
514
512
|
defaultget = partial(
|
|
@@ -882,12 +880,11 @@ class Config(object):
|
|
|
882
880
|
|
|
883
881
|
@cached_property
|
|
884
882
|
def secretfile(self):
|
|
885
|
-
import warnings
|
|
886
883
|
if not self.args.secretfile:
|
|
887
884
|
secretfile = self.serverdir.join('.secret')
|
|
888
885
|
if not secretfile.check(file=True):
|
|
889
886
|
return None
|
|
890
|
-
|
|
887
|
+
log.warn(
|
|
891
888
|
"Using deprecated existing secret file at '%s', use "
|
|
892
889
|
"--secretfile to explicitly provide the location." % secretfile)
|
|
893
890
|
return secretfile
|
|
@@ -139,15 +139,12 @@ class FileStore:
|
|
|
139
139
|
# dir_hash_spec is set for toxresult files
|
|
140
140
|
if dir_hash_spec is None:
|
|
141
141
|
dir_hash_spec = get_default_hash_spec(content_or_file)
|
|
142
|
-
# prevent hashing twice
|
|
143
|
-
hash_spec = dir_hash_spec
|
|
144
142
|
hashdir_a, hashdir_b = make_splitdir(dir_hash_spec)
|
|
145
143
|
key = self.keyfs.STAGEFILE(
|
|
146
144
|
user=user, index=index,
|
|
147
145
|
hashdir_a=hashdir_a, hashdir_b=hashdir_b, filename=basename)
|
|
148
146
|
entry = FileEntry(key, readonly=False)
|
|
149
|
-
entry.file_set_content(
|
|
150
|
-
content_or_file, hash_spec=hash_spec)
|
|
147
|
+
entry.file_set_content(content_or_file)
|
|
151
148
|
return entry
|
|
152
149
|
|
|
153
150
|
|
|
@@ -87,11 +87,12 @@ def export(pluginmanager=None, argv=None):
|
|
|
87
87
|
fatal("The path '%s' contains no devpi-server data, use devpi-init to initialize." % config.serverdir)
|
|
88
88
|
xom = xom_from_config(config)
|
|
89
89
|
do_export(config.args.directory, xom)
|
|
90
|
-
return 0
|
|
91
90
|
except Fatal as e:
|
|
92
91
|
tw = py.io.TerminalWriter(sys.stderr)
|
|
93
92
|
tw.line("fatal: %s" % e.args[0], red=True)
|
|
94
93
|
return 1
|
|
94
|
+
else:
|
|
95
|
+
return 0
|
|
95
96
|
|
|
96
97
|
|
|
97
98
|
def do_import(path, xom):
|
|
@@ -151,11 +152,12 @@ def import_(pluginmanager=None, argv=None):
|
|
|
151
152
|
xom.thread_pool.start_one(xom.keyfs.notifier)
|
|
152
153
|
init_default_indexes(xom)
|
|
153
154
|
do_import(config.args.directory, xom)
|
|
154
|
-
return 0
|
|
155
155
|
except Fatal as e:
|
|
156
156
|
tw = py.io.TerminalWriter(sys.stderr)
|
|
157
157
|
tw.line("fatal: %s" % e.args[0], red=True)
|
|
158
158
|
return 1
|
|
159
|
+
else:
|
|
160
|
+
return 0
|
|
159
161
|
|
|
160
162
|
|
|
161
163
|
class Exporter:
|
|
@@ -45,8 +45,9 @@ def init(pluginmanager=None, argv=None):
|
|
|
45
45
|
set_state_version(config, DATABASE_VERSION)
|
|
46
46
|
xom = xom_from_config(config, init=True)
|
|
47
47
|
init_default_indexes(xom)
|
|
48
|
-
return 0
|
|
49
48
|
except Fatal as e:
|
|
50
49
|
tw = py.io.TerminalWriter(sys.stderr)
|
|
51
50
|
tw.line("fatal: %s" % e.args[0], red=True)
|
|
52
51
|
return 1
|
|
52
|
+
else:
|
|
53
|
+
return 0
|
|
@@ -28,6 +28,10 @@ from devpi_common.types import cached_property
|
|
|
28
28
|
absent = object()
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
class KeyfsTimeoutError(TimeoutError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
31
35
|
class MissingFileException(Exception):
|
|
32
36
|
def __init__(self, relpath, serial):
|
|
33
37
|
msg = "missing file '%s' at serial %s" % (relpath, serial)
|
|
@@ -318,7 +322,7 @@ class KeyFS(object):
|
|
|
318
322
|
|
|
319
323
|
@property
|
|
320
324
|
def tx(self):
|
|
321
|
-
return
|
|
325
|
+
return self._threadlocal.tx
|
|
322
326
|
|
|
323
327
|
def add_key(self, name, path, type):
|
|
324
328
|
assert isinstance(path, str)
|
|
@@ -461,7 +465,6 @@ def iter_serial_and_value_backwards(conn, relpath, last_serial):
|
|
|
461
465
|
|
|
462
466
|
# we could not find any change below at_serial which means
|
|
463
467
|
# the key didn't exist at that point in time
|
|
464
|
-
return
|
|
465
468
|
|
|
466
469
|
|
|
467
470
|
def iter_relpaths_at(self, typedkeys, at_serial):
|
|
@@ -762,7 +765,11 @@ class Transaction(object):
|
|
|
762
765
|
"restarting %s transaction afresh as %s transaction",
|
|
763
766
|
"write" if self.write else "read",
|
|
764
767
|
"write" if write else "read")
|
|
765
|
-
|
|
768
|
+
try:
|
|
769
|
+
newtx = self.__class__(self.keyfs, write=write)
|
|
770
|
+
except BaseException:
|
|
771
|
+
self.doomed = True
|
|
772
|
+
raise
|
|
766
773
|
self.__dict__ = newtx.__dict__
|
|
767
774
|
|
|
768
775
|
def doom(self):
|
|
@@ -2,6 +2,7 @@ from devpi_common.types import cached_property
|
|
|
2
2
|
from .config import hookimpl
|
|
3
3
|
from .fileutil import dumps, loads
|
|
4
4
|
from .interfaces import IStorageConnection2
|
|
5
|
+
from .keyfs import KeyfsTimeoutError
|
|
5
6
|
from .keyfs import RelpathInfo
|
|
6
7
|
from .keyfs import get_relpath_at
|
|
7
8
|
from .log import threadlog, thread_push_log, thread_pop_log
|
|
@@ -115,7 +116,11 @@ class BaseConnection:
|
|
|
115
116
|
self._sqlconn.commit()
|
|
116
117
|
|
|
117
118
|
def rollback(self):
|
|
118
|
-
|
|
119
|
+
try:
|
|
120
|
+
self._sqlconn.rollback()
|
|
121
|
+
except sqlite3.ProgrammingError as e:
|
|
122
|
+
if not e.args or 'closed database' not in e.args[0]:
|
|
123
|
+
raise
|
|
119
124
|
|
|
120
125
|
@cached_property
|
|
121
126
|
def last_changelog_serial(self):
|
|
@@ -379,7 +384,6 @@ class BaseStorage(object):
|
|
|
379
384
|
conn = self._get_sqlconn_uri_kw(uri)
|
|
380
385
|
# remember for next time
|
|
381
386
|
self._get_sqlconn = self._get_sqlconn_uri_kw
|
|
382
|
-
return conn
|
|
383
387
|
except TypeError as e:
|
|
384
388
|
if e.args and 'uri' in e.args[0] and 'keyword argument' in e.args[0]:
|
|
385
389
|
threadlog.warn(
|
|
@@ -396,12 +400,13 @@ class BaseStorage(object):
|
|
|
396
400
|
threadlog.warn(
|
|
397
401
|
"The installed version of sqlite3 doesn't support the uri "
|
|
398
402
|
"keyword for 'sqlite3.connect'.")
|
|
403
|
+
else:
|
|
404
|
+
return conn
|
|
399
405
|
try:
|
|
400
406
|
# sqlite3 might be compiled with default URI support
|
|
401
407
|
conn = self._get_sqlconn_uri(uri)
|
|
402
408
|
# remember for next time
|
|
403
409
|
self._get_sqlconn = self._get_sqlconn_uri
|
|
404
|
-
return conn
|
|
405
410
|
except sqlite3.OperationalError as e:
|
|
406
411
|
# log the error and switch to using the path
|
|
407
412
|
threadlog.warn("%s" % e)
|
|
@@ -413,6 +418,8 @@ class BaseStorage(object):
|
|
|
413
418
|
# remember for next time
|
|
414
419
|
self._get_sqlconn = self._get_sqlconn_path
|
|
415
420
|
return conn
|
|
421
|
+
else:
|
|
422
|
+
return conn
|
|
416
423
|
|
|
417
424
|
def get_connection(self, closing=True, write=False, timeout=30):
|
|
418
425
|
# we let the database serialize all writers at connection time
|
|
@@ -439,7 +446,8 @@ class BaseStorage(object):
|
|
|
439
446
|
elapsed = time.monotonic() - start_time
|
|
440
447
|
if elapsed > timeout:
|
|
441
448
|
# if it takes this long, something is wrong
|
|
442
|
-
raise
|
|
449
|
+
raise KeyfsTimeoutError(
|
|
450
|
+
f"Timeout after {int(elapsed)} seconds.") from e
|
|
443
451
|
conn = self.Connection(sqlconn, self.basedir, self)
|
|
444
452
|
if closing:
|
|
445
453
|
return contextlib.closing(conn)
|
|
@@ -278,9 +278,8 @@ def check_pending_renames(basedir, pending_relnames):
|
|
|
278
278
|
rename(path, dst)
|
|
279
279
|
threadlog.warn("completed file-commit from crashed tx: %s",
|
|
280
280
|
dst)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
raise OSError("missing file %s" % dst)
|
|
281
|
+
elif not os.path.exists(dst):
|
|
282
|
+
raise OSError("missing file %s" % dst)
|
|
284
283
|
else:
|
|
285
284
|
try:
|
|
286
285
|
os.remove(path) # was already removed
|
|
@@ -198,7 +198,7 @@ class AsyncioLoopThread(object):
|
|
|
198
198
|
threadlog.exception("Exception in asyncio event loop")
|
|
199
199
|
finally:
|
|
200
200
|
threadlog.info("The asyncio event loop stopped")
|
|
201
|
-
|
|
201
|
+
return
|
|
202
202
|
|
|
203
203
|
def thread_shutdown(self):
|
|
204
204
|
loop = self.loop
|
|
@@ -220,6 +220,7 @@ class XOM:
|
|
|
220
220
|
self.config = config
|
|
221
221
|
self.thread_pool = mythread.ThreadPool()
|
|
222
222
|
self.async_thread = AsyncioLoopThread(self)
|
|
223
|
+
self.async_tasks = set()
|
|
223
224
|
self.thread_pool.register(self.async_thread)
|
|
224
225
|
if httpget is not None:
|
|
225
226
|
self.httpget = httpget
|
|
@@ -265,7 +266,11 @@ class XOM:
|
|
|
265
266
|
return future.result()
|
|
266
267
|
|
|
267
268
|
def create_task(self, coroutine):
|
|
268
|
-
asyncio.ensure_future(coroutine, loop=self.async_thread.loop)
|
|
269
|
+
task = asyncio.ensure_future(coroutine, loop=self.async_thread.loop)
|
|
270
|
+
# keep a strong reference
|
|
271
|
+
self.async_tasks.add(task)
|
|
272
|
+
# automatically remove the reference when done
|
|
273
|
+
task.add_done_callback(self.async_tasks.discard)
|
|
269
274
|
|
|
270
275
|
def get_singleton(self, indexpath, key):
|
|
271
276
|
""" return a per-xom singleton for the given indexpath and key
|
|
@@ -430,7 +435,6 @@ class XOM:
|
|
|
430
435
|
allow_redirects=allow_redirects,
|
|
431
436
|
headers=headers,
|
|
432
437
|
timeout=timeout or self.config.args.request_timeout)
|
|
433
|
-
return resp
|
|
434
438
|
except OSError as e:
|
|
435
439
|
location = get_caller_location()
|
|
436
440
|
threadlog.warn(
|
|
@@ -449,6 +453,8 @@ class XOM:
|
|
|
449
453
|
"HTTPError during httpget of %s at %s: %s",
|
|
450
454
|
url, location, lazy_format_exception_only(e))
|
|
451
455
|
return FatalResponse(url, repr(sys.exc_info()[1]))
|
|
456
|
+
else:
|
|
457
|
+
return resp
|
|
452
458
|
|
|
453
459
|
def view_deriver(self, view, info):
|
|
454
460
|
if self.is_replica():
|
|
@@ -618,10 +624,16 @@ class FatalResponse:
|
|
|
618
624
|
def __init__(self, url, reason):
|
|
619
625
|
self.url = url
|
|
620
626
|
self.reason = reason
|
|
627
|
+
self.status = self.status_code
|
|
621
628
|
|
|
622
629
|
def __repr__(self):
|
|
623
630
|
return "%s(%r)" % (self.__class__.__name__, self.reason)
|
|
624
631
|
|
|
632
|
+
# an adapter to allow this to be used in async_httpget
|
|
633
|
+
def __iter__(self):
|
|
634
|
+
yield self
|
|
635
|
+
yield self.reason
|
|
636
|
+
|
|
625
637
|
def close(self):
|
|
626
638
|
pass
|
|
627
639
|
|
|
@@ -18,4 +18,6 @@ class OutsideURLMiddleware(object):
|
|
|
18
18
|
environ['HTTP_HOST'] = outside_url.netloc
|
|
19
19
|
if outside_url.path:
|
|
20
20
|
environ['SCRIPT_NAME'] = outside_url.path
|
|
21
|
+
if environ['PATH_INFO'].startswith(outside_url.path):
|
|
22
|
+
environ['PATH_INFO'] = environ['PATH_INFO'][len(outside_url.path):]
|
|
21
23
|
return self.app(environ, start_response)
|
|
@@ -9,7 +9,6 @@ import asyncio
|
|
|
9
9
|
import time
|
|
10
10
|
|
|
11
11
|
import re
|
|
12
|
-
from devpi_common.vendor._pip import HTMLPage
|
|
13
12
|
from devpi_common.url import URL
|
|
14
13
|
from devpi_common.metadata import BasenameMeta
|
|
15
14
|
from devpi_common.metadata import is_archive_of_project
|
|
@@ -22,10 +21,12 @@ from .exceptions import lazy_format_exception
|
|
|
22
21
|
from .filestore import key_from_link
|
|
23
22
|
from .model import BaseStageCustomizer
|
|
24
23
|
from .model import BaseStage
|
|
24
|
+
from .model import Unknown
|
|
25
25
|
from .model import ensure_boolean
|
|
26
26
|
from .model import join_links_data
|
|
27
27
|
from .readonly import ensure_deeply_readonly
|
|
28
28
|
from .log import threadlog
|
|
29
|
+
from .vendor._pip import HTMLPage
|
|
29
30
|
from .views import SIMPLE_API_V1_JSON
|
|
30
31
|
from .views import make_uuid_headers
|
|
31
32
|
import json
|
|
@@ -235,16 +236,24 @@ class MirrorStage(BaseStage):
|
|
|
235
236
|
url = self.mirror_url
|
|
236
237
|
return dict(username=url.username, password=url.password)
|
|
237
238
|
|
|
239
|
+
@property
|
|
240
|
+
def no_project_list(self):
|
|
241
|
+
return self.ixconfig.get('mirror_no_project_list', False)
|
|
242
|
+
|
|
238
243
|
@property
|
|
239
244
|
def use_external_url(self):
|
|
240
245
|
return self.ixconfig.get('mirror_use_external_urls', False)
|
|
241
246
|
|
|
242
247
|
def get_possible_indexconfig_keys(self):
|
|
243
248
|
return tuple(dict(self.get_default_config_items())) + (
|
|
244
|
-
"custom_data",
|
|
245
|
-
"
|
|
249
|
+
"custom_data",
|
|
250
|
+
"description",
|
|
251
|
+
"mirror_cache_expiry",
|
|
252
|
+
"mirror_no_project_list",
|
|
253
|
+
"mirror_url",
|
|
246
254
|
"mirror_use_external_urls",
|
|
247
|
-
"mirror_web_url_fmt",
|
|
255
|
+
"mirror_web_url_fmt",
|
|
256
|
+
"title")
|
|
248
257
|
|
|
249
258
|
def get_default_config_items(self):
|
|
250
259
|
return [("volatile", True)]
|
|
@@ -264,6 +273,8 @@ class MirrorStage(BaseStage):
|
|
|
264
273
|
raise self.InvalidIndexconfig([
|
|
265
274
|
"'mirror_cache_expiry' option must be an integer"])
|
|
266
275
|
return value
|
|
276
|
+
if key == "mirror_no_project_list":
|
|
277
|
+
return ensure_boolean(value)
|
|
267
278
|
if key == "mirror_use_external_urls":
|
|
268
279
|
return ensure_boolean(value)
|
|
269
280
|
if key in ("custom_data", "description", "mirror_web_url_fmt", "title"):
|
|
@@ -427,6 +438,10 @@ class MirrorStage(BaseStage):
|
|
|
427
438
|
if self.offline:
|
|
428
439
|
threadlog.warn("offline mode: using stale projects list")
|
|
429
440
|
return self._stale_list_projects_perstage()
|
|
441
|
+
if self.no_project_list:
|
|
442
|
+
# upstream of mirror configured as not having a project list
|
|
443
|
+
# return only locally known projects
|
|
444
|
+
return self._stale_list_projects_perstage()
|
|
430
445
|
# try without lock first
|
|
431
446
|
if not self.cache_projectnames.is_expired(self.cache_expiry):
|
|
432
447
|
projects = self.cache_projectnames.get()
|
|
@@ -692,11 +707,15 @@ class MirrorStage(BaseStage):
|
|
|
692
707
|
if not is_retrieval_expired:
|
|
693
708
|
raise self.UpstreamNotFoundError(
|
|
694
709
|
"cached not found for project %s" % project)
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
self.
|
|
698
|
-
|
|
699
|
-
|
|
710
|
+
else:
|
|
711
|
+
exists = self.has_project_perstage(project)
|
|
712
|
+
if exists is Unknown and self.no_project_list:
|
|
713
|
+
pass
|
|
714
|
+
elif not exists:
|
|
715
|
+
# immediately cache the not found with no ETag
|
|
716
|
+
self.cache_retrieve_times.refresh(project, None)
|
|
717
|
+
raise self.UpstreamNotFoundError(
|
|
718
|
+
"project %s not found" % project)
|
|
700
719
|
|
|
701
720
|
newlinks_future = self.xom.create_future()
|
|
702
721
|
# we need to set this up here, as these access the database and
|
|
@@ -760,6 +779,10 @@ class MirrorStage(BaseStage):
|
|
|
760
779
|
project = normalize_name(project)
|
|
761
780
|
if self.is_project_cached(project):
|
|
762
781
|
return True
|
|
782
|
+
if self.no_project_list:
|
|
783
|
+
if project in self._stale_list_projects_perstage():
|
|
784
|
+
return True
|
|
785
|
+
return Unknown
|
|
763
786
|
# use the internal method to avoid a copy
|
|
764
787
|
return project in self._list_projects_perstage()
|
|
765
788
|
|