devpi-web 4.2.1__tar.gz → 4.2.3__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-web-4.2.3/.flake8 +2 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/CHANGELOG +30 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/MANIFEST.in +1 -1
- {devpi-web-4.2.1 → devpi-web-4.2.3}/PKG-INFO +32 -31
- devpi-web-4.2.3/devpi_web/__init__.py +1 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/clear_index.py +2 -1
- devpi-web-4.2.3/devpi_web/compat.py +42 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/doczip.py +12 -7
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/main.py +3 -1
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/static/docview.js +5 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/static/style.css +22 -1
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/views.py +97 -48
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/whoosh_index.py +24 -15
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web.egg-info/PKG-INFO +32 -31
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web.egg-info/SOURCES.txt +2 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web.egg-info/requires.txt +1 -0
- devpi-web-4.2.3/pyproject.toml +83 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/setup.py +3 -2
- devpi-web-4.2.3/tests/conftest.py +65 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_indexing.py +15 -10
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_main.py +9 -7
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_theme.py +3 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_views.py +10 -2
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_views_docs.py +9 -4
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_views_misc.py +127 -44
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_views_search.py +7 -2
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_views_toxresults.py +7 -3
- {devpi-web-4.2.1 → devpi-web-4.2.3}/tests/test_whoosh_index.py +6 -3
- devpi-web-4.2.3/tox.ini +59 -0
- devpi-web-4.2.1/devpi_web/__init__.py +0 -1
- devpi-web-4.2.1/pyproject.toml +0 -26
- devpi-web-4.2.1/tests/conftest.py +0 -51
- devpi-web-4.2.1/tox.ini +0 -36
- {devpi-web-4.2.1 → devpi-web-4.2.3}/AUTHORS +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/LICENSE +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/README.rst +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/config.py +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/description.py +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/hookspecs.py +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/indexing.py +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/null_index.py +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/static/common.js +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/static/favicon.ico +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/static/jquery-3.6.0.min.js +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/doc.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/error.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/index.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/macros.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/notfound.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/project.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/root.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/search.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/search_help.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/status.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/toxresult.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/toxresults.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/user.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web/templates/version.pt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web.egg-info/dependency_links.txt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web.egg-info/entry_points.txt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web.egg-info/not-zip-safe +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/devpi_web.egg-info/top_level.txt +0 -0
- {devpi-web-4.2.1 → devpi-web-4.2.3}/setup.cfg +0 -0
devpi-web-4.2.3/.flake8
ADDED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
4.2.3 (2024-09-19)
|
|
6
|
+
==================
|
|
7
|
+
|
|
8
|
+
Bug Fixes
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- Fix deprecation warnings from devpi-server 6.13.0.
|
|
12
|
+
|
|
13
|
+
- Lazily evaluate file information. Especially with devpi-postgresql this safes many database accesses on most pages.
|
|
14
|
+
|
|
15
|
+
- Guard against missing doczip files, which can happen on replicas during replication.
|
|
16
|
+
|
|
17
|
+
- Increase threshold for index status from 60 s to 300 s for warnings and from 300 s to 3600 s for fatal.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
4.2.2 (2024-04-20)
|
|
22
|
+
==================
|
|
23
|
+
|
|
24
|
+
Bug Fixes
|
|
25
|
+
---------
|
|
26
|
+
|
|
27
|
+
- style.css: Always let content be full browser height. This also gives more height with some documentation themes when content is short.
|
|
28
|
+
|
|
29
|
+
- style.css: set ``scrollbar-gutter: stable`` on ``body`` to prevent jumping content in documentation iframe.
|
|
30
|
+
|
|
31
|
+
- Fix #970: overwrite fixed html/body heights like ``100%`` in documentation iframe content.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
5
35
|
4.2.1 (2023-07-02)
|
|
6
36
|
==================
|
|
7
37
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devpi-web
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.3
|
|
4
4
|
Summary: devpi-web: a web view for devpi-server
|
|
5
5
|
Home-page: https://devpi.net
|
|
6
6
|
Maintainer: Florian Schulze
|
|
@@ -27,6 +27,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
27
27
|
Classifier: Programming Language :: Python :: 3.9
|
|
28
28
|
Classifier: Programming Language :: Python :: 3.10
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.11
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
30
31
|
Requires-Python: >=3.4
|
|
31
32
|
License-File: LICENSE
|
|
32
33
|
License-File: AUTHORS
|
|
@@ -76,80 +77,80 @@ Changelog
|
|
|
76
77
|
|
|
77
78
|
.. towncrier release notes start
|
|
78
79
|
|
|
79
|
-
4.2.
|
|
80
|
+
4.2.3 (2024-09-19)
|
|
80
81
|
==================
|
|
81
82
|
|
|
82
83
|
Bug Fixes
|
|
83
84
|
---------
|
|
84
85
|
|
|
85
|
-
- Fix
|
|
86
|
+
- Fix deprecation warnings from devpi-server 6.13.0.
|
|
86
87
|
|
|
87
|
-
-
|
|
88
|
+
- Lazily evaluate file information. Especially with devpi-postgresql this safes many database accesses on most pages.
|
|
88
89
|
|
|
90
|
+
- Guard against missing doczip files, which can happen on replicas during replication.
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
==================
|
|
92
|
+
- Increase threshold for index status from 60 s to 300 s for warnings and from 300 s to 3600 s for fatal.
|
|
92
93
|
|
|
93
|
-
Features
|
|
94
|
-
--------
|
|
95
94
|
|
|
96
|
-
- Set ETag header to the doczip hash and max-age to 60 seconds for all documentation files.
|
|
97
|
-
|
|
98
|
-
- Add ``--keep-docs-packed`` option.
|
|
99
95
|
|
|
96
|
+
4.2.2 (2024-04-20)
|
|
97
|
+
==================
|
|
100
98
|
|
|
101
99
|
Bug Fixes
|
|
102
100
|
---------
|
|
103
101
|
|
|
104
|
-
-
|
|
102
|
+
- style.css: Always let content be full browser height. This also gives more height with some documentation themes when content is short.
|
|
105
103
|
|
|
106
|
-
-
|
|
104
|
+
- style.css: set ``scrollbar-gutter: stable`` on ``body`` to prevent jumping content in documentation iframe.
|
|
107
105
|
|
|
108
|
-
-
|
|
106
|
+
- Fix #970: overwrite fixed html/body heights like ``100%`` in documentation iframe content.
|
|
109
107
|
|
|
110
|
-
- Fix project names from mirrors with devpi-server >= 6.8.0.
|
|
111
108
|
|
|
112
|
-
- Fix exception in not found page of project URLs.
|
|
113
109
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
4.1.1 (2022-09-28)
|
|
110
|
+
4.2.1 (2023-07-02)
|
|
118
111
|
==================
|
|
119
112
|
|
|
120
|
-
|
|
121
113
|
Bug Fixes
|
|
122
114
|
---------
|
|
123
115
|
|
|
124
|
-
-
|
|
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.
|
|
125
119
|
|
|
126
120
|
|
|
127
|
-
4.
|
|
121
|
+
4.2.0 (2022-12-05)
|
|
128
122
|
==================
|
|
129
123
|
|
|
130
124
|
Features
|
|
131
125
|
--------
|
|
132
126
|
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
- project.pt: Fix #601: add refresh button to project page.
|
|
127
|
+
- Set ETag header to the doczip hash and max-age to 60 seconds for all documentation files.
|
|
136
128
|
|
|
137
|
-
-
|
|
129
|
+
- Add ``--keep-docs-packed`` option.
|
|
138
130
|
|
|
139
131
|
|
|
140
132
|
Bug Fixes
|
|
141
133
|
---------
|
|
142
134
|
|
|
143
|
-
- Fix #
|
|
135
|
+
- common.js, macros.pt: Fix #823: Remove moment.js and update jquery to 3.6.0.
|
|
144
136
|
|
|
145
|
-
- Fix #
|
|
137
|
+
- common.js: Fix #764: Jumping to anchors with whitespace in documentation now works.
|
|
146
138
|
|
|
139
|
+
- doc.pt, docview.js, style.css: Fix #764: Rewrote setting height of documentation iframe which also fixes scrolling to anchors generated by JavaScript.
|
|
147
140
|
|
|
148
|
-
|
|
141
|
+
- Fix project names from mirrors with devpi-server >= 6.8.0.
|
|
142
|
+
|
|
143
|
+
- Fix exception in not found page of project URLs.
|
|
144
|
+
|
|
145
|
+
- toxresults.pt, version.pt: Fix anchor generation for toxresults URLs.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
4.1.1 (2022-09-28)
|
|
149
149
|
==================
|
|
150
150
|
|
|
151
|
+
|
|
151
152
|
Bug Fixes
|
|
152
153
|
---------
|
|
153
154
|
|
|
154
|
-
-
|
|
155
|
+
- Generalize GET redirect from URLs with trailing slash to ones without for consistency.
|
|
155
156
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '4.2.3'
|
|
@@ -31,7 +31,8 @@ def clear_index(argv=None):
|
|
|
31
31
|
configure_cli_logging(config.args)
|
|
32
32
|
xom = xom_from_config(config)
|
|
33
33
|
log = xom.log
|
|
34
|
-
log.warn(
|
|
34
|
+
log.warn( # noqa: G010 - this is actually TagLogger from devpi-server, but devpi-web still needs to support older devpi-server versions
|
|
35
|
+
"You should stop devpi-server before running this command.")
|
|
35
36
|
ix = get_indexer(xom)
|
|
36
37
|
ix.delete_index()
|
|
37
38
|
log.info("Index deleted, start devpi-server again to let the index rebuild automatically.")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from devpi_server.filestore import get_hashes
|
|
3
|
+
|
|
4
|
+
def get_default_hash_spec(content_or_file):
|
|
5
|
+
return get_hashes(content_or_file).get_default_spec()
|
|
6
|
+
except ImportError:
|
|
7
|
+
from devpi_server.filestore import get_default_hash_spec # noqa: F401
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from devpi_server.main import Fatal
|
|
12
|
+
|
|
13
|
+
def fatal(msg, *, exc=None):
|
|
14
|
+
raise Fatal(msg) from exc
|
|
15
|
+
except ImportError:
|
|
16
|
+
from devpi_server.main import fatal # noqa: F401
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_entry_hash_spec(entry):
|
|
20
|
+
return (
|
|
21
|
+
entry.best_available_hash_spec
|
|
22
|
+
if hasattr(entry, 'best_available_hash_spec') else
|
|
23
|
+
entry.hash_spec)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_entry_hash_value(entry):
|
|
27
|
+
return (
|
|
28
|
+
entry.best_available_hash_value
|
|
29
|
+
if hasattr(entry, 'best_available_hash_value') else
|
|
30
|
+
entry.hash_value)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_transaction(keyfs):
|
|
34
|
+
if hasattr(keyfs, 'read_transaction'):
|
|
35
|
+
return keyfs.read_transaction()
|
|
36
|
+
return keyfs.transaction(write=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def write_transaction(keyfs):
|
|
40
|
+
if hasattr(keyfs, 'write_transaction'):
|
|
41
|
+
return keyfs.write_transaction()
|
|
42
|
+
return keyfs.transaction(write=True)
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
from collections.abc import MutableMapping as DictMixin
|
|
2
1
|
from bs4 import BeautifulSoup
|
|
2
|
+
from collections.abc import MutableMapping as DictMixin
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
from devpi_common.archive import Archive
|
|
5
5
|
from devpi_common.types import cached_property
|
|
6
6
|
from devpi_common.validation import normalize_name
|
|
7
7
|
from devpi_server.log import threadlog
|
|
8
|
+
from devpi_web.compat import get_entry_hash_spec
|
|
9
|
+
import json
|
|
10
|
+
import py
|
|
11
|
+
|
|
12
|
+
|
|
8
13
|
try:
|
|
9
14
|
import fcntl
|
|
10
15
|
except ImportError:
|
|
11
|
-
fcntl = None # type: ignore
|
|
12
|
-
import json
|
|
13
|
-
import py
|
|
16
|
+
fcntl = None # type: ignore[assignment]
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
def get_unpack_path(stage, name, version):
|
|
17
20
|
path = stage.xom.config.args.documentation_path
|
|
18
21
|
if path is None:
|
|
19
|
-
path = stage.keyfs.basedir
|
|
22
|
+
path = py.path.local(stage.keyfs.base_path) if hasattr(stage.keyfs, "base_path") else stage.keyfs.basedir
|
|
20
23
|
else:
|
|
21
24
|
path = py.path.local(path)
|
|
22
25
|
return path.join(
|
|
@@ -90,7 +93,7 @@ def unpack_docs(stage, name, version, entry):
|
|
|
90
93
|
# we are not losing the original zip file anyway
|
|
91
94
|
with locked_unpack_path(stage, name, version) as (hash_file, unpack_path):
|
|
92
95
|
hash_file.seek(0)
|
|
93
|
-
if hash_file.read().strip() == entry
|
|
96
|
+
if hash_file.read().strip() == get_entry_hash_spec(entry):
|
|
94
97
|
return unpack_path
|
|
95
98
|
if unpack_path.exists():
|
|
96
99
|
try:
|
|
@@ -98,11 +101,13 @@ def unpack_docs(stage, name, version, entry):
|
|
|
98
101
|
except py.error.ENOENT:
|
|
99
102
|
# there is a rare possibility of a race condition here
|
|
100
103
|
pass
|
|
104
|
+
if not entry.file_exists():
|
|
105
|
+
return unpack_path
|
|
101
106
|
with entry.file_open_read() as f:
|
|
102
107
|
with Archive(f) as archive:
|
|
103
108
|
archive.extract(unpack_path)
|
|
104
109
|
hash_file.seek(0)
|
|
105
|
-
hash_file.write(entry
|
|
110
|
+
hash_file.write(get_entry_hash_spec(entry))
|
|
106
111
|
hash_file.truncate()
|
|
107
112
|
threadlog.debug("%s: unpacked %s-%s docs to %s",
|
|
108
113
|
stage.name, name, version, unpack_path)
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
from chameleon.config import AUTO_RELOAD
|
|
2
|
+
from chameleon.config import DEBUG_MODE
|
|
2
3
|
from collections.abc import Mapping
|
|
3
4
|
from devpi_common.metadata import get_latest_version
|
|
4
5
|
from devpi_common.validation import normalize_name
|
|
6
|
+
from devpi_web.compat import fatal
|
|
5
7
|
from devpi_web.config import add_indexer_backend_option
|
|
6
8
|
from devpi_web.config import get_pluginmanager
|
|
7
9
|
from devpi_web.doczip import remove_docs
|
|
8
10
|
from devpi_web.indexing import ProjectIndexingInfo
|
|
9
11
|
from devpi_web.indexing import is_project_cached
|
|
10
12
|
from devpi_server.log import threadlog
|
|
11
|
-
from devpi_server.main import fatal
|
|
12
13
|
from pluggy import HookimplMarker
|
|
13
14
|
from pyramid.renderers import get_renderer
|
|
14
15
|
from pyramid_chameleon.renderer import ChameleonRendererLookup
|
|
@@ -121,6 +122,7 @@ def query_docs_html(request):
|
|
|
121
122
|
|
|
122
123
|
class ThemeChameleonRendererLookup(ChameleonRendererLookup):
|
|
123
124
|
auto_reload = AUTO_RELOAD
|
|
125
|
+
debug = DEBUG_MODE
|
|
124
126
|
|
|
125
127
|
def __call__(self, info):
|
|
126
128
|
# if the template exists in the theme, we will use it instead of the
|
|
@@ -12,6 +12,7 @@ function onIFrameLoad(event) {
|
|
|
12
12
|
$iframe = $(iframe),
|
|
13
13
|
$doc = $(iframe.contentWindow.document),
|
|
14
14
|
$docHtml = $doc.find('html'),
|
|
15
|
+
$docBody = $docHtml.find('body'),
|
|
15
16
|
scrolled_to_anchor = false,
|
|
16
17
|
iframeResizeObserver = new ResizeObserver((entries) => {
|
|
17
18
|
for (const entry of entries) {
|
|
@@ -28,6 +29,10 @@ function onIFrameLoad(event) {
|
|
|
28
29
|
}
|
|
29
30
|
});
|
|
30
31
|
|
|
32
|
+
$docHtml.css('height', 'auto');
|
|
33
|
+
$docHtml.css('overflow-y', 'hidden');
|
|
34
|
+
$docBody.css('height', 'auto');
|
|
35
|
+
$docBody.css('overflow-y', 'hidden');
|
|
31
36
|
// make keyboard actions affect the actual documentation
|
|
32
37
|
// in the iframe by default
|
|
33
38
|
iframe.contentWindow.focus();
|
|
@@ -7,6 +7,17 @@ body, html {
|
|
|
7
7
|
padding: 0;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
html {
|
|
11
|
+
height: 100%;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
min-height: 100%;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
scrollbar-gutter: stable;
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
pre, table {
|
|
11
22
|
font-size: inherit;
|
|
12
23
|
}
|
|
@@ -23,6 +34,10 @@ a:hover {
|
|
|
23
34
|
text-decoration: underline;
|
|
24
35
|
}
|
|
25
36
|
|
|
37
|
+
#content {
|
|
38
|
+
flex: auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
form, #content, #navigation, .searchresults {
|
|
27
42
|
position: relative;
|
|
28
43
|
margin: 0 1em;
|
|
@@ -545,10 +560,16 @@ table.projectinfos .closed .value:after,
|
|
|
545
560
|
}
|
|
546
561
|
|
|
547
562
|
div.iframe {
|
|
563
|
+
flex: auto;
|
|
548
564
|
margin: 0;
|
|
565
|
+
display: flex;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
div.iframe iframe {
|
|
569
|
+
flex: auto;
|
|
549
570
|
}
|
|
550
571
|
|
|
551
|
-
div.iframe, iframe {
|
|
572
|
+
div.iframe, div.iframe iframe {
|
|
552
573
|
width: 100%;
|
|
553
574
|
border: 0;
|
|
554
575
|
}
|
|
@@ -2,12 +2,15 @@ from defusedxml.xmlrpc import DefusedExpatParser
|
|
|
2
2
|
from devpi_common.metadata import Version
|
|
3
3
|
from devpi_common.metadata import get_pyversion_filetype
|
|
4
4
|
from devpi_common.metadata import get_sorted_versions
|
|
5
|
+
from devpi_common.types import cached_property
|
|
5
6
|
from devpi_common.validation import normalize_name
|
|
6
7
|
from devpi_common.viewhelp import iter_toxresults
|
|
7
8
|
from devpi_server.log import threadlog as log
|
|
8
9
|
from devpi_server.readonly import SeqViewReadonly
|
|
9
10
|
from devpi_server.views import StatusView, url_for_entrypath
|
|
10
11
|
from devpi_server.views import PyPIView
|
|
12
|
+
from devpi_web.compat import get_entry_hash_spec
|
|
13
|
+
from devpi_web.compat import get_entry_hash_value
|
|
11
14
|
from devpi_web.description import get_description
|
|
12
15
|
from devpi_web.doczip import Docs
|
|
13
16
|
from devpi_web.doczip import docs_exist
|
|
@@ -151,7 +154,7 @@ def get_doc_info(context, request, version=None, check_content=True):
|
|
|
151
154
|
if not relpath_exists:
|
|
152
155
|
raise HTTPNotFound("File %s not found in documentation." % relpath)
|
|
153
156
|
result = dict(
|
|
154
|
-
etag=entry
|
|
157
|
+
etag=get_entry_hash_value(entry),
|
|
155
158
|
relpath=relpath,
|
|
156
159
|
doc_version=doc_version,
|
|
157
160
|
version_mismatch=doc_version != navigation_version(context))
|
|
@@ -215,7 +218,7 @@ def doc_show(context, request):
|
|
|
215
218
|
project=name, version='latest', relpath="index.html")))
|
|
216
219
|
try:
|
|
217
220
|
stable_doc_info = get_doc_info(context, request, version='stable', check_content=False)
|
|
218
|
-
if stable_doc_info['doc_version']
|
|
221
|
+
if stable_doc_info['doc_version'] not in (doc_info['doc_version'], latest_doc_info['doc_version']):
|
|
219
222
|
version_links.append(dict(
|
|
220
223
|
title="Stable documentation",
|
|
221
224
|
url=request.route_url(
|
|
@@ -331,47 +334,88 @@ def make_history_view_item(request, log_item):
|
|
|
331
334
|
return result
|
|
332
335
|
|
|
333
336
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
337
|
+
class FileInfo:
|
|
338
|
+
def __init__(self, request, link, linkstore, show_toxresults):
|
|
339
|
+
self.entry = link.entry
|
|
340
|
+
self.link = link
|
|
341
|
+
self.linkstore = linkstore
|
|
342
|
+
self.request = request
|
|
343
|
+
self.show_toxresults = show_toxresults
|
|
344
|
+
|
|
345
|
+
@cached_property
|
|
346
|
+
def basename(self):
|
|
347
|
+
return self.entry.basename
|
|
348
|
+
|
|
349
|
+
@cached_property
|
|
350
|
+
def dist_type(self):
|
|
351
|
+
(py_version, file_type) = self.pyversion_filetype
|
|
352
|
+
return dist_file_types.get(file_type, '')
|
|
353
|
+
|
|
354
|
+
def get(self, key, default=None):
|
|
355
|
+
return getattr(self, key, default)
|
|
356
|
+
|
|
357
|
+
@cached_property
|
|
358
|
+
def hash_spec(self):
|
|
359
|
+
return get_entry_hash_spec(self.entry)
|
|
360
|
+
|
|
361
|
+
@cached_property
|
|
362
|
+
def history(self):
|
|
351
363
|
try:
|
|
352
|
-
|
|
353
|
-
make_history_view_item(request, x)
|
|
354
|
-
for x in link.get_logs()]
|
|
364
|
+
return [
|
|
365
|
+
make_history_view_item(self.request, x)
|
|
366
|
+
for x in self.link.get_logs()]
|
|
355
367
|
except AttributeError:
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
368
|
+
return []
|
|
369
|
+
|
|
370
|
+
@cached_property
|
|
371
|
+
def last_modified(self):
|
|
372
|
+
return format_timetuple(parsedate(self.entry.last_modified))
|
|
373
|
+
|
|
374
|
+
@cached_property
|
|
375
|
+
def py_version(self):
|
|
376
|
+
(py_version, file_type) = self.pyversion_filetype
|
|
377
|
+
return '' if py_version == 'source' else py_version
|
|
378
|
+
|
|
379
|
+
@cached_property
|
|
380
|
+
def pyversion_filetype(self):
|
|
381
|
+
return get_pyversion_filetype(self.basename)
|
|
382
|
+
|
|
383
|
+
@cached_property
|
|
384
|
+
def size(self):
|
|
385
|
+
if self.entry.file_exists():
|
|
386
|
+
(value, unit) = sizeof_fmt(self.entry.file_size())
|
|
387
|
+
return f"{value:.0f} {unit}"
|
|
388
|
+
return ''
|
|
389
|
+
|
|
390
|
+
@cached_property
|
|
391
|
+
def title(self):
|
|
392
|
+
return self.basename
|
|
393
|
+
|
|
394
|
+
@cached_property
|
|
395
|
+
def toxresults(self):
|
|
396
|
+
if not self.show_toxresults:
|
|
397
|
+
return []
|
|
398
|
+
return get_toxresults_info(self.linkstore, self.link)
|
|
399
|
+
|
|
400
|
+
@cached_property
|
|
401
|
+
def toxresults_state(self):
|
|
402
|
+
return get_toxresults_state(self.toxresults)
|
|
403
|
+
|
|
404
|
+
@cached_property
|
|
405
|
+
def url(self):
|
|
406
|
+
url = url_for_entrypath(self.request, self.entry.relpath)
|
|
407
|
+
if self.hash_spec:
|
|
408
|
+
url = f"{url}#{self.hash_spec}"
|
|
409
|
+
return url
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def get_files_info(request, linkstore, *, show_toxresults=False):
|
|
413
|
+
filedata = sorted(
|
|
414
|
+
linkstore.get_links(rel='releasefile'),
|
|
415
|
+
key=attrgetter('basename'))
|
|
416
|
+
return [
|
|
417
|
+
FileInfo(request, link, linkstore, show_toxresults)
|
|
418
|
+
for link in filedata]
|
|
375
419
|
|
|
376
420
|
|
|
377
421
|
def load_toxresult(link):
|
|
@@ -559,7 +603,7 @@ def index_get(context, request):
|
|
|
559
603
|
request.route_url, "toxresults",
|
|
560
604
|
user=stage.user.name, index=stage.index,
|
|
561
605
|
project=name, version=ver),
|
|
562
|
-
files=get_files_info(request, linkstore, show_toxresults),
|
|
606
|
+
files=get_files_info(request, linkstore, show_toxresults=show_toxresults),
|
|
563
607
|
docs=get_docs_info(request, stage, linkstore),
|
|
564
608
|
_version_data=verdata))
|
|
565
609
|
packages.sort(key=lambda x: x["info"]["title"])
|
|
@@ -728,7 +772,7 @@ def version_get(context, request):
|
|
|
728
772
|
infos.append((escape(key), value))
|
|
729
773
|
show_toxresults = (stage.ixconfig['type'] != 'mirror')
|
|
730
774
|
linkstore = stage.get_linkstore_perstage(name, version)
|
|
731
|
-
files = get_files_info(request, linkstore, show_toxresults)
|
|
775
|
+
files = get_files_info(request, linkstore, show_toxresults=show_toxresults)
|
|
732
776
|
docs = get_docs_info(request, stage, linkstore)
|
|
733
777
|
home_page = verdata.get("home_page")
|
|
734
778
|
nav_links = []
|
|
@@ -856,23 +900,28 @@ def statusview(request):
|
|
|
856
900
|
in_request=replica.get('in-request', 'unknown'),
|
|
857
901
|
last_request=format_timestamp(
|
|
858
902
|
replica.get('last-request', 'unknown'))))
|
|
903
|
+
primary_url = status.get('primary-url', status.get('master-url'))
|
|
904
|
+
primary_uuid = status.get('primary-uuid', status.get('master-uuid'))
|
|
905
|
+
primary_serial = status.get('primary-serial', status.get('master-serial'))
|
|
906
|
+
primary_serial_ts = status.get('primary-serial-timestamp', status.get('master-serial-timestamp'))
|
|
907
|
+
update_from_primary_at = status.get('update-from-primary-at', status.get('update-from-master-at'))
|
|
859
908
|
return dict(
|
|
860
909
|
msgs=request.status_info['msgs'],
|
|
861
910
|
info=dict(
|
|
862
911
|
uuid=status.get('uuid', 'unknown'),
|
|
863
912
|
role=status.get('role', 'unknown'),
|
|
864
913
|
outside_url=status.get('outside-url', 'unknown'),
|
|
865
|
-
master_url=
|
|
866
|
-
master_uuid=
|
|
867
|
-
master_serial=
|
|
914
|
+
master_url=primary_url,
|
|
915
|
+
master_uuid=primary_uuid,
|
|
916
|
+
master_serial=primary_serial,
|
|
868
917
|
master_serial_timestamp=format_timestamp(
|
|
869
|
-
|
|
918
|
+
primary_serial_ts, unset_value="never"),
|
|
870
919
|
replica_started_at=format_timestamp(
|
|
871
920
|
status.get('replica-started-at')),
|
|
872
921
|
replica_in_sync_at=format_timestamp(
|
|
873
922
|
status.get('replica-in-sync-at'), unset_value="never"),
|
|
874
923
|
update_from_master_at=format_timestamp(
|
|
875
|
-
|
|
924
|
+
update_from_primary_at, unset_value="never"),
|
|
876
925
|
serial=status.get('serial', 'unknown'),
|
|
877
926
|
last_commit_timestamp=format_timestamp(
|
|
878
927
|
status.get('last-commit-timestamp', 'unknown')),
|