oarepo-runtime 2.0.0.dev36__py3-none-any.whl → 2.0.0.dev38__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.0dev36"
22
+ __version__ = "2.0.0dev38"
23
23
 
24
24
  __all__ = ("Model", "OARepoRuntime", "__version__", "current_runtime")
oarepo_runtime/ext.py CHANGED
@@ -15,6 +15,7 @@ from functools import cached_property
15
15
  from typing import TYPE_CHECKING, Any, cast
16
16
 
17
17
  from flask import current_app
18
+ from invenio_db import db
18
19
  from invenio_pidstore.errors import PIDDoesNotExistError
19
20
  from invenio_pidstore.models import PersistentIdentifier
20
21
  from invenio_records.api import Record as RecordBase
@@ -146,20 +147,20 @@ class OARepoRuntime:
146
147
 
147
148
  If the filter matches multiple services, an error is raised.
148
149
  """
149
- pids = PersistentIdentifier.query.filter_by(**filter_kwargs).all()
150
+ pids = db.session.query(PersistentIdentifier).filter_by(**filter_kwargs).all()
150
151
 
151
152
  filtered_pids = [pid for pid in pids if pid.pid_type in self.record_class_by_pid_type]
152
153
  if not filtered_pids:
153
154
  raise PIDDoesNotExistError(
154
- None,
155
- filter_kwargs,
155
+ "unknown_pid",
156
+ str(filter_kwargs),
156
157
  "The pid value/record uuid is not associated with any record.",
157
158
  )
158
159
 
159
160
  if len(filtered_pids) > 1:
160
161
  raise PIDDoesNotExistError(
161
- None,
162
- filter_kwargs,
162
+ "unknown_pid",
163
+ str(filter_kwargs),
163
164
  f"Multiple records found for pid value/record uuid: {filtered_pids}",
164
165
  )
165
166
  return filtered_pids[0]
@@ -1,13 +1,12 @@
1
1
  #
2
2
  # Copyright (c) 2025 CESNET z.s.p.o.
3
3
  #
4
- # This file is a part of oarepo-runtime (see http://github.com/oarepo/oarepo-runtime).
4
+ # This file is a part of oarepo-runtime (see https://github.com/oarepo/oarepo-runtime).
5
5
  #
6
6
  # oarepo-runtime is free software; you can redistribute it and/or modify it
7
7
  # under the terms of the MIT License; see LICENSE file for more details.
8
8
  #
9
- # TODO: better docstring
10
- """Info Resource."""
9
+ """Resource for serving machine-readable information about the repository."""
11
10
 
12
11
  from __future__ import annotations
13
12
 
@@ -28,7 +28,7 @@ class UniversalPIDMixin(RecordIdProviderV2):
28
28
  unpid_default_status = PIDStatus.REGISTERED
29
29
 
30
30
  @classmethod
31
- def create(
31
+ def create( # type: ignore[override] # as pid type and value are given
32
32
  cls,
33
33
  object_type: str | None = None,
34
34
  object_uuid: str | None = None,
@@ -50,7 +50,7 @@ class UniversalPIDMixin(RecordIdProviderV2):
50
50
 
51
51
  PersistentIdentifier.create(
52
52
  cls.unpid_pid_type,
53
- pid.pid.pid_value,
53
+ cast("str", pid.pid.pid_value),
54
54
  pid_provider=None,
55
55
  object_type=object_type,
56
56
  object_uuid=object_uuid,
@@ -0,0 +1,307 @@
1
+ #
2
+ # Copyright (c) 2025 CESNET z.s.p.o.
3
+ #
4
+ # This file is a part of oarepo-runtime (see https://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
+ """A relation field that allows arbitrarily nested lists of relations."""
10
+
11
+ from __future__ import annotations
12
+
13
+ from itertools import zip_longest
14
+ from typing import TYPE_CHECKING, Any, override
15
+
16
+ from invenio_records.dictutils import dict_lookup, dict_set
17
+ from invenio_records.systemfields.relations import (
18
+ InvalidRelationValue,
19
+ ListRelation,
20
+ RelationListResult,
21
+ )
22
+ from invenio_records_resources.records.systemfields.relations import (
23
+ PIDRelation,
24
+ )
25
+
26
+ if TYPE_CHECKING:
27
+ from collections.abc import Callable, Generator
28
+
29
+ from invenio_records.api import Record
30
+
31
+
32
+ class ArbitraryNestedListResult(RelationListResult):
33
+ """Relation access result."""
34
+
35
+ @override
36
+ def __call__(self, force: bool = True):
37
+ """Resolve the relation."""
38
+ try:
39
+ # not as efficient as it could be as we create the list of lists first
40
+ # before returning the iterator, but simpler to implement
41
+ return iter(
42
+ _for_each_deep(
43
+ self._lookup_data(),
44
+ lambda v: self.resolve(v[self._value_key_suffix]),
45
+ levels=len(self.field.path_elements),
46
+ )
47
+ )
48
+ except KeyError:
49
+ return None
50
+
51
+ def _lookup_data(self) -> Any:
52
+ """Lookup the data from the record."""
53
+
54
+ # recursively lookup the data following the path elements. The end of the path
55
+ # must always be an array of objects.
56
+ def _lookup(r: Any, paths: list[str]) -> Any:
57
+ if not paths:
58
+ if self.field.relation_field:
59
+ try:
60
+ return dict_lookup(r, self.field.relation_field)
61
+ except KeyError: # pragma: no cover
62
+ return None
63
+ return r
64
+ try:
65
+ level_values = dict_lookup(r, paths[0])
66
+ if not isinstance(level_values, list):
67
+ raise InvalidRelationValue( # pragma: no cover
68
+ f'Invalid structure, expecting list at "{paths[0]}", got {level_values}. '
69
+ f'Complete paths: "{self.field.path_elements}"'
70
+ )
71
+ ret = [_lookup(v, paths[1:]) for v in level_values]
72
+ return [v for v in ret if v is not None]
73
+ except KeyError:
74
+ return []
75
+
76
+ return _lookup(self.record, self.field.path_elements)
77
+
78
+ @override
79
+ def validate(self) -> None:
80
+ """Validate the field."""
81
+ try:
82
+ values = self._lookup_data()
83
+ # not as efficient as it could be as we create the list of lists first
84
+ # before returning, but simpler to implement
85
+ _for_each_deep(
86
+ values,
87
+ lambda v: self._validate_single_value(v),
88
+ levels=len(self.field.path_elements),
89
+ )
90
+ except KeyError: # pragma: no cover
91
+ return
92
+
93
+ def _validate_single_value(self, v: Any) -> None:
94
+ """Validate a single value."""
95
+ if isinstance(v, list):
96
+ raise InvalidRelationValue(f"Invalid value {v}, should not be list.")
97
+ relation_id = self._lookup_id(v)
98
+ if not self.exists(relation_id):
99
+ raise InvalidRelationValue(f"Invalid value {relation_id}.")
100
+ if self.value_check: # pragma: no cover # not testing, copied from invenio
101
+ obj = self.resolve(v[self.field._value_key_suffix]) # noqa: SLF001 # private attr
102
+ self._value_check(self.value_check, obj)
103
+
104
+ @override
105
+ def _apply_items( # type: ignore[override]
106
+ self,
107
+ func: Callable,
108
+ keys: list[str] | None = None,
109
+ attrs: list[str] | None = None,
110
+ ) -> list[Any] | None:
111
+ """Iterate over the list of objects."""
112
+ # The attributes we want to get from the related record.
113
+ attrs = attrs or self.attrs
114
+ keys = keys or self.keys
115
+ try:
116
+ # Get the list of objects we have to dereference/clean.
117
+ values = self._lookup_data()
118
+ return _for_each_deep(
119
+ values,
120
+ lambda v: func(v, keys, attrs),
121
+ levels=len(self.field.path_elements),
122
+ )
123
+ except KeyError: # pragma: no cover
124
+ return None
125
+
126
+
127
+ class ArbitraryNestedListRelation(ListRelation):
128
+ """Arbitrary nested relation list type.
129
+
130
+ self.path_elements contain the segments of the path that are within lists.
131
+ For example:
132
+ - For paths like "a.b.c", path = [], relation_field="a.b.c"
133
+ - For paths like "a.b.0.c", path = ["a.b"], relation_field="c"
134
+ - For paths like "a.0.b.1.c", path = ["a", "b"], relation_field="c"
135
+ - For paths like "a.1", path = ["a"], relation_field=None
136
+
137
+ ids and values are stored as lists that can contain other lists arbitrarily nested.
138
+ The total depth of nesting is given by the length of self.path_elements + 1 if self.relation_field is not None
139
+ or length of self.path_elements if self.relation_field is None.
140
+ """
141
+
142
+ result_cls = ArbitraryNestedListResult
143
+
144
+ def __init__(
145
+ self,
146
+ *args: Any,
147
+ array_paths: list[str] | None = None,
148
+ relation_field: str | None = None,
149
+ **kwargs: Any,
150
+ ):
151
+ """Initialize the relation."""
152
+ if not array_paths:
153
+ raise ValueError("array_paths are required for ArbitraryNestedListRelation.")
154
+ self.path_elements = array_paths
155
+ super().__init__(*args, relation_field=relation_field, **kwargs)
156
+
157
+ @override
158
+ def exists_many(self, ids: Any) -> bool: # type: ignore[override]
159
+ """Return True if all ids exists."""
160
+
161
+ # ids is a list that might recursively contain lists that contain ids
162
+ def flatten(nested_list: Any) -> Generator[Any]:
163
+ for item in nested_list:
164
+ if isinstance(item, (list, tuple)):
165
+ yield from flatten(item)
166
+ else:
167
+ yield item
168
+
169
+ return all(self.exists(i) for i in flatten(ids))
170
+
171
+ @override
172
+ def parse_value(self, value: list[Any] | tuple[Any]) -> list[Any]: # type: ignore[override]
173
+ """Parse a record (or ID) to the ID to be stored."""
174
+ return _for_each_deep(value, lambda v: self._parse_single_value(v), levels=len(self.path_elements))
175
+
176
+ def _parse_single_value(self, value: Any) -> Any:
177
+ """Parse a single value using the parent class method.
178
+
179
+ Note: we are skipping the list's parse_value here and calling
180
+ the next one after ListRelation in mro chain. That might be, for example,
181
+ PIDRelation.parse_value
182
+ """
183
+ if self.relation_field:
184
+ try:
185
+ return super(ListRelation, self).parse_value(dict_lookup(value, self.relation_field))
186
+ except KeyError: # pragma: no cover
187
+ return None
188
+ else:
189
+ return super(ListRelation, self).parse_value(value)
190
+
191
+ @override
192
+ def set_value(
193
+ self,
194
+ record: Record,
195
+ value: list[Any] | tuple[Any],
196
+ ) -> None: # type: ignore[override]
197
+ """Set the relation value."""
198
+ store_values = self.parse_value(value)
199
+
200
+ if not self.exists_many(store_values):
201
+ raise InvalidRelationValue(f'One of the values "{store_values}" is invalid.')
202
+
203
+ total_depth = len(self.path_elements)
204
+
205
+ for path_indices, path_value in _deep_enumerate(store_values, total_depth):
206
+ r: Any = record
207
+ self._set_value_at_path(r, path_indices, {self._value_key_suffix: path_value})
208
+
209
+ def _set_value_at_path(self, r: Any, path_indices: list[int], path_value: Any) -> None:
210
+ """Set the value at the given path indices."""
211
+ pe = [
212
+ *self.path_elements,
213
+ ]
214
+ if self.relation_field:
215
+ pe.append(self.relation_field)
216
+
217
+ # pe might be 1 longer than path_indices, that's why we use zip_longest
218
+ zipped = list(zip_longest(pe, path_indices, fillvalue=None))
219
+ for idx, (subpath, index) in enumerate(zipped[:-1]):
220
+ if not subpath:
221
+ raise InvalidRelationValue( # pragma: no cover
222
+ f"Invalid structure, missing key at index {idx} in [{self.path_elements}, {path_indices}]."
223
+ )
224
+ if index is None:
225
+ raise InvalidRelationValue( # pragma: no cover
226
+ f"Invalid structure, missing index at {subpath} in [{self.path_elements}, {path_indices}]."
227
+ )
228
+ r = self._set_default_value_at_path(r, subpath, index, path_indices)
229
+
230
+ last_path, last_index = zipped[-1]
231
+ if last_path is None: # pragma: no cover
232
+ raise InvalidRelationValue("Implementation error.")
233
+ if last_index is None:
234
+ # we have a relation_field at the end, so set it directly
235
+ dict_set(r, last_path, path_value)
236
+ else:
237
+ # no relation_field at the end, so we set the whole object at the index
238
+ try:
239
+ val = dict_lookup(r, last_path)
240
+ except KeyError:
241
+ val = []
242
+ dict_set(r, last_path, val)
243
+ if last_index < len(val):
244
+ val[last_index] = path_value
245
+ elif last_index == len(val):
246
+ val.append(path_value)
247
+ else:
248
+ raise InvalidRelationValue( # pragma: no cover # just sanity check
249
+ f"Invalid structure, missing index {last_index} "
250
+ f"at {last_path} in [{self.path_elements}, {path_indices}]."
251
+ )
252
+
253
+ def _set_default_value_at_path(self, r: Any, subpath: str, index: int, path_indices: list[int]) -> Any:
254
+ """Set default value of [] at the given path if missing."""
255
+ # look up the subpath and create it if missing
256
+ try:
257
+ val = dict_lookup(r, subpath)
258
+ except KeyError:
259
+ dict_set(r, subpath, [])
260
+ val = dict_lookup(r, subpath)
261
+ if not isinstance(val, list):
262
+ raise InvalidRelationValue( # pragma: no cover
263
+ f"Invalid structure, expecting list at {subpath} in [{self.path_elements}, {path_indices}]."
264
+ )
265
+
266
+ # now we have the array - if the index is within array, return the value at the index
267
+ if index < len(val):
268
+ return val[index]
269
+
270
+ # if the index is exactly at the end of the array, we can append a new default value,
271
+ # which is always an empty dict
272
+ if index == len(val):
273
+ # append new default value which is always empty dict
274
+ r = {}
275
+ val.append(r)
276
+ return r
277
+
278
+ # we can not skip indices, so if that would happen, raise error
279
+ raise InvalidRelationValue( # pragma: no cover
280
+ f"Invalid structure, missing index {index} at {subpath} in [{self.path_elements}, {path_indices}]."
281
+ )
282
+
283
+
284
+ def _deep_enumerate(nested_list: Any, max_depth: int, depth: int = 0) -> Generator[tuple[list[int], Any]]:
285
+ """Enumerate all non-list items in a nested list structure."""
286
+ for index, item in enumerate(nested_list):
287
+ current_path = [index]
288
+ if depth < max_depth - 1 and isinstance(item, (list, tuple)):
289
+ for sub_path, sub_item in _deep_enumerate(item, max_depth, depth + 1):
290
+ yield current_path + sub_path, sub_item
291
+ else:
292
+ yield current_path, item
293
+
294
+
295
+ def _for_each_deep(nested_list: Any, func: Any, levels: int) -> list[Any]:
296
+ """Apply a function to each non-list item in a nested list structure."""
297
+ result = []
298
+ for item in nested_list:
299
+ if isinstance(item, (list, tuple)) and levels > 1:
300
+ result.append(_for_each_deep(item, func, levels=levels - 1))
301
+ else:
302
+ result.append(func(item))
303
+ return result
304
+
305
+
306
+ class PIDArbitraryNestedListRelation(ArbitraryNestedListRelation, PIDRelation): # type: ignore[override, misc]
307
+ """PID list relation type."""
@@ -15,7 +15,6 @@ import logging
15
15
  from typing import TYPE_CHECKING, Any
16
16
 
17
17
  from invenio_access.permissions import Identity
18
- from invenio_records.api import RecordBase
19
18
  from invenio_records_resources.errors import _iter_errors_dict
20
19
  from invenio_records_resources.services.records.results import (
21
20
  RecordItem as BaseRecordItem,
@@ -27,7 +26,7 @@ from invenio_records_resources.services.records.results import (
27
26
  if TYPE_CHECKING:
28
27
  from invenio_access.permissions import Identity
29
28
  from invenio_drafts_resources.records.api import Draft
30
- from invenio_records.api import RecordBase
29
+ from invenio_records_resources.records.api import Record
31
30
 
32
31
 
33
32
  log = logging.getLogger(__name__)
@@ -45,7 +44,7 @@ class ResultComponent:
45
44
  self._record_item = record_item
46
45
  self._record_list = record_list
47
46
 
48
- def update_data(self, identity: Identity, record: RecordBase, projection: dict, expand: bool) -> None:
47
+ def update_data(self, identity: Identity, record: Record, projection: dict, expand: bool) -> None:
49
48
  """Update the projection data with additional information.
