oarepo-runtime 2.0.0.dev18__tar.gz → 2.0.0.dev20__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 (41) hide show
  1. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/PKG-INFO +1 -1
  2. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/__init__.py +1 -1
  3. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/api.py +13 -5
  4. oarepo_runtime-2.0.0.dev20/oarepo_runtime/records/systemfields/custom_fields.py +63 -0
  5. oarepo_runtime-2.0.0.dev20/oarepo_runtime/records/systemfields/selectors.py +51 -0
  6. oarepo_runtime-2.0.0.dev20/oarepo_runtime/services/records/custom_fields.py +44 -0
  7. oarepo_runtime-2.0.0.dev20/oarepo_runtime/typing.py +60 -0
  8. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/.gitignore +0 -0
  9. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/LICENSE +0 -0
  10. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/README.md +0 -0
  11. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/cli/__init__.py +0 -0
  12. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/cli/search.py +0 -0
  13. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/config.py +0 -0
  14. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/ext.py +0 -0
  15. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/proxies.py +0 -0
  16. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/py.typed +0 -0
  17. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/records/__init__.py +0 -0
  18. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/records/drafts.py +0 -0
  19. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/records/mapping.py +0 -0
  20. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/records/pid_providers.py +0 -0
  21. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/records/systemfields/__init__.py +0 -0
  22. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/records/systemfields/mapping.py +0 -0
  23. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/records/systemfields/publication_status.py +0 -0
  24. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/resources/__init__.py +0 -0
  25. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/resources/config.py +0 -0
  26. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/__init__.py +0 -0
  27. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/config/__init__.py +0 -0
  28. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/config/components.py +0 -0
  29. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/config/link_conditions.py +0 -0
  30. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/config/permissions.py +0 -0
  31. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/facets/__init__.py +0 -0
  32. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/facets/params.py +0 -0
  33. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/generators.py +0 -0
  34. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/records/__init__.py +0 -0
  35. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/records/links.py +0 -0
  36. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/records/mapping.py +0 -0
  37. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/results.py +0 -0
  38. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/schema/__init__.py +0 -0
  39. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/schema/i18n.py +0 -0
  40. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/oarepo_runtime/services/schema/i18n_ui.py +0 -0
  41. {oarepo_runtime-2.0.0.dev18 → oarepo_runtime-2.0.0.dev20}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oarepo-runtime
3
- Version: 2.0.0.dev18
3
+ Version: 2.0.0.dev20
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
@@ -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.0dev18"
22
+ __version__ = "2.0.0dev20"
23
23
 
24
24
  __all__ = ("Model", "OARepoRuntime", "__version__", "current_runtime")
@@ -126,16 +126,17 @@ class Model[
126
126
  exports: list[Export] | None = None,
127
127
  records_alias_enabled: bool = True,
128
128
  model_metadata: ModelMetadata | None = None,
129
+ features: Mapping[str, Any] | None = None,
129
130
  ):
130
131
  """Initialize the model configuration.
131
132
 
132
- :param name: Name of the model, human readable.
133
- :param version: Version of the model, should be a valid semantic version.
134
- :param description: Description of the model, human readable.
133
+ :param name: Name of the model, human-readable.
134
+ :param version: Version of the model should be a valid semantic version.
135
+ :param description: Description of the model, human-readable.
135
136
  :param service: Name of the service inside the `current_service_registry` or
136
137
  a configured service instance.
137
138
  :param service_config: Service configuration, if not provided,
138
- if will be taken from the service.
139
+ it will be taken from the service.
139
140
  :param record: Record class, if not provided, it will be taken from the service
140
141
  configuration.
141
142
  :param draft: Draft class, if not provided, it will be taken from the service
@@ -146,9 +147,10 @@ class Model[
146
147
  taken from the resource class.
147
148
  :param exports: List of export formats that can be used to export the record.
148
149
  If not provided, no exports are available.
149
- :param records_alias_enabled: Whether the records alias is enabled for this model.
150
+ :param records_alias_enabled: Whether the record alias is enabled for this model.
150
151
  Such models will be searchable via the `/api/records` endpoint.
151
152
  :param model_metadata: Metadata of the model.
153
+ :param features: Features of the model. Filled by the feature presets themselves during registration.
152
154
  """
153
155
  self._code = code
154
156
  self._name = name
