devpi-web 4.3.0__tar.gz → 5.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. {devpi-web-4.3.0 → devpi_web-5.0.0}/CHANGELOG +58 -0
  2. devpi_web-5.0.0/CHANGELOG.short.rst +119 -0
  3. {devpi-web-4.3.0 → devpi_web-5.0.0}/PKG-INFO +85 -44
  4. devpi_web-5.0.0/devpi_web/__init__.py +1 -0
  5. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/clear_index.py +4 -3
  6. devpi_web-5.0.0/devpi_web/compat.py +9 -0
  7. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/doczip.py +81 -87
  8. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/indexing.py +1 -0
  9. devpi_web-5.0.0/devpi_web/macroregistry.py +362 -0
  10. devpi_web-5.0.0/devpi_web/macros.py +121 -0
  11. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/main.py +49 -25
  12. devpi_web-5.0.0/devpi_web/static/search.js +25 -0
  13. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/static/style.css +4 -0
  14. devpi_web-5.0.0/devpi_web/templates/favicon.pt +1 -0
  15. devpi_web-5.0.0/devpi_web/templates/footer.pt +4 -0
  16. devpi_web-5.0.0/devpi_web/templates/footer_versions.pt +3 -0
  17. devpi_web-5.0.0/devpi_web/templates/head.pt +3 -0
  18. devpi_web-5.0.0/devpi_web/templates/header.pt +9 -0
  19. devpi_web-5.0.0/devpi_web/templates/header_breadcrumbs.pt +3 -0
  20. devpi_web-5.0.0/devpi_web/templates/header_search.pt +14 -0
  21. devpi_web-5.0.0/devpi_web/templates/header_status.pt +11 -0
  22. devpi_web-5.0.0/devpi_web/templates/html_head_css.pt +4 -0
  23. devpi_web-5.0.0/devpi_web/templates/html_head_scripts.pt +4 -0
  24. devpi_web-5.0.0/devpi_web/templates/logo.pt +1 -0
  25. devpi_web-5.0.0/devpi_web/templates/query_docs.pt +1 -0
  26. devpi_web-5.0.0/devpi_web/templates/root_above_user_index_list.pt +0 -0
  27. devpi_web-5.0.0/devpi_web/templates/root_below_user_index_list.pt +0 -0
  28. devpi_web-5.0.0/devpi_web/templates/status_badge.pt +4 -0
  29. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/views.py +66 -40
  30. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/whoosh_index.py +5 -9
  31. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web.egg-info/PKG-INFO +85 -44
  32. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web.egg-info/SOURCES.txt +21 -5
  33. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web.egg-info/requires.txt +7 -4
  34. devpi_web-5.0.0/mypy.ini +42 -0
  35. devpi_web-5.0.0/pyproject.toml +195 -0
  36. devpi_web-5.0.0/setup.cfg +4 -0
  37. devpi_web-5.0.0/tests/conftest.py +60 -0
  38. {devpi-web-4.3.0 → devpi_web-5.0.0}/tests/test_indexing.py +15 -17
  39. devpi_web-5.0.0/tests/test_macro_registry.py +30 -0
  40. {devpi-web-4.3.0 → devpi_web-5.0.0}/tests/test_main.py +37 -14
  41. devpi_web-5.0.0/tests/test_theme.py +204 -0
  42. {devpi-web-4.3.0 → devpi_web-5.0.0}/tests/test_views_docs.py +3 -2
  43. {devpi-web-4.3.0 → devpi_web-5.0.0}/tests/test_views_misc.py +14 -6
  44. {devpi-web-4.3.0 → devpi_web-5.0.0}/tests/test_views_search.py +2 -3
  45. {devpi-web-4.3.0 → devpi_web-5.0.0}/tests/test_views_toxresults.py +1 -2
  46. {devpi-web-4.3.0 → devpi_web-5.0.0}/tox.ini +15 -10
  47. devpi-web-4.3.0/AUTHORS +0 -7
  48. devpi-web-4.3.0/devpi_web/__init__.py +0 -1
  49. devpi-web-4.3.0/devpi_web/compat.py +0 -42
  50. devpi-web-4.3.0/devpi_web/templates/macros.pt +0 -114
  51. devpi-web-4.3.0/devpi_web.egg-info/not-zip-safe +0 -1
  52. devpi-web-4.3.0/pyproject.toml +0 -83
  53. devpi-web-4.3.0/setup.cfg +0 -7
  54. devpi-web-4.3.0/setup.py +0 -77
  55. devpi-web-4.3.0/tests/conftest.py +0 -65
  56. devpi-web-4.3.0/tests/test_theme.py +0 -47
  57. {devpi-web-4.3.0 → devpi_web-5.0.0}/.flake8 +0 -0
  58. {devpi-web-4.3.0 → devpi_web-5.0.0}/LICENSE +0 -0
  59. {devpi-web-4.3.0 → devpi_web-5.0.0}/MANIFEST.in +0 -0
  60. {devpi-web-4.3.0 → devpi_web-5.0.0}/README.rst +0 -0
  61. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/config.py +0 -0
  62. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/description.py +0 -0
  63. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/hookspecs.py +0 -0
  64. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/null_index.py +0 -0
  65. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/static/common.js +0 -0
  66. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/static/docview.js +0 -0
  67. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/static/favicon.ico +0 -0
  68. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/static/jquery-3.6.0.min.js +0 -0
  69. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/doc.pt +0 -0
  70. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/error.pt +0 -0
  71. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/index.pt +0 -0
  72. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/notfound.pt +0 -0
  73. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/project.pt +0 -0
  74. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/root.pt +0 -0
  75. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/search.pt +0 -0
  76. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/search_help.pt +0 -0
  77. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/status.pt +0 -0
  78. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/toxresult.pt +0 -0
  79. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/toxresults.pt +0 -0
  80. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/user.pt +0 -0
  81. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web/templates/version.pt +0 -0
  82. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web.egg-info/dependency_links.txt +0 -0
  83. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web.egg-info/entry_points.txt +0 -0
  84. {devpi-web-4.3.0 → devpi_web-5.0.0}/devpi_web.egg-info/top_level.txt +0 -0
  85. {devpi-web-4.3.0 → devpi_web-5.0.0}/tests/test_views.py +0 -0
  86. {devpi-web-4.3.0 → devpi_web-5.0.0}/tests/test_whoosh_index.py +0 -0
