oarepo-runtime 2.0.0.dev3__py3-none-any.whl → 2.0.0.dev5__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.

Potentially problematic release.


This version of oarepo-runtime might be problematic. Click here for more details.

@@ -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.0dev3"
22
+ __version__ = "2.0.0dev5"
23
23
 
24
24
  __all__ = ("Model", "OARepoRuntime", "__version__", "current_runtime")
oarepo_runtime/api.py CHANGED
@@ -11,22 +11,66 @@
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- from typing import TYPE_CHECKING, cast
14
+ import dataclasses
15
+ from functools import cached_property
16
+ from typing import TYPE_CHECKING, Any, cast
15
17
 
18
+ from flask import current_app
19
+ from invenio_base import invenio_url_for
20
+ from invenio_base.utils import obj_or_import_string
16
21
  from invenio_records_resources.proxies import current_service_registry
17
22
 
18
23
  if TYPE_CHECKING:
19
24
  from flask_babel.speaklater import LazyString
25
+ from flask_resources.responses import ResponseHandler
26
+ from flask_resources.serializers import BaseSerializer
20
27
  from invenio_drafts_resources.records.api import Draft
21
28
  from invenio_records_resources.records.api import RecordBase
29
+ from invenio_records_resources.resources.records.config import RecordResourceConfig
30
+ from invenio_records_resources.resources.records.resource import RecordResource
22
31
  from invenio_records_resources.services import RecordService, RecordServiceConfig
23
32
 
24
33
 
34
+ @dataclasses.dataclass
35
+ class Export:
36
+ """Configuration of an export format.
37
+
38
+ Exports are shown on the record landing page and user can download them.
39
+ """
40
+
41
+ code: str
42
+ """Code of the export format, used to identify the export format in the URL."""
43
+
44
+ name: LazyString
45
+ """Name of the export format, human readable."""
46
+
47
+ mimetype: str
48
+ """MIME type of the export format."""
49
+
50
+ serializer: BaseSerializer
51
+ """Serializer used to serialize the record into the export format."""
52
+
53
+ display: bool = True
54
+ """Whether the export format is displayed in the UI."""
55
+
56
+ oai_metadata_prefix: str | None = None
57
+ """OAI metadata prefix, if applicable. If not set, the export can not be used in OAI-PMH responses."""
58
+
59
+ oai_schema: str | None = None
60
+ """OAI schema, if applicable. If not set, the export can not be used in OAI-PMH responses."""
61
+
62
+ oai_namespace: str | None = None
63
+ """OAI namespace, if applicable. If not set, the export can not be used in OAI-PMH responses."""
64
+
65
+
25
66
  class Model[
26
67
  S: RecordService = RecordService,
27
68
  C: RecordServiceConfig = RecordServiceConfig,
28
69
  R: RecordBase = RecordBase,
29
70
  D: Draft = Draft,
71
+ # not sure why this is flagged by pyright as an error
72
+ RR: RecordResource = RecordResource, # pyright: ignore[reportGeneralTypeIssues]
73
+ RC: RecordResourceConfig = RecordResourceConfig,
30
74
  ]:
31
75
  """Model configuration.
32
76
 
@@ -35,22 +79,38 @@ class Model[
35
79
  variable.
36
80
  """
37
81
 
82
+ code: str
83
+ """Code of the model, used to identify the model"""
84
+
38
85
  name: str | LazyString
86
+ """Name of the model, human readable."""
87
+
39
88
  version: str
89
+ """Version of the model, should be a valid semantic version."""
90
+
40
91
  description: str | LazyString | None = None
41
- global_search_enabled: bool = False
92
+ """Description of the model, human readable."""
93
+
94
+ records_alias_enabled: bool = False
95
+ """Whether the records alias is enabled for this model. Such models will be searchable
96
+ via the `/api/records` endpoint."""
42
97
 
