devpi-server 6.9.2__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.
Files changed (137) hide show
  1. devpi_server-6.11.0/.flake8 +9 -0
  2. {devpi-server-6.9.2 → devpi_server-6.11.0}/CHANGELOG +42 -0
  3. devpi_server-6.11.0/CHANGELOG.short.rst +95 -0
  4. {devpi-server-6.9.2 → devpi_server-6.11.0}/MANIFEST.in +1 -1
  5. {devpi-server-6.9.2 → devpi_server-6.11.0}/PKG-INFO +56 -35
  6. devpi_server-6.11.0/devpi_server/__init__.py +1 -0
  7. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/auth.py +4 -2
  8. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/supervisor-devpi.conf.template +0 -2
  9. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/config.py +75 -41
  10. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/filestore.py +43 -29
  11. devpi_server-6.11.0/devpi_server/filestore_fs.py +136 -0
  12. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/fileutil.py +2 -2
  13. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/fsck.py +16 -18
  14. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/hookspecs.py +13 -1
  15. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/importexport.py +82 -84
  16. devpi_server-6.11.0/devpi_server/init.py +38 -0
  17. devpi_server-6.11.0/devpi_server/interfaces.py +220 -0
  18. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/keyfs.py +122 -104
  19. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/keyfs_sqlite.py +30 -31
  20. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/keyfs_sqlite_fs.py +7 -102
  21. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/log.py +2 -1
  22. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/main.py +94 -43
  23. devpi_server-6.11.0/devpi_server/markers.py +14 -0
  24. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/middleware.py +0 -2
  25. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/mirror.py +78 -27
  26. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/model.py +45 -27
  27. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/passwd.py +9 -20
  28. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/readonly.py +5 -4
  29. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/replica.py +94 -111
  30. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/sizeof.py +1 -1
  31. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/views.py +214 -204
  32. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server.egg-info/PKG-INFO +56 -35
  33. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server.egg-info/SOURCES.txt +7 -0
  34. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server.egg-info/requires.txt +1 -1
  35. devpi_server-6.11.0/pytest_devpi_server/__init__.py +4 -0
  36. {devpi-server-6.9.2 → devpi_server-6.11.0}/setup.py +3 -3
  37. devpi_server-6.11.0/test_devpi_server/__init__.py +0 -0
  38. devpi_server-6.11.0/test_devpi_server/conftest.py +17 -0
  39. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/example.py +1 -1
  40. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/functional.py +10 -10
  41. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/mirrordata/dataindex.json +1 -1
  42. devpi_server-6.11.0/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +1 -0
  43. devpi-server-6.9.2/test_devpi_server/conftest.py → devpi_server-6.11.0/test_devpi_server/plugin.py +92 -43
  44. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/reqmock.py +19 -19
  45. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/simpypi.py +9 -16
  46. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_auth.py +5 -4
  47. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_config.py +15 -13
  48. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_filestore.py +59 -47
  49. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_fileutil.py +5 -5
  50. devpi_server-6.11.0/test_devpi_server/test_fsck.py +41 -0
  51. devpi_server-6.11.0/test_devpi_server/test_genconfig.py +50 -0
  52. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_importexport.py +135 -115
  53. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_keyfs.py +105 -89
  54. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_keyfs_sqlite_fs.py +4 -4
  55. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_log.py +3 -2
  56. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_main.py +6 -6
  57. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_mirror.py +109 -55
  58. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_model.py +100 -116
  59. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_nginx.py +5 -2
  60. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_nginx_replica.py +5 -5
  61. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_permissions.py +8 -6
  62. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_readonly.py +2 -2
  63. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_replica.py +88 -75
  64. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_replica_functional.py +6 -6
  65. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_stage_customizer.py +15 -15
  66. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_streaming.py +2 -1
  67. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_streaming_nginx.py +1 -1
  68. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_streaming_replica.py +1 -1
  69. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_streaming_replica_nginx.py +1 -1
  70. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_view_auth.py +10 -8
  71. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_views.py +74 -79
  72. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_views_push_external.py +1 -1
  73. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_views_status.py +1 -1
  74. devpi_server-6.11.0/tox.ini +40 -0
  75. devpi-server-6.9.2/devpi_server/__init__.py +0 -1
  76. devpi-server-6.9.2/devpi_server/init.py +0 -53
  77. devpi-server-6.9.2/devpi_server/interfaces.py +0 -147
  78. devpi-server-6.9.2/pytest_devpi_server/__init__.py +0 -25
  79. devpi-server-6.9.2/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
  80. devpi-server-6.9.2/test_devpi_server/test_genconfig.py +0 -19
  81. devpi-server-6.9.2/tox.ini +0 -49
  82. {devpi-server-6.9.2 → devpi_server-6.11.0}/AUTHORS +0 -0
  83. {devpi-server-6.9.2 → devpi_server-6.11.0}/LICENSE +0 -0
  84. {devpi-server-6.9.2 → devpi_server-6.11.0}/README.rst +0 -0
  85. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/__main__.py +0 -0
  86. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/auth_basic.py +0 -0
  87. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/auth_devpi.py +0 -0
  88. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/__init__.py +0 -0
  89. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/crontab.template +0 -0
  90. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/devpi.service.template +0 -0
  91. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/launchd-macos.txt.template +0 -0
  92. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
  93. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
  94. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
  95. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
  96. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
  97. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/supervisord.conf.template +0 -0
  98. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/cfg/windows-service.txt.template +0 -0
  99. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/exceptions.py +0 -0
  100. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/genconfig.py +0 -0
  101. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/keyfs_types.py +0 -0
  102. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/mythread.py +0 -0
  103. {devpi-server-6.9.2/test_devpi_server → devpi_server-6.11.0/devpi_server/vendor}/__init__.py +0 -0
  104. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/vendor/_pip.py +0 -0
  105. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server/view_auth.py +0 -0
  106. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server.egg-info/dependency_links.txt +0 -0
  107. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server.egg-info/entry_points.txt +0 -0
  108. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server.egg-info/not-zip-safe +0 -0
  109. {devpi-server-6.9.2 → devpi_server-6.11.0}/devpi_server.egg-info/top_level.txt +0 -0
  110. {devpi-server-6.9.2 → devpi_server-6.11.0}/pyproject.toml +0 -0
  111. {devpi-server-6.9.2 → devpi_server-6.11.0}/setup.cfg +0 -0
  112. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
  113. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
  114. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
  115. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
  116. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
  117. /devpi-server-6.9.2/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
  118. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
  119. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
  120. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
  121. /devpi-server-6.9.2/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
  122. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
  123. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
  124. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
  125. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
  126. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
  127. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
  128. /devpi-server-6.9.2/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
  129. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
  130. {devpi-server-6.9.2 → 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
  131. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
  132. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_authcheck.py +0 -0
  133. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_conftest.py +0 -0
  134. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_mirror_no_project_list.py +0 -0
  135. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_mythread.py +0 -0
  136. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_reqmock.py +0 -0
  137. {devpi-server-6.9.2 → devpi_server-6.11.0}/test_devpi_server/test_views_patch.py +0 -0
@@ -0,0 +1,9 @@
1
+ [flake8]
2
+ ignore = E501,E741,W503
3
+ per-file-ignores =
4
+ plugin.py:E127,E128,E225,E231,E265,E402,E712
5
+ importexport.py:E127
6
+ model.py:E128,E225,E231
7
+ setup.py:E121,E126
8
+ test_importexport.py:E121
9
+ test*.py:E117,E126,E127,E128,E225,E226,E231,E251,E265,E712,E721
@@ -2,6 +2,48 @@
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
+
26
+ 6.10.0 (2023-12-19)
27
+ ===================
28
+
29
+ Features
30
+ --------
31
+
32
+ - Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
33
+
34
+ - Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
35
+
36
+
37
+
38
+ Bug Fixes
39
+ ---------
40
+
41
+ - Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
42
+
43
+ - Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
44
+
45
+
46
+
5
47
  6.9.2 (2023-08-06)
6
48
  ==================
7
49
 
@@ -0,0 +1,95 @@
1
+
2
+
3
+ =========
4
+ Changelog
5
+ =========
6
+
7
+
8
+
9
+
10
+ .. towncrier release notes start
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
+
33
+ 6.9.2 (2023-08-06)
34
+ ==================
35
+
36
+ Bug Fixes
37
+ ---------
38
+
39
+ - Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
40
+
41
+
42
+ 6.9.1 (2023-07-02)
43
+ ==================
44
+
45
+ Bug Fixes
46
+ ---------
47
+
48
+ - Prevent error in find_pre_existing_file in case of incomplete metadata.
49
+
50
+ - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
51
+
52
+
53
+ 6.9.0 (2023-05-23)
54
+ ==================
55
+
56
+ Features
57
+ --------
58
+
59
+ - Support export directory layout for ``--replica-file-search-path`` option.
60
+
61
+ - 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.
62
+
63
+
64
+ Bug Fixes
65
+ ---------
66
+
67
+ - Keep a reference to async tasks to avoid their removal mid execution.
68
+
69
+ - Support changed default of ``enforce_content_length`` in urllib3 >= 2.
70
+
71
+ - Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
72
+
73
+ - Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
74
+
75
+ - Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
76
+
77
+
78
+ 6.8.0 (2022-12-05)
79
+ ==================
80
+
81
+ Features
82
+ --------
83
+
84
+ - Fix #929: Cache normalized project names per transaction on mirror index instances.
85
+
86
+
87
+ Bug Fixes
88
+ ---------
89
+
90
+ - Fix #914: add locking to list_projects_perstage of mirror indexes to prevent multiple slow concurrent updates of the full project name list.
91
+
92
+ - Catch exceptions in async_httpget analog to httpget.
93
+
94
+ - Add locking to mirror name cache to prevent race condition on updates.
95
+
@@ -1,4 +1,4 @@
1
- include CHANGELOG *.ini *.rst LICENSE
1
+ include .flake8 CHANGELOG *.ini *.rst LICENSE
2
2
  recursive-include devpi_server *.template
3
3
  recursive-include test_devpi_server *.py
4
4
  recursive-include test_devpi_server/importexportdata *.gz* *.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devpi-server
3
- Version: 6.9.2
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
@@ -26,9 +26,26 @@ Classifier: Programming Language :: Python :: 3.8
26
26
  Classifier: Programming Language :: Python :: 3.9
27
27
  Classifier: Programming Language :: Python :: 3.10
28
28
  Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Programming Language :: Python :: 3.12
29
30
  Requires-Python: >=3.7
30
31
  License-File: LICENSE
31
32
  License-File: AUTHORS
33
+ Requires-Dist: py>=1.4.23
34
+ Requires-Dist: httpx
35
+ Requires-Dist: argon2-cffi
36
+ Requires-Dist: attrs>=21.3.0
37
+ Requires-Dist: defusedxml
38
+ Requires-Dist: devpi_common<5,>3.6.0
39
+ Requires-Dist: itsdangerous>=0.24
40
+ Requires-Dist: platformdirs
41
+ Requires-Dist: pyramid>=2
42
+ Requires-Dist: waitress>=1.0.1
43
+ Requires-Dist: repoze.lru>=0.6
44
+ Requires-Dist: passlib[argon2]
45
+ Requires-Dist: pluggy<2.0,>=0.6.0
46
+ Requires-Dist: ruamel.yaml
47
+ Requires-Dist: strictyaml
48
+ Requires-Dist: lazy
32
49
 
33
50
  =============================================================================
34
51
  devpi-server: server for private package indexes and PyPI caching
@@ -102,85 +119,89 @@ Changelog
102
119
 
103
120
  .. towncrier release notes start
104
121
 
105
- 6.9.2 (2023-08-06)
106
- ==================
122
+ 6.11.0 (2024-04-20)
123
+ ===================
107
124
 
108
- Bug Fixes
109
- ---------
125
+ Features
126
+ --------
110
127
 
111
- - Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
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.
112
131
 
113
132
 
114
- 6.9.1 (2023-07-02)
115
- ==================
116
133
 
117
134
  Bug Fixes
118
135
  ---------
119
136
 
120
- - Prevent error in find_pre_existing_file in case of incomplete metadata.
137
+ - Preserve last modified of docs and toxresults during export/import.
121
138
 
122
- - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
139
+ - Fix #1033: Use ``int`` for ``--mirror-cache-expiry`` to fix value of ``proxy_cache_valid`` in nginx caching config.
123
140
 
124
141
 
125
- 6.9.0 (2023-05-23)
126
- ==================
142
+
143
+ 6.10.0 (2023-12-19)
144
+ ===================
127
145
 
128
146
  Features
129
147
  --------
130
148
 
131
- - Support export directory layout for ``--replica-file-search-path`` option.
149
+ - Use ``Authorization`` header instead of adding username/password to URL when fetching from mirror.
150
+
151
+ - Fix #998: Use the pure Python httpx library instead of aiohttp to prevent delays in supporting newest Python releases.
132
152
 
133
- - 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.
134
153
 
135
154
 
136
155
  Bug Fixes
137
156
  ---------
138
157
 
139
- - Keep a reference to async tasks to avoid their removal mid execution.
140
-
141
- - Support changed default of ``enforce_content_length`` in urllib3 >= 2.
142
-
143
- - Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
158
+ - Fix #996: support hashes other than sha256 in application/vnd.pypi.simple.v1+json responses.
144
159
 
145
- - Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
160
+ - Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
146
161
 
147
- - Fix wrong hash metadata introduced in 6.5.0 for toxresults which prevents replication. The metadata can be fixed by an export/import cycle.
148
162
 
149
163
 
150
- 6.8.0 (2022-12-05)
164
+ 6.9.2 (2023-08-06)
151
165
  ==================
152
166
 
153
- Features
154
- --------
167
+ Bug Fixes
168
+ ---------
155
169
 
156
- - Fix #929: Cache normalized project names per transaction on mirror index instances.
170
+ - Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
157
171
 
158
172
 
173
+ 6.9.1 (2023-07-02)
174
+ ==================
175
+
159
176
  Bug Fixes
160
177
  ---------
161
178
 
162
- - Fix #914: add locking to list_projects_perstage of mirror indexes to prevent multiple slow concurrent updates of the full project name list.
163
-
164
- - Catch exceptions in async_httpget analog to httpget.
179
+ - Prevent error in find_pre_existing_file in case of incomplete metadata.
165
180
 
166
- - Add locking to mirror name cache to prevent race condition on updates.
181
+ - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
167
182
 
168
183
 
169
- 6.7.0 (2022-09-28)
184
+ 6.9.0 (2023-05-23)
170
185
  ==================
171
186
 
172
187
  Features
173
188
  --------
174
189
 
175
- - Add nginx example to ``devpi-gen-config`` with caching of simple pages for installers like pip.
176
-
177
- - Automatically check for ``+files`` when using ``--replica-file-search-path``.
190
+ - Support export directory layout for ``--replica-file-search-path`` option.
178
191
 
179
- - Set headers to prevent caching for simple links with stale results.
192
+ - Fix #931: Add ``mirror_no_project_list`` setting for mirror indexes that have no full project list like google cloud artifacts or if you want to prevent downloading the full list for huge indexes like PyPI.
180
193
 
181
194
 
182
195
  Bug Fixes
183
196
  ---------
184
197
 
185
- - Fix #840: Correct url scheme in config if nginx is behind another proxy.
198
+ - Keep a reference to async tasks to avoid their removal mid execution.
199
+
200
+ - Support changed default of ``enforce_content_length`` in urllib3 >= 2.
201
+
202
+ - Fix #934: Properly set PATH_INFO when outside URL is used with sub-path.
203
+
204
+ - Fix #945: Adapt FatalError to be usable as an async HTTP response when updating a project on a mirror.
205
+
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
 
@@ -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(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
 
@@ -1,7 +1,5 @@
1
-
2
1
  [program:devpi-server]
3
2
  command={devpibin} {server_args}
4
- user = {user}
5
3
  priority = 999
6
4
  startsecs = 5
7
5
  redirect_stderr = True
@@ -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.")
@@ -420,7 +420,8 @@ def get_parser(pluginmanager):
420
420
  "mirror of pypi.org and is created by default. "
421
421
  "All indices are suitable for pip or easy_install usage "
422
422
  "and setup.py upload ... invocations.",
423
- add_help=False)
423
+ add_help=False,
424
+ pluginmanager=pluginmanager)
424
425
  addoptions(parser, pluginmanager)
