devpi-server 6.10.0__tar.gz → 6.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. {devpi-server-6.10.0 → devpi_server-6.12.0}/CHANGELOG +41 -1
  2. {devpi-server-6.10.0 → devpi_server-6.12.0}/CHANGELOG.short.rst +38 -34
  3. {devpi-server-6.10.0 → devpi_server-6.12.0}/PKG-INFO +30 -34
  4. devpi_server-6.12.0/devpi_server/__init__.py +1 -0
  5. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/auth.py +4 -2
  6. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/config.py +1 -1
  7. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/filestore.py +39 -21
  8. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/filestore_fs.py +1 -6
  9. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/fsck.py +7 -0
  10. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/hookspecs.py +43 -1
  11. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/importexport.py +19 -6
  12. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/keyfs.py +45 -57
  13. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/keyfs_sqlite.py +5 -6
  14. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/keyfs_sqlite_fs.py +1 -6
  15. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/main.py +4 -1
  16. devpi_server-6.12.0/devpi_server/markers.py +14 -0
  17. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/middleware.py +0 -2
  18. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/mirror.py +46 -10
  19. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/model.py +40 -22
  20. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/replica.py +71 -81
  21. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/views.py +187 -157
  22. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/PKG-INFO +30 -34
  23. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/SOURCES.txt +1 -0
  24. {devpi-server-6.10.0 → devpi_server-6.12.0}/setup.py +1 -1
  25. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/functional.py +23 -0
  26. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/mirrordata/dataindex.json +1 -1
  27. devpi_server-6.12.0/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +1 -0
  28. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/plugin.py +11 -22
  29. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/reqmock.py +16 -9
  30. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/simpypi.py +4 -4
  31. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_auth.py +3 -3
  32. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_config.py +9 -7
  33. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_filestore.py +30 -28
  34. devpi_server-6.12.0/test_devpi_server/test_fsck.py +41 -0
  35. devpi_server-6.12.0/test_devpi_server/test_genconfig.py +50 -0
  36. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_importexport.py +88 -68
  37. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_keyfs.py +11 -1
  38. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_main.py +1 -1
  39. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_mirror.py +32 -4
  40. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_model.py +29 -45
  41. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_nginx.py +4 -1
  42. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_permissions.py +8 -6
  43. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_replica.py +45 -33
  44. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_stage_customizer.py +8 -8
  45. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_streaming.py +1 -0
  46. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_view_auth.py +10 -8
  47. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_views.py +77 -24
  48. {devpi-server-6.10.0 → devpi_server-6.12.0}/tox.ini +7 -2
  49. devpi-server-6.10.0/devpi_server/__init__.py +0 -1
  50. devpi-server-6.10.0/devpi_server/markers.py +0 -6
  51. devpi-server-6.10.0/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
  52. devpi-server-6.10.0/test_devpi_server/test_genconfig.py +0 -19
  53. {devpi-server-6.10.0 → devpi_server-6.12.0}/.flake8 +0 -0
  54. {devpi-server-6.10.0 → devpi_server-6.12.0}/AUTHORS +0 -0
  55. {devpi-server-6.10.0 → devpi_server-6.12.0}/LICENSE +0 -0
  56. {devpi-server-6.10.0 → devpi_server-6.12.0}/MANIFEST.in +0 -0
  57. {devpi-server-6.10.0 → devpi_server-6.12.0}/README.rst +0 -0
  58. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/__main__.py +0 -0
  59. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/auth_basic.py +0 -0
  60. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/auth_devpi.py +0 -0
  61. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/__init__.py +0 -0
  62. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/crontab.template +0 -0
  63. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/devpi.service.template +0 -0
  64. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/launchd-macos.txt.template +0 -0
  65. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
  66. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
  67. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
  68. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
  69. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
  70. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
  71. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/supervisord.conf.template +0 -0
  72. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/cfg/windows-service.txt.template +0 -0
  73. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/exceptions.py +0 -0
  74. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/fileutil.py +0 -0
  75. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/genconfig.py +0 -0
  76. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/init.py +0 -0
  77. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/interfaces.py +0 -0
  78. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/keyfs_types.py +0 -0
  79. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/log.py +0 -0
  80. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/mythread.py +0 -0
  81. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/passwd.py +0 -0
  82. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/readonly.py +0 -0
  83. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/sizeof.py +0 -0
  84. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/vendor/__init__.py +0 -0
  85. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/vendor/_pip.py +0 -0
  86. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server/view_auth.py +0 -0
  87. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/dependency_links.txt +0 -0
  88. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/entry_points.txt +0 -0
  89. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/not-zip-safe +0 -0
  90. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/requires.txt +0 -0
  91. {devpi-server-6.10.0 → devpi_server-6.12.0}/devpi_server.egg-info/top_level.txt +0 -0
  92. {devpi-server-6.10.0 → devpi_server-6.12.0}/pyproject.toml +0 -0
  93. {devpi-server-6.10.0 → devpi_server-6.12.0}/pytest_devpi_server/__init__.py +0 -0
  94. {devpi-server-6.10.0 → devpi_server-6.12.0}/setup.cfg +0 -0
  95. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/__init__.py +0 -0
  96. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/conftest.py +0 -0
  97. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/example.py +0 -0
  98. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
  99. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
  100. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
  101. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
  102. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
  103. /devpi-server-6.10.0/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz → /devpi_server-6.12.0/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
  104. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
  105. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
  106. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
  107. /devpi-server-6.10.0/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz → /devpi_server-6.12.0/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
  108. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
  109. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
  110. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
  111. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
  112. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
  113. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
  114. /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.12.0/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
  115. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
  116. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
  117. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
  118. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_authcheck.py +0 -0
  119. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_conftest.py +0 -0
  120. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_fileutil.py +0 -0
  121. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_keyfs_sqlite_fs.py +0 -0
  122. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_log.py +0 -0
  123. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_mirror_no_project_list.py +0 -0
  124. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_mythread.py +0 -0
  125. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_nginx_replica.py +0 -0
  126. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_readonly.py +0 -0
  127. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_replica_functional.py +0 -0
  128. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_reqmock.py +0 -0
  129. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_streaming_nginx.py +0 -0
  130. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_streaming_replica.py +0 -0
  131. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
  132. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_views_patch.py +0 -0
  133. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_views_push_external.py +0 -0
  134. {devpi-server-6.10.0 → devpi_server-6.12.0}/test_devpi_server/test_views_status.py +0 -0