50
49
 
51
50
  :param identity: The identity of the user making the request.
@@ -151,7 +150,9 @@ class RecordList(BaseRecordList):
151
150
  # Project the record
152
151
  # TODO: check if this logic is correct
153
152
  versions = hit_dict.get("versions", {})
154
- if versions.get("is_latest_draft") and not versions.get("is_latest"):
153
+ if (versions.get("is_latest_draft") and not versions.get("is_latest")) or (
154
+ "publication_status" in hit_dict and hit_dict["publication_status"] == "draft"
155
+ ):
155
156
  draft_class: type[Draft] | None = getattr(self._service, "draft_cls", None)
156
157
  if draft_class is None:
157
158
  raise RuntimeError("Draft class is not defined in the service") # pragma: no cover
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oarepo-runtime
3
- Version: 2.0.0.dev36
3
+ Version: 2.0.0.dev38
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
@@ -8,11 +8,9 @@ License-File: LICENSE
8
8
  Requires-Python: <3.14,>=3.13
9
9
  Requires-Dist: langcodes>=3.5.0
10
10
  Requires-Dist: oarepo-invenio-typing-stubs>=0.1.0
11
- Requires-Dist: oarepo[rdm,tests]<15,>=13
11
+ Requires-Dist: oarepo[rdm,tests]<15,>=14
12
12
  Provides-Extra: dev