@@ -2,6 +2,64 @@
2
2
 
3
3
  .. towncrier release notes start
4
4
 
5
+ 5.0.0 (2025-06-12)
6
+ ==================
7
+
8
+ Deprecations and Removals
9
+ -------------------------
10
+
11
+ - Removed ``macros.pt``, the contained macros have all been moved to separate templates. See other news entries for details.
12
+
13
+ - Remove support for Python below 3.9. In the future end of life Python 3 versions aren't supported anymore.
14
+
15
+ - Future releases will more often require newer devpi-server than before.
16
+
17
+
18
+
19
+ Bug Fixes
20
+ ---------
21
+
22
+ - Fix #930: remove remaining uses of unmaintained py library.
23
+
24
+
25
+
26
+ Other Changes
27
+ -------------
28
+
29
+ - style.css: Added styling for readme/description code block / literals.
30
+
31
+ - macros.pt (``navigation`` macro): Move ``breadcrumbs`` out of ``navigation`` macro to separate ``header_breadcrumbs.pt`` template.
32
+
33
+ - macros.pt (``head`` macro): Move ``favicon`` out of ``head`` macro to separate ``favicon.pt`` template.
34
+
35
+ - macros.pt: Move ``footer`` macro to separate ``footer.pt`` template.
36
+
37
+ - macros.pt: Move ``head`` macro to separate ``head.pt`` template.
38
+
39
+ - macros.pt: Move ``headcss`` macro to separate ``html_head_css.pt`` template.
40
+
41
+ - macros.pt: Move ``headscript`` macro to separate ``html_head_scripts.pt`` template.
42
+
43
+ - macros.pt: Move ``logo`` macro to separate ``logo.pt`` template.
44
+
45
+ - macros.pt: Move ``navigation`` macro to separate ``header.pt`` template.
46
+
47
+ - macros.pt: Move ``query_doc`` macro to separate ``query_doc.pt`` template.
48
+
49
+ - macros.pt: Move ``rootaboveuserindexlist`` macro to separate ``root_above_user_index_list.pt`` template.
50
+
51
+ - macros.pt: Move ``rootbelowuserindexlist`` macro to separate ``root_below_user_index_list.pt`` template.
52
+
53
+ - macros.pt: Move ``search`` macro to separate ``header_search.pt`` template.
54
+
55
+ - macros.pt: Move ``status`` macro to separate ``header_status.pt`` template.
56
+
57
+ - macros.pt: Move ``statusbadge`` macro to separate ``status_badge.pt`` template.
58
+
59
+ - macros.pt: Move ``versions`` macro to separate ``footer_versions.pt`` template.
60
+
61
+
62
+
5
63
  4.3.0 (2024-10-16)
6
64
  ==================
7
65
 
@@ -0,0 +1,119 @@
1
+
2
+
3
+ =========
4
+ Changelog
5
+ =========
6
+
7
+
8
+
9
+
10
+ .. towncrier release notes start
11
+
12
+ 5.0.0 (2025-06-12)
13
+ ==================
14
+
15
+ Deprecations and Removals
16
+ -------------------------
17
+
18
+ - Removed ``macros.pt``, the contained macros have all been moved to separate templates. See other news entries for details.
19
+
20
+ - Remove support for Python below 3.9. In the future end of life Python 3 versions aren't supported anymore.
21
+
22
+ - Future releases will more often require newer devpi-server than before.
23
+
24
+
25
+
26
+ Bug Fixes
27
+ ---------
28
+
29
+ - Fix #930: remove remaining uses of unmaintained py library.
30
+
31
+
32
+
33
+ Other Changes
34
+ -------------
35
+
36
+ - style.css: Added styling for readme/description code block / literals.
37
+
38
+ - macros.pt (``navigation`` macro): Move ``breadcrumbs`` out of ``navigation`` macro to separate ``header_breadcrumbs.pt`` template.
39
+
40
+ - macros.pt (``head`` macro): Move ``favicon`` out of ``head`` macro to separate ``favicon.pt`` template.
41
+
42
+ - macros.pt: Move ``footer`` macro to separate ``footer.pt`` template.
43
+
44
+ - macros.pt: Move ``head`` macro to separate ``head.pt`` template.
45
+
46
+ - macros.pt: Move ``headcss`` macro to separate ``html_head_css.pt`` template.
47
+
48
+ - macros.pt: Move ``headscript`` macro to separate ``html_head_scripts.pt`` template.
49
+
50
+ - macros.pt: Move ``logo`` macro to separate ``logo.pt`` template.
51
+
52
+ - macros.pt: Move ``navigation`` macro to separate ``header.pt`` template.
53
+
54
+ - macros.pt: Move ``query_doc`` macro to separate ``query_doc.pt`` template.
55
+
56
+ - macros.pt: Move ``rootaboveuserindexlist`` macro to separate ``root_above_user_index_list.pt`` template.
57
+
58
+ - macros.pt: Move ``rootbelowuserindexlist`` macro to separate ``root_below_user_index_list.pt`` template.
59
+
60
+ - macros.pt: Move ``search`` macro to separate ``header_search.pt`` template.
61
+
62
+ - macros.pt: Move ``status`` macro to separate ``header_status.pt`` template.
63
+
64
+ - macros.pt: Move ``statusbadge`` macro to separate ``status_badge.pt`` template.
65
+
66
+ - macros.pt: Move ``versions`` macro to separate ``footer_versions.pt`` template.
67
+
68
+
69
+
70
+ 4.3.0 (2024-10-16)
71
+ ==================
72
+
73
+ Features
74
+ --------
75
+
76
+ - index.pt, project.pt, version.pt: Fix #1062: Added a link to download the documentation as zip-file to the index, project and version view.
77
+
78
+
79
+
80
+ 4.2.3 (2024-09-19)
81
+ ==================
82
+
83
+ Bug Fixes
84
+ ---------
85
+
86
+ - Fix deprecation warnings from devpi-server 6.13.0.
87
+
88
+ - Lazily evaluate file information. Especially with devpi-postgresql this safes many database accesses on most pages.
89
+
90
+ - Guard against missing doczip files, which can happen on replicas during replication.
91
+
92
+ - Increase threshold for index status from 60 s to 300 s for warnings and from 300 s to 3600 s for fatal.
93
+
94
+
95
+
96
+ 4.2.2 (2024-04-20)
97
+ ==================
98
+
99
+ Bug Fixes
100
+ ---------
101
+
102
+ - style.css: Always let content be full browser height. This also gives more height with some documentation themes when content is short.
103
+
104
+ - style.css: set ``scrollbar-gutter: stable`` on ``body`` to prevent jumping content in documentation iframe.
105
+
106
+ - Fix #970: overwrite fixed html/body heights like ``100%`` in documentation iframe content.
107
+
108
+
109
+
110
+ 4.2.1 (2023-07-02)
111
+ ==================
112
+
113
+ Bug Fixes
114
+ ---------
115
+
116
+ - Fix #953: Exception when browsers send ETag for documentation pages.
117
+
118
+ - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
119
+
@@ -1,36 +1,44 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: devpi-web
3
- Version: 4.3.0
3
+ Version: 5.0.0
4
4
  Summary: devpi-web: a web view for devpi-server
