oarepo-runtime 2.0.0.dev3__tar.gz → 2.0.0.dev5__tar.gz

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.
Files changed (33) hide show
  1. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/PKG-INFO +2 -1
  2. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/__init__.py +1 -1
  3. oarepo_runtime-2.0.0.dev5/oarepo_runtime/api.py +229 -0
  4. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/config.py +31 -12
  5. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/records/systemfields/publication_status.py +4 -2
  6. oarepo_runtime-2.0.0.dev5/oarepo_runtime/services/facets/__init__.py +12 -0
  7. oarepo_runtime-2.0.0.dev5/oarepo_runtime/services/facets/params.py +127 -0
  8. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/pyproject.toml +3 -0
  9. oarepo_runtime-2.0.0.dev3/oarepo_runtime/api.py +0 -111
  10. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/.gitignore +0 -0
  11. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/LICENSE +0 -0
  12. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/README.md +0 -0
  13. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/cli/__init__.py +0 -0
  14. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/cli/search.py +0 -0
  15. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/ext.py +0 -0
  16. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/proxies.py +0 -0
  17. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/records/__init__.py +0 -0
  18. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/records/drafts.py +0 -0
  19. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/records/mapping.py +0 -0
  20. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/records/pid_providers.py +0 -0
  21. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/records/systemfields/__init__.py +0 -0
  22. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/records/systemfields/mapping.py +0 -0
  23. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/__init__.py +0 -0
  24. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/config/__init__.py +0 -0
  25. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/config/link_conditions.py +0 -0
  26. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/config/permissions.py +0 -0
  27. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/records/__init__.py +0 -0
  28. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/records/links.py +0 -0
  29. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/records/mapping.py +0 -0
  30. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/results.py +0 -0
  31. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/schema/__init__.py +0 -0
  32. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/schema/i18n.py +0 -0
  33. {oarepo_runtime-2.0.0.dev3 → oarepo_runtime-2.0.0.dev5}/oarepo_runtime/services/schema/i18n_ui.py +0 -0
