devpi-server 6.8.0__tar.gz → 6.9.1__tar.gz

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