13
13
  Requires-Dist: pytest>=7.1.2; extra == 'dev'
14
- Provides-Extra: oarepo13
15
- Requires-Dist: oarepo[rdm]<14,>=13; extra == 'oarepo13'
16
14
  Provides-Extra: oarepo14
17
15
  Requires-Dist: oarepo[rdm]<15,>=14; extra == 'oarepo14'
18
16
  Provides-Extra: tests
@@ -1,29 +1,30 @@
1
- oarepo_runtime/__init__.py,sha256=Y5ZPbHF2q3Fi0ElItUf0wzaM3aP_xOw-MkJntFLkO8E,686
1
+ oarepo_runtime/__init__.py,sha256=hHdSN2hRXm0UHFuHSaaokxvEIdJtIoobD_jdybp_vKY,686
2
2
  oarepo_runtime/api.py,sha256=6AHyFnf0Cg1nfhGaQFMPjynVvrp1UURo8vX5vb5LvC4,14169
3
3
  oarepo_runtime/config.py,sha256=RUEPFn_5bKp9Wb0OY-Fb3VK30m35vF5IsLjYaQHhP3g,3838
4
- oarepo_runtime/ext.py,sha256=NgiRNl_hwTvEWcXnNwVh_XCPJyvwr3dZkdPmkWvN1xo,8785
4
+ oarepo_runtime/ext.py,sha256=hA_OmJJHum6W28iPkjxvFrKHzFwi_Ki_0Fy2Mgzl7UQ,8851
5
5
  oarepo_runtime/proxies.py,sha256=x8Y1iTP8QIzSI67s90VR0_5fvXuT1xlJXtAHsaoXFwg,903
6
6
  oarepo_runtime/py.typed,sha256=RznSCjXReEUI9zkmD25E8XniG_MvPpLBF6MyNZA8MmE,42
7
7
  oarepo_runtime/typing.py,sha256=VtINHm4BZ5OJ4KdRAwnFXPZiAEgPRIYTtPC9fIzK1bU,1876
8
8
  oarepo_runtime/cli/__init__.py,sha256=H7GOeOBf0udgKWOdlAQswIMvRrD8BwcEjOVxIqP0Suw,731
9
9
  oarepo_runtime/cli/search.py,sha256=4fHkrjltUUPVUzJiuWaiWxTk62rIYxal3_3jRsZVMmI,1175
10
10
  oarepo_runtime/info/__init__.py,sha256=qRG3mSyoiw7sKm9StiuBJs6l15HrdAQ4sphsAQsJtQc,336
11
- oarepo_runtime/info/views.py,sha256=JwESs-KVq163OBeqWVwfy0RU4WXR_MBolVR7W-dGtm0,16954
11
+ oarepo_runtime/info/views.py,sha256=ObeRjbuH5_eKMOkVfT3O5EhPLIQ0HuCLD3fXSEv2z7U,16987
12
12
  oarepo_runtime/records/__init__.py,sha256=AbWzmVCY7MhrpdEeI0e3lKzeugPMUSo8T08-NBVeig4,339