@@ -2,6 +2,46 @@
2
2
 
3
3
  .. towncrier release notes start
4
4
 
5
+ 6.12.0 (2024-06-25)
6
+ ===================
7
+
8
+ Features
9
+ --------
10
+
11
+ - Added ``devpiserver_on_toxresult_store`` hook to allow blocking or skipping a toxresult upload on more specific conditions as ``acl_toxresult_upload`` would allow.
12
+
13
+ - Added ``devpiserver_on_toxresult_upload_forbidden`` hook to allow returning a custom message and result (403 or 200).
14
+
15
+
16
+
17
+ Bug Fixes
18
+ ---------
19
+
20
+ - Return json data if toxresult upload is forbidden.
21
+
22
+
23
+
24
+ 6.11.0 (2024-04-20)
25
+ ===================
26
+
27
+ Features
28
+ --------
29
+
30
+ - The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
31
+
32
+ - Fix #983: Add plugin hook for mirror authentication header.
33
+
34
+
35
+
36
+ Bug Fixes
37
+ ---------
38
+
39
+ - Preserve last modified of docs and toxresults during export/import.
40
+
41
+ - Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
42
+
43
+
44
+
5
45
  6.10.0 (2023-12-19)
6
46
  ===================
7
47
 
@@ -10,7 +50,7 @@ Features
10
50
 
11
51
  - Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
12
52
 
13
- - Fix #993: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
53
+ - Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
14
54
 
15
55
 
16
56
 
@@ -9,85 +9,89 @@ Changelog
9
9
 
10
10
  .. towncrier release notes start
11
11
 
12
- 6.9.2 (2023-08-06)
13
- ==================
12
+ 6.11.0 (2024-04-20)
13
+ ===================
14
14
 
15
- Bug Fixes
16
- ---------
15
+ Features
16
+ --------
17
17
 
18
- - Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
18
+ - The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
19
+
20
+ - Fix #983: Add plugin hook for mirror authentication header.
19
21
 
20
22
 
21
- 6.9.1 (2023-07-02)
22
- ==================
23
23
 
24
24
  Bug Fixes
25
25
  ---------
26
26
 
27
- - Prevent error in find_pre_existing_file in case of incomplete metadata.
27
+ - Preserve last modified of docs and toxresults during export/import.
28
28
 
29
- - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
29
+ - Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
30
30
 
31
31
 