5
- Home-page: https://devpi.net
6
- Maintainer: Florian Schulze
7
- Maintainer-email: mail@pyfidelity.com
8
- License: MIT
5
+ Maintainer-email: Florian Schulze <mail@pyfidelity.com>
6
+ License-Expression: MIT
9
7
  Project-URL: Bug Tracker, https://github.com/devpi/devpi/issues
10
8
  Project-URL: Changelog, https://github.com/devpi/devpi/blob/main/web/CHANGELOG
11
9
  Project-URL: Documentation, https://doc.devpi.net
10
+ Project-URL: Homepage, https://devpi.net
12
11
  Project-URL: Source Code, https://github.com/devpi/devpi
13
12
  Classifier: Development Status :: 5 - Production/Stable
14
13
  Classifier: Environment :: Web Environment
15
14
  Classifier: Intended Audience :: Developers
16
15
  Classifier: Intended Audience :: System Administrators
17
- Classifier: License :: OSI Approved :: MIT License
18
16
  Classifier: Programming Language :: Python
19
- Classifier: Topic :: Internet :: WWW/HTTP
20
- Classifier: Programming Language :: Python :: Implementation :: PyPy
21
- Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
22
- Classifier: Programming Language :: Python :: 3.4
23
- Classifier: Programming Language :: Python :: 3.5
24
- Classifier: Programming Language :: Python :: 3.6
25
- Classifier: Programming Language :: Python :: 3.7
26
- Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3 :: Only
27
18
  Classifier: Programming Language :: Python :: 3.9
28
19
  Classifier: Programming Language :: Python :: 3.10
29
20
  Classifier: Programming Language :: Python :: 3.11
30
21
  Classifier: Programming Language :: Python :: 3.12
31
- Requires-Python: >=3.4
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
24
+ Classifier: Topic :: Internet :: WWW/HTTP
25
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/x-rst
32
28
  License-File: LICENSE
33
- License-File: AUTHORS
29
+ Requires-Dist: Whoosh<3
30
+ Requires-Dist: attrs>=22.2.0
31
+ Requires-Dist: beautifulsoup4!=4.12.1,>=4.3.2
32
+ Requires-Dist: defusedxml
33
+ Requires-Dist: devpi-server>=6.13.0
34
+ Requires-Dist: devpi-common>=4.0.0
35
+ Requires-Dist: docutils>=0.11
36
+ Requires-Dist: pygments>=1.6
37
+ Requires-Dist: pyramid>=2
38
+ Requires-Dist: pyramid-chameleon
39
+ Requires-Dist: readme-renderer[md]>=23.0
40
+ Requires-Dist: tomli; python_version < "3.11"
41
+ Dynamic: license-file
34
42
 
35
43
  ================================================
36
44
  devpi-web: web interface plugin for devpi-server
@@ -69,14 +77,74 @@ For support contracts and paid help contact ``mail at pyfidelity.com``.
69
77
  .. _GitHub Discussions: https://github.com/devpi/devpi/discussions
70
78
 
71
79
 
80
+
72
81
  =========
73
82
  Changelog
74
83
  =========
75
84
 
76
85
 
77
86
 
87
+
78
88
  .. towncrier release notes start
79
89
 