13
13
  oarepo_runtime/records/drafts.py,sha256=b45ROjd9lwy6ratrpAruimcKvQmJradk5JgILoBAHmY,1965
14
14
  oarepo_runtime/records/mapping.py,sha256=fn6M208axxBqHtRV6qKQukwUw1z0hq_KF4qfuB2rr98,2630
15
- oarepo_runtime/records/pid_providers.py,sha256=DE8uW-QmJumA9OqWksDOydezjJ9LLC296CRa0zDrlEc,1747
15
+ oarepo_runtime/records/pid_providers.py,sha256=B7nzj8nUZ3tut8SSkGWbYN-ON7z-5t3V3uhqiXA2Ifo,1820
16
16
  oarepo_runtime/records/systemfields/__init__.py,sha256=tJzOOQ8dBlCyD8tCAriIUZggZ73UjMyMOp6IlSV-Tb4,594
17
17
  oarepo_runtime/records/systemfields/base.py,sha256=EWSdVsWePkdwksRQ9yaMMk9Mrhicw-ZE0vdApn2EjWQ,1602
18
18
  oarepo_runtime/records/systemfields/custom_fields.py,sha256=PEoaCEnvanysFQAaqTtD9-VwaBmnFkoP2pmpCl9ZFfI,2237
19
19
  oarepo_runtime/records/systemfields/mapping.py,sha256=GcNp_-Ho3G8nk4-SXgoWWk_IPdGsM0LZ-DBl5fnYJvE,1699