32
- 6.9.0 (2023-05-23)
33
- ==================
32
+
33
+ 6.10.0 (2023-12-19)
34
+ ===================
34
35
 
35
36
  Features
36
37
  --------
37
38
 
38
- - Support export directory layout for ``--replica-file-search-path`` option.
39
+ - Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
40
+
41
+ - Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
39
42
 
40
- - 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.
41
43
 
42
44
 
43
45
  Bug Fixes
44
46
  ---------
45
47
 
46
- - Keep a reference to async tasks to avoid their removal mid execution.
47
-
48
- - Support changed default of ``enforce_content_length`` in urllib3 >= 2.
49
-
50
- - Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
48
+ - Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
51
49
 
52
- - Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
50
+ - Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
53
51
 
54
- - Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
55
52
 
56
53
 
57
- 6.8.0 (2022-12-05)
54
+ 6.9.2 (2023-08-06)
58
55
  ==================
59
56
 
60
- Features
61
- --------
57
+ Bug Fixes
58
+ ---------
62
59
 
63
- - Fix #929: Cache normalized project names per transaction on mirror index instances.
60
+ - Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
64
61
 
65
62
 
63
+ 6.9.1 (2023-07-02)
64
+ ==================
65
+
66
66
  Bug Fixes
67
67
  ---------
68
68
 
69
- - Fix #914: add locking to list_projects_perstage of mirror indexes to prevent multiple slow concurrent updates of the full project name list.
70
-
71
- - Catch exceptions in async_httpget analog to httpget.
69
+ - Prevent error in find_pre_existing_file in case of incomplete metadata.
72
70
 
73
- - Add locking to mirror name cache to prevent race condition on updates.
71
+ - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
74
72
 
75
73
 
76
- 6.7.0 (2022-09-28)
74
+ 6.9.0 (2023-05-23)
77
75
  ==================
78
76
 
79
77
  Features
80
78
  --------
81
79
 
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``.
80
+ - Support export directory layout for ``--replica-file-search-path`` option.
85
81
 
86
- - Set headers to prevent caching for simple links with stale results.
82
+ - 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.
87
83
 
88
84
 
89
85
  Bug Fixes
90
86
  ---------
91
87
 
92
- - Fix #840: Correct url scheme in config if nginx is behind another proxy.
88
+ - Keep a reference to async tasks to avoid their removal mid execution.
89
+
90
+ - Support changed default of ``enforce_content_length`` in urllib3 >= 2.
91
+
92
+ - Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
93
+
94
+ - Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
95
+
96
+ - Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
93
97
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devpi-server
3
- Version: 6.10.0
3
+ Version: 6.12.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,87 +119,83 @@ Changelog
119
119
 
120
120
  .. towncrier release notes start
121
121
 
122
- 6.10.0 (2023-12-19)
122
+ 6.12.0 (2024-06-25)
123
123
  ===================
124
124
 
125
125
  Features
126
126
  --------
127
127
 
128
- - Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
128
+ - Added ``devpiserver_on_toxresult_store`` hook to allow blocking or skipping a toxresult upload on more specific conditions as ``acl_toxresult_upload`` would allow.
129
129
 
130
- - Fix #993: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
130
+ - Added ``devpiserver_on_toxresult_upload_forbidden`` hook to allow returning a custom message and result (403 or 200).
131
131
 
132
132
 
133
133
 
134
134
  Bug Fixes
135
135
  ---------
136
136
 
137
- - Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
137
+ - Return json data if toxresult upload is forbidden.
138
138
 
139
- - Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
140
139
 
141
140
 
141
+ 6.11.0 (2024-04-20)
142
+ ===================
142
143
 
143
- 6.9.2 (2023-08-06)
144
- ==================
144
+ Features
145
+ --------
145
146
 
146
- Bug Fixes
147
- ---------
147
+ - The ``devpi-fsck`` script now returns an error code when there have been missing files or checksum errors.
148
148
 
149
- - Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
149
+ - Fix #983: Add plugin hook for mirror authentication header.
150
150
 
151
151
 
152
- 6.9.1 (2023-07-02)
153
- ==================
154
152
 
155
153
  Bug Fixes
156
154
  ---------
157
155
 
158
- - Prevent error in find_pre_existing_file in case of incomplete metadata.
156
+ - Preserve last modified of docs and toxresults during export/import.
159
157
 
160
- - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
158
+ - Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
161
159
 
162
160
 
163
- 6.9.0 (2023-05-23)
164
- ==================
161
+
162
+ 6.10.0 (2023-12-19)
163
+ ===================
165
164
 
