devpi-server 6.12.0__tar.gz → 6.12.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devpi_server-6.12.0 → devpi-server-6.12.1}/CHANGELOG +14 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/PKG-INFO +16 -28
- devpi-server-6.12.1/devpi_server/__init__.py +1 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/config.py +4 -16
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/keyfs.py +3 -7
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/mirror.py +2 -2
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/model.py +6 -7
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/replica.py +15 -8
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/vendor/_pip.py +6 -6
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/views.py +10 -16
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server.egg-info/PKG-INFO +16 -28
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server.egg-info/SOURCES.txt +0 -1
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server.egg-info/requires.txt +4 -1
- {devpi_server-6.12.0 → devpi-server-6.12.1}/setup.py +4 -3
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/functional.py +12 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/plugin.py +23 -41
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_fileutil.py +2 -2
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_keyfs.py +44 -4
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_mirror.py +6 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_nginx.py +2 -2
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_views.py +1 -1
- devpi_server-6.12.0/CHANGELOG.short.rst +0 -97
- devpi_server-6.12.0/devpi_server/__init__.py +0 -1
- {devpi_server-6.12.0 → devpi-server-6.12.1}/.flake8 +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/AUTHORS +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/LICENSE +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/MANIFEST.in +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/README.rst +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/__main__.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/auth.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/auth_basic.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/auth_devpi.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/__init__.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/crontab.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/exceptions.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/filestore.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/filestore_fs.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/fileutil.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/fsck.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/genconfig.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/hookspecs.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/importexport.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/init.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/interfaces.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/keyfs_sqlite.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/keyfs_sqlite_fs.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/keyfs_types.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/log.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/main.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/markers.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/middleware.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/mythread.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/passwd.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/readonly.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/sizeof.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/vendor/__init__.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server/view_auth.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server.egg-info/not-zip-safe +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/pyproject.toml +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/pytest_devpi_server/__init__.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/setup.cfg +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/__init__.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/conftest.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/example.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/reqmock.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/simpypi.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_auth.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_authcheck.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_config.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_conftest.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_filestore.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_fsck.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_genconfig.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_importexport.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_keyfs_sqlite_fs.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_log.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_main.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_model.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_mythread.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_nginx_replica.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_permissions.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_readonly.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_replica.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_replica_functional.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_reqmock.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_stage_customizer.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_streaming.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_streaming_nginx.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_streaming_replica.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_view_auth.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_views_patch.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_views_push_external.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/test_devpi_server/test_views_status.py +0 -0
- {devpi_server-6.12.0 → devpi-server-6.12.1}/tox.ini +0 -0
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.12.1 (2024-07-24)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Bug Fixes
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- Support Python 3.13 by depending on legacy-cgi.
|
|
12
|
+
|
|
13
|
+
- Preserve query string when proxying requests from replica to primary. This fixes force removal on non-volatile indexes and probably other bugs.
|
|
14
|
+
|
|
15
|
+
- Fix #1044: Correctly update cache expiry time when mirrored server returns 304 Not Modified.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
5
19
|
6.12.0 (2024-06-25)
|
|
6
20
|
===================
|
|
7
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.12.
|
|
3
|
+
Version: 6.12.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
|
|
@@ -27,25 +27,10 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
27
27
|
Classifier: Programming Language :: Python :: 3.10
|
|
28
28
|
Classifier: Programming Language :: Python :: 3.11
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
30
31
|
Requires-Python: >=3.7
|
|
31
32
|
License-File: LICENSE
|
|
32
33
|
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
|
|
49
34
|
|
|
50
35
|
=============================================================================
|
|
51
36
|
devpi-server: server for private package indexes and PyPI caching
|
|
@@ -119,6 +104,20 @@ Changelog
|
|
|
119
104
|
|
|
120
105
|
.. towncrier release notes start
|
|
121
106
|
|
|
107
|
+
6.12.1 (2024-07-24)
|
|
108
|
+
===================
|
|
109
|
+
|
|
110
|
+
Bug Fixes
|
|
111
|
+
---------
|
|
112
|
+
|
|
113
|
+
- Support Python 3.13 by depending on legacy-cgi.
|
|
114
|
+
|
|
115
|
+
- Preserve query string when proxying requests from replica to primary. This fixes force removal on non-volatile indexes and probably other bugs.
|
|
116
|
+
|
|
117
|
+
- Fix #1044: Correctly update cache expiry time when mirrored server returns 304 Not Modified.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
122
121
|
6.12.0 (2024-06-25)
|
|
123
122
|
===================
|
|
124
123
|
|
|
@@ -188,14 +187,3 @@ Bug Fixes
|
|
|
188
187
|
|
|
189
188
|
- Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
|
|
190
189
|
|
|
191
|
-
|
|
192
|
-
6.9.1 (2023-07-02)
|
|
193
|
-
==================
|
|
194
|
-
|
|
195
|
-
Bug Fixes
|
|
196
|
-
---------
|
|
197
|
-
|
|
198
|
-
- Prevent error in find_pre_existing_file in case of incomplete metadata.
|
|
199
|
-
|
|
200
|
-
- Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
|
|
201
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '6.12.1'
|
|
@@ -321,27 +321,15 @@ def add_deploy_options(parser, pluginmanager):
|
|
|
321
321
|
|
|
322
322
|
parser.addoption(
|
|
323
323
|
"--argon2-memory-cost", type=int, default=DEFAULT_ARGON2_MEMORY_COST,
|
|
324
|
-
help=
|
|
325
|
-
"This is *not* for the user password hashes. "
|
|
326
|
-
"There should be no need to touch this setting, "
|
|
327
|
-
"except you really know what this is about! "
|
|
328
|
-
"Replicas need to use the same parameters as the master.")
|
|
324
|
+
help=argparse.SUPPRESS)
|
|
329
325
|
|
|
330
326
|
parser.addoption(
|
|
331
327
|
"--argon2-parallelism", type=int, default=DEFAULT_ARGON2_PARALLELISM,
|
|
332
|
-
help=
|
|
333
|
-
"This is *not* for the user password hashes. "
|
|
334
|
-
"There should be no need to touch this setting, "
|
|
335
|
-
"except you really know what this is about! "
|
|
336
|
-
"Replicas need to use the same parameters as the master.")
|
|
328
|
+
help=argparse.SUPPRESS)
|
|
337
329
|
|
|
338
330
|
parser.addoption(
|
|
339
331
|
"--argon2-time-cost", type=int, default=DEFAULT_ARGON2_TIME_COST,
|
|
340
|
-
help=
|
|
341
|
-
"This is *not* for the user password hashes. "
|
|
342
|
-
"There should be no need to touch this setting, "
|
|
343
|
-
"except you really know what this is about! "
|
|
344
|
-
"Replicas need to use the same parameters as the master.")
|
|
332
|
+
help=argparse.SUPPRESS)
|
|
345
333
|
|
|
346
334
|
parser.addoption(
|
|
347
335
|
"--requests-only", action="store_true",
|
|
@@ -567,7 +555,7 @@ class MyArgumentParser(argparse.ArgumentParser):
|
|
|
567
555
|
default = action.default
|
|
568
556
|
if isinstance(action, argparse._StoreFalseAction):
|
|
569
557
|
default = not default
|
|
570
|
-
if action.help and action.default
|
|
558
|
+
if action.help and action.help is not argparse.SUPPRESS and action.default is not argparse.SUPPRESS:
|
|
571
559
|
action.help += " [%s]" % default
|
|
572
560
|
|
|
573
561
|
def addgroup(self, *args, **kwargs):
|
|
@@ -673,13 +673,9 @@ class Transaction(object):
|
|
|
673
673
|
def exists(self, typedkey):
|
|
674
674
|
if typedkey in self.cache:
|
|
675
675
|
val = self.cache[typedkey]
|
|
676
|
-
|
|
677
|
-
return False
|
|
678
|
-
return True
|
|
676
|
+
return val not in (absent, deleted)
|
|
679
677
|
(serial, val) = self.get_original(typedkey)
|
|
680
|
-
|
|
681
|
-
return False
|
|
682
|
-
return True
|
|
678
|
+
return val not in (absent, deleted)
|
|
683
679
|
|
|
684
680
|
def delete(self, typedkey):
|
|
685
681
|
if not self.write:
|
|
@@ -692,7 +688,7 @@ class Transaction(object):
|
|
|
692
688
|
raise self.keyfs.ReadOnly()
|
|
693
689
|
# sanity check for dictionaries: we always want to have unicode
|
|
694
690
|
# keys, not bytes
|
|
695
|
-
if typedkey.type
|
|
691
|
+
if typedkey.type is dict:
|
|
696
692
|
check_unicode_keys(val)
|
|
697
693
|
assert val is not None
|
|
698
694
|
self.cache[typedkey] = val
|
|
@@ -794,8 +794,8 @@ class MirrorStage(BaseStage):
|
|
|
794
794
|
f"timeout after {self.timeout} seconds while getting data for {project!r}")
|
|
795
795
|
except self.UpstreamNotModified as e:
|
|
796
796
|
if links is not None:
|
|
797
|
-
|
|
798
|
-
|
|
797
|
+
# immediately update the cache
|
|
798
|
+
self.cache_retrieve_times.refresh(project, e.etag)
|
|
799
799
|
return self.SimpleLinks(links)
|
|
800
800
|
if e.etag is None:
|
|
801
801
|
threadlog.error(
|
|
@@ -42,9 +42,9 @@ def join_links_data(links, requires_python, yanked):
|
|
|
42
42
|
# build list of (key, href, require_python, yanked) tuples
|
|
43
43
|
result = []
|
|
44
44
|
links = zip_longest(links, requires_python, yanked, fillvalue=None)
|
|
45
|
-
for link, require_python,
|
|
45
|
+
for link, require_python, link_yanked in links:
|
|
46
46
|
key, href = link
|
|
47
|
-
result.append((key, href, require_python,
|
|
47
|
+
result.append((key, href, require_python, link_yanked))
|
|
48
48
|
return result
|
|
49
49
|
|
|
50
50
|
|
|
@@ -1615,11 +1615,10 @@ class LinkStore:
|
|
|
1615
1615
|
|
|
1616
1616
|
@property
|
|
1617
1617
|
def metadata(self):
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
return metadata
|
|
1618
|
+
return {
|
|
1619
|
+
k: v
|
|
1620
|
+
for k, v in get_mutable_deepcopy(self.verdata).items()
|
|
1621
|
+
if not k.startswith("+")}
|
|
1623
1622
|
|
|
1624
1623
|
def get_file_entry(self, relpath):
|
|
1625
1624
|
return self.filestore.get_file_entry(relpath)
|
|
@@ -6,6 +6,7 @@ import secrets
|
|
|
6
6
|
import threading
|
|
7
7
|
import time
|
|
8
8
|
import traceback
|
|
9
|
+
from contextlib import suppress
|
|
9
10
|
from functools import partial
|
|
10
11
|
from pluggy import HookimplMarker
|
|
11
12
|
from pyramid.httpexceptions import HTTPNotFound, HTTPAccepted, HTTPBadRequest
|
|
@@ -1174,18 +1175,22 @@ class BodyFileWrapper:
|
|
|
1174
1175
|
self.len = length
|
|
1175
1176
|
|
|
1176
1177
|
|
|
1177
|
-
def proxy_request_to_master(xom, request, stream=False):
|
|
1178
|
+
def proxy_request_to_master(xom, request, *, stream=False):
|
|
1178
1179
|
master_url = xom.config.master_url
|
|
1179
|
-
|
|
1180
|
+
request_url = URL(request.url)
|
|
1181
|
+
url = (
|
|
1182
|
+
master_url
|
|
1183
|
+
.joinpath(request_url.path)
|
|
1184
|
+
.replace(query=request_url.query)
|
|
1185
|
+
.url)
|
|
1180
1186
|
assert url.startswith(master_url.url)
|
|
1181
1187
|
http = xom._httpsession
|
|
1182
1188
|
with threadlog.around("info", "relaying: %s %s", request.method, url):
|
|
1183
1189
|
try:
|
|
1184
1190
|
headers = clean_request_headers(request)
|
|
1185
|
-
|
|
1191
|
+
length = None
|
|
1192
|
+
with suppress(ValueError, TypeError):
|
|
1186
1193
|
length = int(headers.get('Content-Length'))
|
|
1187
|
-
except (ValueError, TypeError):
|
|
1188
|
-
length = None
|
|
1189
1194
|
if length:
|
|
1190
1195
|
body = BodyFileWrapper(request.body_file, length)
|
|
1191
1196
|
else:
|
|
@@ -1197,7 +1202,8 @@ def proxy_request_to_master(xom, request, stream=False):
|
|
|
1197
1202
|
allow_redirects=False,
|
|
1198
1203
|
timeout=xom.config.args.proxy_timeout)
|
|
1199
1204
|
except http.Errors as e:
|
|
1200
|
-
|
|
1205
|
+
msg = f"proxy-write-to-master {url}: {e}"
|
|
1206
|
+
raise UpstreamError(msg) from e
|
|
1201
1207
|
|
|
1202
1208
|
|
|
1203
1209
|
def proxy_write_to_master(xom, request):
|
|
@@ -1231,10 +1237,11 @@ def proxy_write_to_master(xom, request):
|
|
|
1231
1237
|
headers=headers)
|
|
1232
1238
|
|
|
1233
1239
|
|
|
1234
|
-
def proxy_view_to_master(
|
|
1240
|
+
def proxy_view_to_master(_context, request):
|
|
1235
1241
|
xom = request.registry["xom"]
|
|
1236
1242
|
tx = getattr(xom.keyfs, "tx", None)
|
|
1237
|
-
|
|
1243
|
+
if getattr(tx, "write", False):
|
|
1244
|
+
raise RuntimeError("there should be no write transaction")
|
|
1238
1245
|
return proxy_write_to_master(xom, request)
|
|
1239
1246
|
|
|
1240
1247
|
|
|
@@ -14,12 +14,12 @@ class HTMLPage(object):
|
|
|
14
14
|
"""Represents one page, along with its URL"""
|
|
15
15
|
|
|
16
16
|
# FIXME: these regexes are horrible hacks:
|
|
17
|
-
_homepage_re = re.compile(r'<th>\s*home\s*page', re.
|
|
18
|
-
_download_re = re.compile(r'<th>\s*download\s+url', re.
|
|
17
|
+
_homepage_re = re.compile(r'<th>\s*home\s*page', re.IGNORECASE)
|
|
18
|
+
_download_re = re.compile(r'<th>\s*download\s+url', re.IGNORECASE)
|
|
19
19
|
# These aren't so awful:
|
|
20
|
-
_rel_re = re.compile(r"""<[^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*>""", re.
|
|
21
|
-
_href_re = re.compile('href=(?:"([^"]*)"|\'([^\']*)\'|([^>\\s\\n]*))', re.
|
|
22
|
-
_base_re = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.
|
|
20
|
+
_rel_re = re.compile(r"""<[^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*>""", re.IGNORECASE)
|
|
21
|
+
_href_re = re.compile('href=(?:"([^"]*)"|\'([^\']*)\'|([^>\\s\\n]*))', re.IGNORECASE | re.DOTALL)
|
|
22
|
+
_base_re = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.IGNORECASE)
|
|
23
23
|
|
|
24
24
|
def __init__(self, content, url, headers=None):
|
|
25
25
|
self.content = content
|
|
@@ -113,7 +113,7 @@ class HTMLPage(object):
|
|
|
113
113
|
url = self.clean_link(urljoin(self.base_url, url))
|
|
114
114
|
yield Link(url, self)
|
|
115
115
|
|
|
116
|
-
_clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.
|
|
116
|
+
_clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.IGNORECASE)
|
|
117
117
|
|
|
118
118
|
def clean_link(self, url):
|
|
119
119
|
"""Makes sure a link is fully encoded. That is, if a ' ' shows up in
|
|
@@ -81,9 +81,7 @@ def _select_simple_content_type(request):
|
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def is_simple_json(request):
|
|
84
|
-
|
|
85
|
-
return True
|
|
86
|
-
return False
|
|
84
|
+
return _select_simple_content_type(request) == SIMPLE_API_V1_JSON
|
|
87
85
|
|
|
88
86
|
|
|
89
87
|
def is_requested_by_installer(request):
|
|
@@ -449,9 +447,7 @@ def version_in_filename(version, filename):
|
|
|
449
447
|
if version in filename:
|
|
450
448
|
return True
|
|
451
449
|
# PEP 427 escaped wheels
|
|
452
|
-
|
|
453
|
-
return True
|
|
454
|
-
return False
|
|
450
|
+
return re.sub(r"[^\w\d.]+", "_", version, flags=re.UNICODE) in filename
|
|
455
451
|
|
|
456
452
|
|
|
457
453
|
class PyPIView:
|
|
@@ -596,9 +592,7 @@ class PyPIView:
|
|
|
596
592
|
def _use_absolute_urls(self):
|
|
597
593
|
if self.xom.config.args.absolute_urls:
|
|
598
594
|
return True
|
|
599
|
-
|
|
600
|
-
return True
|
|
601
|
-
return False
|
|
595
|
+
return 'HTTP_X_DEVPI_ABSOLUTE_URLS' in self.request.environ
|
|
602
596
|
|
|
603
597
|
@view_config(route_name="/{user}/{index}/+simple/{project}")
|
|
604
598
|
def simple_list_project_redirect(self):
|
|
@@ -786,18 +780,18 @@ class PyPIView:
|
|
|
786
780
|
# depending on remote networks
|
|
787
781
|
content_type = _select_simple_content_type(self.request)
|
|
788
782
|
if content_type == SIMPLE_API_V1_JSON:
|
|
789
|
-
app_iter = self._simple_list_all_json_v1(
|
|
783
|
+
app_iter = self._simple_list_all_json_v1(stage_results)
|
|
790
784
|
elif is_requested_by_installer(self.request):
|
|
791
|
-
app_iter = self._simple_list_all_installer(
|
|
785
|
+
app_iter = self._simple_list_all_installer(stage_results)
|
|
792
786
|
else:
|
|
793
|
-
app_iter = self._simple_list_all(stage, stage_results)
|
|
787
|
+
app_iter = self._simple_list_all(stage.name, stage_results)
|
|
794
788
|
return Response(
|
|
795
789
|
app_iter=buffered_iterator(app_iter),
|
|
796
790
|
content_type=content_type,
|
|
797
791
|
vary=set(["Accept", "User-Agent"]))
|
|
798
792
|
|
|
799
|
-
def _simple_list_all(self,
|
|
800
|
-
title = "
|
|
793
|
+
def _simple_list_all(self, stage_name, stage_results):
|
|
794
|
+
title = f"{stage_name}: simple list (including inherited indices)"
|
|
801
795
|
yield f"<!DOCTYPE html><html><head><title>{title}</title></head><body><h1>{title}</h1>".encode("utf-8")
|
|
802
796
|
last_index = len(stage_results) - 1
|
|
803
797
|
seen = set()
|
|
@@ -814,7 +808,7 @@ class PyPIView:
|
|
|
814
808
|
seen.add(name)
|
|
815
809
|
yield "</body></html>".encode("utf-8")
|
|
816
810
|
|
|
817
|
-
def _simple_list_all_installer(self,
|
|
811
|
+
def _simple_list_all_installer(self, stage_results):
|
|
818
812
|
yield "<!DOCTYPE html><html><body>".encode("utf-8")
|
|
819
813
|
last_index = len(stage_results) - 1
|
|
820
814
|
seen = set()
|
|
@@ -826,7 +820,7 @@ class PyPIView:
|
|
|
826
820
|
seen.add(name)
|
|
827
821
|
yield "</body></html>".encode("utf-8")
|
|
828
822
|
|
|
829
|
-
def _simple_list_all_json_v1(self,
|
|
823
|
+
def _simple_list_all_json_v1(self, stage_results):
|
|
830
824
|
yield '{"meta":{"api-version":"1.0"},"projects":['.encode("utf-8")
|
|
831
825
|
last_index = len(stage_results) - 1
|
|
832
826
|
seen = set()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.12.
|
|
3
|
+
Version: 6.12.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
|
|
@@ -27,25 +27,10 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
27
27
|
Classifier: Programming Language :: Python :: 3.10
|
|
28
28
|
Classifier: Programming Language :: Python :: 3.11
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
30
31
|
Requires-Python: >=3.7
|
|
31
32
|
License-File: LICENSE
|
|
32
33
|
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
|
|
49
34
|
|
|
50
35
|
=============================================================================
|
|
51
36
|
devpi-server: server for private package indexes and PyPI caching
|
|
@@ -119,6 +104,20 @@ Changelog
|
|
|
119
104
|
|
|
120
105
|
.. towncrier release notes start
|
|
121
106
|
|
|
107
|
+
6.12.1 (2024-07-24)
|
|
108
|
+
===================
|
|
109
|
+
|
|
110
|
+
Bug Fixes
|
|
111
|
+
---------
|
|
112
|
+
|
|
113
|
+
- Support Python 3.13 by depending on legacy-cgi.
|
|
114
|
+
|
|
115
|
+
- Preserve query string when proxying requests from replica to primary. This fixes force removal on non-volatile indexes and probably other bugs.
|
|
116
|
+
|
|
117
|
+
- Fix #1044: Correctly update cache expiry time when mirrored server returns 304 Not Modified.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
122
121
|
6.12.0 (2024-06-25)
|
|
123
122
|
===================
|
|
124
123
|
|
|
@@ -188,14 +187,3 @@ Bug Fixes
|
|
|
188
187
|
|
|
189
188
|
- Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
|
|
190
189
|
|
|
191
|
-
|
|
192
|
-
6.9.1 (2023-07-02)
|
|
193
|
-
==================
|
|
194
|
-
|
|
195
|
-
Bug Fixes
|
|
196
|
-
---------
|
|
197
|
-
|
|
198
|
-
- Prevent error in find_pre_existing_file in case of incomplete metadata.
|
|
199
|
-
|
|
200
|
-
- Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
|
|
201
|
-
|
|
@@ -29,10 +29,11 @@ if __name__ == "__main__":
|
|
|
29
29
|
install_requires = ["py>=1.4.23",
|
|
30
30
|
"httpx",
|
|
31
31
|
"argon2-cffi",
|
|
32
|
-
"attrs>=
|
|
32
|
+
"attrs>=22.2.0",
|
|
33
33
|
"defusedxml",
|
|
34
34
|
"devpi_common<5,>3.6.0",
|
|
35
35
|
"itsdangerous>=0.24",
|
|
36
|
+
"legacy-cgi;python_version>='3.13'",
|
|
36
37
|
"platformdirs",
|
|
37
38
|
"pyramid>=2",
|
|
38
39
|
"waitress>=1.0.1",
|
|
@@ -57,7 +58,7 @@ if __name__ == "__main__":
|
|
|
57
58
|
'Documentation': 'https://doc.devpi.net',
|
|
58
59
|
'Source Code': 'https://github.com/devpi/devpi'
|
|
59
60
|
},
|
|
60
|
-
version='6.12.
|
|
61
|
+
version='6.12.1',
|
|
61
62
|
maintainer="Florian Schulze",
|
|
62
63
|
maintainer_email="mail@pyfidelity.com",
|
|
63
64
|
packages=[
|
|
@@ -82,7 +83,7 @@ if __name__ == "__main__":
|
|
|
82
83
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
83
84
|
] + [
|
|
84
85
|
("Programming Language :: Python :: %s" % x)
|
|
85
|
-
for x in "3.7 3.8 3.9 3.10 3.11 3.12".split()],
|
|
86
|
+
for x in "3.7 3.8 3.9 3.10 3.11 3.12 3.13".split()],
|
|
86
87
|
install_requires=install_requires,
|
|
87
88
|
extras_require=extras_require,
|
|
88
89
|
python_requires='>=3.7',
|
|
@@ -349,6 +349,18 @@ class TestIndexPushThings:
|
|
|
349
349
|
|
|
350
350
|
@pytest.mark.nomocking
|
|
351
351
|
class TestProjectThings:
|
|
352
|
+
def test_non_volatile_force_delete(self, mapp, server_version):
|
|
353
|
+
force_delete_fix_version = parse_version("6.12.1dev")
|
|
354
|
+
if server_version < force_delete_fix_version:
|
|
355
|
+
pytest.skip("devpi-server without force delete via replica fix")
|
|
356
|
+
mapp.create_and_login_user("cuser3")
|
|
357
|
+
mapp.create_index("dev", indexconfig={"volatile": False})
|
|
358
|
+
mapp.use("cuser3/dev")
|
|
359
|
+
content = mapp.makepkg("hello-1.0.tar.gz", b"content", "hello", "1.0")
|
|
360
|
+
mapp.upload_file_pypi("hello-1.0.tar.gz", content, "hello", "1.0")
|
|
361
|
+
mapp.delete_project("hello", code=403)
|
|
362
|
+
mapp.delete_project("hello", force=True)
|
|
363
|
+
|
|
352
364
|
def test_toxresult(self, mapp):
|
|
353
365
|
import json
|
|
354
366
|
mapp.create_and_use('pruser1/dev')
|
|
@@ -364,22 +364,21 @@ def httpget(pypiurls):
|
|
|
364
364
|
self._md5 = hashlib.md5() # noqa: S324
|
|
365
365
|
self.call_log = []
|
|
366
366
|
|
|
367
|
-
async def async_httpget(self, url, allow_redirects, timeout=None, extra_headers=None):
|
|
368
|
-
response = self.__call__(url, allow_redirects, extra_headers, timeout=timeout)
|
|
369
|
-
if response.status_code < 300
|
|
370
|
-
text = response.text
|
|
371
|
-
else:
|
|
372
|
-
text = None
|
|
367
|
+
async def async_httpget(self, url, *, allow_redirects, timeout=None, extra_headers=None):
|
|
368
|
+
response = self.__call__(url, allow_redirects=allow_redirects, extra_headers=extra_headers, timeout=timeout)
|
|
369
|
+
text = response.text if response.status_code < 300 else None
|
|
373
370
|
return (response, text)
|
|
374
371
|
|
|
375
|
-
def __call__(self, url, allow_redirects=False, extra_headers=None, **kw):
|
|
372
|
+
def __call__(self, url, *, allow_redirects=False, extra_headers=None, **kw):
|
|
376
373
|
class mockresponse:
|
|
377
374
|
def __init__(xself, url):
|
|
378
375
|
fakeresponse = self.url2response.get(url)
|
|
376
|
+
from_list = False
|
|
379
377
|
if isinstance(fakeresponse, list):
|
|
380
378
|
if not fakeresponse:
|
|
381
379
|
pytest.fail(
|
|
382
380
|
f"http_api call to {url} has no further replies")
|
|
381
|
+
from_list = True
|
|
383
382
|
fakeresponse = fakeresponse.pop(0)
|
|
384
383
|
if fakeresponse is None:
|
|
385
384
|
fakeresponse = dict(
|
|
@@ -396,6 +395,13 @@ def httpget(pypiurls):
|
|
|
396
395
|
xself.headers.setdefault('content-type', fakeresponse.get(
|
|
397
396
|
'content_type', 'text/html'))
|
|
398
397
|
if "etag" in fakeresponse:
|
|
398
|
+
# add follow up response
|
|
399
|
+
new_response = dict(fakeresponse, status_code=304)
|
|
400
|
+
new_response.pop("etag")
|
|
401
|
+
if from_list:
|
|
402
|
+
self.url2response[url].append(new_response)
|
|
403
|
+
else:
|
|
404
|
+
self.url2response[url] = new_response
|
|
399
405
|
fakeresponse["headers"]["ETag"] = fakeresponse["etag"]
|
|
400
406
|
|
|
401
407
|
def close(xself):
|
|
@@ -455,12 +461,7 @@ def httpget(pypiurls):
|
|
|
455
461
|
if pypiserial is not None:
|
|
456
462
|
headers["X-PYPI-LAST-SERIAL"] = str(pypiserial)
|
|
457
463
|
kw.setdefault("url", URL(remoteurl).joinpath(name).asdir().url)
|
|
458
|
-
|
|
459
|
-
etag = kw.pop("etag")
|
|
460
|
-
self.add(text=text, **kw, etag=etag)
|
|
461
|
-
self.add(text=text, **kw, status_code=304)
|
|
462
|
-
else:
|
|
463
|
-
self.mockresponse(text=text, **kw)
|
|
464
|
+
self.mockresponse(text=text, **kw)
|
|
464
465
|
return ret
|
|
465
466
|
|
|
466
467
|
def _getmd5digest(self, s):
|
|
@@ -665,21 +666,14 @@ class Mapp(MappMixin):
|
|
|
665
666
|
assert res.get("email") == email
|
|
666
667
|
|
|
667
668
|
def modify_user(self, user, code=200, password=None, **kwargs):
|
|
668
|
-
reqdict =
|
|
669
|
+
reqdict = dict(kwargs)
|
|
669
670
|
if password:
|
|
670
671
|
reqdict["password"] = password
|
|
671
|
-
for key, value in kwargs.items():
|
|
672
|
-
reqdict[key] = value
|
|
673
672
|
r = self.testapp.patch_json("/%s" % user, reqdict, expect_errors=True)
|
|
674
673
|
assert r.status_code == code
|
|
675
674
|
if code == 200:
|
|
676
675
|
assert r.json == dict(message="user updated")
|
|
677
676
|
|
|
678
|
-
def create_user_fails(self, user, password, email="hello@example.com"):
|
|
679
|
-
with pytest.raises(webtest.AppError) as excinfo:
|
|
680
|
-
self.create_user(user, password)
|
|
681
|
-
assert "409" in excinfo.value.args[0]
|
|
682
|
-
|
|
683
677
|
def create_and_login_user(self, user="someuser", password="123"): # noqa: S107
|
|
684
678
|
self.create_user(user, password)
|
|
685
679
|
self.login(user, password)
|
|
@@ -796,11 +790,15 @@ class Mapp(MappMixin):
|
|
|
796
790
|
r = self.testapp.get_json("/%s" % indexname)
|
|
797
791
|
return r.json["result"]["mirror_whitelist"]
|
|
798
792
|
|
|
799
|
-
def delete_project(
|
|
800
|
-
|
|
793
|
+
def delete_project(
|
|
794
|
+
self, project, *,
|
|
795
|
+
code=200, indexname=None, force=False, waithooks=False,
|
|
796
|
+
):
|
|
801
797
|
indexname = self._getindexname(indexname)
|
|
802
|
-
|
|
803
|
-
|
|
798
|
+
path = f"/{indexname}/{project}"
|
|
799
|
+
if force:
|
|
800
|
+
path = f"{path}?force"
|
|
801
|
+
r = self.testapp.delete_json(path, {}, expect_errors=True)
|
|
804
802
|
assert r.status_code == code
|
|
805
803
|
if waithooks:
|
|
806
804
|
self._wait_for_serial_in_result(r)
|
|
@@ -1326,22 +1324,6 @@ def simpypi(simpypiserver):
|
|
|
1326
1324
|
return simpypiserver.simpypi
|
|
1327
1325
|
|
|
1328
1326
|
|
|
1329
|
-
# incremental testing
|
|
1330
|
-
|
|
1331
|
-
def pytest_runtest_makereport(item, call):
|
|
1332
|
-
if "incremental" in item.keywords:
|
|
1333
|
-
if call.excinfo is not None:
|
|
1334
|
-
parent = item.parent
|
|
1335
|
-
parent._previousfailed = item
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
def pytest_runtest_setup(item):
|
|
1339
|
-
if "incremental" in item.keywords:
|
|
1340
|
-
previousfailed = getattr(item.parent, "_previousfailed", None)
|
|
1341
|
-
if previousfailed is not None:
|
|
1342
|
-
pytest.xfail("previous test failed (%s)" %previousfailed.name)
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
1327
|
@pytest.fixture
|
|
1346
1328
|
def gen():
|
|
1347
1329
|
return Gen()
|
|
@@ -72,10 +72,10 @@ def test_execnet_opcodes():
|
|
|
72
72
|
def test_loads(data, expected):
|
|
73
73
|
result = loads(data)
|
|
74
74
|
assert result == expected
|
|
75
|
-
assert type(result)
|
|
75
|
+
assert type(result) is type(expected)
|
|
76
76
|
result = _loads(data)
|
|
77
77
|
assert result == expected
|
|
78
|
-
assert type(result)
|
|
78
|
+
assert type(result) is type(expected)
|
|
79
79
|
# try round-trip
|
|
80
80
|
dump = dumps(expected)
|
|
81
81
|
assert len(dump) == dumplen(expected)
|