20
20
  oarepo_runtime/records/systemfields/publication_status.py,sha256=5D8L_-Wsf-Or-Er7EhuOnwzXfGY28TRPTg1XmMEKxM8,1995
21
+ oarepo_runtime/records/systemfields/relations.py,sha256=EyFTpdglkRCeCtNg3lXTh3H2_F3zTTjttjMQmuwt1M0,12240
21
22
  oarepo_runtime/records/systemfields/selectors.py,sha256=ijVDwAXaXTV5NtcXsrALkhddgCogLNe2eEscFr23qyg,1656
22
23
  oarepo_runtime/resources/__init__.py,sha256=voynQULXoOEviADkbOpekMphZPTAz4IOTg5BF9xPwTM,453
23
24
  oarepo_runtime/resources/config.py,sha256=Lbx1QPWAJ8z1truhYntbnhGGWp2OCcwqKm6BuvPJNT0,1330
24
25
  oarepo_runtime/services/__init__.py,sha256=OGtBgEeaDTyk2RPDNXuKbU9_7egFBZr42SM0gN5FrF4,341
25
26
  oarepo_runtime/services/generators.py,sha256=8Z2QGzob4c2vaaNqhcMZsRybmwtOt30Plgf3EFmcJXw,4622
26
- oarepo_runtime/services/results.py,sha256=qzoIQW5-ShL1YJDgPjfTlPnsgfEZhojNehhHNTUxRgI,7120
27
+ oarepo_runtime/services/results.py,sha256=EwMW1ed7u6uozgOLZpFa07-NKC89hJlHaVSD8-D5ibU,7211
27
28
  oarepo_runtime/services/config/__init__.py,sha256=SX1kfIGk8HkohdLQrNpRQUTltksEyDcCa-kFXxrX4e8,711