166
165
  Features
167
166
  --------
168
167
 
169
- - Support export directory layout for ``--replica-file-search-path`` option.
168
+ - Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
169
+
170
+ - Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
170
171
 
171
- - 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.
172
172
 
173
173
 
174
174
  Bug Fixes
175
175
  ---------
176
176
 
177
- - Keep a reference to async tasks to avoid their removal mid execution.
178
-
179
- - Support changed default of ``enforce_content_length`` in urllib3 >= 2.
180
-
181
- - Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
177
+ - Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
182
178
 
183
- - Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
179
+ - Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
184
180
 
185
- - 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
181
 
187
182
 
188
- 6.8.0 (2022-12-05)
183
+ 6.9.2 (2023-08-06)
189
184
  ==================
190
185
 
191
- Features
192
- --------
186
+ Bug Fixes
187
+ ---------
193
188
 
194
- - Fix #929: Cache normalized project names per transaction on mirror index instances.
189
+ - Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
195
190
 
196
191
 
192
+ 6.9.1 (2023-07-02)
193
+ ==================
194
+
197
195
  Bug Fixes
198
196
  ---------
199
197
 
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.
198
+ - Prevent error in find_pre_existing_file in case of incomplete metadata.
203
199
 
204
- - Add locking to mirror name cache to prevent race condition on updates.
200
+ - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
205
201
 
@@ -0,0 +1 @@
1
+ __version__ = '6.12.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(set(sum(groups, []))))
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=float, metavar="SECS",
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
- if content_or_file.seekable():
44
- content_or_file.seek(0)
45
- hexdigest = get_file_hash(
46
- content_or_file, hash_type)
47
- content_or_file.seek(0)
48
- return f"{hash_type}={hexdigest}"
49
- else:
50
- content_or_file = content_or_file.read()
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 not None:
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 ValueError(
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 ValueError(
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
@@ -1,5 +1,13 @@
1
-
1
+ from __future__ import annotations
2
2
  from pluggy import HookspecMarker
3
+ from typing import Optional
4
+ from typing import TYPE_CHECKING
5
+
6
+
7
+ if TYPE_CHECKING:
8
+ from devpi_server.views import ToxResultHandling
9
+ from pyramid.request import Request
10
+
3
11
 
4
12
  hookspec = HookspecMarker("devpiserver")
5
13
 
@@ -200,6 +208,18 @@ def devpiserver_auth_denials(request, acl, user, stage):
200
208
  """
201
209
 
202
210
 
211
+ @hookspec
212
+ def devpiserver_get_mirror_auth(mirror_url, www_authenticate_header):
213
+ """Provide an http "Authorization" header to access a mirror.
214
+
215
+ Return a string which will be set as the authorization header for all http
216
+ requests to this mirror.
217
+
218
+ Return None or an empty string if no credentials can be determined for the
219
+ supplied url.
220
+ """
221
+
222
+
203
223
  @hookspec
204
224
  def devpiserver_get_stage_customizer_classes():
205
225
  """EXPERIMENTAL!
@@ -243,6 +263,28 @@ def devpiserver_on_replicated_file(stage, project, version, link, serial, back_s
243
263
  """Called when a file was downloaded from master on replica."""
244
264
 
245
265
 
266
+ @hookspec(firstresult=True)
267
+ def devpiserver_on_toxresult_store(request: Request, tox_result_handling: ToxResultHandling) -> Optional[ToxResultHandling]:
268
+ """Called when a toxresult is about to be stored.
269
+
270
+ Stops at first non-None result.
271
+
272
+ :returns: A ToxResultHandling object which determines how the upload is processed.
273
+ """
274
+
275
+
276
+ @hookspec(firstresult=True)
277
+ def devpiserver_on_toxresult_upload_forbidden(request: Request, tox_result_handling: ToxResultHandling) -> Optional[str]:
278
+ """Called when the permission check for toxresult upload failed.
279
+
280
+ Stops at first non-None result.
281
+
282
+ :returns: A ToxResultHandling object which determines whether an error
283
+ with message (default using ``block``) or a success with a message
284
+ (using ``skip``) is returned.
285
+ """
286
+
287
+
246
288
  @hookspec
247
289
  def devpiserver_metrics(request):
248
290
  """ called for status view.
@@ -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("doczip", project, relpath, version=version)
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
- link = stage.store_doczip(project, version, f)
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(sum(self.name2bases.values(), []))
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: