oarepo-runtime 1.5.88__py3-none-any.whl → 1.5.90__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/services/components.py +162 -14
- oarepo_runtime/services/schema/validation.py +7 -1
- {oarepo_runtime-1.5.88.dist-info → oarepo_runtime-1.5.90.dist-info}/METADATA +1 -1
- {oarepo_runtime-1.5.88.dist-info → oarepo_runtime-1.5.90.dist-info}/RECORD +8 -8
- {oarepo_runtime-1.5.88.dist-info → oarepo_runtime-1.5.90.dist-info}/LICENSE +0 -0
- {oarepo_runtime-1.5.88.dist-info → oarepo_runtime-1.5.90.dist-info}/WHEEL +0 -0
- {oarepo_runtime-1.5.88.dist-info → oarepo_runtime-1.5.90.dist-info}/entry_points.txt +0 -0
- {oarepo_runtime-1.5.88.dist-info → oarepo_runtime-1.5.90.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,29 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import inspect
|
2
4
|
from collections import defaultdict
|
5
|
+
from dataclasses import dataclass, field
|
3
6
|
from typing import Type
|
4
7
|
|
5
8
|
from flask import current_app
|
6
9
|
from invenio_accounts.models import User
|
10
|
+
from invenio_drafts_resources.services.records.config import (
|
11
|
+
RecordServiceConfig as DraftsRecordServiceConfig,
|
12
|
+
)
|
13
|
+
from invenio_rdm_records.services.config import RDMRecordServiceConfig
|
7
14
|
from invenio_records import Record
|
15
|
+
from invenio_records_resources.services import FileServiceConfig
|
16
|
+
from invenio_records_resources.services.records.config import (
|
17
|
+
RecordServiceConfig as RecordsRecordServiceConfig,
|
18
|
+
)
|
8
19
|
|
9
|
-
from oarepo_runtime.services.custom_fields import
|
20
|
+
from oarepo_runtime.services.custom_fields import (
|
21
|
+
CustomFields,
|
22
|
+
CustomFieldsMixin,
|
23
|
+
InlinedCustomFields,
|
24
|
+
)
|
10
25
|
from oarepo_runtime.services.generators import RecordOwners
|
11
|
-
|
12
|
-
from invenio_drafts_resources.services.records.config import RecordServiceConfig as DraftsRecordServiceConfig
|
13
|
-
from invenio_records_resources.services.records.config import RecordServiceConfig as RecordsRecordServiceConfig
|
14
|
-
from invenio_records_resources.services import FileServiceConfig
|
26
|
+
|
15
27
|
try:
|
16
28
|
from invenio_drafts_resources.services.records.uow import ParentRecordCommitOp
|
17
29
|
except ImportError:
|
@@ -62,18 +74,23 @@ class DateIssuedComponent(ServiceComponent):
|
|
62
74
|
if "dateIssued" not in record["metadata"]:
|
63
75
|
record["metadata"]["dateIssued"] = datetime.today().strftime("%Y-%m-%d")
|
64
76
|
|
77
|
+
|
65
78
|
class CFRegistry:
|
66
79
|
def __init__(self):
|
67
80
|
self.custom_field_names = defaultdict(list)
|
68
81
|
|
69
82
|
def lookup(self, record_type: Type[Record]):
|
70
83
|
if record_type not in self.custom_field_names:
|
71
|
-
for fld in inspect.getmembers(
|
84
|
+
for fld in inspect.getmembers(
|
85
|
+
record_type, lambda x: isinstance(x, CustomFieldsMixin)
|
86
|
+
):
|
72
87
|
self.custom_field_names[record_type].append(fld[1])
|
73
88
|
return self.custom_field_names[record_type]
|
74
89
|
|
90
|
+
|
75
91
|
cf_registry = CFRegistry()
|
76
92
|
|
93
|
+
|
77
94
|
class CustomFieldsComponent(ServiceComponent):
|
78
95
|
def create(self, identity, data=None, record=None, **kwargs):
|
79
96
|
"""Create a new record."""
|
@@ -90,16 +107,16 @@ class CustomFieldsComponent(ServiceComponent):
|
|
90
107
|
elif isinstance(cf, InlinedCustomFields):
|
91
108
|
config = current_app.config.get(cf.config_key, {})
|
92
109
|
for c in config:
|
93
|
-
record[c.name] =data.get(c.name)
|
110
|
+
record[c.name] = data.get(c.name)
|
94
111
|
|
95
|
-
def process_service_configs(service_config):
|
96
112
|
|
113
|
+
def process_service_configs(service_config, *additional_components):
|
97
114
|
processed_components = []
|
98
115
|
target_classes = {
|
99
116
|
RDMRecordServiceConfig,
|
100
117
|
DraftsRecordServiceConfig,
|
101
118
|
RecordsRecordServiceConfig,
|
102
|
-
FileServiceConfig
|
119
|
+
FileServiceConfig,
|
103
120
|
}
|
104
121
|
|
105
122
|
for end_index, cls in enumerate(type(service_config).mro()):
|
@@ -110,19 +127,150 @@ def process_service_configs(service_config):
|
|
110
127
|
# there are two service_config instances in the MRO (Method Resolution Order) output.
|
111
128
|
start_index = 2 if hasattr(service_config, "build") else 1
|
112
129
|
|
113
|
-
service_configs = type(service_config).mro()[start_index:end_index + 1]
|
130
|
+
service_configs = type(service_config).mro()[start_index : end_index + 1]
|
114
131
|
for config in service_configs:
|
115
|
-
|
116
132
|
if hasattr(config, "build"):
|
117
133
|
config = config.build(current_app)
|
118
134
|
|
119
|
-
if hasattr(config,
|
135
|
+
if hasattr(config, "components"):
|
120
136
|
component_property = config.components
|
121
137
|
if isinstance(component_property, list):
|
122
138
|
processed_components.extend(component_property)
|
123
139
|
elif isinstance(component_property, tuple):
|
124
|
-
processed_components.extend(list
|
140
|
+
processed_components.extend(list(component_property))
|
125
141
|
else:
|
126
142
|
raise ValueError(f"{config} component's definition is not supported")
|
127
143
|
|
128
|
-
|
144
|
+
processed_components.extend(additional_components)
|
145
|
+
processed_components = _sort_components(processed_components)
|
146
|
+
return processed_components
|
147
|
+
|
148
|
+
|
149
|
+
@dataclass
|
150
|
+
class ComponentPlacement:
|
151
|
+
"""Component placement in the list of components.
|
152
|
+
|
153
|
+
This is a helper class used in the component ordering algorithm.
|
154
|
+
"""
|
155
|
+
|
156
|
+
component: Type[ServiceComponent]
|
157
|
+
"""Component to be ordered."""
|
158
|
+
|
159
|
+
depends_on: list[ComponentPlacement] = field(default_factory=list)
|
160
|
+
"""List of components this one depends on.
|
161
|
+
|
162
|
+
The components must be classes of ServiceComponent or '*' to denote
|
163
|
+
that this component depends on all other components and should be placed last.
|
164
|
+
"""
|
165
|
+
|
166
|
+
affects: list[ComponentPlacement] = field(default_factory=list)
|
167
|
+
"""List of components that depend on this one.
|
168
|
+
|
169
|
+
This is a temporary list used for evaluation of '*' dependencies
|
170
|
+
but does not take part in the sorting algorithm."""
|
171
|
+
|
172
|
+
def __hash__(self) -> int:
|
173
|
+
return id(self.component)
|
174
|
+
|
175
|
+
def __eq__(self, other: ComponentPlacement) -> bool:
|
176
|
+
return self.component is other.component
|
177
|
+
|
178
|
+
|
179
|
+
def _sort_components(components):
|
180
|
+
"""Sort components based on their dependencies while trying to
|
181
|
+
keep the initial order as far as possible."""
|
182
|
+
|
183
|
+
placements: list[ComponentPlacement] = _prepare_component_placement(components)
|
184
|
+
placements = _propagate_dependencies(placements)
|
185
|
+
|
186
|
+
ret = []
|
187
|
+
while placements:
|
188
|
+
without_dependencies = [p for p in placements if not p.depends_on]
|
189
|
+
if not without_dependencies:
|
190
|
+
raise ValueError("Circular dependency detected in components.")
|
191
|
+
for p in without_dependencies:
|
192
|
+
ret.append(p.component)
|
193
|
+
placements.remove(p)
|
194
|
+
for p2 in placements:
|
195
|
+
if p in p2.depends_on:
|
196
|
+
p2.depends_on.remove(p)
|
197
|
+
return ret
|
198
|
+
|
199
|
+
|
200
|
+
def _matching_placements(placements, dep_class_or_factory):
|
201
|
+
for pl in placements:
|
202
|
+
pl_component = pl.component
|
203
|
+
if not inspect.isclass(pl_component):
|
204
|
+
pl_component = type(pl_component(service=object()))
|
205
|
+
if issubclass(pl_component, dep_class_or_factory):
|
206
|
+
yield pl
|
207
|
+
|
208
|
+
|
209
|
+
def _prepare_component_placement(components) -> list[ComponentPlacement]:
|
210
|
+
"""Convert components to ComponentPlacement instances and resolve dependencies."""
|
211
|
+
placements = []
|
212
|
+
for idx, c in enumerate(components):
|
213
|
+
placement = ComponentPlacement(component=c)
|
214
|
+
placements.append(placement)
|
215
|
+
|
216
|
+
# direct dependencies
|
217
|
+
for idx, placement in enumerate(placements):
|
218
|
+
placements_without_this = placements[:idx] + placements[idx + 1 :]
|
219
|
+
for dep in getattr(placement.component, "depends_on", []):
|
220
|
+
if dep == "*":
|
221
|
+
continue
|
222
|
+
for pl in _matching_placements(placements_without_this, dep):
|
223
|
+
placement.depends_on.append(pl)
|
224
|
+
pl.affects.append(placement)
|
225
|
+
|
226
|
+
for dep in getattr(placement.component, "affects", []):
|
227
|
+
if dep == "*":
|
228
|
+
continue
|
229
|
+
for pl in _matching_placements(placements_without_this, dep):
|
230
|
+
placement.affects.append(pl)
|
231
|
+
pl.depends_on.append(placement)
|
232
|
+
|
233
|
+
# star dependencies
|
234
|
+
for idx, placement in enumerate(placements):
|
235
|
+
placements_without_this = placements[:idx] + placements[idx + 1 :]
|
236
|
+
if "*" in getattr(placement.component, "depends_on", []):
|
237
|
+
for pl in placements_without_this:
|
238
|
+
# if this placement is not in placements that pl depends on
|
239
|
+
# (added via direct dependencies above), add it
|
240
|
+
if placement not in pl.depends_on:
|
241
|
+
placement.depends_on.append(pl)
|
242
|
+
pl.affects.append(placement)
|
243
|
+
|
244
|
+
if "*" in getattr(placement.component, "affects", []):
|
245
|
+
for pl in placements_without_this:
|
246
|
+
# if this placement is not in placements that pl affects
|
247
|
+
# (added via direct dependencies above), add it
|
248
|
+
if placement not in pl.affects:
|
249
|
+
placement.affects.append(pl)
|
250
|
+
pl.depends_on.append(placement)
|
251
|
+
return placements
|
252
|
+
|
253
|
+
|
254
|
+
def _propagate_dependencies(
|
255
|
+
placements: list[ComponentPlacement],
|
256
|
+
) -> list[ComponentPlacement]:
|
257
|
+
# now propagate dependencies
|
258
|
+
dependency_propagated = True
|
259
|
+
while dependency_propagated:
|
260
|
+
dependency_propagated = False
|
261
|
+
for placement in placements:
|
262
|
+
for dep in placement.depends_on:
|
263
|
+
for dep_of_dep in dep.depends_on:
|
264
|
+
if dep_of_dep not in placement.depends_on:
|
265
|
+
placement.depends_on.append(dep_of_dep)
|
266
|
+
dep_of_dep.affects.append(placement)
|
267
|
+
dependency_propagated = True
|
268
|
+
|
269
|
+
for dep in placement.affects:
|
270
|
+
for dep_of_dep in dep.affects:
|
271
|
+
if dep_of_dep not in placement.affects:
|
272
|
+
placement.affects.append(dep_of_dep)
|
273
|
+
dep_of_dep.depends_on.append(placement)
|
274
|
+
dependency_propagated = True
|
275
|
+
|
276
|
+
return placements
|
@@ -2,6 +2,7 @@ import functools
|
|
2
2
|
import re
|
3
3
|
from datetime import datetime
|
4
4
|
from idutils import normalize_pid
|
5
|
+
from isbnlib import canonical, mask
|
5
6
|
|
6
7
|
from marshmallow.exceptions import ValidationError
|
7
8
|
from marshmallow_utils.fields.edtfdatestring import EDTFValidator
|
@@ -11,6 +12,11 @@ from invenio_i18n import gettext as _
|
|
11
12
|
|
12
13
|
def validate_identifier(value):
|
13
14
|
try:
|
15
|
+
if value["scheme"].lower() == "isbn":
|
16
|
+
canonical_isbn = canonical(value["identifier"])
|
17
|
+
value["identifier"] = mask(canonical_isbn)
|
18
|
+
return value["identifier"]
|
19
|
+
|
14
20
|
original_identifier = (value["identifier"] or '').strip()
|
15
21
|
normalized_identifier = normalize_pid(
|
16
22
|
value["identifier"], value["scheme"].lower()
|
@@ -57,4 +63,4 @@ class CachedMultilayerEDTFValidator(EDTFValidator):
|
|
57
63
|
datetime.strptime(value, "%Y-%m-%d")
|
58
64
|
return value
|
59
65
|
except:
|
60
|
-
return super().__call__(value)
|
66
|
+
return super().__call__(value)
|
@@ -73,7 +73,7 @@ oarepo_runtime/resources/file_resource.py,sha256=Ta3bFce7l0xwqkkOMOEu9mxbB8BbKj5
|
|
73
73
|
oarepo_runtime/resources/json_serializer.py,sha256=82_-xQEtxKaPakv8R1oBAFbGnxskF_Ve4tcfcy4PetI,963
|
74
74
|
oarepo_runtime/resources/localized_ui_json_serializer.py,sha256=3V9cJaG_e1PMXKVX_wKfBp1LmbeForwHyBNYdyha4uQ,1878
|
75
75
|
oarepo_runtime/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
76
|
-
oarepo_runtime/services/components.py,sha256=
|
76
|
+
oarepo_runtime/services/components.py,sha256=ZLGaOiaGJfKC_LNvYq4F061CP-sMG29FK4lUypY__Ws,10191
|
77
77
|
oarepo_runtime/services/generators.py,sha256=j87HitHA_w2awsz0C5IAAJ0qjg9JMtvdO3dvh6FQyfg,250
|
78
78
|
oarepo_runtime/services/results.py,sha256=Ap2mUJHl3V4BSduTrBWPuco0inQVq0QsuCbVhez48uY,5705
|
79
79
|
oarepo_runtime/services/search.py,sha256=9xGTN5Yg6eTdptQ9qjO_umbacf9ooMuHYGXWYfla4-M,6227
|
@@ -121,7 +121,7 @@ oarepo_runtime/services/schema/oneofschema.py,sha256=GnWH4Or_G5M0NgSmCoqMI6PBrJg
|
|
121
121
|
oarepo_runtime/services/schema/polymorphic.py,sha256=bAbUoTIeDBiJPYPhpLEKKZekEdkHlpqkmNxk1hN3PDw,564
|
122
122
|
oarepo_runtime/services/schema/rdm.py,sha256=4gi44LIMWS9kWcZfEoHaJSq585qfZfMuUYM5IvL1nQo,531
|
123
123
|
oarepo_runtime/services/schema/ui.py,sha256=xQgW-zLyZoHldGw47uVtXQj-5LexVNKTholyq4MiBZo,3777
|
124
|
-
oarepo_runtime/services/schema/validation.py,sha256=
|
124
|
+
oarepo_runtime/services/schema/validation.py,sha256=VFOKSxQLHwFb7bW8BJAFXWe_iTAZFOfqOnb2Ko_Yxxc,2085
|
125
125
|
oarepo_runtime/translations/default_translations.py,sha256=060GBlA1ghWxfeumo6NqxCCZDb-6OezOuF6pr-_GEOQ,104
|
126
126
|
oarepo_runtime/translations/messages.pot,sha256=jyC7mRH5P9uwVVqz9ycvn-T8gcohr62x__O0sC6g2w4,1846
|
127
127
|
oarepo_runtime/translations/cs/LC_MESSAGES/messages.mo,sha256=cSTRnVoi8DxfrXD-ImHYUmxdnNQMnxIn2yFRTObwRzI,801
|
@@ -135,9 +135,9 @@ tests/marshmallow_to_json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
135
135
|
tests/marshmallow_to_json/test_datacite_ui_schema.py,sha256=82iLj8nW45lZOUewpWbLX3mpSkpa9lxo-vK-Qtv_1bU,48552
|
136
136
|
tests/marshmallow_to_json/test_simple_schema.py,sha256=izZN9p0v6kovtSZ6AdxBYmK_c6ZOti2_z_wPT_zXIr0,1500
|
137
137
|
tests/pkg_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
138
|
-
oarepo_runtime-1.5.
|
139
|
-
oarepo_runtime-1.5.
|
140
|
-
oarepo_runtime-1.5.
|
141
|
-
oarepo_runtime-1.5.
|
142
|
-
oarepo_runtime-1.5.
|
143
|
-
oarepo_runtime-1.5.
|
138
|
+
oarepo_runtime-1.5.90.dist-info/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
|
139
|
+
oarepo_runtime-1.5.90.dist-info/METADATA,sha256=MjXQ_JQCvfx_q854xrRa73nGe0K-tYLLp-02yGZRwPI,4720
|
140
|
+
oarepo_runtime-1.5.90.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
141
|
+
oarepo_runtime-1.5.90.dist-info/entry_points.txt,sha256=k7O5LZUOGsVeSpB7ulU0txBUNp1CVQG7Q7TJIVTPbzU,491
|
142
|
+
oarepo_runtime-1.5.90.dist-info/top_level.txt,sha256=bHhlkT1_RQC4IkfTQCqA3iN4KCB6cSFQlsXpQMSP-bE,21
|
143
|
+
oarepo_runtime-1.5.90.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|