@@ -171,6 +173,7 @@ class Model[
171
173
  self._resource_config = resource_config
172
174
  self._exports = exports or []
173
175
  self._model_metadata = model_metadata
176
+ self._features = features
174
177
 
175
178
  @property
176
179
  def code(self) -> str:
@@ -342,3 +345,8 @@ class Model[
342
345
  def response_handlers(self) -> dict[str, ResponseHandler]:
343
346
  """Get all response handlers from the resource configuration."""
344
347
  return cast("dict[str, ResponseHandler]", self.resource_config.response_handlers)
348
+
349
+ @property
350
+ def features(self) -> Mapping[str, Any] | None:
351
+ """Get a mapping of features."""
352
+ return self._features
@@ -0,0 +1,63 @@
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
+ """Module to update the mapping of system fields in a record class."""
10
+
11
+ from __future__ import annotations
12
+
13
+ import inspect
14
+ from typing import TYPE_CHECKING
15
+
16
+ from flask import current_app
17
+ from invenio_records.systemfields.relations import MultiRelationsField
18
+ from invenio_vocabularies.records.systemfields.relations import CustomFieldsRelation
19
+
20
+ from oarepo_runtime.records.mapping import prefixed_index, update_record_index
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Iterable
24
+
25
+ from invenio_records.api import RecordBase
26
+
27
+
28
+ def update_record_system_fields_mapping_relation_field(
29
+ record_class: type[RecordBase],
30
+ ) -> None:
31
+ """Update mapping for system fields in the record class.
32
+
33
+ :param record_class: The record class which index mapping should be updated.
34
+ :raise search.RequestError: If there is an error while updating the mapping.
35
+ """
36
+ index = getattr(record_class, "index", None)
37
+ if not index:
38
+ return
39
+
40
+ for field_name, fld in get_mapping_relation_fields(record_class):
41
+ custom_fields = current_app.config.get(fld._fields_var, []) # noqa: SLF001
42
+
43
+ props: dict[str, dict] = {}
44
+ mapping = {field_name: {"type": "object", "properties": props}}
45
+ for cf in custom_fields:
46
+ # get mapping
47
+ props[cf.name] = cf.mapping
48
+
49
+ # upload mapping
50
+ if props:
51
+ update_record_index(prefixed_index(index), {}, mapping, None)
52
+
53
+
54
+ def get_mapping_relation_fields(
55
+ record_class: type[RecordBase],
56
+ ) -> Iterable[tuple[str, CustomFieldsRelation]]:
57
+ """Get all mapping fields from the record class."""
58
+ for _, relation_fields in inspect.getmembers(record_class, lambda x: isinstance(x, MultiRelationsField)):
59
+ yield from (
60
+ (field_name, relation_field)
61
+ for field_name, relation_field in relation_fields._original_fields.items() # noqa: SLF001
62
+ if isinstance(relation_field, CustomFieldsRelation)
63
+ )
@@ -0,0 +1,51 @@
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
+ """Selectors for extracting values from records based on specified paths."""
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, Protocol
14
+
15
+
16
+ class Selector(Protocol):
17
+ """Protocol for selectors that extract values from records."""
18
+
19
+ def select(self, record: dict) -> list[Any]: # noqa: ARG002
20
+ """Select values from the record based on the selector's logic."""
21
+ return []
22
+
23
+
24
+ class PathSelector(Selector):
25
+ """Selector that extracts values from records based on specified paths."""
26
+
27
+ def __init__(self, *paths: str) -> None:
28
+ """Initialize the PathSelector with given paths."""
29
+ self.paths = [x.split(".") for x in paths]
30
+
31
+ def select(self, record: dict) -> list[Any]:
32
+ """Select values from the record based on the specified paths."""
33
+ ret = []
34
+ for path in self.paths:
35
+ ret.extend(list(getter(record, path)))
36
+ return ret
37
+
38
+
39
+ def getter(data: list | dict, path: list) -> Any:
40
+ """Recursively get values from data based on the provided path."""
41
+ if len(path) == 0:
42
+ if isinstance(data, list):
43
+ yield from data
44
+ else:
45
+ yield data
46
+ elif isinstance(data, dict):
47
+ if path[0] in data:
48
+ yield from getter(data[path[0]], path[1:])
49
+ elif isinstance(data, list):
50
+ for item in data:
51
+ yield from getter(item, path)
@@ -0,0 +1,44 @@
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
+ """Services for updating custom fields mappings in opensearch."""
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING
14
+
15
+ from invenio_records_resources.services.records import (
16
+ RecordService,
17
+ RecordServiceConfig,
18
+ )
19
+
20
+ from oarepo_runtime import current_runtime
21
+ from oarepo_runtime.records.systemfields.custom_fields import (
22
+ update_record_system_fields_mapping_relation_field,
23
+ )
24
+
25
+ if TYPE_CHECKING:
26
+ from invenio_records_resources.services.base import Service
27
+
28
+
29
+ def update_all_records_mappings_relation_fields() -> None:
30
+ """Update all mappings for the registered record classes."""
31
+ service: Service
32
+ for service in current_runtime.services.values():
33
+ if not isinstance(service, RecordService):
34
+ continue
35
+
36
+ config: RecordServiceConfig = service.config
37
+
38
+ record_class = getattr(config, "record_cls", None)
39
+ if record_class:
40
+ update_record_system_fields_mapping_relation_field(record_class)
41
+
42
+ draft_class = getattr(config, "draft_cls", None)
43
+ if draft_class:
44
+ update_record_system_fields_mapping_relation_field(draft_class)
@@ -0,0 +1,60 @@
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
+ """Module for typing related functionality."""
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Callable
18
+
19
+
20
+ def require_kwargs(*kwargs_names: str) -> Any:
21
+ """Wrap function to require specific kwargs in a function call.
22
+
23
+ This decorator is used to fix typing errors in inherited classes where the base class defines kwargs and the
24
+ inherited class needs to access a specific kwarg.
25
+
26
+ Example:
27
+ ```python
28
+ # base class
29
+ class ConditionalGenerator(
30
+ InvenioConditionalGenerator, ABC
31
+ ):
32
+ @abstractmethod
33
+ def _condition(
34
+ self, **kwargs: Any
35
+ ) -> bool: ...
36
+
37
+
38
+ # inherited class
39
+ class IfRecordHasField(
40
+ ConditionalGenerator
41
+ ):
42
+ @override
43
+ @require_kwargs("field")
44
+ def _condition(
45
+ self, *, field, **kwargs: Any
46
+ ) -> bool: ...
47
+ ```
48
+
49
+ """
50
+
51
+ def wrapper(f: Callable) -> Callable:
52
+ def wrapped_f(*args: Any, **kwargs: Any) -> Any:
53
+ for kwarg_name in kwargs_names:
54
+ if kwarg_name not in kwargs:
55
+ raise ValueError(f"Keyword argument {kwarg_name} not found in function call.")
56
+ return f(*args, **kwargs)
57
+
58
+ return wrapped_f
59
+
60
+ return wrapper