oarepo-runtime 1.5.102__py3-none-any.whl → 1.5.103__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 +211 -74
- {oarepo_runtime-1.5.102.dist-info → oarepo_runtime-1.5.103.dist-info}/METADATA +1 -1
- {oarepo_runtime-1.5.102.dist-info → oarepo_runtime-1.5.103.dist-info}/RECORD +7 -7
- {oarepo_runtime-1.5.102.dist-info → oarepo_runtime-1.5.103.dist-info}/LICENSE +0 -0
- {oarepo_runtime-1.5.102.dist-info → oarepo_runtime-1.5.103.dist-info}/WHEEL +0 -0
- {oarepo_runtime-1.5.102.dist-info → oarepo_runtime-1.5.103.dist-info}/entry_points.txt +0 -0
- {oarepo_runtime-1.5.102.dist-info → oarepo_runtime-1.5.103.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import inspect
|
4
4
|
from collections import defaultdict
|
5
5
|
from dataclasses import dataclass, field
|
6
|
-
from typing import Type
|
6
|
+
from typing import Any, Type
|
7
7
|
|
8
8
|
from flask import current_app
|
9
9
|
from invenio_accounts.models import User
|
@@ -17,6 +17,7 @@ from invenio_records_resources.services import FileServiceConfig
|
|
17
17
|
from invenio_records_resources.services.records.config import (
|
18
18
|
RecordServiceConfig as RecordsRecordServiceConfig,
|
19
19
|
)
|
20
|
+
|
20
21
|
from oarepo_runtime.proxies import current_oarepo
|
21
22
|
from oarepo_runtime.services.custom_fields import (
|
22
23
|
CustomFields,
|
@@ -147,7 +148,7 @@ def process_service_configs(service_config, *additional_components):
|
|
147
148
|
for excluded_component in current_oarepo.rdm_excluded_components:
|
148
149
|
if excluded_component in processed_components:
|
149
150
|
processed_components.remove(excluded_component)
|
150
|
-
|
151
|
+
|
151
152
|
processed_components = _sort_components(processed_components)
|
152
153
|
return processed_components
|
153
154
|
|
@@ -165,44 +166,228 @@ class ComponentPlacement:
|
|
165
166
|
depends_on: list[ComponentPlacement] = field(default_factory=list)
|
166
167
|
"""List of components this one depends on.
|
167
168
|
|
168
|
-
The components must be classes of ServiceComponent
|
169
|
-
that this component depends on all other components and should be placed last.
|
169
|
+
The components must be classes of ServiceComponent.
|
170
170
|
"""
|
171
171
|
|
172
172
|
affects: list[ComponentPlacement] = field(default_factory=list)
|
173
|
-
"""List of components that depend on this one.
|
174
|
-
|
175
|
-
|
176
|
-
|
173
|
+
"""List of components that depend on this one."""
|
174
|
+
|
175
|
+
star_depends: bool = False
|
176
|
+
"""True if this component depends on all other components."""
|
177
|
+
|
178
|
+
star_affects: bool = False
|
179
|
+
"""True if this component affects all other components."""
|
177
180
|
|
178
181
|
def __hash__(self) -> int:
|
179
182
|
return id(self.component)
|
180
183
|
|
181
|
-
def __eq__(self, other:
|
184
|
+
def __eq__(self, other: Any) -> bool:
|
182
185
|
return self.component is other.component
|
183
186
|
|
184
|
-
|
185
|
-
|
187
|
+
def __repr__(self) -> str:
|
188
|
+
depends_on = [d.component.__name__ for d in self.depends_on]
|
189
|
+
affects = [a.component.__name__ for a in self.affects]
|
190
|
+
if self.star_affects:
|
191
|
+
affects.append("*")
|
192
|
+
if self.star_depends:
|
193
|
+
depends_on.append("*")
|
194
|
+
r = [f"<{self.component.__name__}"]
|
195
|
+
if depends_on:
|
196
|
+
r.append(f" depends_on: {depends_on}")
|
197
|
+
if affects:
|
198
|
+
r.append(f" affects: {affects}")
|
199
|
+
r.append(">")
|
200
|
+
return "".join(r)
|
201
|
+
|
202
|
+
def __str__(self) -> str:
|
203
|
+
return repr(self)
|
204
|
+
|
205
|
+
|
206
|
+
def _sort_components(components) -> list[Type[ServiceComponent]]:
|
186
207
|
"""Sort components based on their dependencies while trying to
|
187
|
-
keep the initial order as far as possible.
|
208
|
+
keep the initial order as far as possible.
|
209
|
+
|
210
|
+
Sorting algorithm:
|
211
|
+
|
212
|
+
1. Select all "affects" components that has "*" in their "affects" list.
|
213
|
+
2. Merge these components preserving other other "affects" and "depends_on" settings
|
214
|
+
and output these
|
215
|
+
|
216
|
+
3. put "depends_on: *" components aside for the moment
|
217
|
+
4. Sort the remaining components by their dependencies
|
218
|
+
|
219
|
+
5. process depends_on: * in a similar way as in 2.
|
220
|
+
"""
|
188
221
|
|
189
222
|
placements: list[ComponentPlacement] = _prepare_component_placement(components)
|
190
|
-
placements = _propagate_dependencies(placements)
|
191
223
|
|
224
|
+
# placements that must be first as they affect all other components
|
225
|
+
affects_placements = [p for p in placements if p.star_affects]
|
226
|
+
|
227
|
+
# placements that must be last as they depend on all other components
|
228
|
+
depends_on_placements = [p for p in placements if p.star_depends]
|
229
|
+
|
230
|
+
# if a component affects another that affects all components,
|
231
|
+
# add it to affects_placements
|
232
|
+
#
|
233
|
+
# A[affects *] B[affects A] C => adds B to affects_placements
|
234
|
+
modified = True
|
235
|
+
while modified:
|
236
|
+
modified = False
|
237
|
+
for p in placements:
|
238
|
+
if (
|
239
|
+
any(q in affects_placements for q in p.affects)
|
240
|
+
and p not in affects_placements
|
241
|
+
):
|
242
|
+
affects_placements.append(p)
|
243
|
+
modified = True
|
244
|
+
|
245
|
+
# if a component depends on another that depends on all components,
|
246
|
+
# add it to depends_on_placements
|
247
|
+
# A[depends_on *] B[depends_on A] C => adds B to depends_on_placements
|
248
|
+
modified = True
|
249
|
+
while modified:
|
250
|
+
modified = False
|
251
|
+
for p in placements:
|
252
|
+
if (
|
253
|
+
any(q in depends_on_placements for q in p.depends_on)
|
254
|
+
and p not in depends_on_placements
|
255
|
+
):
|
256
|
+
depends_on_placements.append(p)
|
257
|
+
modified = True
|
258
|
+
|
259
|
+
# those that do not affect or depend on all components
|
260
|
+
middle_placements = [
|
261
|
+
p
|
262
|
+
for p in placements
|
263
|
+
if p not in depends_on_placements and p not in affects_placements
|
264
|
+
]
|
265
|
+
|
266
|
+
# sort placements inside each group by their respective depends on and affects
|
267
|
+
# relationships, ignoring the star relationships
|
192
268
|
ret = []
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
raise ValueError("Circular dependency detected in components.")
|
197
|
-
for p in without_dependencies:
|
198
|
-
ret.append(p.component)
|
199
|
-
placements.remove(p)
|
200
|
-
for p2 in placements:
|
201
|
-
if p in p2.depends_on:
|
202
|
-
p2.depends_on.remove(p)
|
269
|
+
ret.extend(_sort_placements(affects_placements))
|
270
|
+
ret.extend(_sort_placements(middle_placements))
|
271
|
+
ret.extend(_sort_placements(depends_on_placements))
|
203
272
|
return ret
|
204
273
|
|
205
274
|
|
275
|
+
def _sort_placements(placements):
|
276
|
+
"""Sort placements based on their dependencies.
|
277
|
+
|
278
|
+
The algorithm tries to keep the initial order as far as possible,
|
279
|
+
while still respecting the dependencies.
|
280
|
+
|
281
|
+
At first, for each component that affects another component, the algorithm
|
282
|
+
moves the affecting component before the affected one one step at a time.
|
283
|
+
|
284
|
+
When no more such moves are possible, the algorithm moves to the next step
|
285
|
+
and does similar thing for components that depend on another component.
|
286
|
+
|
287
|
+
The algorithm is repeated until all components are sorted. If they can not
|
288
|
+
be after a set number of iterations, the algorithm raises an exception.
|
289
|
+
"""
|
290
|
+
|
291
|
+
_filter_depends_on_and_affects(placements)
|
292
|
+
|
293
|
+
for _ in range(10):
|
294
|
+
for __ in range(10):
|
295
|
+
# move components that affect other components before them
|
296
|
+
modified = _move_affecting_components(placements)
|
297
|
+
|
298
|
+
# move components that depend on other components after them
|
299
|
+
if _move_depends_on_components(placements):
|
300
|
+
modified = True
|
301
|
+
|
302
|
+
# if the order was not modified, we are done
|
303
|
+
if not modified:
|
304
|
+
return (p.component for p in placements)
|
305
|
+
else:
|
306
|
+
# could not sort the components by simple move. This means that we are
|
307
|
+
# in a situation where A C B[affects A depends on C] - we will try to
|
308
|
+
# swap A with C and try again.
|
309
|
+
_swap_out_of_order_components(placements)
|
310
|
+
|
311
|
+
raise ValueError(f"Can not order components: {placements}")
|
312
|
+
|
313
|
+
|
314
|
+
def _swap_out_of_order_components(placements):
|
315
|
+
for idx, placement in enumerate(placements):
|
316
|
+
|
317
|
+
# we are looking for a situation: A C placement[affects A depends on C]
|
318
|
+
# so if there are not depends on and affects, try the next one
|
319
|
+
if not placement.depends_on or not placement.affects:
|
320
|
+
continue
|
321
|
+
|
322
|
+
depends_on = [(p, placements.index(p)) for p in placement.depends_on]
|
323
|
+
affects = [(p, placements.index(p)) for p in placement.affects]
|
324
|
+
|
325
|
+
# if there are no indices to swap, continue
|
326
|
+
if not depends_on or not affects:
|
327
|
+
continue
|
328
|
+
|
329
|
+
# keep the indices in order
|
330
|
+
depends_on.sort(key=lambda x: x[1])
|
331
|
+
affects.sort(key=lambda x: x[1])
|
332
|
+
|
333
|
+
depends_on_indices = [d[1] for d in depends_on]
|
334
|
+
affects_indices = [a[1] for a in affects]
|
335
|
+
|
336
|
+
swapped_depends_on = [d[0] for d in depends_on]
|
337
|
+
swapped_affects = [a[0] for a in affects]
|
338
|
+
|
339
|
+
# if the index of the component the current placement depends on
|
340
|
+
# is lower that any of the components that depend on the current placement,
|
341
|
+
# we are ok as the placement can be moved to the space between them,
|
342
|
+
# so just continue
|
343
|
+
if max(depends_on_indices) < min(affects_indices):
|
344
|
+
continue
|
345
|
+
|
346
|
+
# add all to an array and sort them. This way depends on will be after
|
347
|
+
# affects, keeping the positions inside the group
|
348
|
+
indices = sorted([*affects_indices, *depends_on_indices])
|
349
|
+
|
350
|
+
for idx, dep in zip(
|
351
|
+
indices,
|
352
|
+
[*swapped_depends_on, *swapped_affects],
|
353
|
+
):
|
354
|
+
placements[idx] = dep
|
355
|
+
|
356
|
+
|
357
|
+
def _filter_depends_on_and_affects(placements):
|
358
|
+
"""Filter out dependencies on components that are not in the placements list."""
|
359
|
+
for placement in placements:
|
360
|
+
placement.depends_on = [p for p in placement.depends_on if p in placements]
|
361
|
+
placement.affects = [p for p in placement.affects if p in placements]
|
362
|
+
|
363
|
+
|
364
|
+
def _move_affecting_components(placements):
|
365
|
+
modified = False
|
366
|
+
|
367
|
+
for idx, placement in list(enumerate(placements)):
|
368
|
+
if not placement.affects:
|
369
|
+
continue
|
370
|
+
min_index = min(placements.index(a) for a in placement.affects)
|
371
|
+
if min_index < idx:
|
372
|
+
placements.remove(placement)
|
373
|
+
placements.insert(min_index, placement)
|
374
|
+
modified = True
|
375
|
+
return modified
|
376
|
+
|
377
|
+
|
378
|
+
def _move_depends_on_components(placements):
|
379
|
+
modified = False
|
380
|
+
for idx, placement in reversed(list(enumerate(placements))):
|
381
|
+
if not placement.depends_on:
|
382
|
+
continue
|
383
|
+
max_index = max(placements.index(a) for a in placement.depends_on)
|
384
|
+
if max_index > idx:
|
385
|
+
placements.remove(placement)
|
386
|
+
placements.insert(max_index, placement)
|
387
|
+
modified = True
|
388
|
+
return modified
|
389
|
+
|
390
|
+
|
206
391
|
def _matching_placements(placements, dep_class_or_factory):
|
207
392
|
for pl in placements:
|
208
393
|
pl_component = pl.component
|
@@ -222,6 +407,9 @@ def _prepare_component_placement(components) -> list[ComponentPlacement]:
|
|
222
407
|
# direct dependencies
|
223
408
|
for idx, placement in enumerate(placements):
|
224
409
|
placements_without_this = placements[:idx] + placements[idx + 1 :]
|
410
|
+
placement.star_depends = "*" in getattr(placement.component, "depends_on", [])
|
411
|
+
placement.star_affects = "*" in getattr(placement.component, "affects", [])
|
412
|
+
|
225
413
|
for dep in getattr(placement.component, "depends_on", []):
|
226
414
|
if dep == "*":
|
227
415
|
continue
|
@@ -229,8 +417,6 @@ def _prepare_component_placement(components) -> list[ComponentPlacement]:
|
|
229
417
|
for pl in _matching_placements(placements_without_this, dep):
|
230
418
|
if pl not in placement.depends_on:
|
231
419
|
placement.depends_on.append(pl)
|
232
|
-
if placement not in pl.affects:
|
233
|
-
pl.affects.append(placement)
|
234
420
|
|
235
421
|
for dep in getattr(placement.component, "affects", []):
|
236
422
|
if dep == "*":
|
@@ -239,54 +425,5 @@ def _prepare_component_placement(components) -> list[ComponentPlacement]:
|
|
239
425
|
for pl in _matching_placements(placements_without_this, dep):
|
240
426
|
if pl not in placement.affects:
|
241
427
|
placement.affects.append(pl)
|
242
|
-
if placement not in pl.depends_on:
|
243
|
-
pl.depends_on.append(placement)
|
244
|
-
|
245
|
-
# star dependencies
|
246
|
-
for idx, placement in enumerate(placements):
|
247
|
-
placements_without_this = placements[:idx] + placements[idx + 1 :]
|
248
|
-
if "*" in getattr(placement.component, "depends_on", []):
|
249
|
-
for pl in placements_without_this:
|
250
|
-
# if this placement is not in placements that pl depends on
|
251
|
-
# (added via direct dependencies above), add it
|
252
|
-
if placement not in pl.depends_on:
|
253
|
-
if pl not in placement.depends_on:
|
254
|
-
placement.depends_on.append(pl)
|
255
|
-
if placement not in pl.affects:
|
256
|
-
pl.affects.append(placement)
|
257
|
-
|
258
|
-
if "*" in getattr(placement.component, "affects", []):
|
259
|
-
for pl in placements_without_this:
|
260
|
-
# if this placement is not in placements that pl affects
|
261
|
-
# (added via direct dependencies above), add it
|
262
|
-
if placement not in pl.affects:
|
263
|
-
if pl not in placement.affects:
|
264
|
-
placement.affects.append(pl)
|
265
|
-
if placement not in pl.depends_on:
|
266
|
-
pl.depends_on.append(placement)
|
267
|
-
return placements
|
268
|
-
|
269
|
-
|
270
|
-
def _propagate_dependencies(
|
271
|
-
placements: list[ComponentPlacement],
|
272
|
-
) -> list[ComponentPlacement]:
|
273
|
-
# now propagate dependencies
|
274
|
-
dependency_propagated = True
|
275
|
-
while dependency_propagated:
|
276
|
-
dependency_propagated = False
|
277
|
-
for placement in placements:
|
278
|
-
for dep in placement.depends_on:
|
279
|
-
for dep_of_dep in dep.depends_on:
|
280
|
-
if dep_of_dep not in placement.depends_on:
|
281
|
-
placement.depends_on.append(dep_of_dep)
|
282
|
-
dep_of_dep.affects.append(placement)
|
283
|
-
dependency_propagated = True
|
284
|
-
|
285
|
-
for dep in placement.affects:
|
286
|
-
for dep_of_dep in dep.affects:
|
287
|
-
if dep_of_dep not in placement.affects:
|
288
|
-
placement.affects.append(dep_of_dep)
|
289
|
-
dep_of_dep.depends_on.append(placement)
|
290
|
-
dependency_propagated = True
|
291
428
|
|
292
429
|
return placements
|
@@ -80,7 +80,7 @@ oarepo_runtime/resources/file_resource.py,sha256=Ta3bFce7l0xwqkkOMOEu9mxbB8BbKj5
|
|
80
80
|
oarepo_runtime/resources/json_serializer.py,sha256=82_-xQEtxKaPakv8R1oBAFbGnxskF_Ve4tcfcy4PetI,963
|
81
81
|
oarepo_runtime/resources/localized_ui_json_serializer.py,sha256=3V9cJaG_e1PMXKVX_wKfBp1LmbeForwHyBNYdyha4uQ,1878
|
82
82
|
oarepo_runtime/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
83
|
-
oarepo_runtime/services/components.py,sha256=
|
83
|
+
oarepo_runtime/services/components.py,sha256=0aqmuNbGCQhfOI22f6mM2DxuKSTqstGOGyisOiqw9KE,15481
|
84
84
|
oarepo_runtime/services/generators.py,sha256=j87HitHA_w2awsz0C5IAAJ0qjg9JMtvdO3dvh6FQyfg,250
|
85
85
|
oarepo_runtime/services/results.py,sha256=Ap2mUJHl3V4BSduTrBWPuco0inQVq0QsuCbVhez48uY,5705
|
86
86
|
oarepo_runtime/services/search.py,sha256=t0WEe2VrbCzZ06-Jgz7C9-pc9y27BqAgTEXldEHskfk,9409
|
@@ -142,9 +142,9 @@ tests/marshmallow_to_json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
142
142
|
tests/marshmallow_to_json/test_datacite_ui_schema.py,sha256=82iLj8nW45lZOUewpWbLX3mpSkpa9lxo-vK-Qtv_1bU,48552
|
143
143
|
tests/marshmallow_to_json/test_simple_schema.py,sha256=izZN9p0v6kovtSZ6AdxBYmK_c6ZOti2_z_wPT_zXIr0,1500
|
144
144
|
tests/pkg_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
145
|
-
oarepo_runtime-1.5.
|
146
|
-
oarepo_runtime-1.5.
|
147
|
-
oarepo_runtime-1.5.
|
148
|
-
oarepo_runtime-1.5.
|
149
|
-
oarepo_runtime-1.5.
|
150
|
-
oarepo_runtime-1.5.
|
145
|
+
oarepo_runtime-1.5.103.dist-info/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
|
146
|
+
oarepo_runtime-1.5.103.dist-info/METADATA,sha256=bLIBuMCjXm85bclvhOvWFWBfpCglssd-Xt4AiPUB6rI,4721
|
147
|
+
oarepo_runtime-1.5.103.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
148
|
+
oarepo_runtime-1.5.103.dist-info/entry_points.txt,sha256=k7O5LZUOGsVeSpB7ulU0txBUNp1CVQG7Q7TJIVTPbzU,491
|
149
|
+
oarepo_runtime-1.5.103.dist-info/top_level.txt,sha256=bHhlkT1_RQC4IkfTQCqA3iN4KCB6cSFQlsXpQMSP-bE,21
|
150
|
+
oarepo_runtime-1.5.103.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|