devpi-server 6.19.3__tar.gz → 6.20.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.19.3 → devpi_server-6.20.1}/CHANGELOG +23 -0
- devpi_server-6.20.1/CHANGELOG.short.rst +77 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/PKG-INFO +24 -61
- devpi_server-6.20.1/devpi_server/__init__.py +1 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/config.py +6 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/filestore.py +1 -1
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/keyfs.py +4 -1
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/keyfs_sqlite.py +6 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/mirror.py +38 -9
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/model.py +24 -5
- devpi_server-6.20.1/devpi_server/proxy.py +46 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/replica.py +4 -37
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/views.py +92 -13
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server.egg-info/PKG-INFO +24 -61
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server.egg-info/SOURCES.txt +1 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/plugin.py +19 -9
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_keyfs.py +17 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_mirror.py +34 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_views.py +35 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/tox.ini +1 -1
- devpi_server-6.19.3/CHANGELOG.short.rst +0 -114
- devpi_server-6.19.3/devpi_server/__init__.py +0 -1
- {devpi_server-6.19.3 → devpi_server-6.20.1}/.flake8 +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/LICENSE +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/MANIFEST.in +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/README.rst +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/__main__.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/auth.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/auth_basic.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/auth_devpi.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/__init__.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/crontab.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/compat.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/exceptions.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/filestore_db.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/filestore_fs.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/filestore_fs_base.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/filestore_hash_hl.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/fileutil.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/fsck.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/genconfig.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/hookspecs.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/htmlpage.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/httpclient.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/importexport.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/init.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/interfaces.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/keyfs_sqlite_fs.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/keyfs_types.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/log.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/main.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/markers.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/middleware.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/mythread.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/normalized.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/passwd.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/py.typed +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/readonly.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/sizeof.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server/view_auth.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server.egg-info/requires.txt +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/mypy.ini +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/pyproject.toml +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/pytest_devpi_server/__init__.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/setup.cfg +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/__init__.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/conftest.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/example.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/functional.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/dashes_v1/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/dashes_v1/user1/dev/hello/hello-1.2_3.tar.gz +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/no_history_log/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/no_history_log/user1/dev/hello/hello-1.0.tar.gz +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/py.typed +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/reqmock.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/simpypi.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_auth.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_authcheck.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_config.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_conftest.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_filestore.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_filestore_fs.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_fileutil.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_fsck.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_genconfig.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_importexport.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_log.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_main.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_model.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_mythread.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_nginx.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_nginx_replica.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_permissions.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_readonly.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_replica.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_replica_functional.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_stage_customizer.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_streaming.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_streaming_nginx.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_streaming_replica.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_view_auth.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_views_patch.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_views_push_external.py +0 -0
- {devpi_server-6.19.3 → devpi_server-6.20.1}/test_devpi_server/test_views_status.py +0 -0
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.20.1 (2026-05-11)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Bug Fixes
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- Pass through request headers when streaming .metadata from mirror. Refs #1018
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
6.20.0 (2026-04-30)
|
|
15
|
+
===================
|
|
16
|
+
|
|
17
|
+
Features
|
|
18
|
+
--------
|
|
19
|
+
|
|
20
|
+
- Add experimental bare bones core-metadata ([PEP 658](https://peps.python.org/pep-0658/), [PEP 714](https://peps.python.org/pep-0714/)) support with ``--enable-core-metadata`` command line option and ``mirror_provides_core_metadata`` mirror index option. Refs #1018
|
|
21
|
+
|
|
22
|
+
Bug Fixes
|
|
23
|
+
---------
|
|
24
|
+
|
|
25
|
+
- Update replica status when the replica is waiting for new serials using the streaming changelog endpoint.
|
|
26
|
+
|
|
27
|
+
|
|
5
28
|
6.19.3 (2026-04-13)
|
|
6
29
|
===================
|
|
7
30
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
=========
|
|
4
|
+
Changelog
|
|
5
|
+
=========
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
.. towncrier release notes start
|
|
11
|
+
|
|
12
|
+
6.20.1 (2026-05-11)
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Bug Fixes
|
|
16
|
+
---------
|
|
17
|
+
|
|
18
|
+
- Pass through request headers when streaming .metadata from mirror. Refs #1018
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
6.20.0 (2026-04-30)
|
|
22
|
+
===================
|
|
23
|
+
|
|
24
|
+
Features
|
|
25
|
+
--------
|
|
26
|
+
|
|
27
|
+
- Add experimental bare bones core-metadata ([PEP 658](https://peps.python.org/pep-0658/), [PEP 714](https://peps.python.org/pep-0714/)) support with ``--enable-core-metadata`` command line option and ``mirror_provides_core_metadata`` mirror index option. Refs #1018
|
|
28
|
+
|
|
29
|
+
Bug Fixes
|
|
30
|
+
---------
|
|
31
|
+
|
|
32
|
+
- Update replica status when the replica is waiting for new serials using the streaming changelog endpoint.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
6.19.3 (2026-04-13)
|
|
36
|
+
===================
|
|
37
|
+
|
|
38
|
+
Bug Fixes
|
|
39
|
+
---------
|
|
40
|
+
|
|
41
|
+
- Fix #1112: Parse simple JSON reply even with wrong content-type in reply if the body seems to contain JSON.
|
|
42
|
+
|
|
43
|
+
- Return stale project list for mirrors when the lock can't be acquired within the timeout.
|
|
44
|
+
|
|
45
|
+
- Fix importing of toxresults from devpi-server 6.5.0 to 6.9.0 where the wrong hash was stored.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
6.19.2 (2026-03-17)
|
|
49
|
+
===================
|
|
50
|
+
|
|
51
|
+
Bug Fixes
|
|
52
|
+
---------
|
|
53
|
+
|
|
54
|
+
- Preserve log for documentation uploads in export.
|
|
55
|
+
|
|
56
|
+
- Any missing file on mirrors will be ignored during event processing as is already the case in other places.
|
|
57
|
+
|
|
58
|
+
- Use short timeout when project list is requested for ``has_project`` call on mirrors instead of the long one used for ``list_projects``. This prevents installers from timing out and retrying several times.
|
|
59
|
+
|
|
60
|
+
- Fix error handling for proxy requests from replica to primary.
|
|
61
|
+
|
|
62
|
+
Other Changes
|
|
63
|
+
-------------
|
|
64
|
+
|
|
65
|
+
- Removed limit of reported missing files for devpi-fsck.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
6.19.1 (2026-02-09)
|
|
69
|
+
===================
|
|
70
|
+
|
|
71
|
+
Bug Fixes
|
|
72
|
+
---------
|
|
73
|
+
|
|
74
|
+
- Pin setuptools as pyramid still requires pkg_resources.
|
|
75
|
+
|
|
76
|
+
- Always allow replicas to access deleted releases to get the proper ``410 Gone`` instead of ``403 Forbidden`` when ``devpi-lockdown`` is in use.
|
|
77
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.20.1
|
|
4
4
|
Summary: devpi-server: backend for hosting private package indexes and PyPI on-demand mirrors
|
|
5
5
|
Maintainer-email: Florian Schulze <mail@pyfidelity.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -121,6 +121,29 @@ Changelog
|
|
|
121
121
|
|
|
122
122
|
.. towncrier release notes start
|
|
123
123
|
|
|
124
|
+
6.20.1 (2026-05-11)
|
|
125
|
+
===================
|
|
126
|
+
|
|
127
|
+
Bug Fixes
|
|
128
|
+
---------
|
|
129
|
+
|
|
130
|
+
- Pass through request headers when streaming .metadata from mirror. Refs #1018
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
6.20.0 (2026-04-30)
|
|
134
|
+
===================
|
|
135
|
+
|
|
136
|
+
Features
|
|
137
|
+
--------
|
|
138
|
+
|
|
139
|
+
- Add experimental bare bones core-metadata ([PEP 658](https://peps.python.org/pep-0658/), [PEP 714](https://peps.python.org/pep-0714/)) support with ``--enable-core-metadata`` command line option and ``mirror_provides_core_metadata`` mirror index option. Refs #1018
|
|
140
|
+
|
|
141
|
+
Bug Fixes
|
|
142
|
+
---------
|
|
143
|
+
|
|
144
|
+
- Update replica status when the replica is waiting for new serials using the streaming changelog endpoint.
|
|
145
|
+
|
|
146
|
+
|
|
124
147
|
6.19.3 (2026-04-13)
|
|
125
148
|
===================
|
|
126
149
|
|
|
@@ -164,63 +187,3 @@ Bug Fixes
|
|
|
164
187
|
|
|
165
188
|
- Always allow replicas to access deleted releases to get the proper ``410 Gone`` instead of ``403 Forbidden`` when ``devpi-lockdown`` is in use.
|
|
166
189
|
|
|
167
|
-
|
|
168
|
-
6.19.0 (2026-02-06)
|
|
169
|
-
===================
|
|
170
|
-
|
|
171
|
-
Features
|
|
172
|
-
--------
|
|
173
|
-
|
|
174
|
-
- Add ``--autocreate-users`` server option.
|
|
175
|
-
Automatically creates users that don't exist in devpi, but have successfully authenticated via an authentication plugin.
|
|
176
|
-
A typical example of when to enable this would be when authenticating via an LDAP directory.
|
|
177
|
-
Automatically created users do not have passwords, and have no password hash to prevent local authentication.
|
|
178
|
-
|
|
179
|
-
- Add ``replica-files-in-sync-at``, ``replica-init-queue-finished-at`` and ``replica-metadata-in-sync-at`` to status view, the existing ``replica-in-sync-at`` is now a combination of all three instead of just metadata.
|
|
180
|
-
|
|
181
|
-
- Warn when an unknown option is found in config file to detect typos. Be aware that some commands don't use all the options, that is why this only warns instead of exiting.
|
|
182
|
-
|
|
183
|
-
- Add new ``devpiserver_user_created`` hook which can be used to create default indexes or other setup for newly created users.
|
|
184
|
-
|
|
185
|
-
Bug Fixes
|
|
186
|
-
---------
|
|
187
|
-
|
|
188
|
-
- Fix ``+status`` json encoding errors by making sure the ``FatalResponse.url`` attribute is a string.
|
|
189
|
-
|
|
190
|
-
- Ignore existing unknown index options from uninstalled plugins when patching other options with ``+=`` and ``-=``.
|
|
191
|
-
|
|
192
|
-
- Fix removal with ``-=`` of index options with default values from ``devpiserver_indexconfig_defaults`` hooks.
|
|
193
|
-
|
|
194
|
-
- Fix #1110: a list for the ``listen`` option in a config file stopped working in 6.18.0.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
6.18.0 (2026-01-27)
|
|
198
|
-
===================
|
|
199
|
-
|
|
200
|
-
Features
|
|
201
|
-
--------
|
|
202
|
-
|
|
203
|
-
- Store all available hashes of files.
|
|
204
|
-
|
|
205
|
-
- Validate hashes of all files during devpi-import, not only releases.
|
|
206
|
-
|
|
207
|
-
Bug Fixes
|
|
208
|
-
---------
|
|
209
|
-
|
|
210
|
-
- Apply argparse transformations on values read from config file or environment.
|
|
211
|
-
|
|
212
|
-
- Restore Python and platform info in user agent string after switch to httpx.
|
|
213
|
-
|
|
214
|
-
- Remove all database entries on project deletion instead of only emptying them.
|
|
215
|
-
|
|
216
|
-
- Fix error at end of replica streaming caused by changed behavior from switch to httpx.
|
|
217
|
-
|
|
218
|
-
- Fix #1102: The data stream was cut off after 64k when proxying from replica to primary after switching to httpx.
|
|
219
|
-
|
|
220
|
-
- Fix #1107: retry file downloads if there has been an error during download.
|
|
221
|
-
|
|
222
|
-
Other Changes
|
|
223
|
-
-------------
|
|
224
|
-
|
|
225
|
-
- The filenames of some exported doczip files change due to normalization of the project name caused by changing the internals during export to allow ``--hard-links`` to work.
|
|
226
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "6.20.1"
|
|
@@ -245,6 +245,12 @@ def add_web_options(
|
|
|
245
245
|
help="use absolute URLs everywhere. "
|
|
246
246
|
"This will become the default at some point.")
|
|
247
247
|
|
|
248
|
+
parser.addoption(
|
|
249
|
+
"--enable-core-metadata",
|
|
250
|
+
action="store_true",
|
|
251
|
+
help="(experimental) Enable minimal core-metadata support in simple API.",
|
|
252
|
+
)
|
|
253
|
+
|
|
248
254
|
parser.addoption(
|
|
249
255
|
"--profile-requests", type=int, metavar="NUM", default=0,
|
|
250
256
|
help="profile NUM requests and print out cumulative stats. "
|
|
@@ -700,7 +700,7 @@ class BaseFileEntry:
|
|
|
700
700
|
headers = {}
|
|
701
701
|
headers["last-modified"] = str(self.last_modified)
|
|
702
702
|
m = mimetypes.guess_type(self.basename)[0]
|
|
703
|
-
headers["content-type"] =
|
|
703
|
+
headers["content-type"] = "application/octet-stream" if m is None else m
|
|
704
704
|
headers["content-length"] = str(self.file_size())
|
|
705
705
|
headers["cache-control"] = "max-age=365000000, immutable, public"
|
|
706
706
|
return headers
|
|
@@ -386,7 +386,10 @@ class KeyFS:
|
|
|
386
386
|
relpaths: Iterable[RelPath],
|
|
387
387
|
) -> Iterable[FilePathInfo]:
|
|
388
388
|
for relpath in relpaths:
|
|
389
|
-
(_,
|
|
389
|
+
(_, back_serial, val) = conn.get_relpath_at(relpath, serial)
|
|
390
|
+
if val is None:
|
|
391
|
+
# the file was deleted, get the data from before
|
|
392
|
+
(_, _, val) = conn.get_relpath_at(relpath, back_serial)
|
|
390
393
|
if (
|
|
391
394
|
isinstance(val, (dict, DictViewReadonly))
|
|
392
395
|
and "hash_spec" in val
|
|
@@ -464,6 +464,7 @@ class BaseStorage(object):
|
|
|
464
464
|
self._execute_conn_pragmas(sqlconn)
|
|
465
465
|
if write:
|
|
466
466
|
start_time = time.monotonic()
|
|
467
|
+
log_delay = 2
|
|
467
468
|
thread = current_thread()
|
|
468
469
|
while 1:
|
|
469
470
|
try:
|
|
@@ -475,6 +476,11 @@ class BaseStorage(object):
|
|
|
475
476
|
if hasattr(thread, "exit_if_shutdown"):
|
|
476
477
|
thread.exit_if_shutdown()
|
|
477
478
|
elapsed = time.monotonic() - start_time
|
|
479
|
+
if elapsed >= log_delay:
|
|
480
|
+
threadlog.warn(
|
|
481
|
+
"Waiting on database connection for %s seconds", log_delay
|
|
482
|
+
)
|
|
483
|
+
log_delay = log_delay * 1.5
|
|
478
484
|
if elapsed > timeout:
|
|
479
485
|
# if it takes this long, something is wrong
|
|
480
486
|
raise KeyfsTimeoutError(
|
|
@@ -485,6 +485,10 @@ class MirrorStage(BaseStage):
|
|
|
485
485
|
return f"Basic {b64encode(auth).decode()}"
|
|
486
486
|
return None
|
|
487
487
|
|
|
488
|
+
@property
|
|
489
|
+
def provides_core_metadata(self) -> bool:
|
|
490
|
+
return self.ixconfig.get("mirror_provides_core_metadata", False)
|
|
491
|
+
|
|
488
492
|
@property
|
|
489
493
|
def no_project_list(self) -> bool:
|
|
490
494
|
return self.ixconfig.get('mirror_no_project_list', False)
|
|
@@ -501,6 +505,7 @@ class MirrorStage(BaseStage):
|
|
|
501
505
|
"mirror_cache_expiry",
|
|
502
506
|
"mirror_ignore_serial_header",
|
|
503
507
|
"mirror_no_project_list",
|
|
508
|
+
"mirror_provides_core_metadata",
|
|
504
509
|
"mirror_url",
|
|
505
510
|
"mirror_use_external_urls",
|
|
506
511
|
"mirror_web_url_fmt",
|
|
@@ -529,6 +534,8 @@ class MirrorStage(BaseStage):
|
|
|
529
534
|
return ensure_boolean(value)
|
|
530
535
|
if key == "mirror_no_project_list":
|
|
531
536
|
return ensure_boolean(value)
|
|
537
|
+
if key == "mirror_provides_core_metadata":
|
|
538
|
+
return ensure_boolean(value)
|
|
532
539
|
if key == "mirror_use_external_urls":
|
|
533
540
|
return ensure_boolean(value)
|
|
534
541
|
if key in ("custom_data", "description", "mirror_web_url_fmt", "title"):
|
|
@@ -747,6 +754,9 @@ class MirrorStage(BaseStage):
|
|
|
747
754
|
return (self.cache_projectnames.get(), False)
|
|
748
755
|
lock = self._list_projects_perstage_lock
|
|
749
756
|
projects_timeout = self.get_projects_timeout(timeout)
|
|
757
|
+
threadlog.debug(
|
|
758
|
+
"Acquiring projects list lock (%r) with timeout %s", lock, timeout
|
|
759
|
+
)
|
|
750
760
|
if lock.acquire(timeout=projects_timeout):
|
|
751
761
|
try:
|
|
752
762
|
# retry in case it was updated in another thread
|
|
@@ -756,6 +766,7 @@ class MirrorStage(BaseStage):
|
|
|
756
766
|
return self._update_projects(timeout=timeout)
|
|
757
767
|
finally:
|
|
758
768
|
lock.release()
|
|
769
|
+
threadlog.debug("Released projects list lock (%r)", lock)
|
|
759
770
|
return (self._stale_list_projects_perstage(), True)
|
|
760
771
|
|
|
761
772
|
def list_projects_perstage(self) -> dict[str, NormalizedName | str]:
|
|
@@ -968,7 +979,9 @@ class MirrorStage(BaseStage):
|
|
|
968
979
|
self.keyfs.tx.on_commit_success(
|
|
969
980
|
partial(self.cache_retrieve_times.refresh, project, info.etag)
|
|
970
981
|
)
|
|
971
|
-
return self.SimpleLinks(
|
|
982
|
+
return self.SimpleLinks(
|
|
983
|
+
links, core_metadata=self.provides_core_metadata
|
|
984
|
+
)
|
|
972
985
|
raise self.UpstreamError("no cache links from primary for %s" %
|
|
973
986
|
project)
|
|
974
987
|
|
|
@@ -992,7 +1005,7 @@ class MirrorStage(BaseStage):
|
|
|
992
1005
|
info.serial,
|
|
993
1006
|
info.etag,
|
|
994
1007
|
)
|
|
995
|
-
return self.SimpleLinks(newlinks)
|
|
1008
|
+
return self.SimpleLinks(newlinks, core_metadata=self.provides_core_metadata)
|
|
996
1009
|
|
|
997
1010
|
async def _update_simplelinks_in_future(
|
|
998
1011
|
self,
|
|
@@ -1038,7 +1051,9 @@ class MirrorStage(BaseStage):
|
|
|
1038
1051
|
threadlog.warn(
|
|
1039
1052
|
"serving stale links for %r, waiting for existing request timed out after %s seconds",
|
|
1040
1053
|
project, self.timeout)
|
|
1041
|
-
return self.SimpleLinks(
|
|
1054
|
+
return self.SimpleLinks(
|
|
1055
|
+
links, core_metadata=self.provides_core_metadata, stale=True
|
|
1056
|
+
)
|
|
1042
1057
|
raise self.UpstreamError(
|
|
1043
1058
|
f"timeout after {self.timeout} seconds while getting data for {project!r}")
|
|
1044
1059
|
|
|
@@ -1049,7 +1064,9 @@ class MirrorStage(BaseStage):
|
|
|
1049
1064
|
threadlog.debug(
|
|
1050
1065
|
"using stale links for %r due to offline mode", project)
|
|
1051
1066
|
self._offline_logging.add(project)
|
|
1052
|
-
return self.SimpleLinks(
|
|
1067
|
+
return self.SimpleLinks(
|
|
1068
|
+
links, core_metadata=self.provides_core_metadata, stale=True
|
|
1069
|
+
)
|
|
1053
1070
|
|
|
1054
1071
|
if links is None:
|
|
1055
1072
|
is_retrieval_expired = self.cache_retrieve_times.is_expired(
|
|
@@ -1090,14 +1107,18 @@ class MirrorStage(BaseStage):
|
|
|
1090
1107
|
threadlog.warn(
|
|
1091
1108
|
"serving stale links for %r, getting data timed out after %s seconds",
|
|
1092
1109
|
project, self.timeout)
|
|
1093
|
-
return self.SimpleLinks(
|
|
1110
|
+
return self.SimpleLinks(
|
|
1111
|
+
links, core_metadata=self.provides_core_metadata, stale=True
|
|
1112
|
+
)
|
|
1094
1113
|
raise self.UpstreamError(
|
|
1095
1114
|
f"timeout after {self.timeout} seconds while getting data for {project!r}")
|
|
1096
1115
|
except self.UpstreamNotModified as e:
|
|
1097
1116
|
if links is not None:
|
|
1098
1117
|
# immediately update the cache
|
|
1099
1118
|
self.cache_retrieve_times.refresh(project, e.etag)
|
|
1100
|
-
return self.SimpleLinks(
|
|
1119
|
+
return self.SimpleLinks(
|
|
1120
|
+
links, core_metadata=self.provides_core_metadata
|
|
1121
|
+
)
|
|
1101
1122
|
if e.etag is None:
|
|
1102
1123
|
threadlog.error(
|
|
1103
1124
|
"server returned 304 Not Modified, but we have no links")
|
|
@@ -1113,7 +1134,9 @@ class MirrorStage(BaseStage):
|
|
|
1113
1134
|
threadlog.warn(
|
|
1114
1135
|
"serving stale links, because of exception %s",
|
|
1115
1136
|
lazy_format_exception(e))
|
|
1116
|
-
return self.SimpleLinks(
|
|
1137
|
+
return self.SimpleLinks(
|
|
1138
|
+
links, core_metadata=self.provides_core_metadata, stale=True
|
|
1139
|
+
)
|
|
1117
1140
|
raise
|
|
1118
1141
|
|
|
1119
1142
|
info = newlinks_future.result()
|
|
@@ -1122,7 +1145,7 @@ class MirrorStage(BaseStage):
|
|
|
1122
1145
|
if links is not None and set(links) == set(newlinks):
|
|
1123
1146
|
# no changes
|
|
1124
1147
|
self.cache_retrieve_times.refresh(project, info.etag)
|
|
1125
|
-
return self.SimpleLinks(links)
|
|
1148
|
+
return self.SimpleLinks(links, core_metadata=self.provides_core_metadata)
|
|
1126
1149
|
|
|
1127
1150
|
return self._update_simplelinks(project, info, links, newlinks)
|
|
1128
1151
|
|
|
@@ -1300,7 +1323,12 @@ class ProjectUpdateLock:
|
|
|
1300
1323
|
self.project = project
|
|
1301
1324
|
|
|
1302
1325
|
def acquire(self, timeout: float) -> bool:
|
|
1303
|
-
threadlog.debug(
|
|
1326
|
+
threadlog.debug(
|
|
1327
|
+
"Acquiring lock (%r) for %r with timeout %s",
|
|
1328
|
+
self.lock,
|
|
1329
|
+
self.project,
|
|
1330
|
+
timeout,
|
|
1331
|
+
)
|
|
1304
1332
|
assert self.lock is not None
|
|
1305
1333
|
return self.lock.acquire(timeout=timeout)
|
|
1306
1334
|
|
|
@@ -1317,6 +1345,7 @@ class ProjectUpdateLock:
|
|
|
1317
1345
|
def release(self) -> None:
|
|
1318
1346
|
if self.lock is not None:
|
|
1319
1347
|
self.lock.release()
|
|
1348
|
+
threadlog.debug("Released lock (%r) for %r", self.lock, self.project)
|
|
1320
1349
|
self.lock = None
|
|
1321
1350
|
|
|
1322
1351
|
def __repr__(self) -> str:
|
|
@@ -675,14 +675,20 @@ class SimpleLinks:
|
|
|
675
675
|
stale: bool
|
|
676
676
|
|
|
677
677
|
def __init__(
|
|
678
|
-
self,
|
|
678
|
+
self,
|
|
679
|
+
links: Sequence[JoinedLink] | SimpleLinks,
|
|
680
|
+
*,
|
|
681
|
+
core_metadata: bool = False,
|
|
682
|
+
stale: bool = False,
|
|
679
683
|
) -> None:
|
|
680
684
|
assert links is not None
|
|
681
685
|
if isinstance(links, SimpleLinks):
|
|
682
686
|
self._links = links._links
|
|
683
687
|
self.stale = links.stale or stale
|
|
684
688
|
else:
|
|
685
|
-
self._links = [
|
|
689
|
+
self._links = [
|
|
690
|
+
SimplelinkMeta(x, core_metadata=core_metadata) for x in links
|
|
691
|
+
]
|
|
686
692
|
self.stale = stale
|
|
687
693
|
|
|
688
694
|
def __hash__(self):
|
|
@@ -1596,7 +1602,8 @@ class PrivateStage(BaseStage):
|
|
|
1596
1602
|
requires_python = cast("RequiresPythonList", data.get("requires_python", []))
|
|
1597
1603
|
yanked: YankedList = [] # PEP 592 isn't supported for private stages yet
|
|
1598
1604
|
return self.SimpleLinks(
|
|
1599
|
-
join_links_data(links, requires_python, yanked)
|
|
1605
|
+
join_links_data(links, requires_python, yanked), core_metadata=True
|
|
1606
|
+
)
|
|
1600
1607
|
|
|
1601
1608
|
def _regen_simplelinks(self, project_input):
|
|
1602
1609
|
project = normalize_name(project_input)
|
|
@@ -2129,6 +2136,7 @@ class SimplelinkMeta:
|
|
|
2129
2136
|
"__path",
|
|
2130
2137
|
"__url",
|
|
2131
2138
|
"__version",
|
|
2139
|
+
"core_metadata",
|
|
2132
2140
|
"href",
|
|
2133
2141
|
"key",
|
|
2134
2142
|
"require_python",
|
|
@@ -2137,7 +2145,12 @@ class SimplelinkMeta:
|
|
|
2137
2145
|
__cmpval: tuple | NotSet
|
|
2138
2146
|
__hashes: Digests | NotSet
|
|
2139
2147
|
|
|
2140
|
-
def __init__(
|
|
2148
|
+
def __init__(
|
|
2149
|
+
self,
|
|
2150
|
+
link_info: tuple[str, str, RequiresPython, Yanked],
|
|
2151
|
+
*,
|
|
2152
|
+
core_metadata: bool = False,
|
|
2153
|
+
) -> None:
|
|
2141
2154
|
self.__basename = notset
|
|
2142
2155
|
self.__cmpval = notset
|
|
2143
2156
|
self.__ext = notset
|
|
@@ -2147,7 +2160,10 @@ class SimplelinkMeta:
|
|
|
2147
2160
|
self.__path = notset
|
|
2148
2161
|
self.__url = notset
|
|
2149
2162
|
self.__version = notset
|
|
2163
|
+
self.core_metadata = False
|
|
2150
2164
|
(self.key, self.href, self.require_python, self.yanked) = link_info
|
|
2165
|
+
if core_metadata and self.basename.endswith(".whl"):
|
|
2166
|
+
self.core_metadata = True
|
|
2151
2167
|
|
|
2152
2168
|
def __hash__(self) -> int:
|
|
2153
2169
|
return hash(
|
|
@@ -2161,6 +2177,7 @@ class SimplelinkMeta:
|
|
|
2161
2177
|
self.__path,
|
|
2162
2178
|
self.__url,
|
|
2163
2179
|
self.__version,
|
|
2180
|
+
self.core_metadata,
|
|
2164
2181
|
self.href,
|
|
2165
2182
|
self.key,
|
|
2166
2183
|
self.require_python,
|
|
@@ -2283,8 +2300,10 @@ class SimplelinkMeta:
|
|
|
2283
2300
|
f"<{clsname} "
|
|
2284
2301
|
f"key={self.key!r} "
|
|
2285
2302
|
f"href={self.href!r} "
|
|
2303
|
+
f"core_metadata={self.core_metadata!r} "
|
|
2286
2304
|
f"require_python={self.require_python!r} "
|
|
2287
|
-
f"yanked={self.yanked!r}>"
|
|
2305
|
+
f"yanked={self.yanked!r}>"
|
|
2306
|
+
)
|
|
2288
2307
|
|
|
2289
2308
|
|
|
2290
2309
|
def make_key_and_href(entry):
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
from webob.headers import EnvironHeaders
|
|
5
|
+
from webob.headers import ResponseHeaders
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from httpx import Response
|
|
10
|
+
from pyramid.request import Request
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
hop_by_hop = frozenset(
|
|
14
|
+
{
|
|
15
|
+
"connection",
|
|
16
|
+
"keep-alive",
|
|
17
|
+
"proxy-authenticate",
|
|
18
|
+
"proxy-authorization",
|
|
19
|
+
"te",
|
|
20
|
+
"trailers",
|
|
21
|
+
"transfer-encoding",
|
|
22
|
+
"upgrade",
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def clean_request_headers(request: Request) -> EnvironHeaders:
|
|
28
|
+
result = EnvironHeaders({})
|
|
29
|
+
result.update(request.headers)
|
|
30
|
+
result.pop("host", None)
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def clean_response_headers(response: Response) -> ResponseHeaders:
|
|
35
|
+
headers = ResponseHeaders()
|
|
36
|
+
# remove hop by hop headers, see:
|
|
37
|
+
# https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
|
38
|
+
hop_keys = set(hop_by_hop)
|
|
39
|
+
connection = response.headers.get("connection")
|
|
40
|
+
if connection and connection.lower() != "close":
|
|
41
|
+
hop_keys.update(x.strip().lower() for x in connection.split(","))
|
|
42
|
+
for k, v in response.headers.items():
|
|
43
|
+
if k.lower() in hop_keys:
|
|
44
|
+
continue
|
|
45
|
+
headers[k] = v
|
|
46
|
+
return headers
|
|
@@ -19,6 +19,8 @@ from .markers import Absent
|
|
|
19
19
|
from .markers import absent
|
|
20
20
|
from .model import UpstreamError
|
|
21
21
|
from .normalized import normalize_name
|
|
22
|
+
from .proxy import clean_request_headers
|
|
23
|
+
from .proxy import clean_response_headers
|
|
22
24
|
from .views import FileStreamer
|
|
23
25
|
from .views import H_MASTER_UUID
|
|
24
26
|
from .views import H_PRIMARY_UUID
|
|
@@ -33,8 +35,6 @@ from pyramid.response import Response
|
|
|
33
35
|
from pyramid.view import view_config
|
|
34
36
|
from repoze.lru import LRUCache
|
|
35
37
|
from typing import TYPE_CHECKING
|
|
36
|
-
from webob.headers import EnvironHeaders
|
|
37
|
-
from webob.headers import ResponseHeaders
|
|
38
38
|
import contextlib
|
|
39
39
|
import io
|
|
40
40
|
import itsdangerous
|
|
@@ -330,7 +330,8 @@ class PrimaryChangelogRequest:
|
|
|
330
330
|
start_serial = int(self.request.matchdict["serial"])
|
|
331
331
|
|
|
332
332
|
keyfs = self.xom.keyfs
|
|
333
|
-
self.
|
|
333
|
+
with self.update_replica_status(start_serial):
|
|
334
|
+
self._wait_for_serial(start_serial)
|
|
334
335
|
devpi_serial = keyfs.tx.conn.last_changelog_serial
|
|
335
336
|
threadlog.info("Streaming from %s to %s", start_serial, devpi_serial)
|
|
336
337
|
|
|
@@ -1441,40 +1442,6 @@ class SimpleLinksChanged:
|
|
|
1441
1442
|
cache_projectnames.add(project)
|
|
1442
1443
|
|
|
1443
1444
|
|
|
1444
|
-
hop_by_hop = frozenset((
|
|
1445
|
-
'connection',
|
|
1446
|
-
'keep-alive',
|
|
1447
|
-
'proxy-authenticate',
|
|
1448
|
-
'proxy-authorization',
|
|
1449
|
-
'te',
|
|
1450
|
-
'trailers',
|
|
1451
|
-
'transfer-encoding',
|
|
1452
|
-
'upgrade'
|
|
1453
|
-
))
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
def clean_request_headers(request):
|
|
1457
|
-
result = EnvironHeaders({})
|
|
1458
|
-
result.update(request.headers)
|
|
1459
|
-
result.pop('host', None)
|
|
1460
|
-
return result
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
def clean_response_headers(response):
|
|
1464
|
-
headers = ResponseHeaders()
|
|
1465
|
-
# remove hop by hop headers, see:
|
|
1466
|
-
# https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
|
1467
|
-
hop_keys = set(hop_by_hop)
|
|
1468
|
-
connection = response.headers.get('connection')
|
|
1469
|
-
if connection and connection.lower() != 'close':
|
|
1470
|
-
hop_keys.update(x.strip().lower() for x in connection.split(','))
|
|
1471
|
-
for k, v in response.headers.items():
|
|
1472
|
-
if k.lower() in hop_keys:
|
|
1473
|
-
continue
|
|
1474
|
-
headers[k] = v
|
|
1475
|
-
return headers
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
1445
|
class BodyFileWrapper:
|
|
1479
1446
|
# required to provide length to prevent transfer-encoding: chunked
|
|
1480
1447
|
|