devpi-server 6.18.0__tar.gz → 6.19.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.18.0 → devpi_server-6.19.1}/CHANGELOG +39 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/CHANGELOG.short.rst +39 -27
- {devpi_server-6.18.0 → devpi_server-6.19.1}/PKG-INFO +42 -28
- devpi_server-6.19.1/devpi_server/__init__.py +1 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/auth.py +23 -11
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/config.py +58 -18
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/exceptions.py +10 -4
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/hookspecs.py +8 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/httpclient.py +2 -1
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/mirror.py +0 -1
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/model.py +9 -9
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/replica.py +116 -30
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/view_auth.py +5 -1
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/views.py +42 -5
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server.egg-info/PKG-INFO +42 -28
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server.egg-info/requires.txt +1 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/pyproject.toml +15 -0
- devpi_server-6.19.1/test_devpi_server/__init__.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/functional.py +2 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/plugin.py +11 -11
- devpi_server-6.19.1/test_devpi_server/py.typed +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_auth.py +49 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_config.py +21 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_replica.py +122 -3
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_replica_functional.py +10 -2
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_stage_customizer.py +0 -11
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_views.py +4 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_views_patch.py +55 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_views_status.py +6 -2
- {devpi_server-6.18.0 → devpi_server-6.19.1}/tox.ini +1 -1
- devpi_server-6.18.0/devpi_server/__init__.py +0 -1
- devpi_server-6.18.0/devpi_server/py.typed +0 -1
- devpi_server-6.18.0/test_devpi_server/py.typed +0 -1
- {devpi_server-6.18.0 → devpi_server-6.19.1}/.flake8 +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/LICENSE +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/MANIFEST.in +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/README.rst +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/__main__.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/auth_basic.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/auth_devpi.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/__init__.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/crontab.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/compat.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/filestore.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/filestore_db.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/filestore_fs.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/filestore_fs_base.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/filestore_hash_hl.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/fileutil.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/fsck.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/genconfig.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/htmlpage.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/importexport.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/init.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/interfaces.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/keyfs.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/keyfs_sqlite.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/keyfs_sqlite_fs.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/keyfs_types.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/log.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/main.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/markers.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/middleware.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/mythread.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/normalized.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/passwd.py +0 -0
- /devpi_server-6.18.0/test_devpi_server/__init__.py → /devpi_server-6.19.1/devpi_server/py.typed +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/readonly.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server/sizeof.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server.egg-info/SOURCES.txt +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/mypy.ini +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/pytest_devpi_server/__init__.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/setup.cfg +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/conftest.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/example.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/reqmock.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/simpypi.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_authcheck.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_conftest.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_filestore.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_filestore_fs.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_fileutil.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_fsck.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_genconfig.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_importexport.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_keyfs.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_log.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_main.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_mirror.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_model.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_mythread.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_nginx.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_nginx_replica.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_permissions.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_readonly.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_streaming.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_streaming_nginx.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_streaming_replica.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_streaming_replica_nginx.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_view_auth.py +0 -0
- {devpi_server-6.18.0 → devpi_server-6.19.1}/test_devpi_server/test_views_push_external.py +0 -0
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.19.1 (2026-02-09)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Bug Fixes
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- Pin setuptools as pyramid still requires pkg_resources.
|
|
12
|
+
|
|
13
|
+
- Always allow replicas to access deleted releases to get the proper ``410 Gone`` instead of ``403 Forbidden`` when ``devpi-lockdown`` is in use.
|
|
14
|
+
|
|
15
|
+
6.19.0 (2026-02-06)
|
|
16
|
+
===================
|
|
17
|
+
|
|
18
|
+
Features
|
|
19
|
+
--------
|
|
20
|
+
|
|
21
|
+
- Add ``--autocreate-users`` server option.
|
|
22
|
+
Automatically creates users that don't exist in devpi, but have successfully authenticated via an authentication plugin.
|
|
23
|
+
A typical example of when to enable this would be when authenticating via an LDAP directory.
|
|
24
|
+
Automatically created users do not have passwords, and have no password hash to prevent local authentication.
|
|
25
|
+
|
|
26
|
+
- Add ``replica-files-in-sync-at``, ``replica-init-queue-finished-at`` and ``replica-metadata-in-sync-at`` to status view, the existing ``replica-in-sync-at`` is now a combination of all three instead of just metadata.
|
|
27
|
+
|
|
28
|
+
- Warn when an unknown option is found in config file to detect typos. Be aware that some commands don't use all the options, that is why this only warns instead of exiting.
|
|
29
|
+
|
|
30
|
+
- Add new ``devpiserver_user_created`` hook which can be used to create default indexes or other setup for newly created users.
|
|
31
|
+
|
|
32
|
+
Bug Fixes
|
|
33
|
+
---------
|
|
34
|
+
|
|
35
|
+
- Fix ``+status`` json encoding errors by making sure the ``FatalResponse.url`` attribute is a string.
|
|
36
|
+
|
|
37
|
+
- Ignore existing unknown index options from uninstalled plugins when patching other options with ``+=`` and ``-=``.
|
|
38
|
+
|
|
39
|
+
- Fix removal with ``-=`` of index options with default values from ``devpiserver_indexconfig_defaults`` hooks.
|
|
40
|
+
|
|
41
|
+
- Fix #1110: a list for the ``listen`` option in a config file stopped working in 6.18.0.
|
|
42
|
+
|
|
43
|
+
|
|
5
44
|
6.18.0 (2026-01-27)
|
|
6
45
|
===================
|
|
7
46
|
|
|
@@ -9,6 +9,45 @@ Changelog
|
|
|
9
9
|
|
|
10
10
|
.. towncrier release notes start
|
|
11
11
|
|
|
12
|
+
6.19.1 (2026-02-09)
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Bug Fixes
|
|
16
|
+
---------
|
|
17
|
+
|
|
18
|
+
- Pin setuptools as pyramid still requires pkg_resources.
|
|
19
|
+
|
|
20
|
+
- Always allow replicas to access deleted releases to get the proper ``410 Gone`` instead of ``403 Forbidden`` when ``devpi-lockdown`` is in use.
|
|
21
|
+
|
|
22
|
+
6.19.0 (2026-02-06)
|
|
23
|
+
===================
|
|
24
|
+
|
|
25
|
+
Features
|
|
26
|
+
--------
|
|
27
|
+
|
|
28
|
+
- Add ``--autocreate-users`` server option.
|
|
29
|
+
Automatically creates users that don't exist in devpi, but have successfully authenticated via an authentication plugin.
|
|
30
|
+
A typical example of when to enable this would be when authenticating via an LDAP directory.
|
|
31
|
+
Automatically created users do not have passwords, and have no password hash to prevent local authentication.
|
|
32
|
+
|
|
33
|
+
- Add ``replica-files-in-sync-at``, ``replica-init-queue-finished-at`` and ``replica-metadata-in-sync-at`` to status view, the existing ``replica-in-sync-at`` is now a combination of all three instead of just metadata.
|
|
34
|
+
|
|
35
|
+
- Warn when an unknown option is found in config file to detect typos. Be aware that some commands don't use all the options, that is why this only warns instead of exiting.
|
|
36
|
+
|
|
37
|
+
- Add new ``devpiserver_user_created`` hook which can be used to create default indexes or other setup for newly created users.
|
|
38
|
+
|
|
39
|
+
Bug Fixes
|
|
40
|
+
---------
|
|
41
|
+
|
|
42
|
+
- Fix ``+status`` json encoding errors by making sure the ``FatalResponse.url`` attribute is a string.
|
|
43
|
+
|
|
44
|
+
- Ignore existing unknown index options from uninstalled plugins when patching other options with ``+=`` and ``-=``.
|
|
45
|
+
|
|
46
|
+
- Fix removal with ``-=`` of index options with default values from ``devpiserver_indexconfig_defaults`` hooks.
|
|
47
|
+
|
|
48
|
+
- Fix #1110: a list for the ``listen`` option in a config file stopped working in 6.18.0.
|
|
49
|
+
|
|
50
|
+
|
|
12
51
|
6.18.0 (2026-01-27)
|
|
13
52
|
===================
|
|
14
53
|
|
|
@@ -108,30 +147,3 @@ Bug Fixes
|
|
|
108
147
|
|
|
109
148
|
- Keep original metadata_version in database.
|
|
110
149
|
|
|
111
|
-
|
|
112
|
-
6.15.0 (2025-05-18)
|
|
113
|
-
===================
|
|
114
|
-
|
|
115
|
-
Features
|
|
116
|
-
--------
|
|
117
|
-
|
|
118
|
-
- Add ``--connection-limit`` option to devpi-server passed on to waitress.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
6.14.0 (2024-10-16)
|
|
122
|
-
===================
|
|
123
|
-
|
|
124
|
-
Features
|
|
125
|
-
--------
|
|
126
|
-
|
|
127
|
-
- Allow pushing of versions which only have documentation and no releases.
|
|
128
|
-
|
|
129
|
-
- Allow pushing of release files only with no documentation. Requires devpi-client 7.2.0.
|
|
130
|
-
|
|
131
|
-
- Allow pushing of documentation only with no release files. Requires devpi-client 7.2.0.
|
|
132
|
-
|
|
133
|
-
Bug Fixes
|
|
134
|
-
---------
|
|
135
|
-
|
|
136
|
-
- No longer automatically "register" a project when pushing releases to PyPI. The reply changed from HTTP status 410 to 400 breaking the upload. With devpi-client 7.2.0 there is a ``--register-project`` option if it is still required for some other package registry.
|
|
137
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.19.1
|
|
4
4
|
Summary: devpi-server: reliable, private, and pypi.org caching server
|
|
5
5
|
Maintainer-email: Florian Schulze <mail@pyfidelity.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
26
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
26
27
|
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
27
28
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
@@ -42,6 +43,7 @@ Requires-Dist: pluggy<2.0,>=0.6.0
|
|
|
42
43
|
Requires-Dist: py>=1.4.23
|
|
43
44
|
Requires-Dist: pyramid>=2
|
|
44
45
|
Requires-Dist: repoze.lru>=0.6
|
|
46
|
+
Requires-Dist: setuptools<=81
|
|
45
47
|
Requires-Dist: strenum; python_version < "3.11"
|
|
46
48
|
Requires-Dist: strictyaml
|
|
47
49
|
Requires-Dist: waitress>=1.0.1
|
|
@@ -122,6 +124,45 @@ Changelog
|
|
|
122
124
|
|
|
123
125
|
.. towncrier release notes start
|
|
124
126
|
|
|
127
|
+
6.19.1 (2026-02-09)
|
|
128
|
+
===================
|
|
129
|
+
|
|
130
|
+
Bug Fixes
|
|
131
|
+
---------
|
|
132
|
+
|
|
133
|
+
- Pin setuptools as pyramid still requires pkg_resources.
|
|
134
|
+
|
|
135
|
+
- Always allow replicas to access deleted releases to get the proper ``410 Gone`` instead of ``403 Forbidden`` when ``devpi-lockdown`` is in use.
|
|
136
|
+
|
|
137
|
+
6.19.0 (2026-02-06)
|
|
138
|
+
===================
|
|
139
|
+
|
|
140
|
+
Features
|
|
141
|
+
--------
|
|
142
|
+
|
|
143
|
+
- Add ``--autocreate-users`` server option.
|
|
144
|
+
Automatically creates users that don't exist in devpi, but have successfully authenticated via an authentication plugin.
|
|
145
|
+
A typical example of when to enable this would be when authenticating via an LDAP directory.
|
|
146
|
+
Automatically created users do not have passwords, and have no password hash to prevent local authentication.
|
|
147
|
+
|
|
148
|
+
- Add ``replica-files-in-sync-at``, ``replica-init-queue-finished-at`` and ``replica-metadata-in-sync-at`` to status view, the existing ``replica-in-sync-at`` is now a combination of all three instead of just metadata.
|
|
149
|
+
|
|
150
|
+
- Warn when an unknown option is found in config file to detect typos. Be aware that some commands don't use all the options, that is why this only warns instead of exiting.
|
|
151
|
+
|
|
152
|
+
- Add new ``devpiserver_user_created`` hook which can be used to create default indexes or other setup for newly created users.
|
|
153
|
+
|
|
154
|
+
Bug Fixes
|
|
155
|
+
---------
|
|
156
|
+
|
|
157
|
+
- Fix ``+status`` json encoding errors by making sure the ``FatalResponse.url`` attribute is a string.
|
|
158
|
+
|
|
159
|
+
- Ignore existing unknown index options from uninstalled plugins when patching other options with ``+=`` and ``-=``.
|
|
160
|
+
|
|
161
|
+
- Fix removal with ``-=`` of index options with default values from ``devpiserver_indexconfig_defaults`` hooks.
|
|
162
|
+
|
|
163
|
+
- Fix #1110: a list for the ``listen`` option in a config file stopped working in 6.18.0.
|
|
164
|
+
|
|
165
|
+
|
|
125
166
|
6.18.0 (2026-01-27)
|
|
126
167
|
===================
|
|
127
168
|
|
|
@@ -221,30 +262,3 @@ Bug Fixes
|
|
|
221
262
|
|
|
222
263
|
- Keep original metadata_version in database.
|
|
223
264
|
|
|
224
|
-
|
|
225
|
-
6.15.0 (2025-05-18)
|
|
226
|
-
===================
|
|
227
|
-
|
|
228
|
-
Features
|
|
229
|
-
--------
|
|
230
|
-
|
|
231
|
-
- Add ``--connection-limit`` option to devpi-server passed on to waitress.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
6.14.0 (2024-10-16)
|
|
235
|
-
===================
|
|
236
|
-
|
|
237
|
-
Features
|
|
238
|
-
--------
|
|
239
|
-
|
|
240
|
-
- Allow pushing of versions which only have documentation and no releases.
|
|
241
|
-
|
|
242
|
-
- Allow pushing of release files only with no documentation. Requires devpi-client 7.2.0.
|
|
243
|
-
|
|
244
|
-
- Allow pushing of documentation only with no release files. Requires devpi-client 7.2.0.
|
|
245
|
-
|
|
246
|
-
Bug Fixes
|
|
247
|
-
---------
|
|
248
|
-
|
|
249
|
-
- No longer automatically "register" a project when pushing releases to PyPI. The reply changed from HTTP status 410 to 400 breaking the upload. With devpi-client 7.2.0 there is a ``--register-project`` option if it is still required for some other package registry.
|
|
250
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "6.19.1"
|
|
@@ -24,6 +24,7 @@ class Auth:
|
|
|
24
24
|
self.serializer = itsdangerous.TimedSerializer(secret)
|
|
25
25
|
self.hook = xom.config.hook.devpiserver_auth_request
|
|
26
26
|
self.legacy_hook = xom.config.hook.devpiserver_auth_user
|
|
27
|
+
self.autocreate_users = xom.config.autocreate_users
|
|
27
28
|
|
|
28
29
|
@property
|
|
29
30
|
def model(self):
|
|
@@ -55,18 +56,27 @@ class Auth:
|
|
|
55
56
|
return dict(status="ok", groups=sorted(
|
|
56
57
|
set(itertools.chain.from_iterable(groups))))
|
|
57
58
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
def _autocreate_user(self, authuser):
|
|
60
|
+
if self.model.get_user(authuser) is None:
|
|
61
|
+
# Autocreated users will be authenticated via plugin, and so no
|
|
62
|
+
# valid password should be stored.
|
|
63
|
+
self.model.create_user(authuser, password=None)
|
|
64
|
+
threadlog.debug("automatically created user %r", authuser)
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
def _validate(self, authuser, authpassword, request=None):
|
|
67
|
+
"""Validates user credentials.
|
|
68
|
+
|
|
69
|
+
If authentication plugins are installed, they will be queried.
|
|
70
|
+
|
|
71
|
+
If no user can be found, returns None.
|
|
72
|
+
If the credentials are wrong, returns False.
|
|
73
|
+
On success a list of group names the user is member of will be
|
|
74
|
+
returned. If the plugins don't return any groups, then the list
|
|
75
|
+
will be empty.
|
|
76
|
+
If autocreate-users is enabled, the new user is created, if it
|
|
77
|
+
doesn't already exist.
|
|
78
|
+
The 'root' user is always authenticated with the devpi-server
|
|
79
|
+
credentials, never by plugins.
|
|
70
80
|
"""
|
|
71
81
|
user = self.model.get_user(authuser)
|
|
72
82
|
is_root = authuser == 'root'
|
|
@@ -77,6 +87,8 @@ class Auth:
|
|
|
77
87
|
if result is not None:
|
|
78
88
|
if result["status"] != "ok":
|
|
79
89
|
return dict(status="reject")
|
|
90
|
+
if self.autocreate_users:
|
|
91
|
+
self._autocreate_user(authuser)
|
|
80
92
|
# plugins may never return from_user_object
|
|
81
93
|
result.pop('from_user_object', None)
|
|
82
94
|
return result
|
|
@@ -6,7 +6,6 @@ from .interfaces import IIOFileFactory
|
|
|
6
6
|
from .log import threadlog
|
|
7
7
|
from devpi_common.types import cached_property
|
|
8
8
|
from devpi_common.url import URL
|
|
9
|
-
from functools import partial
|
|
10
9
|
from operator import itemgetter
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
from pluggy import HookimplMarker
|
|
@@ -29,6 +28,8 @@ import warnings
|
|
|
29
28
|
if TYPE_CHECKING:
|
|
30
29
|
from .interfaces import IStorageConnection4
|
|
31
30
|
from collections.abc import Callable
|
|
31
|
+
from collections.abc import MutableMapping
|
|
32
|
+
from typing import Any
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
log = threadlog
|
|
@@ -403,6 +404,20 @@ def add_permission_options(parser, pluginmanager):
|
|
|
403
404
|
"explicitly if wanted.")
|
|
404
405
|
|
|
405
406
|
|
|
407
|
+
def add_autocreate_users_options(
|
|
408
|
+
parser,
|
|
409
|
+
pluginmanager, # noqa: ARG001 - call convention
|
|
410
|
+
):
|
|
411
|
+
parser.addoption(
|
|
412
|
+
"--autocreate-users",
|
|
413
|
+
action="store_true",
|
|
414
|
+
default=False,
|
|
415
|
+
help="when using an authentication plugin, automatically create new "
|
|
416
|
+
"users that authenticate, but don't already exist. This is "
|
|
417
|
+
"helpful for deployments making use of LDAP authentication.",
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
406
421
|
def addoptions(parser, pluginmanager):
|
|
407
422
|
add_help_option(parser, pluginmanager)
|
|
408
423
|
add_configfile_option(parser, pluginmanager)
|
|
@@ -441,6 +456,9 @@ def addoptions(parser, pluginmanager):
|
|
|
441
456
|
add_permission_options(
|
|
442
457
|
parser.addgroup("permission options"),
|
|
443
458
|
pluginmanager)
|
|
459
|
+
add_autocreate_users_options(
|
|
460
|
+
parser.addgroup("autocreate users options"), pluginmanager
|
|
461
|
+
)
|
|
444
462
|
|
|
445
463
|
|
|
446
464
|
def try_argcomplete(parser):
|
|
@@ -514,21 +532,31 @@ def load_config_file(config_file):
|
|
|
514
532
|
return config.data
|
|
515
533
|
|
|
516
534
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
535
|
+
class ArgumentDefaultGetter:
|
|
536
|
+
used_config_options: set[str]
|
|
537
|
+
|
|
538
|
+
def __init__(
|
|
539
|
+
self, *, config_options: dict[str, Any], environ: MutableMapping[str, Any]
|
|
540
|
+
):
|
|
541
|
+
self.config_options = config_options
|
|
542
|
+
self.environ = environ
|
|
543
|
+
self.used_config_options = set()
|
|
544
|
+
|
|
545
|
+
def __call__(self, name: str | None) -> Any | None:
|
|
546
|
+
if name is None:
|
|
547
|
+
return None
|
|
548
|
+
if name == "serverdir" and "DEVPI_SERVERDIR" in self.environ:
|
|
522
549
|
log.warning(
|
|
523
550
|
"Using deprecated DEVPI_SERVERDIR environment variable. "
|
|
524
551
|
"You should switch to use DEVPISERVER_SERVERDIR.")
|
|
525
|
-
return environ["DEVPI_SERVERDIR"]
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
552
|
+
return self.environ["DEVPI_SERVERDIR"]
|
|
553
|
+
envname = f"DEVPISERVER_{name.replace('-', '_').upper()}"
|
|
554
|
+
if envname in self.environ:
|
|
555
|
+
value = self.environ[envname]
|
|
556
|
+
if value:
|
|
557
|
+
return value
|
|
558
|
+
self.used_config_options.add(name)
|
|
559
|
+
return self.config_options[name]
|
|
532
560
|
|
|
533
561
|
|
|
534
562
|
def parseoptions(pluginmanager, argv, parser=None):
|
|
@@ -553,10 +581,9 @@ def parseoptions(pluginmanager, argv, parser=None):
|
|
|
553
581
|
"Error in config file '%s':\n %s", config_file, e
|
|
554
582
|
)
|
|
555
583
|
sys.exit(4)
|
|
556
|
-
defaultget =
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
environ=os.environ)
|
|
584
|
+
defaultget = ArgumentDefaultGetter(
|
|
585
|
+
config_options=config_options, environ=os.environ
|
|
586
|
+
)
|
|
560
587
|
parser.post_process_actions(defaultget=defaultget)
|
|
561
588
|
# the getattr is a workaround for a problem with 3.14t-dev
|
|
562
589
|
# weirdly enough it does not happen with 3.14-dev
|
|
@@ -564,6 +591,11 @@ def parseoptions(pluginmanager, argv, parser=None):
|
|
|
564
591
|
parser.print_help()
|
|
565
592
|
parser.exit()
|
|
566
593
|
args = parser.parse_args(argv[1:])
|
|
594
|
+
if unknown_keys := set(config_options).difference(defaultget.used_config_options):
|
|
595
|
+
exec_name = Path(argv[0]).name or "unknown"
|
|
596
|
+
for unknown_key in unknown_keys:
|
|
597
|
+
msg = f"Unknown {exec_name} option {unknown_key!r} in {config_file}"
|
|
598
|
+
log.warning(msg)
|
|
567
599
|
config = Config(args, pluginmanager=pluginmanager)
|
|
568
600
|
return config
|
|
569
601
|
|
|
@@ -609,7 +641,11 @@ class MyArgumentParser(argparse.ArgumentParser):
|
|
|
609
641
|
except KeyError:
|
|
610
642
|
pass
|
|
611
643
|
else:
|
|
612
|
-
if isinstance(action, argparse.
|
|
644
|
+
if isinstance(action, argparse._AppendAction) and isinstance(
|
|
645
|
+
default, list
|
|
646
|
+
):
|
|
647
|
+
pass
|
|
648
|
+
elif isinstance(action, argparse._StoreTrueAction):
|
|
613
649
|
default = bool(strtobool(default))
|
|
614
650
|
elif isinstance(action, argparse._StoreFalseAction):
|
|
615
651
|
default = not bool(strtobool(default))
|
|
@@ -987,6 +1023,10 @@ class Config:
|
|
|
987
1023
|
rm = frozenset(x.strip() for x in rm.split(','))
|
|
988
1024
|
return rm
|
|
989
1025
|
|
|
1026
|
+
@property
|
|
1027
|
+
def autocreate_users(self):
|
|
1028
|
+
return getattr(self.args, "autocreate_users", False)
|
|
1029
|
+
|
|
990
1030
|
@property
|
|
991
1031
|
def wait_for_events(self):
|
|
992
1032
|
return getattr(self.args, 'wait_for_events', False)
|
|
@@ -8,8 +8,7 @@ class LazyExceptionFormatter:
|
|
|
8
8
|
self.e = e
|
|
9
9
|
|
|
10
10
|
def __str__(self):
|
|
11
|
-
return
|
|
12
|
-
self.e.__class__, self.e, self.e.__traceback__)).strip()
|
|
11
|
+
return format_exception(self.e)
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class LazyExceptionOnlyFormatter:
|
|
@@ -19,8 +18,15 @@ class LazyExceptionOnlyFormatter:
|
|
|
19
18
|
self.e = e
|
|
20
19
|
|
|
21
20
|
def __str__(self):
|
|
22
|
-
return
|
|
23
|
-
|
|
21
|
+
return format_exception_only(self.e)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def format_exception(e):
|
|
25
|
+
return "".join(traceback.format_exception(e.__class__, e, e.__traceback__)).strip()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def format_exception_only(e):
|
|
29
|
+
return "".join(traceback.format_exception_only(e.__class__, e)).strip()
|
|
24
30
|
|
|
25
31
|
|
|
26
32
|
def lazy_format_exception(e):
|
|
@@ -98,6 +98,14 @@ def devpiserver_mirror_initialnames(stage, projectnames):
|
|
|
98
98
|
"""
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
@hookspec
|
|
102
|
+
def devpiserver_user_created(user):
|
|
103
|
+
"""called when a user was successfully created.
|
|
104
|
+
|
|
105
|
+
- user is an instance of model.User
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
101
109
|
@hookspec
|
|
102
110
|
def devpiserver_stage_created(stage):
|
|
103
111
|
""" called when a stage was successfully created. (both for replica and a primary)
|
|
@@ -41,9 +41,10 @@ def get_caller_location(stacklevel: int = 2) -> str:
|
|
|
41
41
|
|
|
42
42
|
class FatalResponse:
|
|
43
43
|
status_code = -1
|
|
44
|
+
url: str
|
|
44
45
|
|
|
45
46
|
def __init__(self, url: URL | str, reason: str):
|
|
46
|
-
self.url = url
|
|
47
|
+
self.url = str(url)
|
|
47
48
|
self.reason_phrase = reason
|
|
48
49
|
self.status = self.status_code
|
|
49
50
|
|
|
@@ -740,7 +740,6 @@ class MirrorStage(BaseStage):
|
|
|
740
740
|
serial: int,
|
|
741
741
|
etag: str | None,
|
|
742
742
|
) -> None:
|
|
743
|
-
assert links != () # we don't store the old "Not Found" marker anymore
|
|
744
743
|
assert isinstance(serial, int)
|
|
745
744
|
assert project == normalize_name(project), project
|
|
746
745
|
data: CacheLinks = {
|
|
@@ -187,6 +187,8 @@ class RootModel:
|
|
|
187
187
|
threadlog.info("created user %r with email %r" % (username, kwargs["email"]))
|
|
188
188
|
else:
|
|
189
189
|
threadlog.info("created user %r" % username)
|
|
190
|
+
# Call any user created hooks, passing along the newly created user object.
|
|
191
|
+
self.xom.config.hook.devpiserver_user_created(user=user)
|
|
190
192
|
return user
|
|
191
193
|
|
|
192
194
|
def create_stage(self, user, index, type="stage", **kwargs):
|
|
@@ -544,9 +546,6 @@ class BaseStageCustomizer:
|
|
|
544
546
|
principals = {':ANONYMOUS:'}
|
|
545
547
|
else:
|
|
546
548
|
principals = set(principals)
|
|
547
|
-
if self.stage.xom.is_primary():
|
|
548
|
-
# replicas always need to be able to download packages
|
|
549
|
-
principals.add("+replica")
|
|
550
549
|
# admins should always be able to read the packages
|
|
551
550
|
if restrict_modify is None:
|
|
552
551
|
principals.add("root")
|
|
@@ -786,12 +785,13 @@ class BaseStage:
|
|
|
786
785
|
% (index_type, ", ".join(sorted(conflicting))))
|
|
787
786
|
for key, value in defaults.items():
|
|
788
787
|
new_value = kwargs.pop(key, value)
|
|
789
|
-
if
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
788
|
+
if new_value is not RemoveValue:
|
|
789
|
+
if isinstance(value, bool):
|
|
790
|
+
new_value = ensure_boolean(new_value)
|
|
791
|
+
elif isinstance(value, ACLList):
|
|
792
|
+
new_value = ensure_acl_list(new_value)
|
|
793
|
+
elif isinstance(value, (list, tuple, set)):
|
|
794
|
+
new_value = ensure_list(new_value)
|
|
795
795
|
ixconfig.setdefault(key, new_value)
|
|
796
796
|
# XXX backward compatibility for old exports where these could appear
|
|
797
797
|
# on mirror indexes
|