28
29
  oarepo_runtime/services/config/components.py,sha256=cyU-JeMzLuBL-9JkUKbUQuu527WAq0yptGs6806XSho,23039
29
30
  oarepo_runtime/services/config/link_conditions.py,sha256=T1ZZ5SbbvIfujm0oJx73s_ku3WmeFqElAOZJwPFTw2o,4388
@@ -42,8 +43,8 @@ oarepo_runtime/services/schema/__init__.py,sha256=jgAPI_uKC6Ug4KQWnwQVg3-aNaw-eH
42
43
  oarepo_runtime/services/schema/i18n.py,sha256=9D1zOQaPKAnYzejB0vO-m2BJYnam0N0Lrq4jID7twfE,3174
43
44
  oarepo_runtime/services/schema/i18n_ui.py,sha256=DbusphhGDeaobTt4nuwNgKZ6Houlu4Sv3SuMGkdjRRY,3582
44
45
  oarepo_runtime/services/schema/ui.py,sha256=Y_jBO-fowkpOgceWz8aqJSJAUiAnKLGSIuNpjNLnp8Q,4612
45
- oarepo_runtime-2.0.0.dev36.dist-info/METADATA,sha256=4wSsN4WdUaamDR4j3jrH-uj9K8b_NYhhpE_BA6TTh90,4707
46
- oarepo_runtime-2.0.0.dev36.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
47
- oarepo_runtime-2.0.0.dev36.dist-info/entry_points.txt,sha256=rOfs8R1oXFN_dLH9zAZ6ydkvr83mDajegc6NBIRsCMQ,318
48
- oarepo_runtime-2.0.0.dev36.dist-info/licenses/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
49
- oarepo_runtime-2.0.0.dev36.dist-info/RECORD,,
46
+ oarepo_runtime-2.0.0.dev38.dist-info/METADATA,sha256=avdajb5WZmRJ1u6EYEABUTGmJiDdGQjPn-lOSDD9aEg,4626
47
+ oarepo_runtime-2.0.0.dev38.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
+ oarepo_runtime-2.0.0.dev38.dist-info/entry_points.txt,sha256=rOfs8R1oXFN_dLH9zAZ6ydkvr83mDajegc6NBIRsCMQ,318
49
+ oarepo_runtime-2.0.0.dev38.dist-info/licenses/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
50
+ oarepo_runtime-2.0.0.dev38.dist-info/RECORD,,