@@ -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
@@ -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")
@@ -0,0 +1,229 @@
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
+ """Runtime API classes that are returned from the current_runtime instance."""
11
+
12
+ from __future__ import annotations
13
+
14
+ import dataclasses
15
+ from functools import cached_property
16
+ from typing import TYPE_CHECKING, Any, cast
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
21
+ from invenio_records_resources.proxies import current_service_registry
22
+
23
+ if TYPE_CHECKING:
24
+ from flask_babel.speaklater import LazyString
25
+ from flask_resources.responses import ResponseHandler
26
+ from flask_resources.serializers import BaseSerializer
27
+ from invenio_drafts_resources.records.api import Draft
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
31
+ from invenio_records_resources.services import RecordService, RecordServiceConfig
32
+
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
+
66
+ class Model[
67
+ S: RecordService = RecordService,
68
+ C: RecordServiceConfig = RecordServiceConfig,
69
+ R: RecordBase = RecordBase,
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,
74
+ ]:
75
+ """Model configuration.
76
+
77
+ Every model in oarepo repository must have this configuration which must be
78
+ registered in the `oarepo.runtime` extension via the OAREPO_MODELS config
79
+ variable.
80
+ """
81
+
82
+ code: str
83
+ """Code of the model, used to identify the model"""
84
+
85
+ name: str | LazyString
86
+ """Name of the model, human readable."""
87
+
88
+ version: str
89
+ """Version of the model, should be a valid semantic version."""
90
+
91
+ description: str | LazyString | None = None
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."""
97
+
98
+ def __init__( # noqa: PLR0913 more attributes as we are creating a config
99
+ self,
100
+ *,
101
+ code: str,
102
+ name: str | LazyString,
103
+ version: str,
104
+ service: str | S,
105
+ resource_config: RC | str,
106
+ # params with default values
107
+ service_config: C | None = None,
108
+ description: str | LazyString | None = None,
109
+ record: type[R] | None = None,
110
+ draft: type[D] | None = None,
111
+ resource: str | RR = "invenio_records_resources.resources.records.resource.RecordResource",
112
+ exports: list[Export] | None = None,
113
+ records_alias_enabled: bool = True,
114
+ ):
115
+ """Initialize the model configuration.
116
+
117
+ :param name: Name of the model, human readable.
118
+ :param version: Version of the model, should be a valid semantic version.
119
+ :param description: Description of the model, human readable.
120
+ :param service: Name of the service inside the `current_service_registry` or
121
+ a configured service instance.
122
+ :param service_config: Service configuration, if not provided,
123
+ if will be taken from the service.
124
+ :param record: Record class, if not provided, it will be taken from the service
125
+ configuration.
126
+ :param draft: Draft class, if not provided, it will be taken from the service
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.
136
+ """
137
+ self.code = code
138
+ self.name = name
139
+ self.version = version
140
+ self.description = description
141
+ self.records_alias_enabled = records_alias_enabled
142
+
143
+ # lazy getters ...
144
+ self._record = record
145
+ self._draft = draft
146
+ self._service = service
147
+ self._service_config = service_config
148
+ self._resource = resource
149
+ self._resource_config = resource_config
150
+ self._exports = exports or []
151
+
152
+ @property
153
+ def service(self) -> S:
154
+ """Get the service."""
155
+ if isinstance(self._service, str):
156
+ return cast(
157
+ "S",
158
+ current_service_registry.get(self._service), # type: ignore[attr-defined]
159
+ )
160
+ return self._service
161
+
162
+ @property
163
+ def service_config(self) -> C:
164
+ """Get the service configuration."""
165
+ if self._service_config is not None:
166
+ return self._service_config
167
+ return cast("C", self.service.config)
168
+
169
+ @property
170
+ def record_cls(self) -> type[R]:
171
+ """Get the record class."""
172
+ if self._record is None:
173
+ return cast("type[R]", self.service.config.record_cls)
174
+ return self._record
175
+
176
+ @property
177
+ def draft_cls(self) -> type[D] | None:
178
+ """Get the draft class."""
179
+ if self._draft is None:
180
+ if hasattr(self.service.config, "draft_cls"):
181
+ return cast("type[D]", self.service.config.draft_cls)
182
+ return None
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)
@@ -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
@@ -14,6 +14,9 @@ dependencies = [
14
14
  ]
15
15
  requires-python = ">=3.13,<3.14"
16
16
 
17
+ [project.urls]
18
+ Homepage = "https://github.com/oarepo/oarepo-runtime"
19
+
17
20
  [project.optional-dependencies]
18
21
  dev = [
19
22
  "pytest>=7.1.2",
@@ -1,111 +0,0 @@
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
- """Runtime API classes that are returned from the current_runtime instance."""
11
-
12
- from __future__ import annotations
13
-
14
- from typing import TYPE_CHECKING, cast
15
-
16
- from invenio_records_resources.proxies import current_service_registry
17
-
18
- if TYPE_CHECKING:
19
- from flask_babel.speaklater import LazyString
20
- from invenio_drafts_resources.records.api import Draft
21
- from invenio_records_resources.records.api import RecordBase
22
- from invenio_records_resources.services import RecordService, RecordServiceConfig
23
-
24
-
25
- class Model[
26
- S: RecordService = RecordService,
27
- C: RecordServiceConfig = RecordServiceConfig,
28
- R: RecordBase = RecordBase,
29
- D: Draft = Draft,
30
- ]:
31
- """Model configuration.
32
-
33
- Every model in oarepo repository must have this configuration which must be
34
- registered in the `oarepo.runtime` extension via the OAREPO_MODELS config
35
- variable.
36
- """
37
-
38
- name: str | LazyString
39
- version: str
40
- description: str | LazyString | None = None
41
- global_search_enabled: bool = False
42
-
43
- def __init__( # noqa: PLR0913 more attributes as we are creating a config
44
- self,
45
- name: str | LazyString,
46
- version: str,
47
- service: str | S,
48
- # params with default values
49
- service_config: C | None = None,
50
- description: str | LazyString | None = None,
51
- record: type[R] | None = None,
52
- draft: type[D] | None = None,
53
- global_search_enabled: bool = True,
54
- ):
55
- """Initialize the model configuration.
56
-
57
- :param name: Name of the model, human readable.
58
- :param version: Version of the model, should be a valid semantic version.
59
- :param description: Description of the model, human readable.
60
- :param service: Name of the service inside the `current_service_registry` or
61
- a configured service instance.
62
- :param service_config: Service configuration, if not provided,
63
- if will be taken from the service.
64
- :param record: Record class, if not provided, it will be taken from the service
65
- configuration.
66
- :param draft: Draft class, if not provided, it will be taken from the service
67
- configuration.
68
- """
69
- self.name = name
70
- self.version = version
71
- self.description = description
72
- self.global_search_enabled = global_search_enabled
73
-
74
- # lazy getters ...
75
- self._record = record
76
- self._draft = draft
77
- self._service = service
78
- self._service_config = service_config
79
-
80
- @property
81
- def service(self) -> S:
82
- """Get the service."""
83
- if isinstance(self._service, str):
84
- return cast(
85
- "S",
86
- current_service_registry.get(self._service), # type: ignore[attr-defined]
87
- )
88
- return self._service
89
-
90
- @property
91
- def service_config(self) -> C:
92
- """Get the service configuration."""
93
- if self._service_config is not None:
94
- return self._service_config
95
- return cast("C", self.service.config)
96
-
97
- @property
98
- def record_cls(self) -> type[R]:
99
- """Get the record class."""
100
- if self._record is None:
101
- return cast("type[R]", self.service.config.record_cls)
102
- return self._record
103
-
104
- @property
105
- def draft_cls(self) -> type[D] | None:
106
- """Get the draft class."""
107
- if self._draft is None:
108
- if hasattr(self.service.config, "draft_cls"):
109
- return cast("type[D]", self.service.config.draft_cls)
110
- return None
111
- return self._draft