90
+ 5.0.0 (2025-06-12)
91
+ ==================
92
+
93
+ Deprecations and Removals
94
+ -------------------------
95
+
96
+ - Removed ``macros.pt``, the contained macros have all been moved to separate templates. See other news entries for details.
97
+
98
+ - Remove support for Python below 3.9. In the future end of life Python 3 versions aren't supported anymore.
99
+
100
+ - Future releases will more often require newer devpi-server than before.
101
+
102
+
103
+
104
+ Bug Fixes
105
+ ---------
106
+
107
+ - Fix #930: remove remaining uses of unmaintained py library.
108
+
109
+
110
+
111
+ Other Changes
112
+ -------------
113
+
114
+ - style.css: Added styling for readme/description code block / literals.
115
+
116
+ - macros.pt (``navigation`` macro): Move ``breadcrumbs`` out of ``navigation`` macro to separate ``header_breadcrumbs.pt`` template.
117
+
118
+ - macros.pt (``head`` macro): Move ``favicon`` out of ``head`` macro to separate ``favicon.pt`` template.
119
+
120
+ - macros.pt: Move ``footer`` macro to separate ``footer.pt`` template.
121
+
122
+ - macros.pt: Move ``head`` macro to separate ``head.pt`` template.
123
+
124
+ - macros.pt: Move ``headcss`` macro to separate ``html_head_css.pt`` template.
125
+
126
+ - macros.pt: Move ``headscript`` macro to separate ``html_head_scripts.pt`` template.
127
+
128
+ - macros.pt: Move ``logo`` macro to separate ``logo.pt`` template.
129
+
130
+ - macros.pt: Move ``navigation`` macro to separate ``header.pt`` template.
131
+
132
+ - macros.pt: Move ``query_doc`` macro to separate ``query_doc.pt`` template.
133
+
134
+ - macros.pt: Move ``rootaboveuserindexlist`` macro to separate ``root_above_user_index_list.pt`` template.
135
+
136
+ - macros.pt: Move ``rootbelowuserindexlist`` macro to separate ``root_below_user_index_list.pt`` template.
137
+
138
+ - macros.pt: Move ``search`` macro to separate ``header_search.pt`` template.
139
+
140
+ - macros.pt: Move ``status`` macro to separate ``header_status.pt`` template.
141
+
142
+ - macros.pt: Move ``statusbadge`` macro to separate ``status_badge.pt`` template.
143
+
144
+ - macros.pt: Move ``versions`` macro to separate ``footer_versions.pt`` template.
145
+
146
+
147
+
80
148
  4.3.0 (2024-10-16)
81
149
  ==================
82
150
 
@@ -127,30 +195,3 @@ Bug Fixes
127
195
 
128
196
  - Fix #980: Remove long deprecated backward compatibility for old pluggy versions to fix error with pluggy 1.1.0.
129
197
 
130
-
131
- 4.2.0 (2022-12-05)
132
- ==================
133
-
134
- Features
135
- --------
136
-
137
- - Set ETag header to the doczip hash and max-age to 60 seconds for all documentation files.
138
-
139
- - Add ``--keep-docs-packed`` option.
140
-
141
-
142
- Bug Fixes
143
- ---------
144
-
145
- - common.js, macros.pt: Fix #823: Remove moment.js and update jquery to 3.6.0.
146
-
147
- - common.js: Fix #764: Jumping to anchors with whitespace in documentation now works.
148
-
149
- - doc.pt, docview.js, style.css: Fix #764: Rewrote setting height of documentation iframe which also fixes scrolling to anchors generated by JavaScript.
150
-
151
- - Fix project names from mirrors with devpi-server >= 6.8.0.
152
-
153
- - Fix exception in not found page of project URLs.
154
-
155
- - toxresults.pt, version.pt: Fix anchor generation for toxresults URLs.
156
-
@@ -0,0 +1 @@
1
+ __version__ = "5.0.0"
@@ -1,14 +1,15 @@
1
+ from devpi_common.terminal import TerminalWriter
1
2
  from devpi_server.config import MyArgumentParser
2
3
  from devpi_server.config import add_configfile_option
3
4
  from devpi_server.config import add_help_option
4
5
  from devpi_server.config import add_storage_options
5
- from devpi_server.config import parseoptions, get_pluginmanager
6
+ from devpi_server.config import get_pluginmanager
7
+ from devpi_server.config import parseoptions
6
8
  from devpi_server.log import configure_cli_logging
7
9
  from devpi_server.main import Fatal
8
10
  from devpi_server.main import xom_from_config
9
11
  from devpi_web.config import add_indexer_backend_option
10
12
  from devpi_web.main import get_indexer
11
- import py
12
13
  import sys
13
14
 
14
15
 
@@ -37,6 +38,6 @@ def clear_index(argv=None):
37
38
  ix.delete_index()
38
39
  log.info("Index deleted, start devpi-server again to let the index rebuild automatically.")
