oarepo-runtime 2.0.0.dev6__py3-none-any.whl → 2.0.0.dev8__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.
@@ -19,6 +19,6 @@ from .api import Model
19
19
  from .ext import OARepoRuntime
20
20
  from .proxies import current_runtime
21
21
 
22
- __version__ = "2.0.0dev6"
22
+ __version__ = "2.0.0dev8"
23
23
 
24
24
  __all__ = ("Model", "OARepoRuntime", "__version__", "current_runtime")
@@ -0,0 +1 @@
1
+ # marker to show that this module is typed
@@ -0,0 +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
+ """Extensions for RDM API resources."""
11
+
12
+ from __future__ import annotations
13
+
14
+ from .config import exports_to_response_handlers
15
+
16
+ __all__ = ("exports_to_response_handlers",)
@@ -0,0 +1,35 @@
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
+ """Extensions for RDM API resources."""
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING
15
+
16
+ from flask_resources.responses import ResponseHandler
17
+ from invenio_records_resources.resources.records.headers import etag_headers
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Iterable
21
+
22
+ from oarepo_runtime.api import Export
23
+
24
+
25
+ def exports_to_response_handlers(
26
+ exports: Iterable[Export],
27
+ ) -> dict[str, ResponseHandler]:
28
+ """Convert exports to a dictionary of mimetype -> response handlers."""
29
+ return {
30
+ export.mimetype: ResponseHandler(
31
+ serializer=export.serializer,
32
+ headers=etag_headers,
33
+ )
34
+ for export in exports
35
+ }
@@ -0,0 +1,526 @@
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
+ """Utilities for deterministic ordering of service components.
10
+
11
+ This module provides a mixin that reorders service components while
12
+ respecting ``affects`` and ``depends_on`` relationships declared on the
13
+ component classes. It supports wildcard semantics (``"*"``) and preserves
14
+ the input order whenever it does not conflict with the declared constraints.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import heapq
20
+ import inspect
21
+ from collections import defaultdict
22
+ from functools import cached_property, partial
23
+ from itertools import chain
24
+ from typing import TYPE_CHECKING, Any, Literal, override
25
+
26
+ from invenio_base.utils import obj_or_import_string
27
+ from invenio_records_resources.services.records.components import ServiceComponent
28
+
29
+ if TYPE_CHECKING:
30
+ from collections.abc import Callable, Generator, Iterable
31
+
32
+ from invenio_records_resources.services.records.config import RecordServiceConfig
33
+ from invenio_records_resources.services.records.service import RecordService
34
+
35
+ else:
36
+ # for mixin typing
37
+ RecordServiceConfig = object
38
+ RecordService = object
39
+
40
+
41
+ class ComponentData:
42
+ """Normalized metadata extracted from a service component.
43
+
44
+ Instances of this helper encapsulate the resolved component class, its
45
+ relevant MRO, and the parsed ``affects``/``depends_on`` declarations for
46
+ use by the ordering algorithm.
47
+ """
48
+
49
+ original_component: Any
50
+ """The original component entry as provided in ``config.components``."""
51
+
52
+ component_class: type[ServiceComponent]
53
+ """The resolved class used for ordering comparisons.
54
+
55
+ If the original component is a class, this is that class (validated to be a
56
+ ``ServiceComponent`` subclass). If the original component is a
57
+ ``functools.partial``, this is ``partial.func``. If it is a factory/callable,
58
+ the callable is invoked and the result's type is used.
59
+ """
60
+
61
+ component_mro: set[type[ServiceComponent]]
62
+ """Set containing ``component_class`` and its mixins/base classes.
63
+
64
+ Classes from the ``ServiceComponent`` hierarchy (including ``object``) are
65
+ excluded. This set is used for efficient membership checks when matching
66
+ dependencies/affects to other components.
67
+ """
68
+
69
+ replaces: set[type[ServiceComponent]]
70
+ """Classes that this component replaces."""
71
+
72
+ replaced_by: set[type[ServiceComponent]]
73
+ """Classes that replace this component."""
74
+
75
+ affects_all: bool
76
+ """Whether the component declares ``affects = "*"`` (i.e., affects all)."""
77
+
78
+ depends_on_all: bool
79
+ """Whether the component declares ``depends_on = "*"`` (i.e., depends on all)."""
80
+
81
+ affects: set[type[ServiceComponent]]
82
+ """Classes that this component affects."""
83
+
84
+ depends_on: set[type[ServiceComponent]]
85
+ """Classes that this component depends on."""
86
+
87
+ idx: int
88
+ """Position of the item within the current working list during sort.
89
+
90
+ This is assigned later by the sorting algorithm and refers to the index in
91
+ the local subset being processed (not necessarily the original input list).
92
+ """
93
+
94
+ indeg: int = 0
95
+ """Number of incoming edges in the dependency graph (direct prerequisites)."""
96
+
97
+ def __init__(
98
+ self,
99
+ original_component: Any,
100
+ service: RecordService,
101
+ ) -> None:
102
+ """Resolve and validate metadata from the provided component entry.
103
+
104
+ Also validates that a component cannot both affect all and depend on all
105
+ at the same time (mutually exclusive wildcards).
106
+ """
107
+ self.original_component = original_component
108
+ self.component_class = self._extract_class_from_component(original_component, service)
109
+
110
+ self.component_mro = self._get_service_mro(self.component_class)
111
+
112
+ self.affects_all = "*" in getattr(self.component_class, "affects", [])
113
+ self.depends_on_all = "*" in getattr(self.component_class, "depends_on", [])
114
+
115
+ if self.affects_all and self.depends_on_all:
116
+ raise ValueError(
117
+ f"Component {self.original_component} cannot affect and depend on all components at the same time."
118
+ )
119
+
120
+ self.affects = self._convert_to_classes(getattr(self.component_class, "affects", None) or [])
121
+ self.depends_on = self._convert_to_classes(getattr(self.component_class, "depends_on", None) or [])
122
+ self.replaces = self._convert_to_classes(getattr(self.component_class, "replaces", None) or [])
123
+ self.replaced_by = self._convert_to_classes(getattr(self.component_class, "replaced_by", None) or [])
124
+
125
+ def _extract_class_from_component(self, component: Any, service: RecordService) -> type[ServiceComponent]:
126
+ """Resolve a comparable class from the component entry.
127
+
128
+ Supported forms:
129
+ - a class that subclasses ``ServiceComponent``;
130
+ - a ``functools.partial`` whose ``func`` ultimately resolves to the class;
131
+ - a factory/callable returning a ``ServiceComponent`` instance when
132
+ called (the returned instance's type is used).
133
+ """
134
+ # if it is a class, return it
135
+ if inspect.isclass(component):
136
+ if not issubclass(component, ServiceComponent):
137
+ raise TypeError(f"Component {component} is not a subclass of ServiceComponent")
138
+ return component
139
+
140
+ # it might be a partial, so check that out
141
+ if isinstance(component, partial):
142
+ return self._extract_class_from_component(component.func, service)
143
+
144
+ # as a last option, instantiate the component and return its type
145
+ inst = component(service)
146
+ return type(inst)
147
+
148
+ def _get_service_mro(self, component_class: type[ServiceComponent]) -> set[type]:
149
+ """Get the relevant MRO for ordering comparisons.
150
+
151
+ Returns the component class and its base classes/mixins, excluding the
152
+ ``ServiceComponent`` hierarchy (and thus also ``object``).
153
+ """
154
+ return {x for x in component_class.mro() if x not in ServiceComponent.mro()}
155
+
156
+ def _convert_to_classes(self, items: Any) -> set[type[ServiceComponent]]:
157
+ """Normalize an input list/tuple to a set of component classes.
158
+
159
+ Accepts classes or import strings. The special value ``"*"`` is handled
160
+ by the caller via ``affects_all``/``depends_on_all`` and is not included
161
+ in the returned set.
162
+ """
163
+ ret: set[type[ServiceComponent]] = set()
164
+
165
+ if not isinstance(items, (list, tuple)):
166
+ if items == "*":
167
+ return ret
168
+ raise TypeError(f"Expected list or tuple, got {type(items)}")
169
+ for item in items:
170
+ if isinstance(item, str):
171
+ item = obj_or_import_string(item) # noqa PLW2901
172
+
173
+ if inspect.isclass(item):
174
+ if not issubclass(item, ServiceComponent):
175
+ raise TypeError(f"Item {item} is not a ServiceComponent subclass")
176
+ ret.add(item)
177
+ else:
178
+ raise TypeError(
179
+ f"affects or depends_on needs to contain classes, item {item} ({type(item)}) is not a class"
180
+ )
181
+ return ret
182
+
183
+ @override
184
+ def __hash__(self):
185
+ return hash(self.component_class)
186
+
187
+ @override
188
+ def __eq__(self, other: object) -> bool:
189
+ return self.component_class == other.component_class if isinstance(other, ComponentData) else False
190
+
191
+ @override
192
+ def __repr__(self):
193
+ return f"CD({self.component_class.__name__})"
194
+
195
+ @override
196
+ def __str__(self):
197
+ ret = [f"CD({self.component_class.__name__}"]
198
+ if self.affects_all:
199
+ ret.append(",a*")
200
+ if self.depends_on_all:
201
+ ret.append(",d*")
202
+ if self.affects:
203
+ ret.append(f",a={{{', '.join(sorted(c.__name__ for c in self.affects))}}}")
204
+ if self.depends_on:
205
+ ret.append(f",d={{{', '.join(sorted(c.__name__ for c in self.depends_on))}}}")
206
+ ret.append(")")
207
+ return "".join(ret)
208
+
209
+
210
+ class ComponentsOrderingMixin(RecordService):
211
+ """Order ``config.components`` while honoring declared relationships.
212
+
213
+ Component classes can declare two optional class attributes:
214
+ - ``depends_on``: a class or a list of classes that must appear before it;
215
+ - ``affects``: a class or a list of classes that must appear after it.
216
+
217
+ Both attributes may also be the wildcard ``"*"`` to express a relationship
218
+ with all other components. For example:
219
+ - if ``A.affects = "*"`` and ``B.affects = A``, then the order must be
220
+ ``B, A, *`` (i.e., ``B`` comes before ``A``);
221
+ - if ``A.depends_on = "*"`` and ``B.depends_on = A``, then the order must be
222
+ ``*, B, A`` (i.e., everything before ``B`` before ``A``).
223
+
224
+ The algorithm performs:
225
+ 1) class deduplication (keep only one occurrence of each class),
226
+ 2) inheritance deduplication (prefer the most specific subclass over its base),
227
+ 3) stable topological sorting that preserves input order whenever possible.
228
+ """
229
+
230
+ @cached_property
231
+ def component_classes(self) -> tuple[type[ServiceComponent], ...]:
232
+ """Return the ordered component classes as an immutable tuple."""
233
+ return self._order_components(self.config.components)
234
+
235
+ @property
236
+ def components(self) -> Generator[ServiceComponent]:
237
+ """Instantiate and yield components in the computed order."""
238
+ return (c(self) for c in self.component_classes)
239
+
240
+ def _order_components(
241
+ self,
242
+ components: Iterable[Any],
243
+ ) -> tuple[Any, ...]:
244
+ """Order components based on ``affects``/``depends_on`` semantics.
245
+
246
+ Splits components into three groups (``affects_all``, ``rest``,
247
+ ``depends_on_all``), propagates transitive relationships into the edge
248
+ groups, topologically sorts each group, and finally concatenates them
249
+ in that order. Returns a tuple of the original component entries.
250
+ """
251
+ component_data = self._deduplicate_components(components)
252
+
253
+ affects_all = [x for x in component_data if x.affects_all]
254
+ depends_on_all = [x for x in component_data if x.depends_on_all]
255
+ rest = [x for x in component_data if not x.affects_all and not x.depends_on_all]
256
+
257
+ # if A is from affects_all
258
+ # * and A[depends_on=B], add B to the affects_all set
259
+ # * and B[affects=A], add B to the affects_all set
260
+ self._propagate_dependencies(affects_all, rest, lambda x: x.depends_on, lambda x: x.affects)
261
+ # if A is from depends_on_all
262
+ # * and A[affects=B], add B to the depends_on_all set
263
+ # * and B[depends_on=A], add B to the depends_on_all set
264
+ self._propagate_dependencies(depends_on_all, rest, lambda x: x.affects, lambda x: x.depends_on)
265
+
266
+ # now the affects_all, rest, depends_on_all are completed and can be sorted
267
+ affects_all = self._topo_sort(affects_all)
268
+ rest = self._topo_sort(rest)
269
+ depends_on_all = self._topo_sort(depends_on_all)
270
+
271
+ return tuple(x.original_component for x in chain(affects_all, rest, depends_on_all))
272
+
273
+ def _topo_sort(self, components: list[ComponentData]) -> list[ComponentData]:
274
+ """Topologically sort by dependencies while preserving relative order.
275
+
276
+ Uses indegree counting with a min-heap keyed by the original index to
277
+ prefer earlier items when multiple nodes become available. Detects and
278
+ raises a ``ValueError`` with the cyclic subgraph if a cycle exists.
279
+ """
280
+ if not components or len(components) == 1:
281
+ return components
282
+
283
+ for idx, comp in enumerate(components):
284
+ comp.idx = idx # set the index for later use
285
+
286
+ graph = self._create_topo_graph(components)
287
+
288
+ # graph gives a mapping of each component to its dependencies, we need to build
289
+ # an inverse as well
290
+ inverse_graph = defaultdict(set)
291
+ for comp, deps in graph.items():
292
+ comp.indeg = len(deps) # set the indegree
293
+ for dep in deps:
294
+ inverse_graph[dep].add(comp)
295
+
296
+ # create a queue of all nodes that have no dependencies on other nodes
297
+ heap = [component.idx for component in components if component.indeg == 0]
298
+ heapq.heapify(heap)
299
+
300
+ ordered: list[ComponentData] = []
301
+ while heap:
302
+ # take the top of the heap and take the associated component and add it to
303
+ # the output sequence
304
+ idx = heapq.heappop(heap)
305
+ component = components[idx]
306
+ ordered.append(component)
307
+
308
+ # for each of the items that depend directly on this one, decrease the indeg
309
+ # of the item. If it reaches zero, add it to the heap. This will reorder
310
+ # the heap in a way that the next heappop will return the item with the
311
+ # lowest index (thus handling B->C, C, D will be returned in C, B, D rather
312
+ # than C, D, B if only indeg would be used).
313
+ for v in inverse_graph[component]:
314
+ v.indeg -= 1
315
+ if v.indeg == 0:
316
+ heapq.heappush(heap, v.idx)
317
+
318
+ if len(ordered) != len(components):
319
+ # get a list of components that form a cycle
320
+ cycle_forming_components = {cd for cd in components if cd.indeg > 0}
321
+ cycled_dependencies = {
322
+ comp: {dep for dep in deps if dep in cycle_forming_components}
323
+ for comp, deps in graph.items()
324
+ if comp in cycle_forming_components
325
+ }
326
+ raise ValueError(f"Cycle detected in dependencies: {cycled_dependencies}")
327
+
328
+ return ordered
329
+
330
+ def _create_topo_graph(self, components: list[ComponentData]) -> dict[ComponentData, set[ComponentData]]:
331
+ """Build a dependency graph suitable for topological sorting.
332
+
333
+ The resulting mapping has nodes as keys and a set of their direct
334
+ prerequisites as values. Specifically:
335
+ - if ``A`` appears in ``B.depends_on``, then ``graph[B]`` contains ``A``;
336
+ - if ``A`` appears in ``B.affects``, then ``graph[A]`` contains ``B``.
337
+ """
338
+ graph: dict[ComponentData, set[ComponentData]] = {}
339
+ for comp in components:
340
+ graph[comp] = set()
341
+
342
+ for comp in components:
343
+ for dep in comp.depends_on:
344
+ for other in self._find_components(components, dep):
345
+ graph[comp].add(other)
346
+ for aff in comp.affects:
347
+ for other in self._find_components(components, aff):
348
+ graph[other].add(comp)
349
+ return graph
350
+
351
+ def _find_components(self, components: list[ComponentData], cls: type) -> list[ComponentData]:
352
+ """Return components whose ``component_mro`` includes the given class."""
353
+ return [comp for comp in components if cls in comp.component_mro]
354
+
355
+ def _propagate_dependencies(
356
+ self,
357
+ selected: list[ComponentData],
358
+ potential_dependencies: list[ComponentData],
359
+ selected_dependency_getter: Callable[[ComponentData], set[type[ServiceComponent]]],
360
+ potential_dependency_getter: Callable[[ComponentData], set[type[ServiceComponent]]],
361
+ ) -> None:
362
+ """Enrich the edge groups with items they require or that require them.
363
+
364
+ - If any item in ``selected`` depends on an item in ``potential_dependencies``
365
+ (via ``selected_dependency_getter``), move that dependency into
366
+ ``selected``.
367
+ - If any item in ``potential_dependencies`` declares it must be together
368
+ with something in ``selected`` (via ``potential_dependency_getter``),
369
+ move it into ``selected`` as well.
370
+
371
+ Both ``selected`` and ``potential_dependencies`` are modified in place,
372
+ and propagation continues until a fixed point is reached (transitive closure).
373
+ """
374
+ modified = True
375
+ already_checked_selected: set[ComponentData] = set()
376
+ while modified:
377
+ modified = False
378
+
379
+ moved_indices = sorted(
380
+ self._get_dependencies_from_selected(
381
+ list(set(selected) - already_checked_selected),
382
+ potential_dependencies,
383
+ selected_dependency_getter,
384
+ )
385
+ | self._get_dependencies_from_potentials(selected, potential_dependencies, potential_dependency_getter)
386
+ )
387
+ already_checked_selected.update(selected)
388
+ if moved_indices:
389
+ modified = True # do another round for transitive dependencies
390
+ selected.extend(potential_dependencies[idx] for idx in moved_indices)
391
+ for idx in reversed(moved_indices):
392
+ del potential_dependencies[idx]
393
+
394
+ def _get_dependencies_from_selected(
395
+ self,
396
+ selected: list[ComponentData],
397
+ potential_dependencies: list[ComponentData],
398
+ selected_dependency_getter: Callable[[ComponentData], set[type[ServiceComponent]]],
399
+ ) -> set[int]:
400
+ """Get indices of potential dependencies required by items in ``selected``.
401
+
402
+ Handles these cases (A in ``selected``, B in ``potential_dependencies``):
403
+ - A in ``affects_all`` and A.depends_on contains B -> move B to ``affects_all``;
404
+ - A in ``depends_on_all`` and A.affects contains B -> move B to ``depends_on_all``.
405
+ """
406
+ additional_selected_items = set[int]()
407
+
408
+ for s in selected:
409
+ additional_selected_from_s = selected_dependency_getter(s)
410
+ if not additional_selected_from_s:
411
+ continue
412
+ for idx, dep in enumerate(potential_dependencies):
413
+ if additional_selected_from_s & dep.component_mro:
414
+ # the dependency matches, so should be in selected
415
+ additional_selected_items.add(idx)
416
+
417
+ return additional_selected_items
418
+
419
+ def _get_dependencies_from_potentials(
420
+ self,
421
+ selected: list[ComponentData],
422
+ potential_dependencies: list[ComponentData],
423
+ potential_dependency_getter: Callable[[ComponentData], set[type[ServiceComponent]]],
424
+ ) -> set[int]:
425
+ """Get indices of potentials that explicitly belong to the edge group.
426
+
427
+ Handles these cases (A in ``selected``, B in ``potential_dependencies``):
428
+ - A in ``affects_all`` and B.affects contains A -> move B to ``affects_all``;
429
+ - A in ``depends_on_all`` and B.depends_on contains A -> move B to ``depends_on_all``.
430
+ """
431
+ additional_selected_items = set[int]()
432
+
433
+ for idx, p in enumerate(potential_dependencies):
434
+ p_should_be_with = potential_dependency_getter(p)
435
+ if not p_should_be_with:
436
+ continue
437
+ for s in selected:
438
+ if p_should_be_with & s.component_mro:
439
+ # the dependency matches, so should be in selected
440
+ additional_selected_items.add(idx)
441
+
442
+ return additional_selected_items
443
+
444
+ def _deduplicate_components(
445
+ self,
446
+ components: Iterable[Any],
447
+ ) -> list[ComponentData]:
448
+ """Build normalized component data and deduplicate by class/inheritance.
449
+
450
+ Rules:
451
+ - keep only one occurrence of a class (class deduplication);
452
+ - if both a base class and its subclass appear, keep the most specific
453
+ subclass and drop the base (inheritance deduplication);
454
+ - otherwise preserve the original input order.
455
+ """
456
+ data: list[ComponentData] = []
457
+
458
+ for candidate_component in components:
459
+ cd = ComponentData(original_component=candidate_component, service=self)
460
+
461
+ replaced_indices = []
462
+ skipped = False
463
+ for idx, component in enumerate(data):
464
+ deduplication_action = self._deduplication_action(cd, component)
465
+ match deduplication_action:
466
+ case "skip":
467
+ skipped = True
468
+ case "replace":
469
+ replaced_indices.append(idx)
470
+ case "ok":
471
+ pass
472
+
473
+ if skipped:
474
+ if replaced_indices:
475
+ # we have replaced indices and at the same time, the replacement
476
+ # should be skipped, so just remove the components at those indices
477
+ # probably never happens, just for sure
478
+ self._remove_indices_from_data(data, replaced_indices)
479
+ continue
480
+
481
+ if replaced_indices:
482
+ # we have replaced indices. Remove all but the first index
483
+ self._remove_indices_from_data(data, replaced_indices[1:])
484
+ # and replace the first index with this component
485
+ data[replaced_indices[0]] = cd
486
+ else:
487
+ # if no replaced indices, append the new component to the end of the list
488
+ data.append(cd)
489
+
490
+ return data
491
+
492
+ def _deduplication_action(
493
+ self, new_component: ComponentData, existing_component: ComponentData
494
+ ) -> Literal["skip", "replace", "ok"]:
495
+ """Get a deduplication action for the given components.
496
+
497
+ :param new_component the component that is being added to the list of components
498
+ :param existing_component the component that is already in the list of components
499
+
500
+ :return: the deduplication action to take
501
+ skip: do not add the new_component to the list of components
502
+ replace: replace the existing_component with the new_component
503
+ ok: it is ok to add the new_component to the list of components as
504
+ it does not interfere with the existing component
505
+ """
506
+ if new_component.component_class == existing_component.component_class:
507
+ # already inside the data, do not include it twice
508
+ return "skip"
509
+ if existing_component.component_class in new_component.replaced_by:
510
+ # already replaced by something in the data, do not include it
511
+ return "skip"
512
+ if existing_component.component_class in new_component.replaces:
513
+ # the class in data is replaced by this one
514
+ return "replace"
515
+ if new_component.component_class in existing_component.replaces:
516
+ # component says that it replaces me, so skip
517
+ return "skip"
518
+ if new_component.component_class in existing_component.replaced_by:
519
+ # component says it is replaced by myself
520
+ return "replace"
521
+ return "ok"
522
+
523
+ def _remove_indices_from_data(self, data: list[ComponentData], indices: list[int]) -> None:
524
+ """Remove items at the given (sorted) indices from ``data`` in place."""
525
+ for idx in reversed(indices):
526
+ del data[idx]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oarepo-runtime
3
- Version: 2.0.0.dev6
3
+ Version: 2.0.0.dev8
4
4
  Summary: A set of runtime extensions of Invenio repository
5
5
  Project-URL: Homepage, https://github.com/oarepo/oarepo-runtime
6
6
  License-Expression: MIT
@@ -1,8 +1,9 @@
1
- oarepo_runtime/__init__.py,sha256=_r7NviuUWi_2RA8KG16wk_5RY4ZxJ_BZYkf6_8K0MUc,685
1
+ oarepo_runtime/__init__.py,sha256=8-qYG9GY0t6VGGGvIq3LNHT9mdmdJ2XrdirOyJuLXLg,685
2
2
  oarepo_runtime/api.py,sha256=P_y8b5AFES0ssv6x_GDg4MtD6TIfoNsOAVMMoiRM560,10330
3
3
  oarepo_runtime/config.py,sha256=RUEPFn_5bKp9Wb0OY-Fb3VK30m35vF5IsLjYaQHhP3g,3838
4
4
  oarepo_runtime/ext.py,sha256=AMb5pMnCSbqIpPyP99YUKlf9vopz_b2ZW-RnvfsEVlk,3254
5
5
  oarepo_runtime/proxies.py,sha256=PXaRiBh5qs5-h8M81cJOgtqypFQcYUSjiSn2TLSujRw,648
6
+ oarepo_runtime/py.typed,sha256=RznSCjXReEUI9zkmD25E8XniG_MvPpLBF6MyNZA8MmE,42
6
7
  oarepo_runtime/cli/__init__.py,sha256=iPs1a4blP7750rwEXobzK3YHgsfGxoVTZxwWMslAlTY,350
7
8
  oarepo_runtime/cli/search.py,sha256=yqYHZauXsDBPpN4odYsPOWNQ9xWmAofQ407EAyqx6CY,1137
8
9
  oarepo_runtime/records/__init__.py,sha256=AbWzmVCY7MhrpdEeI0e3lKzeugPMUSo8T08-NBVeig4,339
@@ -12,9 +13,12 @@ oarepo_runtime/records/pid_providers.py,sha256=pVXVeYmAsXy-IEdM2zHZ7UWkAnzXg1gts
12
13
  oarepo_runtime/records/systemfields/__init__.py,sha256=g-u408qyNnsbUTpDtVVwlcyiJaO68GTjDN0W9rXs9pk,524
13
14
  oarepo_runtime/records/systemfields/mapping.py,sha256=66OQavKewJEUMkghymOxvskIO0LUSP2E-MbHryeT5Nk,1968
14
15
  oarepo_runtime/records/systemfields/publication_status.py,sha256=1g3VXNPh0FsiPCpe-7ZuaMEF4x8ffrDrt37Rqnjp0ng,2027
16
+ oarepo_runtime/resources/__init__.py,sha256=voynQULXoOEviADkbOpekMphZPTAz4IOTg5BF9xPwTM,453
17
+ oarepo_runtime/resources/config.py,sha256=hJewyZ2FlEm4TtYnQS9JsnKnA6hhtSbvo1PC24-7f7Y,980
15
18
  oarepo_runtime/services/__init__.py,sha256=OGtBgEeaDTyk2RPDNXuKbU9_7egFBZr42SM0gN5FrF4,341
16
19
  oarepo_runtime/services/results.py,sha256=fk-Enx_LwZLbw81yZ7CXVTku86vd3_fjprnb8l5sFHk,6657
17
20
  oarepo_runtime/services/config/__init__.py,sha256=SX1kfIGk8HkohdLQrNpRQUTltksEyDcCa-kFXxrX4e8,711
21
+ oarepo_runtime/services/config/components.py,sha256=t6zPWcwsL4d_U4PelmHQ50ymDAY_N4YcgVnM6aklktY,23038
18
22
  oarepo_runtime/services/config/link_conditions.py,sha256=raqf4yaBNLqNYgBxVNblo8MRJneVIFkwVNW7IW3AVYI,4309
19
23
  oarepo_runtime/services/config/permissions.py,sha256=x5k61LGnpXyJfXVoCTq2tTVTtPckmBcBtcBJx4UN9EA,3056
20
24
  oarepo_runtime/services/facets/__init__.py,sha256=k39ZYt1dMVOW01QRSTgx3CfuTYwvEWmL0VYTR3huVsE,349
@@ -25,8 +29,8 @@ oarepo_runtime/services/records/mapping.py,sha256=y3oeToKEnaRYpMV3q2-2cXNzyzyL3X
25
29
  oarepo_runtime/services/schema/__init__.py,sha256=jgAPI_uKC6Ug4KQWnwQVg3-aNaw-eHja323AUFo5ELo,351
26
30
  oarepo_runtime/services/schema/i18n.py,sha256=9D1zOQaPKAnYzejB0vO-m2BJYnam0N0Lrq4jID7twfE,3174
27
31
  oarepo_runtime/services/schema/i18n_ui.py,sha256=DbusphhGDeaobTt4nuwNgKZ6Houlu4Sv3SuMGkdjRRY,3582
28
- oarepo_runtime-2.0.0.dev6.dist-info/METADATA,sha256=C8GjGM-BySVIrhvdW5mon3uYAq0YcP6WNqdKC0-rFIg,4494
29
- oarepo_runtime-2.0.0.dev6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
30
- oarepo_runtime-2.0.0.dev6.dist-info/entry_points.txt,sha256=7HqK5jumIgDVJa7ifjPKizginfIm5_R_qUVKPf_Yq-c,145
31
- oarepo_runtime-2.0.0.dev6.dist-info/licenses/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
32
- oarepo_runtime-2.0.0.dev6.dist-info/RECORD,,
32
+ oarepo_runtime-2.0.0.dev8.dist-info/METADATA,sha256=zCWr2-vXfnWqXgRSmP9PGOBskYE2w8jjd122yxlkNok,4494
33
+ oarepo_runtime-2.0.0.dev8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
34
+ oarepo_runtime-2.0.0.dev8.dist-info/entry_points.txt,sha256=7HqK5jumIgDVJa7ifjPKizginfIm5_R_qUVKPf_Yq-c,145
35
+ oarepo_runtime-2.0.0.dev8.dist-info/licenses/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
36
+ oarepo_runtime-2.0.0.dev8.dist-info/RECORD,,