425
426
  pluginmanager.hook.devpiserver_add_parser_options(parser=parser)
426
427
  return parser
@@ -439,7 +440,7 @@ def find_config_file():
439
440
  if os.path.exists(config_file):
440
441
  config_files.append(config_file)
441
442
  if len(config_files) > 1:
442
- log.warn("Multiple configuration files found:\n%s", "\n".join(config_files))
443
+ log.warning("Multiple configuration files found:\n%s", "\n".join(config_files))
443
444
  if len(config_files):
444
445
  return config_files[-1]
445
446
 
@@ -476,7 +477,7 @@ def default_getter(name, config_options, environ):
476
477
  return
477
478
  if name == "serverdir":
478
479
  if "DEVPI_SERVERDIR" in environ:
479
- log.warn(
480
+ log.warning(
480
481
  "Using deprecated DEVPI_SERVERDIR environment variable. "
481
482
  "You should switch to use DEVPISERVER_SERVERDIR.")
482
483
  return environ["DEVPI_SERVERDIR"]
@@ -540,8 +541,9 @@ def get_action_long_name(action):
540
541
 
541
542
 
542
543
  class MyArgumentParser(argparse.ArgumentParser):
543
- def __init__(self, *args, **kwargs):
544
+ def __init__(self, *args, pluginmanager=None, **kwargs):
544
545
  self.addoption = self.add_argument
546
+ self.pluginmanager = pluginmanager
545
547
  super(MyArgumentParser, self).__init__(*args, **kwargs)
546
548
 
547
549
  def post_process_actions(self, defaultget=None):
@@ -573,6 +575,39 @@ class MyArgumentParser(argparse.ArgumentParser):
573
575
  grp.addoption = grp.add_argument
574
576
  return grp
575
577
 
578
+ def add_all_options(self):
579
+ addoptions(self, self.pluginmanager)
580
+
581
+ def add_configfile_option(self):
582
+ add_configfile_option(self, self.pluginmanager)
583
+
584
+ def add_export_options(self):
585
+ add_export_options(self, self.pluginmanager)
586
+
587
+ def add_hard_links_option(self):
588
+ add_hard_links_option(self, self.pluginmanager)
589
+
590
+ def add_help_option(self):
591
+ add_help_option(self, self.pluginmanager)
592
+
593
+ def add_import_options(self):
594
+ add_import_options(self, self.pluginmanager)
595
+
596
+ def add_init_options(self):
597
+ add_init_options(self, self.pluginmanager)
598
+
599
+ def add_master_url_option(self):
600
+ add_master_url_option(self, self.pluginmanager)
601
+
602
+ def add_role_option(self):
603
+ add_role_option(self, self.pluginmanager)
604
+
605
+ def add_secretfile_option(self):
606
+ add_secretfile_option(self, self.pluginmanager)
607
+
608
+ def add_storage_options(self):
609
+ add_storage_options(self, self.pluginmanager)
610
+
576
611
 
577
612
  def new_secret():
578
613
  return base64.b64encode(secrets.token_bytes(32))
@@ -591,7 +626,7 @@ class Config(object):
591
626
 
592
627
  @cached_property
593
628
  def waitress_info(self):
594
- from .main import fatal
629
+ from .main import Fatal
595
630
  host = self.args.host
596
631
  port = self.args.port
597
632
  default_host_port = (host == 'localhost') and (port == 3141)
@@ -609,7 +644,7 @@ class Config(object):
609
644
  port = None
610
645
  if self.args.listen:
611
646
  if not default_host_port:
612
- fatal("You can use either --listen or --host/--port, not both together.")
647
+ raise Fatal("You can use either --listen or --host/--port, not both together.")
613
648
  host = None
614
649
  port = None
615
650
  for listen in self.args.listen:
@@ -796,13 +831,15 @@ class Config(object):
796
831
  self.nodeinfo["role"] = "standalone"
797
832
 
798
833
  def _automatic_role(self, role):
799
- from .main import fatal
834
+ from .main import Fatal
800
835
  if role == "replica" and not self.master_url:
801
- fatal("configuration error, masterurl isn't set in nodeinfo, but "
802
- "role is set to replica")
836
+ raise Fatal(
837
+ "configuration error, masterurl isn't set in nodeinfo, but "
838
+ "role is set to replica")
803
839
  if role != "replica" and self.master_url:
804
- fatal("configuration error, masterurl set in nodeinfo, but role "
805
- "isn't set to replica")
840
+ raise Fatal(
841
+ "configuration error, masterurl set in nodeinfo, but role "
842
+ "isn't set to replica")
806
843
  if role != "replica":
807
844
  self.master_url = None
808
845
  if role == "master":
@@ -810,13 +847,13 @@ class Config(object):
810
847
  self.nodeinfo["role"] = "standalone"
811
848
 
812
849
  def _change_role(self, old_role, new_role):
813
- from .main import fatal
850
+ from .main import Fatal
814
851
  if new_role == "replica":
815
852
  if old_role and old_role != "replica":
816
- fatal("cannot run as replica, was previously run "
817
- "as %s" % old_role)
853
+ msg = f"cannot run as replica, was previously run as {old_role}"
854
+ raise Fatal(msg)
818
855
  if not self.master_url:
819
- fatal("need to specify --master-url to run as replica")
856
+ raise Fatal("need to specify --master-url to run as replica")
820
857
  else:
821
858
  self.master_url = None
822
859
  self.nodeinfo["role"] = new_role
@@ -839,12 +876,13 @@ class Config(object):
839
876
  self.nodeinfo.pop("masterurl", None)
840
877
 
841
878
  def _storage_info_from_name(self, name, settings):
842
- from .main import fatal
879
+ from .main import Fatal
843
880
  storages = self.pluginmanager.hook.devpiserver_storage_backend(settings=settings)
844
881
  for storage in storages:
845
882
  if storage['name'] == name:
846
883
  return storage
847
- fatal("The backend '%s' can't be found, is the plugin not installed?" % name)
884
+ msg = f"The backend {name!r} can't be found, is the plugin not installed?"
885
+ raise Fatal(msg)
848
886
 
849
887
  def _storage_info(self):
850
888
  name = self.storage_info["name"]
@@ -884,7 +922,7 @@ class Config(object):
884
922
  secretfile = self.serverdir.join('.secret')
885
923
  if not secretfile.check(file=True):
886
924
  return None
887
- log.warn(
925
+ log.warning(
888
926
  "Using deprecated existing secret file at '%s', use "
889
927
  "--secretfile to explicitly provide the location." % secretfile)
890
928
  return secretfile
@@ -892,25 +930,25 @@ class Config(object):
892
930
  os.path.expanduser(self.args.secretfile))
893
931
 
894
932
  def get_validated_secret(self):
895
- from .main import fatal
933
+ from .main import Fatal
896
934
  import stat
897
935
  if not self.secretfile.check(file=True):
898
- fatal("The given secret file doesn't exist.")
936
+ raise Fatal("The given secret file doesn't exist.")
899
937
  if self.secretfile.stat().mode & stat.S_IRWXO and sys.platform != "win32":
900
- fatal("The given secret file is world accessible, the access mode must be user accessible only (0600).")
938
+ raise Fatal("The given secret file is world accessible, the access mode must be user accessible only (0600).")
901
939
  if self.secretfile.stat().mode & stat.S_IRWXG and sys.platform != "win32":
902
- fatal("The given secret file is group accessible, the access mode must be user accessible only (0600).")
940
+ raise Fatal("The given secret file is group accessible, the access mode must be user accessible only (0600).")
903
941
  if self.secretfile.dirpath().stat().mode & stat.S_IWGRP and sys.platform != "win32":
904
- fatal("The folder of the given secret file is group writable, it must only be writable by the user.")
942
+ raise Fatal("The folder of the given secret file is group writable, it must only be writable by the user.")
905
943
  if self.secretfile.dirpath().stat().mode & stat.S_IWOTH and sys.platform != "win32":
906
- fatal("The folder of the given secret file is world writable, it must only be writable by the user.")
944
+ raise Fatal("The folder of the given secret file is world writable, it must only be writable by the user.")
907
945
  secret = self.secretfile.read_binary()
908
946
  if len(secret) < 32:
909
- fatal(
947
+ raise Fatal(
910
948
  "The secret in the given secret file is too short, "
911
949
  "it should be at least 32 characters long.")
912
950
  if len(set(secret)) < 6:
913
- fatal(
951
+ raise Fatal(
914
952
  "The secret in the given secret file is too weak, "
915
953
  "it should use less repetition.")
916
954
  return secret
@@ -918,7 +956,7 @@ class Config(object):
918
956
  @cached_property
919
957
  def basesecret(self):
920
958
  if self.secretfile is None:
921
- log.warn(
959
+ log.warning(
922
960
  "No secret file provided, creating a new random secret. "
923
961
  "Login tokens issued before are invalid. "
924
962
  "Use --secretfile option to provide a persistent secret. "
@@ -970,21 +1008,20 @@ def getpath(path):
970
1008
  def gensecret():
971
1009
  from .log import configure_cli_logging
972
1010
  from .log import threadlog as log
1011
+ from .main import CommandRunner
973
1012
  from .main import Fatal
974
- from .main import fatal
975
1013
  import stat
976
- try:
977
- pluginmanager = get_pluginmanager()
978
- parser = MyArgumentParser(
1014
+ with CommandRunner() as runner:
1015
+ parser = runner.create_parser(
979
1016
  description="Create a random secret.",
980
1017
  add_help=False)
981
- add_help_option(parser, pluginmanager)
982
- add_configfile_option(parser, pluginmanager)
983
- add_secretfile_option(parser, pluginmanager)
984
- config = parseoptions(pluginmanager, sys.argv, parser=parser)
1018
+ parser.add_help_option()
1019
+ parser.add_configfile_option()
1020
+ parser.add_secretfile_option()
1021
+ config = runner.get_config(sys.argv, parser=parser)
985
1022
  configure_cli_logging(config.args)
986
1023
  if config.args.secretfile is None:
987
- fatal("You need to provide a location for the secret file.")
1024
+ raise Fatal("You need to provide a location for the secret file.")
988
1025
  if not config.secretfile.exists():
989
1026
  with config.secretfile.open("wb") as f:
990
1027
  f.write(new_secret())
@@ -999,7 +1036,4 @@ def gensecret():
999
1036
  # run checks
1000
1037
  config.get_validated_secret()
1001
1038
  log.info("Permissions of secret file look good.")
1002
- except Fatal as e:
1003
- tw = py.io.TerminalWriter(sys.stderr)
1004
- tw.line("fatal: %s" % e.args[0], red=True)
1005
- return 1
1039
+ return runner.return_code