39
40
  except Fatal as e:
40
- tw = py.io.TerminalWriter(sys.stderr)
41
+ tw = TerminalWriter(sys.stderr)
41
42
  tw.line("fatal: %s" % e.args[0], red=True)
42
43
  return 1
@@ -0,0 +1,9 @@
1
+ try:
2
+ import tomllib
3
+ except ImportError:
4
+ import tomli as tomllib
5
+
6
+
7
+ __all__ = [
8
+ "tomllib",
9
+ ]
@@ -1,13 +1,15 @@
1
1
  from bs4 import BeautifulSoup
2
- from collections.abc import MutableMapping as DictMixin
2
+ from collections.abc import MutableMapping
3
3
  from contextlib import contextmanager
4
+ from contextlib import suppress
4
5
  from devpi_common.archive import Archive
5
6
  from devpi_common.types import cached_property
6
7
  from devpi_common.validation import normalize_name
7
8
  from devpi_server.log import threadlog
8
- from devpi_web.compat import get_entry_hash_spec
9
+ from pathlib import Path
10
+ import itertools
9
11
  import json
10
- import py
12
+ import shutil
11
13
 
12
14
 
13
15
  try:
@@ -18,21 +20,20 @@ except ImportError:
18
20
 
19
21
  def get_unpack_path(stage, name, version):
20
22
  path = stage.xom.config.args.documentation_path
21
- if path is None:
22
- path = py.path.local(stage.keyfs.base_path) if hasattr(stage.keyfs, "base_path") else stage.keyfs.basedir
23
- else:
24
- path = py.path.local(path)
25
- return path.join(
26
- stage.user.name, stage.index, normalize_name(name), version, "+doc")
23
+ path = stage.keyfs.base_path if path is None else Path(path)
24
+ return path.joinpath(
25
+ stage.user.name, stage.index, normalize_name(name), version, "+doc"
26
+ )
27
27
 
28
28
 
29
29
  @contextmanager
30
- def locked_unpack_path(stage, name, version, remove_lock_file=False):
30
+ def locked_unpack_path(stage, name, version, *, remove_lock_file=False):
31
31
  unpack_path = get_unpack_path(stage, name, version)
32
32
  # we are using the hash file as a lock file
33
- hash_path = unpack_path.new(ext="hash")
33
+ hash_path = unpack_path.with_suffix(".hash")
34
34
  try:
35
- with hash_path.open("a+", ensure=True) as hash_file:
35
+ hash_path.parent.mkdir(parents=True, exist_ok=True)
36
+ with hash_path.open("a+") as hash_file:
36
37
  if fcntl:
37
38
  fcntl.flock(hash_file, fcntl.LOCK_EX)
38
39
  try:
@@ -42,11 +43,9 @@ def locked_unpack_path(stage, name, version, remove_lock_file=False):
42
43
  fcntl.flock(hash_file, fcntl.LOCK_UN)
43
44
  finally:
44
45
  if remove_lock_file and hash_path.exists():
45
- try:
46
- hash_path.remove()
47
- except py.error.ENOENT:
46
+ with suppress(FileNotFoundError):
48
47
  # there is a rare possibility of a race condition here
49
- pass
48
+ hash_path.unlink()
50
49
 
51
50
 
52
51
  def keep_docs_packed(config):
@@ -60,23 +59,21 @@ def docs_exist(stage, name, version, entry):
60
59
  def docs_file_content(stage, name, version, entry, relpath):
61
60
  if not keep_docs_packed(stage.xom.config):
62
61
  return None
63
- with entry.file_open_read() as f:
64
- with Archive(f) as archive:
65
- return archive.read(relpath)
62
+ with entry.file_open_read() as f, Archive(f) as archive:
63
+ return archive.read(relpath)
66
64
 
67
65
 
68
66
  def docs_file_exists(stage, name, version, entry, relpath):
69
67
  if keep_docs_packed(stage.xom.config):
70
- with entry.file_open_read() as f:
71
- with Archive(f) as archive:
72
- try:
73
- if archive.getfile(relpath):
74
- return True
75
- except archive.FileNotExist:
76
- return False
68
+ with entry.file_open_read() as f, Archive(f) as archive:
69
+ try:
70
+ if archive.getfile(relpath):
71
+ return True
72
+ except archive.FileNotExist:
73
+ return False
77
74
  else:
78
75
  doc_path = unpack_docs(stage, name, version, entry)
79
- if doc_path.join(relpath).isfile():
76
+ if doc_path.joinpath(relpath).is_file():
80
77
  return True
81
78
  return False
82
79
 
@@ -85,7 +82,7 @@ def docs_file_path(stage, name, version, entry, relpath):
85
82
  if keep_docs_packed(stage.xom.config):
86
83
  return None
87
84
  doc_path = unpack_docs(stage, name, version, entry)
88
- return doc_path.join(relpath)
85
+ return doc_path.joinpath(relpath)
89
86
 
90
87
 
91
88
  def unpack_docs(stage, name, version, entry):
@@ -93,21 +90,18 @@ def unpack_docs(stage, name, version, entry):
93
90
  # we are not losing the original zip file anyway
94
91
  with locked_unpack_path(stage, name, version) as (hash_file, unpack_path):
95
92
  hash_file.seek(0)
96
- if hash_file.read().strip() == get_entry_hash_spec(entry):
93
+ if hash_file.read().strip() == entry.best_available_hash_spec:
97
94
  return unpack_path
98
95
  if unpack_path.exists():
99
- try:
100
- unpack_path.remove()
101
- except py.error.ENOENT:
96
+ with suppress(FileNotFoundError):
102
97
  # there is a rare possibility of a race condition here
103
- pass
98
+ unpack_path.unlink()
104
99
  if not entry.file_exists():
105
100
  return unpack_path
106
- with entry.file_open_read() as f:
107
- with Archive(f) as archive:
108
- archive.extract(unpack_path)
101
+ with entry.file_open_read() as f, Archive(f) as archive:
102
+ archive.extract(unpack_path)
109
103
  hash_file.seek(0)
110
- hash_file.write(get_entry_hash_spec(entry))
104
+ hash_file.write(entry.best_available_hash_spec)
111
105
  hash_file.truncate()
112
106
  threadlog.debug("%s: unpacked %s-%s docs to %s",
113
107
  stage.name, name, version, unpack_path)
@@ -115,17 +109,18 @@ def unpack_docs(stage, name, version, entry):
115
109
 
116
110
 
117
111
  class PackedEntry:
118
- def __init__(self, basename, body):
119
- self.basename = basename
112
+ def __init__(self, name, body):
113
+ self.name = name
120
114
  self.body = body
121
115
 
122
- def read(self, mode='rb'):
123
- if mode != 'rb':
124
- raise RuntimeError("Unsupported mode %r" % mode)
116
+ def read_bytes(self):
125
117
  return self.body
126
118
 
119
+ def read_text(self):
120
+ return self.body.decode()
121
+
127
122
 
128
- class Docs(DictMixin):
123
+ class Docs(MutableMapping):
129
124
  def __init__(self, stage, name, version):
130
125
  self.stage = stage
131
126
  self.keep_docs_packed = keep_docs_packed(self.stage.xom.config)
@@ -148,40 +143,43 @@ class Docs(DictMixin):
148
143
  # aren't uploaded yet
149
144
  threadlog.warn("Tried to access %s, but it doesn't exist.", self.unpack_path)
150
145
  return {}
151
- if self.keep_docs_packed:
152
- return self._packed_entries()
153
- else:
154
- return self._unpacked_entries()
146
+ return (
147
+ self._packed_entries()
148
+ if self.keep_docs_packed
149
+ else self._unpacked_entries()
150
+ )
155
151
 
156
152
  def _packed_entries(self):
157
153
  html = set()
158
154
  fjson = set()
159
- with self.entry.file_open_read() as f:
160
- with Archive(f) as archive:
161
- for item in archive.namelist():
162
- if item.endswith('.fjson'):
163
- fjson.add(item)
164
- elif item.endswith('.html'):
165
- html.add(item)
166
- if fjson:
167
- # if there is fjson, then we get structured data
168
- # see http://www.sphinx-doc.org/en/master/usage/builders/index.html#serialization-builder-details
169
- return {
170
- k[:-6]: PackedEntry(k, archive.read(k))
171
- for k in fjson}
172
- else:
173
- return {
174
- k[:-5]: PackedEntry(k, archive.read(k))
175
- for k in html}
155
+ with self.entry.file_open_read() as f, Archive(f) as archive:
156
+ for item in archive.namelist():
157
+ if item.endswith(".fjson"):
158
+ fjson.add(item)
159
+ elif item.endswith(".html"):
160
+ html.add(item)
161
+ if fjson:
162
+ # if there is fjson, then we get structured data
163
+ # see http://www.sphinx-doc.org/en/master/usage/builders/index.html#serialization-builder-details
164
+ src = fjson
165
+ s = slice(None, -6)
166
+ else:
167
+ src = html
168
+ s = slice(None, -5)
169
+ return {k[s]: PackedEntry(k, archive.read(k)) for k in src}
176
170
 