43
98
  def __init__( # noqa: PLR0913 more attributes as we are creating a config
44
99
  self,
100
+ *,
101
+ code: str,
45
102
  name: str | LazyString,
46
103
  version: str,
47
104
  service: str | S,
105
+ resource_config: RC | str,
48
106
  # params with default values
49
107
  service_config: C | None = None,
50
108
  description: str | LazyString | None = None,
51
109
  record: type[R] | None = None,
52
110
  draft: type[D] | None = None,
53
- global_search_enabled: bool = True,
111
+ resource: str | RR = "invenio_records_resources.resources.records.resource.RecordResource",
112
+ exports: list[Export] | None = None,
113
+ records_alias_enabled: bool = True,
54
114
  ):
55
115
  """Initialize the model configuration.
56
116
 
@@ -65,17 +125,29 @@ class Model[
65
125
  configuration.
66
126
  :param draft: Draft class, if not provided, it will be taken from the service
67
127
  configuration.
128
+ :param resource: Resource class or string import path to the resource class.
129
+ If not provided, it will be taken from the service configuration.
130
+ :param resource_config: Resource configuration, if not provided, it will be
131
+ taken from the resource class.
132
+ :param exports: List of export formats that can be used to export the record.
133
+ If not provided, no exports are available.
134
+ :param records_alias_enabled: Whether the records alias is enabled for this model.
135
+ Such models will be searchable via the `/api/records` endpoint.
68
136
  """
137
+ self.code = code
69
138
  self.name = name
70
139
  self.version = version
71
140
  self.description = description
72
- self.global_search_enabled = global_search_enabled
141
+ self.records_alias_enabled = records_alias_enabled
73
142
 
74
143
  # lazy getters ...
75
144
  self._record = record
76
145
  self._draft = draft
77
146
  self._service = service
78
147
  self._service_config = service_config
148
+ self._resource = resource
149
+ self._resource_config = resource_config
150
+ self._exports = exports or []
79
151
 
80
152
  @property
81
153
  def service(self) -> S:
@@ -109,3 +181,49 @@ class Model[
109
181
  return cast("type[D]", self.service.config.draft_cls)
110
182
  return None
111
183
  return self._draft
184
+
185
+ @property
186
+ def api_blueprint_name(self) -> str:
187
+ """Get the API blueprint name for the model."""
188
+ return cast("str", self.resource_config.blueprint_name)
189
+
190
+ def api_url(self, view_name: str, **kwargs: Any) -> str:
191
+ """Get the API URL for the model."""
192
+ return cast("str", invenio_url_for(f"{self.api_blueprint_name}.{view_name}", **kwargs))
193
+
194
+ @cached_property
195
+ def resource_config(self) -> RC:
196
+ """Get the resource configuration."""
197
+ if isinstance(self._resource_config, str):
198
+ resource_config_class: type[RC] = cast("type[RC]", obj_or_import_string(self._resource_config))
199
+ # need to import it here to avoid circular import issues
200
+ from .config import build_config
201
+
202
+ return build_config(resource_config_class, current_app)
203
+ return self._resource_config
204
+
205
+ @cached_property
206
+ def resource(self) -> RR:
207
+ """Get the resource."""
208
+ if isinstance(self._resource, str):
209
+ resource_class = obj_or_import_string(self._resource)
210
+ if resource_class is None:
211
+ raise ValueError(f"Resource class {self._resource} can not be None.")
212
+ return cast(
213
+ "RR",
214
+ resource_class(
215
+ service=self.service,
216
+ config=self.resource_config,
217
+ ),
218
+ )
219
+ return self._resource
220
+
221
+ @property
222
+ def exports(self) -> list[Export]:
223
+ """Get all exportable response handlers."""
224
+ return self._exports
225
+
226
+ @property
227
+ def response_handlers(self) -> dict[str, ResponseHandler]:
228
+ """Get all response handlers from the resource configuration."""
229
+ return cast("dict[str, ResponseHandler]", self.resource_config.response_handlers)
oarepo_runtime/config.py CHANGED
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  from typing import TYPE_CHECKING, Any
15
15
 
16
+ from invenio_i18n import lazy_gettext as _
16
17
  from invenio_vocabularies import __version__ as vocabularies_version
17
18
 
18
19
  from .api import Model
@@ -43,50 +44,68 @@ def build_config[T](config_class: type[T], app: Flask, *args: Any, **kwargs: Any
43
44
  OAREPO_MODELS: dict[str, Model] = {
44
45
  # default invenio vocabularies
45
46
  "vocabularies": Model(
46
- name="vocabularies",
47
+ code="vocabularies",
48
+ name=_("Base vocabularies"),
47
49
  version=vocabularies_version,
48
50
  service="vocabularies",
49
51
  description="Base (non-specialized) invenio vocabularies",
50
- global_search_enabled=False,
52
+ records_alias_enabled=False,
53
+ resource_config="invenio_vocabularies.resources.config.VocabulariesResourceConfig",
54
+ resource="invenio_vocabularies.resources.resource.VocabulariesResource",
51
55
  ),
52
56
  # affiliations
53
57
  "affiliations": Model(
54
- name="affiliations",
58
+ code="affiliations",
59
+ name=_("Affiliations"),
55
60
  version=vocabularies_version,
56
61
  service="affiliations",
57
62
  description="Affiliations vocabulary",
58
- global_search_enabled=False,
63
+ records_alias_enabled=False,
64
+ resource_config="invenio_vocabularies.contrib.affiliations.resources.AffiliationsResourceConfig",
65
+ resource="invenio_vocabularies.contrib.affiliations.resources.AffiliationsResource",
59
66
  ),
60
67
  # funders
61
68
  "funders": Model(
62
- name="funders",
69
+ code="funders",
70
+ name=_("Funders"),
63
71
  version=vocabularies_version,
64
72
  service="funders",
65
73
  description="Funders vocabulary",
66
- global_search_enabled=False,
74
+ records_alias_enabled=False,
75
+ resource_config="invenio_vocabularies.contrib.funders.resources.FundersResourceConfig",
76
+ resource="invenio_vocabularies.contrib.funders.resources.FundersResource",
67
77
  ),
68
78
  # awards
69
79
  "awards": Model(
70
- name="awards",
80
+ code="awards",
81
+ name=_("Awards"),
71
82
  version=vocabularies_version,
72
83
  service="awards",
73
84
  description="Awards vocabulary",
74
- global_search_enabled=False,
85
+ records_alias_enabled=False,
86
+ resource_config="invenio_vocabularies.contrib.awards.resources.AwardsResourceConfig",
87
+ resource="invenio_vocabularies.contrib.awards.resources.AwardsResource",
75
88
  ),
76
89
  # names
77
90
  "names": Model(
78
- name="names",
91
+ code="names",
92
+ name=_("Names"),
79
93
  version=vocabularies_version,
80
94
  service="names",
81
95
  description="Names vocabulary",
82
- global_search_enabled=False,
96
+ records_alias_enabled=False,
97
+ resource_config="invenio_vocabularies.contrib.names.resources.NamesResourceConfig",
98
+ resource="invenio_vocabularies.contrib.names.resources.NamesResource",
83
99
  ),
84
100
  # subjects
85
101
  "subjects": Model(
86
- name="subjects",
102
+ code="subjects",
103
+ name=_("Subjects"),
87
104
  version=vocabularies_version,
88
105
  service="subjects",
89
106
  description="Subjects vocabulary",
90
- global_search_enabled=False,
107
+ records_alias_enabled=False,
108
+ resource_config="invenio_vocabularies.contrib.subjects.resources.SubjectsResourceConfig",
109
+ resource="invenio_vocabularies.contrib.subjects.resources.SubjectsResource",
91
110
  ),
92
111
  }
@@ -47,9 +47,11 @@ class PublicationStatusSystemField(MappingSystemFieldMixin, SystemField):
47
47
  @override
48
48
  def post_dump(self, record: RecordBase, data: dict, dumper: Dumper | None = None) -> None:
49
49
  if self.key is None:
50
- return
50
+ return # pragma: no cover
51
51
  if not self.attr_name:
52
- raise ValueError("attr_name must be set for PublicationStatusSystemField")
52
+ raise ValueError( # pragma: no cover
53
+ "attr_name must be set for PublicationStatusSystemField"
54
+ )
53
55
  data[self.key] = getattr(record, self.attr_name)
54
56
 
55
57
  def __get__(self, record: RecordBase | None, owner: Any = None) -> Any:
@@ -0,0 +1,12 @@
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
+ """Facet parameter classes."""
11
+
12
+ from __future__ import annotations
@@ -0,0 +1,127 @@
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
+ """Facet params."""
11
+
12
+ from __future__ import annotations
13
+
14
+ import copy
15
+ import logging
16
+ from typing import TYPE_CHECKING
17
+
18
+ from flask import current_app
19
+ from invenio_access.permissions import system_user_id
20
+ from invenio_app.helpers import obj_or_import_string
21
+ from invenio_records_resources.services.records.facets import FacetsResponse
22
+ from invenio_records_resources.services.records.params import FacetsParam
23
+
24
+ if TYPE_CHECKING:
25
+ from flask_principal import Identity
26
+ from invenio_records_resources.services.records.config import SearchOptions
27
+ from invenio_search.engine import dsl
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+
32
+ class GroupedFacetsParam(FacetsParam):
33
+ """Facet parameter class that supports grouping of facets."""
34
+
35
+ def __init__(self, config: SearchOptions):
36
+ """Initialize the facets parameter with the given config."""
37
+ super().__init__(config)
38
+ self._facets = {**config.facets}
39
+
40
+ @property
41
+ def facets(self) -> dict[str, dsl.Facet]:
42
+ """Return the facets dictionary."""
43
+ return self._facets
44
+
45
+ def identity_facet_groups(self, identity: Identity) -> list[str]:
46
+ """Return the facet groups for the given identity."""
47
+ if "OAREPO_FACET_GROUP_NAME" in current_app.config:
48
+ find_facet_groups_func = obj_or_import_string(current_app.config["OAREPO_FACET_GROUP_NAME"])
49
+ return find_facet_groups_func(identity, self.config, None) # type: ignore[no-any-return]
50
+
51
+ if hasattr(identity, "provides"):
52
+ return [need.value for need in identity.provides if need.method == "role"]
53
+
54
+ return []
55
+
56
+ @property
57
+ def facet_groups(self) -> dict[str, dict[str, dsl.Facet]] | None:
58
+ """Return the facet groups defined in the service config."""
59
+ if hasattr(self.config, "facet_groups"):
60
+ return self.config.facet_groups # type: ignore[no-any-return]
61
+ return None
62
+
63
+ def identity_facets(self, identity: Identity) -> dict[str, dsl.Facet]:
64
+ """Return the facets for the given identity."""
65
+ if not self.facet_groups:
66
+ return self.facets
67
+
68
+ has_system_user_id = identity.id == system_user_id
69
+ has_system_process_need = any(need.method == "system_process" for need in identity.provides)
70
+ if has_system_user_id or has_system_process_need:
71
+ return self.facets
72
+
73
+ return self._filter_user_facets(identity)
74
+
75
+ def aggregate_with_user_facets(self, search: dsl.Search, user_facets: dict[str, dsl.Facet]) -> dsl.Search:
76
+ """Add aggregations representing the user facets."""
77
+ for name, facet in user_facets.items():
78
+ agg = facet.get_aggregation()
79
+ search.aggs.bucket(name, agg)
80
+
81
+ return search
82
+
83
+ def filter(self, search: dsl.Search) -> dsl.Search:
84
+ """Apply a post filter on the search."""
85
+ if not self._filters:
86
+ return search
87
+
88
+ filters = list(self._filters.values())
89
+
90
+ _filter = filters[0]
91
+ for f in filters[1:]:
92
+ _filter &= f
93
+
94
+ return search.filter(_filter).post_filter(_filter)
95
+
96
+ def apply(self, identity: Identity, search: dsl.Search, params: dict) -> dsl.Search:
97
+ """Evaluate the facets on the search."""
98
+ facets_values = params.pop("facets", {})
99
+ for name, values in facets_values.items():
100
+ if name in self.facets:
101
+ self.add_filter(name, values)
102
+
103
+ user_facets = self.identity_facets(identity)
104
+ self_copy = copy.copy(self)
105
+ self_copy._facets = user_facets # noqa: SLF001 - TODO: this looks like a hack
106
+ search = search.response_class(FacetsResponse.create_response_cls(self_copy))
107
+
108
+ search = self.aggregate_with_user_facets(search, user_facets)
109
+ search = self.filter(search)
110
+
111
+ params.update(self.selected_values)
112
+
113
+ return search
114
+
115
+ def _filter_user_facets(self, identity: Identity) -> dict[str, dsl.Facet]:
116
+ """Filter user facets based on the identity."""
117
+ user_facets = {}
118
+ if not self.facet_groups:
119
+ user_facets.update(self.facets)
120
+ else:
121
+ self.facets.clear() # TODO: why is this needed?
122
+ user_facets.update(self.facet_groups.get("default", {}))
123
+
124
+ groups = self.identity_facet_groups(identity)
125
+ for group in groups:
126
+ user_facets.update((self.facet_groups or {}).get(group, {}))
127
+ return user_facets
@@ -1,7 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oarepo-runtime
3
- Version: 2.0.0.dev3
3
+ Version: 2.0.0.dev5
4
4
  Summary: A set of runtime extensions of Invenio repository
5
+ Project-URL: Homepage, https://github.com/oarepo/oarepo-runtime
5
6
  License-Expression: MIT
6
7
  License-File: LICENSE
7
8
  Requires-Python: <3.14,>=3.13
@@ -1,6 +1,6 @@
1
- oarepo_runtime/__init__.py,sha256=hLYRfE2IAzqCkcxlBoCopCZceljM89PTrpiN7k5FHv8,685
2
- oarepo_runtime/api.py,sha256=h3iwvDtL1AFYOQqKd6HJj1cnbxM_gy1IWt4gFcSzzSc,3826
3
- oarepo_runtime/config.py,sha256=auPBzCMXIcYdA1Tee5srMtfu0D5lDd38sn4BO5wGVJ4,2540
1
+ oarepo_runtime/__init__.py,sha256=1nCgSbvNvQVT0Ynpy_ygn15FJOE0XbYHkK2sAzt5bWA,685
2
+ oarepo_runtime/api.py,sha256=yRgU_LKuH3dRvK-M4phly3h9DzNYItaFtgbjqjeMEdA,8627
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
6
  oarepo_runtime/cli/__init__.py,sha256=iPs1a4blP7750rwEXobzK3YHgsfGxoVTZxwWMslAlTY,350
@@ -11,20 +11,22 @@ oarepo_runtime/records/mapping.py,sha256=SJbSzerT1645a93-3-Fgz_i3anzFNlrZqbjjwW2
11
11
  oarepo_runtime/records/pid_providers.py,sha256=pVXVeYmAsXy-IEdM2zHZ7UWkAnzXg1gtssfLc9QZbPA,1717
12
12
  oarepo_runtime/records/systemfields/__init__.py,sha256=g-u408qyNnsbUTpDtVVwlcyiJaO68GTjDN0W9rXs9pk,524
13
13
  oarepo_runtime/records/systemfields/mapping.py,sha256=66OQavKewJEUMkghymOxvskIO0LUSP2E-MbHryeT5Nk,1968
14
- oarepo_runtime/records/systemfields/publication_status.py,sha256=vYIwueI37Auh84IptSxeF9KYC32EGmBe3-Rk1DWXMF8,1957
14
+ oarepo_runtime/records/systemfields/publication_status.py,sha256=1g3VXNPh0FsiPCpe-7ZuaMEF4x8ffrDrt37Rqnjp0ng,2027
15
15
  oarepo_runtime/services/__init__.py,sha256=OGtBgEeaDTyk2RPDNXuKbU9_7egFBZr42SM0gN5FrF4,341
16
16
  oarepo_runtime/services/results.py,sha256=fk-Enx_LwZLbw81yZ7CXVTku86vd3_fjprnb8l5sFHk,6657
17
17
  oarepo_runtime/services/config/__init__.py,sha256=SX1kfIGk8HkohdLQrNpRQUTltksEyDcCa-kFXxrX4e8,711
18
18
  oarepo_runtime/services/config/link_conditions.py,sha256=raqf4yaBNLqNYgBxVNblo8MRJneVIFkwVNW7IW3AVYI,4309
19
19
  oarepo_runtime/services/config/permissions.py,sha256=x5k61LGnpXyJfXVoCTq2tTVTtPckmBcBtcBJx4UN9EA,3056
20
+ oarepo_runtime/services/facets/__init__.py,sha256=k39ZYt1dMVOW01QRSTgx3CfuTYwvEWmL0VYTR3huVsE,349
21
+ oarepo_runtime/services/facets/params.py,sha256=B8RssdAt8bWRzwcTc5ireFkJg-q22DDOAprk9uhIwL4,4691
20
22
  oarepo_runtime/services/records/__init__.py,sha256=c0n4vcMcJhSUWsKz0iyV4TTlGG8oLlJp0YEN0QGCZ8U,428
21
23
  oarepo_runtime/services/records/links.py,sha256=lqvnXabquL4DqWV93cdUYNUVYHx7T5Q0EXXuWAHM33A,1078
22
24
  oarepo_runtime/services/records/mapping.py,sha256=y3oeToKEnaRYpMV3q2-2cXNzyzyL3XXGvY26BifybpE,1332
23
25
  oarepo_runtime/services/schema/__init__.py,sha256=jgAPI_uKC6Ug4KQWnwQVg3-aNaw-eHja323AUFo5ELo,351
24
26
  oarepo_runtime/services/schema/i18n.py,sha256=9D1zOQaPKAnYzejB0vO-m2BJYnam0N0Lrq4jID7twfE,3174
25
27
  oarepo_runtime/services/schema/i18n_ui.py,sha256=DbusphhGDeaobTt4nuwNgKZ6Houlu4Sv3SuMGkdjRRY,3582
26
- oarepo_runtime-2.0.0.dev3.dist-info/METADATA,sha256=nLtfS-x8LURACObTJYTyGeXIzHMQ9jtSzfg_l145cHo,4430
27
- oarepo_runtime-2.0.0.dev3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
- oarepo_runtime-2.0.0.dev3.dist-info/entry_points.txt,sha256=7HqK5jumIgDVJa7ifjPKizginfIm5_R_qUVKPf_Yq-c,145
29
- oarepo_runtime-2.0.0.dev3.dist-info/licenses/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
30
- oarepo_runtime-2.0.0.dev3.dist-info/RECORD,,
28
+ oarepo_runtime-2.0.0.dev5.dist-info/METADATA,sha256=w-FR66FJ2qaM2QLi3u5U5d4icH5AiJoxVULomc4a97U,4494
29
+ oarepo_runtime-2.0.0.dev5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
30
+ oarepo_runtime-2.0.0.dev5.dist-info/entry_points.txt,sha256=7HqK5jumIgDVJa7ifjPKizginfIm5_R_qUVKPf_Yq-c,145
31
+ oarepo_runtime-2.0.0.dev5.dist-info/licenses/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
32
+ oarepo_runtime-2.0.0.dev5.dist-info/RECORD,,