oarepo-runtime 1.10.2__py3-none-any.whl → 2.0.0.dev3__py3-none-any.whl
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.
- oarepo_runtime/__init__.py +24 -0
- oarepo_runtime/api.py +111 -0
- oarepo_runtime/cli/__init__.py +10 -21
- oarepo_runtime/cli/search.py +34 -0
- oarepo_runtime/config.py +86 -13
- oarepo_runtime/ext.py +64 -82
- oarepo_runtime/proxies.py +21 -5
- oarepo_runtime/records/__init__.py +11 -50
- oarepo_runtime/records/drafts.py +24 -18
- oarepo_runtime/records/mapping.py +84 -0
- oarepo_runtime/records/pid_providers.py +43 -7
- oarepo_runtime/records/systemfields/__init__.py +15 -33
- oarepo_runtime/records/systemfields/mapping.py +41 -24
- oarepo_runtime/records/systemfields/publication_status.py +59 -0
- oarepo_runtime/services/__init__.py +12 -0
- oarepo_runtime/services/config/__init__.py +15 -21
- oarepo_runtime/services/config/link_conditions.py +69 -75
- oarepo_runtime/services/config/permissions.py +62 -0
- oarepo_runtime/services/records/__init__.py +14 -1
- oarepo_runtime/services/records/links.py +21 -11
- oarepo_runtime/services/records/mapping.py +42 -0
- oarepo_runtime/services/results.py +98 -109
- oarepo_runtime/services/schema/__init__.py +12 -44
- oarepo_runtime/services/schema/i18n.py +47 -22
- oarepo_runtime/services/schema/i18n_ui.py +61 -24
- {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/METADATA +9 -21
- oarepo_runtime-2.0.0.dev3.dist-info/RECORD +30 -0
- {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/WHEEL +1 -2
- oarepo_runtime-2.0.0.dev3.dist-info/entry_points.txt +5 -0
- oarepo_runtime/cli/assets.py +0 -145
- oarepo_runtime/cli/base.py +0 -25
- oarepo_runtime/cli/cf.py +0 -15
- oarepo_runtime/cli/check.py +0 -167
- oarepo_runtime/cli/configuration.py +0 -51
- oarepo_runtime/cli/fixtures.py +0 -167
- oarepo_runtime/cli/index.py +0 -272
- oarepo_runtime/cli/permissions/__init__.py +0 -6
- oarepo_runtime/cli/permissions/base.py +0 -26
- oarepo_runtime/cli/permissions/evaluate.py +0 -63
- oarepo_runtime/cli/permissions/list.py +0 -239
- oarepo_runtime/cli/permissions/search.py +0 -121
- oarepo_runtime/cli/validate.py +0 -150
- oarepo_runtime/datastreams/__init__.py +0 -38
- oarepo_runtime/datastreams/asynchronous.py +0 -247
- oarepo_runtime/datastreams/catalogue.py +0 -150
- oarepo_runtime/datastreams/datastreams.py +0 -152
- oarepo_runtime/datastreams/errors.py +0 -54
- oarepo_runtime/datastreams/ext.py +0 -41
- oarepo_runtime/datastreams/fixtures.py +0 -265
- oarepo_runtime/datastreams/json.py +0 -4
- oarepo_runtime/datastreams/readers/__init__.py +0 -39
- oarepo_runtime/datastreams/readers/attachments.py +0 -51
- oarepo_runtime/datastreams/readers/excel.py +0 -123
- oarepo_runtime/datastreams/readers/json.py +0 -27
- oarepo_runtime/datastreams/readers/service.py +0 -54
- oarepo_runtime/datastreams/readers/yaml.py +0 -14
- oarepo_runtime/datastreams/semi_asynchronous.py +0 -91
- oarepo_runtime/datastreams/synchronous.py +0 -70
- oarepo_runtime/datastreams/transformers.py +0 -18
- oarepo_runtime/datastreams/types.py +0 -323
- oarepo_runtime/datastreams/utils.py +0 -131
- oarepo_runtime/datastreams/writers/__init__.py +0 -21
- oarepo_runtime/datastreams/writers/attachments_file.py +0 -92
- oarepo_runtime/datastreams/writers/attachments_service.py +0 -118
- oarepo_runtime/datastreams/writers/publish.py +0 -70
- oarepo_runtime/datastreams/writers/service.py +0 -175
- oarepo_runtime/datastreams/writers/utils.py +0 -30
- oarepo_runtime/datastreams/writers/validation_errors.py +0 -20
- oarepo_runtime/datastreams/writers/yaml.py +0 -56
- oarepo_runtime/ext_config.py +0 -67
- oarepo_runtime/i18n/__init__.py +0 -3
- oarepo_runtime/info/__init__.py +0 -0
- oarepo_runtime/info/check.py +0 -95
- oarepo_runtime/info/permissions/__init__.py +0 -0
- oarepo_runtime/info/permissions/debug.py +0 -191
- oarepo_runtime/info/views.py +0 -586
- oarepo_runtime/profile.py +0 -60
- oarepo_runtime/records/dumpers/__init__.py +0 -8
- oarepo_runtime/records/dumpers/edtf_interval.py +0 -38
- oarepo_runtime/records/dumpers/multilingual_dumper.py +0 -34
- oarepo_runtime/records/entity_resolvers/__init__.py +0 -13
- oarepo_runtime/records/entity_resolvers/proxies.py +0 -57
- oarepo_runtime/records/mappings/__init__.py +0 -0
- oarepo_runtime/records/mappings/rdm_parent_mapping.json +0 -483
- oarepo_runtime/records/owners/__init__.py +0 -3
- oarepo_runtime/records/owners/registry.py +0 -22
- oarepo_runtime/records/relations/__init__.py +0 -22
- oarepo_runtime/records/relations/base.py +0 -296
- oarepo_runtime/records/relations/internal.py +0 -46
- oarepo_runtime/records/relations/lookup.py +0 -28
- oarepo_runtime/records/relations/pid_relation.py +0 -102
- oarepo_runtime/records/systemfields/featured_file.py +0 -45
- oarepo_runtime/records/systemfields/has_draftcheck.py +0 -47
- oarepo_runtime/records/systemfields/icu.py +0 -371
- oarepo_runtime/records/systemfields/owner.py +0 -115
- oarepo_runtime/records/systemfields/record_status.py +0 -35
- oarepo_runtime/records/systemfields/selectors.py +0 -98
- oarepo_runtime/records/systemfields/synthetic.py +0 -130
- oarepo_runtime/resources/__init__.py +0 -4
- oarepo_runtime/resources/config.py +0 -12
- oarepo_runtime/resources/file_resource.py +0 -15
- oarepo_runtime/resources/json_serializer.py +0 -27
- oarepo_runtime/resources/localized_ui_json_serializer.py +0 -54
- oarepo_runtime/resources/resource.py +0 -53
- oarepo_runtime/resources/responses.py +0 -20
- oarepo_runtime/services/components.py +0 -429
- oarepo_runtime/services/config/draft_link.py +0 -23
- oarepo_runtime/services/config/permissions_presets.py +0 -174
- oarepo_runtime/services/config/service.py +0 -117
- oarepo_runtime/services/custom_fields/__init__.py +0 -80
- oarepo_runtime/services/custom_fields/mappings.py +0 -188
- oarepo_runtime/services/entity/__init__.py +0 -0
- oarepo_runtime/services/entity/config.py +0 -14
- oarepo_runtime/services/entity/schema.py +0 -9
- oarepo_runtime/services/entity/service.py +0 -48
- oarepo_runtime/services/expansions/__init__.py +0 -0
- oarepo_runtime/services/expansions/expandable_fields.py +0 -21
- oarepo_runtime/services/expansions/service.py +0 -4
- oarepo_runtime/services/facets/__init__.py +0 -33
- oarepo_runtime/services/facets/base.py +0 -12
- oarepo_runtime/services/facets/date.py +0 -72
- oarepo_runtime/services/facets/enum.py +0 -11
- oarepo_runtime/services/facets/facet_groups_names.py +0 -17
- oarepo_runtime/services/facets/max_facet.py +0 -13
- oarepo_runtime/services/facets/multilingual_facet.py +0 -33
- oarepo_runtime/services/facets/nested_facet.py +0 -32
- oarepo_runtime/services/facets/params.py +0 -192
- oarepo_runtime/services/facets/year_histogram.py +0 -200
- oarepo_runtime/services/files/__init__.py +0 -8
- oarepo_runtime/services/files/components.py +0 -62
- oarepo_runtime/services/files/service.py +0 -16
- oarepo_runtime/services/generators.py +0 -10
- oarepo_runtime/services/permissions/__init__.py +0 -3
- oarepo_runtime/services/permissions/generators.py +0 -103
- oarepo_runtime/services/relations/__init__.py +0 -0
- oarepo_runtime/services/relations/components.py +0 -15
- oarepo_runtime/services/relations/errors.py +0 -18
- oarepo_runtime/services/relations/mapping.py +0 -38
- oarepo_runtime/services/schema/cf.py +0 -13
- oarepo_runtime/services/schema/i18n_validation.py +0 -7
- oarepo_runtime/services/schema/marshmallow.py +0 -44
- oarepo_runtime/services/schema/marshmallow_to_json_schema.py +0 -72
- oarepo_runtime/services/schema/oneofschema.py +0 -192
- oarepo_runtime/services/schema/polymorphic.py +0 -21
- oarepo_runtime/services/schema/rdm.py +0 -75
- oarepo_runtime/services/schema/rdm_ui.py +0 -156
- oarepo_runtime/services/schema/ui.py +0 -251
- oarepo_runtime/services/schema/validation.py +0 -70
- oarepo_runtime/services/search.py +0 -282
- oarepo_runtime/services/service.py +0 -61
- oarepo_runtime/tasks.py +0 -6
- oarepo_runtime/translations/cs/LC_MESSAGES/messages.mo +0 -0
- oarepo_runtime/translations/cs/LC_MESSAGES/messages.po +0 -85
- oarepo_runtime/translations/default_translations.py +0 -6
- oarepo_runtime/translations/en/LC_MESSAGES/messages.mo +0 -0
- oarepo_runtime/translations/en/LC_MESSAGES/messages.po +0 -89
- oarepo_runtime/translations/messages.pot +0 -91
- oarepo_runtime/uow.py +0 -146
- oarepo_runtime/utils/__init__.py +0 -0
- oarepo_runtime/utils/functools.py +0 -37
- oarepo_runtime/utils/identity_utils.py +0 -35
- oarepo_runtime/utils/index.py +0 -11
- oarepo_runtime/utils/path.py +0 -97
- oarepo_runtime-1.10.2.dist-info/RECORD +0 -163
- oarepo_runtime-1.10.2.dist-info/entry_points.txt +0 -16
- oarepo_runtime-1.10.2.dist-info/top_level.txt +0 -2
- tests/marshmallow_to_json/__init__.py +0 -0
- tests/marshmallow_to_json/test_datacite_ui_schema.py +0 -1410
- tests/marshmallow_to_json/test_simple_schema.py +0 -52
- tests/pkg_data/__init__.py +0 -0
- {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,16 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 CESNET z.s.p.o.
|
3
|
+
#
|
4
|
+
# This file is a part of oarepo-runtime (see http://github.com/oarepo/oarepo-runtime).
|
5
|
+
#
|
6
|
+
# oarepo-runtime is free software; you can redistribute it and/or modify it
|
7
|
+
# under the terms of the MIT License; see LICENSE file for more details.
|
8
|
+
#
|
9
|
+
|
10
|
+
"""Service records module."""
|
11
|
+
|
12
|
+
from __future__ import annotations
|
13
|
+
|
1
14
|
from .links import pagination_links_html
|
2
15
|
|
3
|
-
__all__ = ("pagination_links_html",)
|
16
|
+
__all__ = ("pagination_links_html",)
|
@@ -1,21 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 CESNET z.s.p.o.
|
3
|
+
#
|
4
|
+
# This file is a part of oarepo-runtime (see http://github.com/oarepo/oarepo-runtime).
|
5
|
+
#
|
6
|
+
# oarepo-runtime is free software; you can redistribute it and/or modify it
|
7
|
+
# under the terms of the MIT License; see LICENSE file for more details.
|
8
|
+
#
|
9
|
+
|
10
|
+
"""Utility for rendering URI template links."""
|
11
|
+
|
12
|
+
from __future__ import annotations
|
13
|
+
|
1
14
|
from invenio_records_resources.services.base.links import Link
|
2
15
|
|
3
|
-
|
4
|
-
|
16
|
+
|
17
|
+
def pagination_links_html(tpl: str) -> dict[str, Link]:
|
18
|
+
"""Create pagination links (prev/self/next) from the same template."""
|
5
19
|
return {
|
6
20
|
"prev_html": Link(
|
7
21
|
tpl,
|
8
|
-
when=lambda pagination,
|
9
|
-
vars=lambda pagination,
|
10
|
-
{"page": pagination.prev_page.page}
|
11
|
-
),
|
22
|
+
when=lambda pagination, _context: pagination.has_prev,
|
23
|
+
vars=lambda pagination, variables: variables["args"].update({"page": pagination.prev_page.page}),
|
12
24
|
),
|
13
25
|
"self_html": Link(tpl),
|
14
26
|
"next_html": Link(
|
15
27
|
tpl,
|
16
|
-
when=lambda pagination,
|
17
|
-
vars=lambda pagination,
|
18
|
-
{"page": pagination.next_page.page}
|
19
|
-
),
|
28
|
+
when=lambda pagination, _context: pagination.has_next,
|
29
|
+
vars=lambda pagination, variables: variables["args"].update({"page": pagination.next_page.page}),
|
20
30
|
),
|
21
|
-
}
|
31
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 CESNET z.s.p.o.
|
3
|
+
#
|
4
|
+
# This file is a part of oarepo-runtime (see http://github.com/oarepo/oarepo-runtime).
|
5
|
+
#
|
6
|
+
# oarepo-runtime is free software; you can redistribute it and/or modify it
|
7
|
+
# under the terms of the MIT License; see LICENSE file for more details.
|
8
|
+
#
|
9
|
+
"""Update mappings for all record classes in the service registry."""
|
10
|
+
|
11
|
+
from __future__ import annotations
|
12
|
+
|
13
|
+
from typing import TYPE_CHECKING
|
14
|
+
|
15
|
+
from invenio_records_resources.services.records import (
|
16
|
+
RecordService,
|
17
|
+
RecordServiceConfig,
|
18
|
+
)
|
19
|
+
|
20
|
+
from oarepo_runtime import current_runtime
|
21
|
+
from oarepo_runtime.records.mapping import update_record_system_fields_mapping
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from invenio_records_resources.services.base import Service
|
25
|
+
|
26
|
+
|
27
|
+
def update_all_records_mappings() -> None:
|
28
|
+
"""Update all mappings for the registered record classes."""
|
29
|
+
service: Service
|
30
|
+
for service in current_runtime.services.values():
|
31
|
+
if not isinstance(service, RecordService):
|
32
|
+
continue
|
33
|
+
|
34
|
+
config: RecordServiceConfig = service.config
|
35
|
+
|
36
|
+
record_class = getattr(config, "record_cls", None)
|
37
|
+
if record_class:
|
38
|
+
update_record_system_fields_mapping(record_class)
|
39
|
+
|
40
|
+
draft_class = getattr(config, "draft_cls", None)
|
41
|
+
if draft_class:
|
42
|
+
update_record_system_fields_mapping(draft_class)
|
@@ -1,5 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 CESNET z.s.p.o.
|
3
|
+
#
|
4
|
+
# This file is a part of oarepo-runtime (see http://github.com/oarepo/oarepo-runtime).
|
5
|
+
#
|
6
|
+
# oarepo-runtime is free software; you can redistribute it and/or modify it
|
7
|
+
# under the terms of the MIT License; see LICENSE file for more details.
|
8
|
+
#
|
9
|
+
|
10
|
+
"""Service results."""
|
11
|
+
|
12
|
+
from __future__ import annotations
|
13
|
+
|
1
14
|
import logging
|
15
|
+
from typing import TYPE_CHECKING, Any
|
2
16
|
|
17
|
+
from invenio_access.permissions import Identity
|
18
|
+
from invenio_records.api import RecordBase
|
3
19
|
from invenio_records_resources.errors import _iter_errors_dict
|
4
20
|
from invenio_records_resources.services.records.results import (
|
5
21
|
RecordItem as BaseRecordItem,
|
@@ -8,26 +24,50 @@ from invenio_records_resources.services.records.results import (
|
|
8
24
|
RecordList as BaseRecordList,
|
9
25
|
)
|
10
26
|
|
27
|
+
if TYPE_CHECKING:
|
28
|
+
from invenio_access.permissions import Identity
|
29
|
+
from invenio_records.api import RecordBase
|
30
|
+
|
11
31
|
log = logging.getLogger(__name__)
|
12
32
|
|
13
33
|
|
14
|
-
class
|
15
|
-
|
16
|
-
|
34
|
+
class ResultComponent:
|
35
|
+
"""Base class for result components that can modify the serialized record data."""
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
record_item: BaseRecordItem | None = None,
|
40
|
+
record_list: BaseRecordList | None = None,
|
41
|
+
):
|
42
|
+
"""Initialize the result component."""
|
43
|
+
self._record_item = record_item
|
44
|
+
self._record_list = record_list
|
45
|
+
|
46
|
+
def update_data(self, identity: Identity, record: RecordBase, projection: dict, expand: bool) -> None:
|
47
|
+
"""Update the projection data with additional information.
|
48
|
+
|
49
|
+
:param identity: The identity of the user making the request.
|
50
|
+
:param record: The record being processed.
|
51
|
+
:param projection: The current projection of the record.
|
52
|
+
:param expand: Whether to expand the record data.
|
53
|
+
"""
|
54
|
+
raise NotImplementedError # pragma: no cover
|
17
55
|
|
18
56
|
|
19
57
|
class RecordItem(BaseRecordItem):
|
20
58
|
"""Single record result."""
|
21
59
|
|
22
|
-
components =
|
60
|
+
components: tuple[type[ResultComponent], ...] = ()
|
61
|
+
"""A list of components that can modify the serialized record data."""
|
23
62
|
|
24
63
|
@property
|
25
|
-
def data(self):
|
64
|
+
def data(self) -> Any:
|
65
|
+
"""Property to get the record."""
|
26
66
|
if self._data:
|
27
67
|
return self._data
|
28
68
|
_data = super().data
|
29
69
|
for c in self.components:
|
30
|
-
c.update_data(
|
70
|
+
c(record_item=self).update_data(
|
31
71
|
identity=self._identity,
|
32
72
|
record=self._record,
|
33
73
|
projection=_data,
|
@@ -36,57 +76,55 @@ class RecordItem(BaseRecordItem):
|
|
36
76
|
return _data
|
37
77
|
|
38
78
|
@property
|
39
|
-
def errors(self):
|
40
|
-
|
79
|
+
def errors(self) -> list[dict]:
|
80
|
+
"""Get the processed errors."""
|
81
|
+
return self.postprocess_errors(self._errors or [])
|
41
82
|
|
42
|
-
def to_dict(self):
|
83
|
+
def to_dict(self) -> Any:
|
43
84
|
"""Get a dictionary for the record."""
|
44
85
|
res = self.data
|
45
86
|
if self._errors:
|
46
87
|
res["errors"] = self.errors
|
47
88
|
return res
|
48
89
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
yield {"field": field_path, "messages": messages}
|
54
|
-
else:
|
55
|
-
str_messages = [msg for msg in messages if isinstance(msg, str)]
|
56
|
-
non_str_messages = [msg for msg in messages if not isinstance(msg, str)]
|
57
|
-
|
58
|
-
if str_messages:
|
59
|
-
yield {"field": field_path, "messages": str_messages}
|
60
|
-
else:
|
61
|
-
for non_str_msg in non_str_messages:
|
62
|
-
yield from _iter_errors_dict(non_str_msg, field_path)
|
63
|
-
|
64
|
-
|
65
|
-
def postprocess_errors(errors: list[dict]):
|
66
|
-
"""Postprocess errors."""
|
67
|
-
converted_errors = []
|
68
|
-
for error in errors:
|
69
|
-
if error.get("messages"):
|
70
|
-
converted_errors.extend(
|
71
|
-
postprocess_error_messages(error["field"], error["messages"])
|
72
|
-
)
|
90
|
+
def postprocess_error_messages(self, field_path: str, messages: Any) -> Any:
|
91
|
+
"""Postprocess error messages, looking for those that were not correctly processed by marshmallow/invenio."""
|
92
|
+
if not isinstance(messages, list):
|
93
|
+
yield {"field": field_path, "messages": messages}
|
73
94
|
else:
|
74
|
-
|
75
|
-
|
95
|
+
str_messages = [msg for msg in messages if isinstance(msg, str)]
|
96
|
+
non_str_messages = [msg for msg in messages if not isinstance(msg, str)]
|
97
|
+
|
98
|
+
if str_messages:
|
99
|
+
yield {"field": field_path, "messages": str_messages}
|
100
|
+
else:
|
101
|
+
for non_str_msg in non_str_messages:
|
102
|
+
yield from _iter_errors_dict(non_str_msg, field_path)
|
103
|
+
|
104
|
+
def postprocess_errors(self, errors: list[dict]) -> list[dict]:
|
105
|
+
"""Postprocess errors."""
|
106
|
+
converted_errors = []
|
107
|
+
for error in errors:
|
108
|
+
if error.get("messages"):
|
109
|
+
converted_errors.extend(self.postprocess_error_messages(error["field"], error["messages"]))
|
110
|
+
else:
|
111
|
+
converted_errors.append(error)
|
112
|
+
return converted_errors
|
76
113
|
|
77
114
|
|
78
115
|
class RecordList(BaseRecordList):
|
79
|
-
|
116
|
+
"""List of records result."""
|
117
|
+
|
118
|
+
components: tuple[type[ResultComponent], ...] = ()
|
80
119
|
|
81
120
|
@property
|
82
|
-
def aggregations(self):
|
121
|
+
def aggregations(self) -> Any:
|
83
122
|
"""Get the search result aggregations."""
|
84
123
|
try:
|
85
124
|
result = super().aggregations
|
86
125
|
if result is None:
|
87
|
-
return result
|
88
|
-
|
89
|
-
for key in result.keys():
|
126
|
+
return result # pragma: no cover
|
127
|
+
for key in result:
|
90
128
|
if "buckets" in result[key]:
|
91
129
|
for bucket in result[key]["buckets"]:
|
92
130
|
val = bucket["key"]
|
@@ -96,12 +134,12 @@ class RecordList(BaseRecordList):
|
|
96
134
|
bucket["key"] = str(val)
|
97
135
|
if not isinstance(label, str):
|
98
136
|
bucket["label"] = str(label)
|
99
|
-
|
100
|
-
|
101
|
-
|
137
|
+
except AttributeError: # pragma: no cover
|
138
|
+
return None # pragma: no cover
|
139
|
+
return result
|
102
140
|
|
103
141
|
@property
|
104
|
-
def hits(self):
|
142
|
+
def hits(self) -> Any:
|
105
143
|
"""Iterator over the hits."""
|
106
144
|
for hit in self._results:
|
107
145
|
# Load dump
|
@@ -109,87 +147,38 @@ class RecordList(BaseRecordList):
|
|
109
147
|
|
110
148
|
try:
|
111
149
|
# Project the record
|
112
|
-
if
|
150
|
+
# TODO: check if this logic is correct
|
151
|
+
versions = hit_dict.get("versions", {})
|
152
|
+
if versions.get("is_latest_draft") and not versions.get("is_latest"):
|
113
153
|
record = self._service.draft_cls.loads(hit_dict)
|
114
154
|
else:
|
115
155
|
record = self._service.record_cls.loads(hit_dict)
|
116
156
|
|
117
157
|
projection = self._schema.dump(
|
118
158
|
record,
|
119
|
-
context=
|
120
|
-
identity
|
121
|
-
record
|
122
|
-
|
159
|
+
context={
|
160
|
+
"identity": self._identity,
|
161
|
+
"record": record,
|
162
|
+
},
|
123
163
|
)
|
124
164
|
if hasattr(self._service.config, "links_search_item"):
|
125
|
-
links_tpl = self._service.config.search_item_links_template(
|
126
|
-
|
127
|
-
|
165
|
+
links_tpl = self._service.config.search_item_links_template(self._service.config.links_search_item)
|
166
|
+
else:
|
167
|
+
links_tpl = self._links_item_tpl
|
168
|
+
|
169
|
+
if links_tpl:
|
128
170
|
projection["links"] = links_tpl.expand(self._identity, record)
|
129
|
-
|
130
|
-
|
131
|
-
self._identity, record
|
132
|
-
)
|
133
|
-
# todo optimization viz FieldsResolver
|
171
|
+
|
172
|
+
# TODO: optimization viz FieldsResolver
|
134
173
|
for c in self.components:
|
135
|
-
c.update_data(
|
174
|
+
c(record_list=self).update_data(
|
136
175
|
identity=self._identity,
|
137
176
|
record=record,
|
138
177
|
projection=projection,
|
139
178
|
expand=self._expand,
|
140
179
|
)
|
141
180
|
yield projection
|
142
|
-
except Exception:
|
181
|
+
except Exception: # pragma: no cover
|
143
182
|
# ignore record with error, put it to log so that it gets to glitchtip
|
144
183
|
# but don't break the whole search
|
145
184
|
log.exception("Error while dumping record %s", hit_dict)
|
146
|
-
|
147
|
-
|
148
|
-
class ArrayRecordItem(RecordItem):
|
149
|
-
"""Single record result."""
|
150
|
-
|
151
|
-
@property
|
152
|
-
def id(self):
|
153
|
-
"""Get the record id."""
|
154
|
-
return self._record["id"]
|
155
|
-
|
156
|
-
|
157
|
-
class ArrayRecordList(RecordList):
|
158
|
-
# move to runtime
|
159
|
-
|
160
|
-
@property
|
161
|
-
def total(self):
|
162
|
-
"""Get total number of hits."""
|
163
|
-
return len(self._results)
|
164
|
-
|
165
|
-
@property
|
166
|
-
def aggregations(self):
|
167
|
-
"""Get the search result aggregations."""
|
168
|
-
return None
|
169
|
-
|
170
|
-
@property
|
171
|
-
def hits(self):
|
172
|
-
"""Iterator over the hits."""
|
173
|
-
for hit in self._results:
|
174
|
-
# Project the record
|
175
|
-
projection = self._schema.dump(
|
176
|
-
hit,
|
177
|
-
context=dict(
|
178
|
-
identity=self._identity,
|
179
|
-
record=hit,
|
180
|
-
),
|
181
|
-
)
|
182
|
-
if self._links_item_tpl:
|
183
|
-
projection["links"] = self._links_item_tpl.expand(self._identity, hit)
|
184
|
-
if self._nested_links_item:
|
185
|
-
for link in self._nested_links_item:
|
186
|
-
link.expand(self._identity, hit, projection)
|
187
|
-
|
188
|
-
for c in self.components:
|
189
|
-
c.update_data(
|
190
|
-
identity=self._identity,
|
191
|
-
record=hit,
|
192
|
-
projection=projection,
|
193
|
-
expand=self._expand,
|
194
|
-
)
|
195
|
-
yield projection
|
@@ -1,44 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# remove classes that are already in mro of others
|
14
|
-
filtered_classes = []
|
15
|
-
for cls in classes:
|
16
|
-
for other_cls in classes:
|
17
|
-
if cls != other_cls and issubclass(other_cls, cls):
|
18
|
-
break
|
19
|
-
else:
|
20
|
-
if cls not in filtered_classes:
|
21
|
-
filtered_classes.append(cls)
|
22
|
-
|
23
|
-
name = [cls.__name__ for cls in filtered_classes]
|
24
|
-
name = "".join(name) + "ConsistentResolution"
|
25
|
-
try:
|
26
|
-
return type(name, tuple(filtered_classes), {})
|
27
|
-
except TypeError:
|
28
|
-
pass
|
29
|
-
|
30
|
-
filtered_classes.sort(key=lambda cls: -len(cls.mro()))
|
31
|
-
try:
|
32
|
-
return type(name, tuple(filtered_classes), {})
|
33
|
-
except TypeError:
|
34
|
-
pass
|
35
|
-
|
36
|
-
bases = ", ".join(cls.__name__ for cls in filtered_classes)
|
37
|
-
orig_bases = ", ".join(cls.__name__ for cls in classes)
|
38
|
-
raise TypeError(
|
39
|
-
f"Cannot create a consistent method resolution order (MRO) "
|
40
|
-
f"for bases {orig_bases}, tried {bases}"
|
41
|
-
)
|
42
|
-
|
43
|
-
|
44
|
-
__all__ = ("PolymorphicSchema", "consistent_resolution")
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 CESNET z.s.p.o.
|
3
|
+
#
|
4
|
+
# This file is a part of oarepo-runtime (see http://github.com/oarepo/oarepo-runtime).
|
5
|
+
#
|
6
|
+
# oarepo-runtime is free software; you can redistribute it and/or modify it
|
7
|
+
# under the terms of the MIT License; see LICENSE file for more details.
|
8
|
+
#
|
9
|
+
|
10
|
+
"""Marshmallow schema module."""
|
11
|
+
|
12
|
+
from __future__ import annotations
|
@@ -1,28 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 CESNET z.s.p.o.
|
3
|
+
#
|
4
|
+
# This file is a part of oarepo-runtime (see http://github.com/oarepo/oarepo-runtime).
|
5
|
+
#
|
6
|
+
# oarepo-runtime is free software; you can redistribute it and/or modify it
|
7
|
+
# under the terms of the MIT License; see LICENSE file for more details.
|
8
|
+
#
|
9
|
+
|
10
|
+
"""Marshmallow schema for multilingual strings."""
|
11
|
+
|
12
|
+
from __future__ import annotations
|
13
|
+
|
1
14
|
from functools import lru_cache
|
15
|
+
from typing import Any
|
2
16
|
|
3
17
|
import langcodes
|
18
|
+
import langcodes.tag_parser
|
4
19
|
from invenio_base.utils import obj_or_import_string
|
5
20
|
from invenio_i18n import gettext as _
|
6
21
|
from marshmallow import Schema, ValidationError, fields, pre_load, validates
|
7
22
|
|
8
|
-
"""
|
9
|
-
Marshmallow schema for multilingual strings. Consider moving this file to a library, not generating
|
10
|
-
it for each project.
|
11
|
-
"""
|
12
|
-
|
13
23
|
|
14
24
|
@lru_cache
|
15
25
|
def get_i18n_schema(
|
16
|
-
lang_name
|
17
|
-
|
26
|
+
lang_name: str,
|
27
|
+
value_name: str,
|
28
|
+
value_field: str = "marshmallow_utils.fields.SanitizedHTML",
|
29
|
+
) -> type[Schema]:
|
30
|
+
"""Dynamically creates and returns I18n Schema class.
|
31
|
+
|
32
|
+
Add custom serialization logic based on the provided `lang_name` and `value_name`.
|
33
|
+
"""
|
34
|
+
|
18
35
|
class I18nMixin:
|
19
36
|
@validates(lang_name)
|
20
|
-
def validate_lang(self, value):
|
21
|
-
|
22
|
-
|
37
|
+
def validate_lang(self, value: str) -> None:
|
38
|
+
try:
|
39
|
+
if value != "_" and not langcodes.Language.get(value).is_valid():
|
40
|
+
raise ValidationError("Invalid language code")
|
41
|
+
except langcodes.tag_parser.LanguageTagError as e:
|
42
|
+
raise ValidationError("Invalid language code") from e
|
23
43
|
|
24
44
|
@pre_load
|
25
|
-
def pre_load_func(self, data, **
|
45
|
+
def pre_load_func(self, data: dict[str, Any], **_kwargs: Any) -> dict[str, Any]:
|
26
46
|
errors = {}
|
27
47
|
if not data.get(lang_name) or not data.get(value_name):
|
28
48
|
errors[lang_name] = [_("Both language and text must be provided.")]
|
@@ -33,7 +53,11 @@ def get_i18n_schema(
|
|
33
53
|
return data
|
34
54
|
|
35
55
|
value_field_class = obj_or_import_string(value_field)
|
36
|
-
|
56
|
+
if value_field_class is None:
|
57
|
+
raise ValueError(
|
58
|
+
f"Invalid value field class provided: '{value_field}'. "
|
59
|
+
"Expected a valid import string for a Marshmallow field class."
|
60
|
+
)
|
37
61
|
return type(
|
38
62
|
f"I18nSchema_{lang_name}_{value_name}",
|
39
63
|
(
|
@@ -48,14 +72,15 @@ def get_i18n_schema(
|
|
48
72
|
|
49
73
|
|
50
74
|
def MultilingualField( # noqa NOSONAR
|
51
|
-
*args,
|
52
|
-
lang_name="lang",
|
53
|
-
value_name="value",
|
54
|
-
value_field="marshmallow_utils.fields.SanitizedHTML",
|
55
|
-
**kwargs,
|
75
|
+
*args: Any,
|
76
|
+
lang_name: str = "lang",
|
77
|
+
value_name: str = "value",
|
78
|
+
value_field: str = "marshmallow_utils.fields.SanitizedHTML",
|
79
|
+
**kwargs: Any,
|
56
80
|
):
|
57
81
|
# TODO: args are not used but oarepo-model-builder-multilingual generates them
|
58
82
|
# should be fixed there and subsequently removed here
|
83
|
+
_ = args
|
59
84
|
return fields.List(
|
60
85
|
fields.Nested(get_i18n_schema(lang_name, value_name, value_field)),
|
61
86
|
**kwargs,
|
@@ -63,11 +88,11 @@ def MultilingualField( # noqa NOSONAR
|
|
63
88
|
|
64
89
|
|
65
90
|
def I18nStrField( # noqa NOSONAR
|
66
|
-
*args,
|
67
|
-
lang_name="lang",
|
68
|
-
value_name="value",
|
69
|
-
value_field="marshmallow_utils.fields.SanitizedHTML",
|
70
|
-
**kwargs,
|
91
|
+
*args: Any,
|
92
|
+
lang_name: str = "lang",
|
93
|
+
value_name: str = "value",
|
94
|
+
value_field: str = "marshmallow_utils.fields.SanitizedHTML",
|
95
|
+
**kwargs: Any,
|
71
96
|
):
|
72
97
|
return fields.Nested(
|
73
98
|
get_i18n_schema(lang_name, value_name, value_field),
|