devpi-web 4.2.2__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.2 → devpi-web-4.2.3}/CHANGELOG +16 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/PKG-INFO +17 -32
- devpi-web-4.2.3/devpi_web/__init__.py +1 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/clear_index.py +1 -1
- devpi-web-4.2.3/devpi_web/compat.py +42 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/doczip.py +11 -6
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/main.py +3 -1
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/views.py +96 -47
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/whoosh_index.py +19 -14
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web.egg-info/PKG-INFO +17 -32
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web.egg-info/SOURCES.txt +1 -1
- {devpi_web-4.2.2 → 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.2 → devpi-web-4.2.3}/setup.py +2 -1
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/conftest.py +8 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_indexing.py +14 -10
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_main.py +4 -3
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_views.py +9 -2
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_views_docs.py +3 -3
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_views_misc.py +124 -42
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_views_search.py +3 -2
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_views_toxresults.py +2 -1
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tox.ini +12 -2
- devpi_web-4.2.2/devpi_web/__init__.py +0 -1
- devpi_web-4.2.2/devpi_web/static/style-old.css +0 -847
- devpi_web-4.2.2/pyproject.toml +0 -26
- {devpi_web-4.2.2 → devpi-web-4.2.3}/.flake8 +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/AUTHORS +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/LICENSE +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/MANIFEST.in +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/README.rst +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/config.py +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/description.py +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/hookspecs.py +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/indexing.py +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/null_index.py +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/static/common.js +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/static/docview.js +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/static/favicon.ico +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/static/jquery-3.6.0.min.js +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/static/style.css +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/doc.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/error.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/index.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/macros.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/notfound.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/project.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/root.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/search.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/search_help.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/status.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/toxresult.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/toxresults.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/user.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web/templates/version.pt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web.egg-info/dependency_links.txt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web.egg-info/entry_points.txt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web.egg-info/not-zip-safe +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/devpi_web.egg-info/top_level.txt +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/setup.cfg +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_theme.py +0 -0
- {devpi_web-4.2.2 → devpi-web-4.2.3}/tests/test_whoosh_index.py +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
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
|
+
|
|
5
21
|
4.2.2 (2024-04-20)
|
|
6
22
|
==================
|
|
7
23
|
|
|
@@ -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
|
|
@@ -31,16 +31,6 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
31
31
|
Requires-Python: >=3.4
|
|
32
32
|
License-File: LICENSE
|
|
33
33
|
License-File: AUTHORS
|
|
34
|
-
Requires-Dist: Whoosh<3
|
|
35
|
-
Requires-Dist: beautifulsoup4!=4.12.1,>=4.3.2
|
|
36
|
-
Requires-Dist: defusedxml
|
|
37
|
-
Requires-Dist: devpi-server>=5.2.0
|
|
38
|
-
Requires-Dist: devpi-common>=3.2.0
|
|
39
|
-
Requires-Dist: docutils>=0.11
|
|
40
|
-
Requires-Dist: pygments>=1.6
|
|
41
|
-
Requires-Dist: pyramid!=1.10a1
|
|
42
|
-
Requires-Dist: pyramid-chameleon
|
|
43
|
-
Requires-Dist: readme-renderer[md]>=23.0
|
|
44
34
|
|
|
45
35
|
================================================
|
|
46
36
|
devpi-web: web interface plugin for devpi-server
|
|
@@ -87,6 +77,22 @@ Changelog
|
|
|
87
77
|
|
|
88
78
|
.. towncrier release notes start
|
|
89
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
|
+
|
|
90
96
|
4.2.2 (2024-04-20)
|
|
91
97
|
==================
|
|
92
98
|
|
|
@@ -148,24 +154,3 @@ Bug Fixes
|
|
|
148
154
|
|
|
149
155
|
- Generalize GET redirect from URLs with trailing slash to ones without for consistency.
|
|
150
156
|
|
|
151
|
-
|
|
152
|
-
4.1.0 (2022-08-16)
|
|
153
|
-
==================
|
|
154
|
-
|
|
155
|
-
Features
|
|
156
|
-
--------
|
|
157
|
-
|
|
158
|
-
- Support original project names for mirrors with devpi-server 6.6.0.
|
|
159
|
-
|
|
160
|
-
- project.pt: Fix #601: add refresh button to project page.
|
|
161
|
-
|
|
162
|
-
- project.pt: add navigation links to original project page for mirrors and to "Simple page" of projects.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
Bug Fixes
|
|
166
|
-
---------
|
|
167
|
-
|
|
168
|
-
- Fix #880: explicitly register views for GET method, so there is a 404 on POSTs, like when using twine with wrong upload url.
|
|
169
|
-
|
|
170
|
-
- Fix #867: AttributeError during ``devpi-import`` and maybe other commands.
|
|
171
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '4.2.3'
|
|
@@ -31,7 +31,7 @@ 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( # noqa:
|
|
34
|
+
log.warn( # noqa: G010 - this is actually TagLogger from devpi-server, but devpi-web still needs to support older devpi-server versions
|
|
35
35
|
"You should stop devpi-server before running this command.")
|
|
36
36
|
ix = get_indexer(xom)
|
|
37
37
|
ix.delete_index()
|
|
@@ -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
16
|
fcntl = None # type: ignore[assignment]
|
|
12
|
-
import json
|
|
13
|
-
import py
|
|
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
|
|
@@ -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))
|
|
@@ -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')),
|
|
@@ -2,14 +2,16 @@ from collections import defaultdict
|
|
|
2
2
|
from devpi_common.validation import normalize_name
|
|
3
3
|
from devpi_server.log import threadlog as log
|
|
4
4
|
from devpi_server.log import thread_push_log
|
|
5
|
-
from devpi_server.main import fatal
|
|
6
5
|
from devpi_server.readonly import get_mutable_deepcopy
|
|
7
6
|
from devpi_server import mythread
|
|
7
|
+
from devpi_web.compat import fatal
|
|
8
|
+
from devpi_web.compat import read_transaction
|
|
8
9
|
from devpi_web.indexing import iter_projects
|
|
9
10
|
from devpi_web.indexing import ProjectIndexingInfo
|
|
10
11
|
from devpi_web.indexing import is_project_cached
|
|
11
12
|
from devpi_web.indexing import preprocess_project
|
|
12
13
|
from devpi_web.main import get_indexer
|
|
14
|
+
from pathlib import Path
|
|
13
15
|
from pluggy import HookimplMarker
|
|
14
16
|
from whoosh import fields
|
|
15
17
|
from whoosh.analysis import Filter, LowercaseFilter, RegexTokenizer
|
|
@@ -24,7 +26,6 @@ from whoosh.searching import ResultsPage
|
|
|
24
26
|
from whoosh.util.text import rcompile
|
|
25
27
|
import itertools
|
|
26
28
|
import os
|
|
27
|
-
import py
|
|
28
29
|
import shutil
|
|
29
30
|
import sys
|
|
30
31
|
import time
|
|
@@ -400,7 +401,7 @@ class IndexerThread(object):
|
|
|
400
401
|
writer = project_ix.writer()
|
|
401
402
|
searcher = project_ix.searcher()
|
|
402
403
|
try:
|
|
403
|
-
with self.xom.keyfs
|
|
404
|
+
with read_transaction(self.xom.keyfs) as tx:
|
|
404
405
|
stage = self.xom.model.getstage(indexname)
|
|
405
406
|
if stage is not None:
|
|
406
407
|
for name in names:
|
|
@@ -435,6 +436,7 @@ class IndexerThread(object):
|
|
|
435
436
|
writer.commit()
|
|
436
437
|
|
|
437
438
|
def tick(self):
|
|
439
|
+
self.thread.exit_if_shutdown()
|
|
438
440
|
self.shared_data.process_next(self.handler)
|
|
439
441
|
|
|
440
442
|
def thread_run(self):
|
|
@@ -470,7 +472,7 @@ class InitialQueueThread(object):
|
|
|
470
472
|
|
|
471
473
|
def thread_run(self):
|
|
472
474
|
thread_push_log("[IDXQ]")
|
|
473
|
-
with self.xom.keyfs
|
|
475
|
+
with read_transaction(self.xom.keyfs) as tx:
|
|
474
476
|
indexer = get_indexer(self.xom)
|
|
475
477
|
searcher = indexer.get_project_ix().searcher()
|
|
476
478
|
self.shared_data.queue_projects(
|
|
@@ -489,20 +491,23 @@ def setup_thread(xom):
|
|
|
489
491
|
return indexer_thread
|
|
490
492
|
|
|
491
493
|
|
|
492
|
-
class Index
|
|
494
|
+
class Index:
|
|
493
495
|
SearchUnavailableException = SearchUnavailableException
|
|
494
496
|
|
|
495
497
|
def __init__(self, config, settings):
|
|
496
498
|
if 'path' not in settings:
|
|
497
|
-
|
|
499
|
+
if hasattr(config, "server_path"):
|
|
500
|
+
index_path = config.server_path / '.indices'
|
|
501
|
+
else:
|
|
502
|
+
index_path = Path(str(config.serverdir.join('.indices')))
|
|
498
503
|
else:
|
|
499
504
|
index_path = settings['path']
|
|
500
505
|
if not os.path.isabs(index_path):
|
|
501
506
|
fatal("The path for Whoosh index files must be absolute.")
|
|
502
|
-
index_path =
|
|
503
|
-
index_path.
|
|
504
|
-
log.info("Using %s for Whoosh index files."
|
|
505
|
-
self.index_path = index_path
|
|
507
|
+
index_path = Path(index_path)
|
|
508
|
+
index_path.mkdir(parents=True, exist_ok=True)
|
|
509
|
+
log.info("Using %s for Whoosh index files.", index_path)
|
|
510
|
+
self.index_path = str(index_path)
|
|
506
511
|
self.indexer_thread = None
|
|
507
512
|
self.shared_data = None
|
|
508
513
|
self.xom = None
|
|
@@ -915,10 +920,10 @@ def devpiweb_get_status_info(request):
|
|
|
915
920
|
last_activity_seconds = (now - shared_data.last_added)
|
|
916
921
|
elif shared_data.last_processed:
|
|
917
922
|
last_activity_seconds = (now - shared_data.last_processed)
|
|
918
|
-
if last_activity_seconds >
|
|
919
|
-
msgs.append(dict(status="fatal", msg="Nothing indexed for more than
|
|
920
|
-
elif last_activity_seconds >
|
|
921
|
-
msgs.append(dict(status="warn", msg="Nothing indexed for more than
|
|
923
|
+
if last_activity_seconds > 3600:
|
|
924
|
+
msgs.append(dict(status="fatal", msg="Nothing indexed for more than 60 minutes"))
|
|
925
|
+
elif last_activity_seconds > 300:
|
|
926
|
+
msgs.append(dict(status="warn", msg="Nothing indexed for more than 5 minutes"))
|
|
922
927
|
if qsize > 10:
|
|
923
928
|
msgs.append(dict(status="warn", msg="%s items in index queue" % qsize))
|
|
924
929
|
error_qsize = shared_data.error_queue.qsize()
|
|
@@ -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
|
|
@@ -31,16 +31,6 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
31
31
|
Requires-Python: >=3.4
|
|
32
32
|
License-File: LICENSE
|
|
33
33
|
License-File: AUTHORS
|
|
34
|
-
Requires-Dist: Whoosh<3
|
|
35
|
-
Requires-Dist: beautifulsoup4!=4.12.1,>=4.3.2
|
|
36
|
-
Requires-Dist: defusedxml
|
|
37
|
-
Requires-Dist: devpi-server>=5.2.0
|
|
38
|
-
Requires-Dist: devpi-common>=3.2.0
|
|
39
|
-
Requires-Dist: docutils>=0.11
|
|
40
|
-
Requires-Dist: pygments>=1.6
|
|
41
|
-
Requires-Dist: pyramid!=1.10a1
|
|
42
|
-
Requires-Dist: pyramid-chameleon
|
|
43
|
-
Requires-Dist: readme-renderer[md]>=23.0
|
|
44
34
|
|
|
45
35
|
================================================
|
|
46
36
|
devpi-web: web interface plugin for devpi-server
|
|
@@ -87,6 +77,22 @@ Changelog
|
|
|
87
77
|
|
|
88
78
|
.. towncrier release notes start
|
|
89
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
|
+
|
|
90
96
|
4.2.2 (2024-04-20)
|
|
91
97
|
==================
|
|
92
98
|
|
|
@@ -148,24 +154,3 @@ Bug Fixes
|
|
|
148
154
|
|
|
149
155
|
- Generalize GET redirect from URLs with trailing slash to ones without for consistency.
|
|
150
156
|
|
|
151
|
-
|
|
152
|
-
4.1.0 (2022-08-16)
|
|
153
|
-
==================
|
|
154
|
-
|
|
155
|
-
Features
|
|
156
|
-
--------
|
|
157
|
-
|
|
158
|
-
- Support original project names for mirrors with devpi-server 6.6.0.
|
|
159
|
-
|
|
160
|
-
- project.pt: Fix #601: add refresh button to project page.
|
|
161
|
-
|
|
162
|
-
- project.pt: add navigation links to original project page for mirrors and to "Simple page" of projects.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
Bug Fixes
|
|
166
|
-
---------
|
|
167
|
-
|
|
168
|
-
- Fix #880: explicitly register views for GET method, so there is a 404 on POSTs, like when using twine with wrong upload url.
|
|
169
|
-
|
|
170
|
-
- Fix #867: AttributeError during ``devpi-import`` and maybe other commands.
|
|
171
|
-
|
|
@@ -10,6 +10,7 @@ setup.py
|
|
|
10
10
|
tox.ini
|
|
11
11
|
devpi_web/__init__.py
|
|
12
12
|
devpi_web/clear_index.py
|
|
13
|
+
devpi_web/compat.py
|
|
13
14
|
devpi_web/config.py
|
|
14
15
|
devpi_web/description.py
|
|
15
16
|
devpi_web/doczip.py
|
|
@@ -30,7 +31,6 @@ devpi_web/static/common.js
|
|
|
30
31
|
devpi_web/static/docview.js
|
|
31
32
|
devpi_web/static/favicon.ico
|
|
32
33
|
devpi_web/static/jquery-3.6.0.min.js
|
|
33
|
-
devpi_web/static/style-old.css
|
|
34
34
|
devpi_web/static/style.css
|
|
35
35
|
devpi_web/templates/doc.pt
|
|
36
36
|
devpi_web/templates/error.pt
|