devpi-server 6.10.0__tar.gz → 6.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devpi-server-6.10.0 → devpi_server-6.11.0}/CHANGELOG +22 -1
- {devpi-server-6.10.0 → devpi_server-6.11.0}/CHANGELOG.short.rst +21 -19
- {devpi-server-6.10.0 → devpi_server-6.11.0}/PKG-INFO +23 -21
- devpi_server-6.11.0/devpi_server/__init__.py +1 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/auth.py +4 -2
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/config.py +1 -1
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/filestore.py +39 -21
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/filestore_fs.py +1 -6
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/fsck.py +7 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/hookspecs.py +12 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/importexport.py +19 -6
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/keyfs.py +45 -57
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/keyfs_sqlite.py +5 -6
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/keyfs_sqlite_fs.py +1 -6
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/main.py +4 -1
- devpi_server-6.11.0/devpi_server/markers.py +14 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/middleware.py +0 -2
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/mirror.py +46 -10
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/model.py +40 -22
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/replica.py +71 -81
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/views.py +139 -150
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server.egg-info/PKG-INFO +23 -21
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server.egg-info/SOURCES.txt +1 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/setup.py +1 -1
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/mirrordata/dataindex.json +1 -1
- devpi_server-6.11.0/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +1 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/plugin.py +11 -22
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/reqmock.py +16 -9
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/simpypi.py +4 -4
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_auth.py +3 -3
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_config.py +9 -7
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_filestore.py +30 -28
- devpi_server-6.11.0/test_devpi_server/test_fsck.py +41 -0
- devpi_server-6.11.0/test_devpi_server/test_genconfig.py +50 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_importexport.py +88 -68
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_keyfs.py +11 -1
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_main.py +1 -1
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_mirror.py +32 -4
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_model.py +29 -45
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_nginx.py +4 -1
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_permissions.py +8 -6
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_replica.py +45 -33
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_stage_customizer.py +8 -8
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_streaming.py +1 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_view_auth.py +10 -8
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_views.py +19 -24
- {devpi-server-6.10.0 → devpi_server-6.11.0}/tox.ini +7 -2
- devpi-server-6.10.0/devpi_server/__init__.py +0 -1
- devpi-server-6.10.0/devpi_server/markers.py +0 -6
- devpi-server-6.10.0/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- devpi-server-6.10.0/test_devpi_server/test_genconfig.py +0 -19
- {devpi-server-6.10.0 → devpi_server-6.11.0}/.flake8 +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/AUTHORS +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/LICENSE +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/MANIFEST.in +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/README.rst +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/__main__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/auth_basic.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/auth_devpi.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/__init__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/crontab.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/exceptions.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/fileutil.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/genconfig.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/init.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/interfaces.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/keyfs_types.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/log.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/mythread.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/passwd.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/readonly.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/sizeof.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/vendor/__init__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/vendor/_pip.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server/view_auth.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server.egg-info/not-zip-safe +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server.egg-info/requires.txt +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/pyproject.toml +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/pytest_devpi_server/__init__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/setup.cfg +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/__init__.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/conftest.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/example.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/functional.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- /devpi-server-6.10.0/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz → /devpi_server-6.11.0/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- /devpi-server-6.10.0/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz → /devpi_server-6.11.0/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- /devpi-server-6.10.0/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz → /devpi_server-6.11.0/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_authcheck.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_conftest.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_fileutil.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_keyfs_sqlite_fs.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_log.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_mythread.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_nginx_replica.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_readonly.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_replica_functional.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_reqmock.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_streaming_nginx.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_streaming_replica.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_views_patch.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_views_push_external.py +0 -0
- {devpi-server-6.10.0 → devpi_server-6.11.0}/test_devpi_server/test_views_status.py +0 -0
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.11.0 (2024-04-20)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Features
|
|
9
|
+
--------
|
|
10
|
+
|
|
11
|
+
- The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
|
|
12
|
+
|
|
13
|
+
- Fix #983: Add plugin hook for mirror authentication header.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Bug Fixes
|
|
18
|
+
---------
|
|
19
|
+
|
|
20
|
+
- Preserve last modified of docs and toxresults during export/import.
|
|
21
|
+
|
|
22
|
+
- Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
5
26
|
6.10.0 (2023-12-19)
|
|
6
27
|
===================
|
|
7
28
|
|
|
@@ -10,7 +31,7 @@ Features
|
|
|
10
31
|
|
|
11
32
|
- Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
|
|
12
33
|
|
|
13
|
-
- Fix #
|
|
34
|
+
- Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
|
|
14
35
|
|
|
15
36
|
|
|
16
37
|
|
|
@@ -9,6 +9,27 @@ Changelog
|
|
|
9
9
|
|
|
10
10
|
.. towncrier release notes start
|
|
11
11
|
|
|
12
|
+
6.10.0 (2023-12-19)
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Features
|
|
16
|
+
--------
|
|
17
|
+
|
|
18
|
+
- Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
|
|
19
|
+
|
|
20
|
+
- Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Bug Fixes
|
|
25
|
+
---------
|
|
26
|
+
|
|
27
|
+
- Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
|
|
28
|
+
|
|
29
|
+
- Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
12
33
|
6.9.2 (2023-08-06)
|
|
13
34
|
==================
|
|
14
35
|
|
|
@@ -72,22 +93,3 @@ Bug Fixes
|
|
|
72
93
|
|
|
73
94
|
- Add locking to mirror name cache to prevent race condition on updates.
|
|
74
95
|
|
|
75
|
-
|
|
76
|
-
6.7.0 (2022-09-28)
|
|
77
|
-
==================
|
|
78
|
-
|
|
79
|
-
Features
|
|
80
|
-
--------
|
|
81
|
-
|
|
82
|
-
- Add nginx example to ``devpi-gen-config`` with caching of simple pages for installers like pip.
|
|
83
|
-
|
|
84
|
-
- Automatically check for ``+files`` when using ``--replica-file-search-path``.
|
|
85
|
-
|
|
86
|
-
- Set headers to prevent caching for simple links with stale results.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Bug Fixes
|
|
90
|
-
---------
|
|
91
|
-
|
|
92
|
-
- Fix #840: Correct url scheme in config if nginx is behind another proxy.
|
|
93
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.11.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
|
|
@@ -119,6 +119,27 @@ Changelog
|
|
|
119
119
|
|
|
120
120
|
.. towncrier release notes start
|
|
121
121
|
|
|
122
|
+
6.11.0 (2024-04-20)
|
|
123
|
+
===================
|
|
124
|
+
|
|
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
|
+
Bug Fixes
|
|
135
|
+
---------
|
|
136
|
+
|
|
137
|
+
- Preserve last modified of docs and toxresults during export/import.
|
|
138
|
+
|
|
139
|
+
- Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
122
143
|
6.10.0 (2023-12-19)
|
|
123
144
|
===================
|
|
124
145
|
|
|
@@ -127,7 +148,7 @@ Features
|
|
|
127
148
|
|
|
128
149
|
- Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
|
|
129
150
|
|
|
130
|
-
- Fix #
|
|
151
|
+
- Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
|
|
131
152
|
|
|
132
153
|
|
|
133
154
|
|
|
@@ -184,22 +205,3 @@ Bug Fixes
|
|
|
184
205
|
|
|
185
206
|
- Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
|
|
186
207
|
|
|
187
|
-
|
|
188
|
-
6.8.0 (2022-12-05)
|
|
189
|
-
==================
|
|
190
|
-
|
|
191
|
-
Features
|
|
192
|
-
--------
|
|
193
|
-
|
|
194
|
-
- Fix #929: Cache normalized project names per transaction on mirror index instances.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
Bug Fixes
|
|
198
|
-
---------
|
|
199
|
-
|
|
200
|
-
- Fix #914: add locking to list_projects_perstage of mirror indexes to prevent multiple slow concurrent updates of the full project name list.
|
|
201
|
-
|
|
202
|
-
- Catch exceptions in async_httpget analog to httpget.
|
|
203
|
-
|
|
204
|
-
- Add locking to mirror name cache to prevent race condition on updates.
|
|
205
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '6.11.0'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import hashlib
|
|
3
|
+
import itertools
|
|
3
4
|
import itsdangerous
|
|
4
5
|
import secrets
|
|
5
6
|
from .log import threadlog
|
|
@@ -53,7 +54,8 @@ class Auth:
|
|
|
53
54
|
# one of the plugins returned valid userinfo
|
|
54
55
|
# return union of all groups which may be contained in that info
|
|
55
56
|
groups = (ui.get('groups', []) for ui in userinfo_list)
|
|
56
|
-
return dict(status="ok", groups=sorted(
|
|
57
|
+
return dict(status="ok", groups=sorted(
|
|
58
|
+
set(itertools.chain.from_iterable(groups))))
|
|
57
59
|
|
|
58
60
|
def _validate(self, authuser, authpassword, request=None):
|
|
59
61
|
""" Validates user credentials.
|
|
@@ -131,7 +133,7 @@ class Auth:
|
|
|
131
133
|
username,
|
|
132
134
|
result.get("groups", []),
|
|
133
135
|
result.get("from_user_object", False)])
|
|
134
|
-
assert not isinstance(pseudopass, str) or pseudopass.encode('ascii')
|
|
136
|
+
assert not isinstance(pseudopass, str) or pseudopass.encode('ascii') # type: ignore[attr-defined]
|
|
135
137
|
return {"password": pseudopass,
|
|
136
138
|
"expiration": self.LOGIN_EXPIRATION}
|
|
137
139
|
|
|
@@ -179,7 +179,7 @@ def add_web_options(parser, pluginmanager):
|
|
|
179
179
|
|
|
180
180
|
def add_mirror_options(parser, pluginmanager):
|
|
181
181
|
parser.addoption(
|
|
182
|
-
"--mirror-cache-expiry", type=
|
|
182
|
+
"--mirror-cache-expiry", type=int, metavar="SECS",
|
|
183
183
|
default=DEFAULT_MIRROR_CACHE_EXPIRY,
|
|
184
184
|
help="(experimental) time after which projects in mirror indexes "
|
|
185
185
|
"are checked for new releases.")
|
|
@@ -10,9 +10,14 @@ import re
|
|
|
10
10
|
from devpi_common.metadata import splitbasename
|
|
11
11
|
from devpi_common.types import parse_hash_spec
|
|
12
12
|
from devpi_server.log import threadlog
|
|
13
|
+
from inspect import currentframe
|
|
13
14
|
from urllib.parse import unquote
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
class ChecksumError(ValueError):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
16
21
|
_nodefault = object()
|
|
17
22
|
|
|
18
23
|
|
|
@@ -38,23 +43,37 @@ def get_file_hash(fp, hash_type):
|
|
|
38
43
|
return running_hash.hexdigest()
|
|
39
44
|
|
|
40
45
|
|
|
46
|
+
def get_seekable_content_or_file(content_or_file):
|
|
47
|
+
if isinstance(content_or_file, bytes):
|
|
48
|
+
return content_or_file
|
|
49
|
+
seekable_method = getattr(content_or_file, "seekable", None)
|
|
50
|
+
seekable = seekable_method() if callable(seekable_method) else False
|
|
51
|
+
if not seekable:
|
|
52
|
+
content_or_file = content_or_file.read()
|
|
53
|
+
if len(content_or_file) > 1048576:
|
|
54
|
+
frame = currentframe()
|
|
55
|
+
if frame is not None and frame.f_back is not None:
|
|
56
|
+
frame = frame.f_back
|
|
57
|
+
if frame is None:
|
|
58
|
+
f_name = "get_seekable_content_or_file"
|
|
59
|
+
else:
|
|
60
|
+
f_name = frame.f_code.co_name
|
|
61
|
+
threadlog.warn(
|
|
62
|
+
"Read %.1f megabytes into memory in %s",
|
|
63
|
+
len(content_or_file) / 1048576, f_name)
|
|
64
|
+
return content_or_file
|
|
65
|
+
|
|
66
|
+
|
|
41
67
|
def get_hash_spec(content_or_file, hash_type):
|
|
42
68
|
if not isinstance(content_or_file, bytes):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if len(content_or_file) > 1048576:
|
|
52
|
-
threadlog.warn(
|
|
53
|
-
"Read %.1f megabytes into memory in get_default_hash_spec",
|
|
54
|
-
len(content_or_file) / 1048576)
|
|
55
|
-
if isinstance(content_or_file, bytes):
|
|
56
|
-
running_hash = getattr(hashlib, hash_type)(content_or_file)
|
|
57
|
-
return f"{running_hash.name}={running_hash.hexdigest()}"
|
|
69
|
+
assert content_or_file.seekable()
|
|
70
|
+
content_or_file.seek(0)
|
|
71
|
+
hexdigest = get_file_hash(
|
|
72
|
+
content_or_file, hash_type)
|
|
73
|
+
content_or_file.seek(0)
|
|
74
|
+
return f"{hash_type}={hexdigest}"
|
|
75
|
+
running_hash = getattr(hashlib, hash_type)(content_or_file)
|
|
76
|
+
return f"{running_hash.name}={running_hash.hexdigest()}"
|
|
58
77
|
|
|
59
78
|
|
|
60
79
|
def make_splitdir(hash_spec):
|
|
@@ -150,8 +169,7 @@ class FileStore:
|
|
|
150
169
|
|
|
151
170
|
def metaprop(name):
|
|
152
171
|
def fget(self):
|
|
153
|
-
if self.meta is
|
|
154
|
-
return self.meta.get(name)
|
|
172
|
+
return None if self.meta is None else self.meta.get(name)
|
|
155
173
|
|
|
156
174
|
def fset(self, val):
|
|
157
175
|
val = unicode_if_bytes(val)
|
|
@@ -251,7 +269,7 @@ class FileEntry(object):
|
|
|
251
269
|
def file_os_path(self):
|
|
252
270
|
return self.tx.conn.io_file_os_path(self._storepath)
|
|
253
271
|
|
|
254
|
-
def file_set_content(self, content_or_file, last_modified=None, hash_spec=None):
|
|
272
|
+
def file_set_content(self, content_or_file, *, last_modified=None, hash_spec=None):
|
|
255
273
|
if last_modified != -1:
|
|
256
274
|
if last_modified is None:
|
|
257
275
|
last_modified = unicode_if_bytes(format_date_time(None))
|
|
@@ -294,7 +312,7 @@ class FileEntry(object):
|
|
|
294
312
|
self.file_delete()
|
|
295
313
|
|
|
296
314
|
def has_existing_metadata(self):
|
|
297
|
-
return self.hash_spec and self.last_modified
|
|
315
|
+
return bool(self.hash_spec and self.last_modified)
|
|
298
316
|
|
|
299
317
|
|
|
300
318
|
def get_checksum_error(content_or_hash, relpath, hash_spec):
|
|
@@ -306,12 +324,12 @@ def get_checksum_error(content_or_hash, relpath, hash_spec):
|
|
|
306
324
|
if callable(hexdigest):
|
|
307
325
|
hexdigest = hexdigest()
|
|
308
326
|
if content_or_hash.name != hash_type:
|
|
309
|
-
return
|
|
327
|
+
return ChecksumError(
|
|
310
328
|
f"{relpath}: hash type mismatch, "
|
|
311
329
|
f"got {content_or_hash.name}, expected {hash_type}")
|
|
312
330
|
else:
|
|
313
331
|
hexdigest = hash_algo(content_or_hash).hexdigest()
|
|
314
332
|
if hexdigest != hash_value:
|
|
315
|
-
return
|
|
333
|
+
return ChecksumError(
|
|
316
334
|
f"{relpath}: {hash_type} mismatch, "
|
|
317
335
|
f"got {hexdigest}, expected {hash_value}")
|
|
@@ -44,15 +44,10 @@ class DirtyFile:
|
|
|
44
44
|
os.link(content_or_file.devpi_srcpath, self.tmppath)
|
|
45
45
|
else:
|
|
46
46
|
with get_write_file_ensure_dir(self.tmppath) as f:
|
|
47
|
-
if not isinstance(content_or_file, bytes) and not callable(getattr(content_or_file, "seekable", None)):
|
|
48
|
-
content_or_file = content_or_file.read()
|
|
49
|
-
if len(content_or_file) > 1048576:
|
|
50
|
-
threadlog.warn(
|
|
51
|
-
"Read %.1f megabytes into memory in keyfs_sqlite_fs from_content for %s, because of unseekable file",
|
|
52
|
-
len(content_or_file) / 1048576, path)
|
|
53
47
|
if isinstance(content_or_file, bytes):
|
|
54
48
|
f.write(content_or_file)
|
|
55
49
|
else:
|
|
50
|
+
assert content_or_file.seekable()
|
|
56
51
|
content_or_file.seek(0)
|
|
57
52
|
shutil.copyfileobj(content_or_file, f)
|
|
58
53
|
return self
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .filestore import FileEntry
|
|
2
2
|
from .log import configure_cli_logging
|
|
3
3
|
from .main import CommandRunner
|
|
4
|
+
from .main import Fatal
|
|
4
5
|
from .main import xom_from_config
|
|
5
6
|
import sys
|
|
6
7
|
import time
|
|
@@ -38,6 +39,7 @@ def fsck():
|
|
|
38
39
|
last_time = time.time()
|
|
39
40
|
processed = 0
|
|
40
41
|
missing_files = 0
|
|
42
|
+
got_errors = False
|
|
41
43
|
with xom.keyfs.read_transaction() as tx:
|
|
42
44
|
log.info("Checking at serial %s" % tx.at_serial)
|
|
43
45
|
relpaths = tx.iter_relpaths_at(keys, tx.at_serial)
|
|
@@ -57,6 +59,7 @@ def fsck():
|
|
|
57
59
|
if not entry.file_exists():
|
|
58
60
|
missing_files += 1
|
|
59
61
|
if missing_files < 10:
|
|
62
|
+
got_errors = True
|
|
60
63
|
log.error("Missing file %s" % entry.relpath)
|
|
61
64
|
elif missing_files == 10:
|
|
62
65
|
log.error("Further missing files will be omitted.")
|
|
@@ -65,6 +68,7 @@ def fsck():
|
|
|
65
68
|
continue
|
|
66
69
|
checksum = entry.file_get_checksum(entry.hash_type)
|
|
67
70
|
if entry.hash_value != checksum:
|
|
71
|
+
got_errors = True
|
|
68
72
|
log.error(
|
|
69
73
|
"%s - %s mismatch, got %s, expected %s"
|
|
70
74
|
% (entry.relpath, entry.hash_type, checksum, entry.hash_value))
|
|
@@ -75,4 +79,7 @@ def fsck():
|
|
|
75
79
|
log.error(
|
|
76
80
|
"A total of %s files are missing."
|
|
77
81
|
% missing_files)
|
|
82
|
+
if got_errors:
|
|
83
|
+
msg = "There have been errors during consistency check."
|
|
84
|
+
raise Fatal(msg)
|
|
78
85
|
return runner.return_code
|
|
@@ -200,6 +200,18 @@ def devpiserver_auth_denials(request, acl, user, stage):
|
|
|
200
200
|
"""
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
@hookspec
|
|
204
|
+
def devpiserver_get_mirror_auth(mirror_url, www_authenticate_header):
|
|
205
|
+
"""Provide an http "Authorization" header to access a mirror.
|
|
206
|
+
|
|
207
|
+
Return a string which will be set as the authorization header for all http
|
|
208
|
+
requests to this mirror.
|
|
209
|
+
|
|
210
|
+
Return None or an empty string if no credentials can be determined for the
|
|
211
|
+
supplied url.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
|
|
203
215
|
@hookspec
|
|
204
216
|
def devpiserver_get_stage_customizer_classes():
|
|
205
217
|
"""EXPERIMENTAL!
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import itertools
|
|
1
2
|
import sys
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
@@ -279,6 +280,7 @@ class IndexDump:
|
|
|
279
280
|
project=linkstore.project,
|
|
280
281
|
relpath=relpath,
|
|
281
282
|
version=linkstore.version,
|
|
283
|
+
entrymapping=tox_link.entry.meta,
|
|
282
284
|
for_entrypath=reflink.entrypath,
|
|
283
285
|
log=tox_link.get_logs())
|
|
284
286
|
|
|
@@ -297,7 +299,9 @@ class IndexDump:
|
|
|
297
299
|
relpath = self.exporter.copy_file(
|
|
298
300
|
entry,
|
|
299
301
|
self.basedir.join("%s-%s.doc.zip" % (project, version)))
|
|
300
|
-
self.add_filedesc(
|
|
302
|
+
self.add_filedesc(
|
|
303
|
+
"doczip", project, relpath,
|
|
304
|
+
version=version, entrymapping=entry.meta)
|
|
301
305
|
|
|
302
306
|
|
|
303
307
|
class Importer:
|
|
@@ -535,8 +539,11 @@ class Importer:
|
|
|
535
539
|
if self.xom.config.hard_links:
|
|
536
540
|
# additional attribute for hard links
|
|
537
541
|
f.devpi_srcpath = p.strpath
|
|
542
|
+
|
|
543
|
+
# docs and toxresults didn't always have entrymapping in export dump
|
|
544
|
+
mapping = filedesc.get("entrymapping", {})
|
|
545
|
+
|
|
538
546
|
if filedesc["type"] == "releasefile":
|
|
539
|
-
mapping = filedesc["entrymapping"]
|
|
540
547
|
if self.dumpversion == "1":
|
|
541
548
|
# previous versions would not add a version attribute
|
|
542
549
|
version = BasenameMeta(p.basename).version
|
|
@@ -554,7 +561,7 @@ class Importer:
|
|
|
554
561
|
url = URL(mapping['url']).replace(fragment=mapping['hash_spec'])
|
|
555
562
|
entry = self.xom.filestore.maplink(
|
|
556
563
|
url, stage.username, stage.index, project)
|
|
557
|
-
entry.file_set_content(f, mapping["last_modified"])
|
|
564
|
+
entry.file_set_content(f, last_modified=mapping["last_modified"])
|
|
558
565
|
(_, links_with_data, serial) = stage._load_cache_links(project)
|
|
559
566
|
if links_with_data is None:
|
|
560
567
|
links_with_data = []
|
|
@@ -579,16 +586,22 @@ class Importer:
|
|
|
579
586
|
# determined here but in store_releasefile/store_doczip/store_toxresult etc
|
|
580
587
|
elif filedesc["type"] == "doczip":
|
|
581
588
|
version = filedesc["version"]
|
|
582
|
-
|
|
589
|
+
# docs didn't always have entrymapping in export dump
|
|
590
|
+
last_modified = mapping.get("last_modified")
|
|
591
|
+
link = stage.store_doczip(
|
|
592
|
+
project, version, f, last_modified=last_modified)
|
|
583
593
|
elif filedesc["type"] == "toxresult":
|
|
584
594
|
linkstore = stage.get_linkstore_perstage(
|
|
585
595
|
filedesc["projectname"], filedesc["version"])
|
|
586
596
|
# we can not search for the full relative path because
|
|
587
597
|
# it might use a different checksum
|
|
588
598
|
basename = posixpath.basename(filedesc["for_entrypath"])
|
|
599
|
+
# toxresults didn't always have entrymapping in export dump
|
|
600
|
+
last_modified = mapping.get("last_modified")
|
|
589
601
|
link, = linkstore.get_links(basename=basename)
|
|
590
602
|
link = stage.store_toxresult(
|
|
591
|
-
link, f, filename=posixpath.basename(filedesc["relpath"])
|
|
603
|
+
link, f, filename=posixpath.basename(filedesc["relpath"]),
|
|
604
|
+
last_modified=last_modified)
|
|
592
605
|
else:
|
|
593
606
|
msg = f"unknown file type: {type}"
|
|
594
607
|
raise Fatal(msg)
|
|
@@ -622,7 +635,7 @@ class IndexTree:
|
|
|
622
635
|
children.append(name)
|
|
623
636
|
|
|
624
637
|
def validate(self):
|
|
625
|
-
all_bases = set(
|
|
638
|
+
all_bases = set(itertools.chain.from_iterable(self.name2bases.values()))
|
|
626
639
|
all_indexes = set(self.name2bases)
|
|
627
640
|
missing = all_bases - all_indexes
|
|
628
641
|
if missing:
|
|
@@ -14,7 +14,7 @@ from .keyfs_types import PTypedKey
|
|
|
14
14
|
from .keyfs_types import TypedKey
|
|
15
15
|
from .log import threadlog, thread_push_log, thread_pop_log
|
|
16
16
|
from .log import thread_change_log_prefix
|
|
17
|
-
from .markers import absent
|
|
17
|
+
from .markers import absent, deleted
|
|
18
18
|
from .model import RootModel
|
|
19
19
|
from .readonly import ensure_deeply_readonly
|
|
20
20
|
from .readonly import get_mutable_deepcopy
|
|
@@ -633,7 +633,7 @@ class Transaction(object):
|
|
|
633
633
|
|
|
634
634
|
def get_key_in_transaction(self, relpath):
|
|
635
635
|
for key in self.cache:
|
|
636
|
-
if key.relpath == relpath and self.cache[key]
|
|
636
|
+
if key.relpath == relpath and self.cache[key] not in (absent, deleted):
|
|
637
637
|
return key
|
|
638
638
|
raise KeyError(relpath)
|
|
639
639
|
|
|
@@ -643,36 +643,28 @@ class Transaction(object):
|
|
|
643
643
|
def get_original(self, typedkey):
|
|
644
644
|
""" Return original value from start of transaction,
|
|
645
645
|
without changes from current transaction."""
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
646
|
+
if typedkey not in self._original:
|
|
647
|
+
tup = self.get_last_serial_and_value_at(
|
|
648
|
+
typedkey, self.at_serial, raise_on_error=False)
|
|
649
|
+
if tup is None:
|
|
650
|
+
serial = -1
|
|
651
|
+
val = absent
|
|
652
|
+
else:
|
|
653
|
+
(serial, val) = tup
|
|
654
|
+
assert is_deeply_readonly(val)
|
|
655
|
+
if val is None:
|
|
656
|
+
val = deleted
|
|
657
|
+
self._original[typedkey] = (serial, val)
|
|
658
|
+
return self._original[typedkey]
|
|
654
659
|
|
|
655
660
|
def get(self, typedkey, readonly=True):
|
|
656
|
-
|
|
657
|
-
either as a readonly-view or as a mutable deep copy. """
|
|
658
|
-
try:
|
|
661
|
+
if typedkey in self.cache:
|
|
659
662
|
val = self.cache[typedkey]
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
try:
|
|
666
|
-
val = self.get_original(typedkey)
|
|
667
|
-
except KeyError:
|
|
668
|
-
absent_from_dirty = True
|
|
669
|
-
if absent_from_dirty:
|
|
670
|
-
# for convenience we return an empty instance
|
|
671
|
-
# but below we still respect the readonly property
|
|
672
|
-
val = typedkey.type()
|
|
673
|
-
else:
|
|
674
|
-
assert is_deeply_readonly(val)
|
|
675
|
-
self.cache[typedkey] = val
|
|
663
|
+
else:
|
|
664
|
+
(back_serial, val) = self.get_original(typedkey)
|
|
665
|
+
if val in (absent, deleted):
|
|
666
|
+
# for convenience we return an empty instance
|
|
667
|
+
val = typedkey.type()
|
|
676
668
|
if readonly:
|
|
677
669
|
return ensure_deeply_readonly(val)
|
|
678
670
|
else:
|
|
@@ -680,42 +672,31 @@ class Transaction(object):
|
|
|
680
672
|
|
|
681
673
|
def exists(self, typedkey):
|
|
682
674
|
if typedkey in self.cache:
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
try:
|
|
687
|
-
val = self.get_value_at(typedkey, self.at_serial)
|
|
688
|
-
except KeyError:
|
|
689
|
-
self.cache[typedkey] = absent
|
|
690
|
-
return False
|
|
691
|
-
else:
|
|
692
|
-
assert val is not absent
|
|
693
|
-
assert is_deeply_readonly(val)
|
|
694
|
-
self.cache[typedkey] = val
|
|
675
|
+
val = self.cache[typedkey]
|
|
676
|
+
if val in (absent, deleted):
|
|
677
|
+
return False
|
|
695
678
|
return True
|
|
679
|
+
(serial, val) = self.get_original(typedkey)
|
|
680
|
+
if val in (absent, deleted):
|
|
681
|
+
return False
|
|
682
|
+
return True
|
|
696
683
|
|
|
697
684
|
def delete(self, typedkey):
|
|
698
685
|
if not self.write:
|
|
699
686
|
raise self.keyfs.ReadOnly()
|
|
700
|
-
self.cache
|
|
687
|
+
self.cache[typedkey] = deleted
|
|
701
688
|
self.dirty.add(typedkey)
|
|
702
689
|
|
|
703
|
-
def set(self, typedkey, val):
|
|
690
|
+
def set(self, typedkey, val): # noqa: A003
|
|
704
691
|
if not self.write:
|
|
705
692
|
raise self.keyfs.ReadOnly()
|
|
706
693
|
# sanity check for dictionaries: we always want to have unicode
|
|
707
694
|
# keys, not bytes
|
|
708
695
|
if typedkey.type == dict:
|
|
709
696
|
check_unicode_keys(val)
|
|
710
|
-
|
|
711
|
-
old_val = self.get_original(typedkey)
|
|
712
|
-
except KeyError:
|
|
713
|
-
old_val = absent
|
|
697
|
+
assert val is not None
|
|
714
698
|
self.cache[typedkey] = val
|
|
715
|
-
|
|
716
|
-
self.dirty.add(typedkey)
|
|
717
|
-
else:
|
|
718
|
-
self.dirty.discard(typedkey)
|
|
699
|
+
self.dirty.add(typedkey)
|
|
719
700
|
|
|
720
701
|
def commit(self):
|
|
721
702
|
if self.doomed:
|
|
@@ -727,18 +708,25 @@ class Transaction(object):
|
|
|
727
708
|
result = self._close()
|
|
728
709
|
self._run_listeners(self._finished_listeners)
|
|
729
710
|
return result
|
|
730
|
-
|
|
711
|
+
records = []
|
|
712
|
+
for typedkey in self.dirty:
|
|
713
|
+
val = self.cache[typedkey]
|
|
714
|
+
assert val is not absent
|
|
715
|
+
(back_serial, old_val) = self.get_original(typedkey)
|
|
716
|
+
if val == old_val:
|
|
717
|
+
continue
|
|
718
|
+
if val is deleted:
|
|
719
|
+
val = None
|
|
720
|
+
records.append((typedkey, val, back_serial, old_val))
|
|
721
|
+
if not records and not self.conn.dirty_files:
|
|
731
722
|
threadlog.debug("nothing to commit, just closing tx")
|
|
732
723
|
result = self._close()
|
|
733
724
|
self._run_listeners(self._finished_listeners)
|
|
734
725
|
return result
|
|
735
726
|
try:
|
|
736
727
|
with self.conn.write_transaction() as fswriter:
|
|
737
|
-
for typedkey in
|
|
738
|
-
|
|
739
|
-
assert val is not absent
|
|
740
|
-
# None signals deletion
|
|
741
|
-
fswriter.record_set(typedkey, val)
|
|
728
|
+
for (typedkey, val, back_serial, old_val) in records:
|
|
729
|
+
fswriter.record_set(typedkey, val, back_serial)
|
|
742
730
|
commit_serial = getattr(fswriter, "commit_serial", absent)
|
|
743
731
|
if commit_serial is absent:
|
|
744
732
|
# for storages which don't have the attribute yet
|
|
@@ -254,16 +254,11 @@ class Connection(BaseConnection):
|
|
|
254
254
|
f = self.dirty_files.get(path, None)
|
|
255
255
|
if f is None:
|
|
256
256
|
f = SpooledTemporaryFile(max_size=1048576)
|
|
257
|
-
if not isinstance(content_or_file, bytes) and not callable(getattr(content_or_file, "seekable", None)):
|
|
258
|
-
content_or_file = content_or_file.read()
|
|
259
|
-
if len(content_or_file) > 1048576:
|
|
260
|
-
threadlog.warn(
|
|
261
|
-
"Read %.1f megabytes into memory in postgresql io_file_set for %s, because of unseekable file",
|
|
262
|
-
len(content_or_file) / 1048576, path)
|
|
263
257
|
if isinstance(content_or_file, bytes):
|
|
264
258
|
f.write(content_or_file)
|
|
265
259
|
f.seek(0)
|
|
266
260
|
else:
|
|
261
|
+
assert content_or_file.seekable()
|
|
267
262
|
content_or_file.seek(0)
|
|
268
263
|
shutil.copyfileobj(content_or_file, f)
|
|
269
264
|
self.dirty_files[path] = f
|
|
@@ -394,6 +389,9 @@ class BaseStorage(object):
|
|
|
394
389
|
return sqlite3.connect(
|
|
395
390
|
self.sqlpath.strpath, timeout=60, isolation_level=None)
|
|
396
391
|
|
|
392
|
+
def _execute_conn_pragmas(self, sqlconn):
|
|
393
|
+
pass
|
|
394
|
+
|
|
397
395
|
def _get_sqlconn(self, uri):
|
|
398
396
|
# we will try different connection methods and overwrite _get_sqlconn
|
|
399
397
|
# with the first successful one
|
|
@@ -450,6 +448,7 @@ class BaseStorage(object):
|
|
|
450
448
|
mode = "rwc"
|
|
451
449
|
uri = "file:%s?mode=%s" % (self.sqlpath, mode)
|
|
452
450
|
sqlconn = self._get_sqlconn(uri)
|
|
451
|
+
self._execute_conn_pragmas(sqlconn)
|
|
453
452
|
if write:
|
|
454
453
|
start_time = time.monotonic()
|
|
455
454
|
thread = current_thread()
|
|
@@ -52,15 +52,10 @@ class DirtyFile(object):
|
|
|
52
52
|
os.link(content_or_file.devpi_srcpath, self.tmppath)
|
|
53
53
|
else:
|
|
54
54
|
with get_write_file_ensure_dir(self.tmppath) as f:
|
|
55
|
-
if not isinstance(content_or_file, bytes) and not callable(getattr(content_or_file, "seekable", None)):
|
|
56
|
-
content_or_file = content_or_file.read()
|
|
57
|
-
if len(content_or_file) > 1048576:
|
|
58
|
-
threadlog.warn(
|
|
59
|
-
"Read %.1f megabytes into memory in keyfs_sqlite_fs from_content for %s, because of unseekable file",
|
|
60
|
-
len(content_or_file) / 1048576, path)
|
|
61
55
|
if isinstance(content_or_file, bytes):
|
|
62
56
|
f.write(content_or_file)
|
|
63
57
|
else:
|
|
58
|
+
assert content_or_file.seekable()
|
|
64
59
|
content_or_file.seek(0)
|
|
65
60
|
shutil.copyfileobj(content_or_file, f)
|
|
66
61
|
return self
|