177
171
  def _unpacked_entries(self):
178
172
  unpack_path = unpack_docs(self.stage, self.name, self.version, self.entry)
179
- if not unpack_path.isdir():
173
+ if not unpack_path.is_dir():
180
174
  return {}
181
175
  html = []
182
176
  fjson = []
183
- for entry in unpack_path.visit():
184
- basename = entry.basename
177
+ entries = itertools.chain(
178
+ unpack_path.glob("**/*.fjson"),
179
+ unpack_path.glob("**/*.html"),
180
+ )
181
+ for entry in entries:
182
+ basename = entry.name
185
183
  if basename.endswith('.fjson'):
186
184
  fjson.append(entry)
187
185
  elif basename.endswith('.html'):
@@ -189,9 +187,12 @@ class Docs(DictMixin):
189
187
  if fjson:
190
188
  # if there is fjson, then we get structured data
191
189
  # see http://www.sphinx-doc.org/en/master/usage/builders/index.html#serialization-builder-details
192
- return {x.relto(unpack_path)[:-6]: x for x in fjson}
190
+ src = fjson
191
+ s = slice(None, -6)
193
192
  else:
194
- return {x.relto(unpack_path)[:-5]: x for x in html}
193
+ src = html
194
+ s = slice(None, -5)
195
+ return {str(x.relative_to(unpack_path))[s]: x for x in src}
195
196
 
196
197
  def keys(self):
197
198
  return self._entries.keys()
@@ -210,26 +211,19 @@ class Docs(DictMixin):
210
211
 
211
212
  def __getitem__(self, name):
212
213
  entry = self._entries[name]
213
- if entry.basename.endswith('.fjson'):
214
- info = json.loads(entry.read())
214
+ if entry.name.endswith(".fjson"):
215
+ info = json.loads(entry.read_text())
215
216
  return dict(
216
217
  title=BeautifulSoup(info.get('title', ''), "html.parser").text,
217
218
  text=BeautifulSoup(info.get('body', ''), "html.parser").text,
218
219
  path=info.get('current_page_name', name))
219
- else:
220
- soup = BeautifulSoup(entry.read(mode='rb'), "html.parser")
221
- body = soup.find('body')
222
- if body is None:
223
- return
224
- title = soup.find('title')
225
- if title is None:
226
- title = ''
227
- else:
228
- title = title.text
229
- return dict(
230
- title=title,
231
- text=body.text,
232
- path=name)
220
+ soup = BeautifulSoup(entry.read_bytes(), "html.parser")
221
+ body = soup.find("body")
222
+ if body is None:
223
+ return None
224
+ title = soup.find("title")
225
+ title = "" if title is None else title.text
226
+ return dict(title=title, text=body.text, path=name)
233
227
 
234
228
 
235
229
  def remove_docs(stage, project, version):
@@ -237,8 +231,8 @@ def remove_docs(stage, project, version):
237
231
  # the stage was removed
238
232
  return
239
233
  with locked_unpack_path(stage, project, version, remove_lock_file=True) as (hash_file, directory):
240
- if not directory.isdir():
234
+ if not directory.is_dir():
241
235
  threadlog.debug("ignoring lost unpacked docs: %s" % directory)
242
236
  else:
243
237
  threadlog.debug("removing unpacked docs: %s" % directory)
244
- directory.remove()
238
+ shutil.rmtree(directory)
@@ -34,6 +34,7 @@ def preprocess_project(project):
34
34
  if not stage.has_project_perstage(name):
35
35
  # project doesn't exist anymore
36
36
  return
37
+ # metadata_keys is only available on private indexes
37
38
  setuptools_metadata = frozenset(getattr(stage, 'metadata_keys', ()))
38
39
  versions = get_sorted_versions(stage.list_versions_perstage(name))
39
40
  result = dict(name=project.name)