oarepo-runtime 1.10.2__py3-none-any.whl → 2.0.0.dev3__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.
Files changed (171) hide show
  1. oarepo_runtime/__init__.py +24 -0
  2. oarepo_runtime/api.py +111 -0
  3. oarepo_runtime/cli/__init__.py +10 -21
  4. oarepo_runtime/cli/search.py +34 -0
  5. oarepo_runtime/config.py +86 -13
  6. oarepo_runtime/ext.py +64 -82
  7. oarepo_runtime/proxies.py +21 -5
  8. oarepo_runtime/records/__init__.py +11 -50
  9. oarepo_runtime/records/drafts.py +24 -18
  10. oarepo_runtime/records/mapping.py +84 -0
  11. oarepo_runtime/records/pid_providers.py +43 -7
  12. oarepo_runtime/records/systemfields/__init__.py +15 -33
  13. oarepo_runtime/records/systemfields/mapping.py +41 -24
  14. oarepo_runtime/records/systemfields/publication_status.py +59 -0
  15. oarepo_runtime/services/__init__.py +12 -0
  16. oarepo_runtime/services/config/__init__.py +15 -21
  17. oarepo_runtime/services/config/link_conditions.py +69 -75
  18. oarepo_runtime/services/config/permissions.py +62 -0
  19. oarepo_runtime/services/records/__init__.py +14 -1
  20. oarepo_runtime/services/records/links.py +21 -11
  21. oarepo_runtime/services/records/mapping.py +42 -0
  22. oarepo_runtime/services/results.py +98 -109
  23. oarepo_runtime/services/schema/__init__.py +12 -44
  24. oarepo_runtime/services/schema/i18n.py +47 -22
  25. oarepo_runtime/services/schema/i18n_ui.py +61 -24
  26. {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/METADATA +9 -21
  27. oarepo_runtime-2.0.0.dev3.dist-info/RECORD +30 -0
  28. {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/WHEEL +1 -2
  29. oarepo_runtime-2.0.0.dev3.dist-info/entry_points.txt +5 -0
  30. oarepo_runtime/cli/assets.py +0 -145
  31. oarepo_runtime/cli/base.py +0 -25
  32. oarepo_runtime/cli/cf.py +0 -15
  33. oarepo_runtime/cli/check.py +0 -167
  34. oarepo_runtime/cli/configuration.py +0 -51
  35. oarepo_runtime/cli/fixtures.py +0 -167
  36. oarepo_runtime/cli/index.py +0 -272
  37. oarepo_runtime/cli/permissions/__init__.py +0 -6
  38. oarepo_runtime/cli/permissions/base.py +0 -26
  39. oarepo_runtime/cli/permissions/evaluate.py +0 -63
  40. oarepo_runtime/cli/permissions/list.py +0 -239
  41. oarepo_runtime/cli/permissions/search.py +0 -121
  42. oarepo_runtime/cli/validate.py +0 -150
  43. oarepo_runtime/datastreams/__init__.py +0 -38
  44. oarepo_runtime/datastreams/asynchronous.py +0 -247
  45. oarepo_runtime/datastreams/catalogue.py +0 -150
  46. oarepo_runtime/datastreams/datastreams.py +0 -152
  47. oarepo_runtime/datastreams/errors.py +0 -54
  48. oarepo_runtime/datastreams/ext.py +0 -41
  49. oarepo_runtime/datastreams/fixtures.py +0 -265
  50. oarepo_runtime/datastreams/json.py +0 -4
  51. oarepo_runtime/datastreams/readers/__init__.py +0 -39
  52. oarepo_runtime/datastreams/readers/attachments.py +0 -51
  53. oarepo_runtime/datastreams/readers/excel.py +0 -123
  54. oarepo_runtime/datastreams/readers/json.py +0 -27
  55. oarepo_runtime/datastreams/readers/service.py +0 -54
  56. oarepo_runtime/datastreams/readers/yaml.py +0 -14
  57. oarepo_runtime/datastreams/semi_asynchronous.py +0 -91
  58. oarepo_runtime/datastreams/synchronous.py +0 -70
  59. oarepo_runtime/datastreams/transformers.py +0 -18
  60. oarepo_runtime/datastreams/types.py +0 -323
  61. oarepo_runtime/datastreams/utils.py +0 -131
  62. oarepo_runtime/datastreams/writers/__init__.py +0 -21
  63. oarepo_runtime/datastreams/writers/attachments_file.py +0 -92
  64. oarepo_runtime/datastreams/writers/attachments_service.py +0 -118
  65. oarepo_runtime/datastreams/writers/publish.py +0 -70
  66. oarepo_runtime/datastreams/writers/service.py +0 -175
  67. oarepo_runtime/datastreams/writers/utils.py +0 -30
  68. oarepo_runtime/datastreams/writers/validation_errors.py +0 -20
  69. oarepo_runtime/datastreams/writers/yaml.py +0 -56
  70. oarepo_runtime/ext_config.py +0 -67
  71. oarepo_runtime/i18n/__init__.py +0 -3
  72. oarepo_runtime/info/__init__.py +0 -0
  73. oarepo_runtime/info/check.py +0 -95
  74. oarepo_runtime/info/permissions/__init__.py +0 -0
  75. oarepo_runtime/info/permissions/debug.py +0 -191
  76. oarepo_runtime/info/views.py +0 -586
  77. oarepo_runtime/profile.py +0 -60
  78. oarepo_runtime/records/dumpers/__init__.py +0 -8
  79. oarepo_runtime/records/dumpers/edtf_interval.py +0 -38
  80. oarepo_runtime/records/dumpers/multilingual_dumper.py +0 -34
  81. oarepo_runtime/records/entity_resolvers/__init__.py +0 -13
  82. oarepo_runtime/records/entity_resolvers/proxies.py +0 -57
  83. oarepo_runtime/records/mappings/__init__.py +0 -0
  84. oarepo_runtime/records/mappings/rdm_parent_mapping.json +0 -483
  85. oarepo_runtime/records/owners/__init__.py +0 -3
  86. oarepo_runtime/records/owners/registry.py +0 -22
  87. oarepo_runtime/records/relations/__init__.py +0 -22
  88. oarepo_runtime/records/relations/base.py +0 -296
  89. oarepo_runtime/records/relations/internal.py +0 -46
  90. oarepo_runtime/records/relations/lookup.py +0 -28
  91. oarepo_runtime/records/relations/pid_relation.py +0 -102
  92. oarepo_runtime/records/systemfields/featured_file.py +0 -45
  93. oarepo_runtime/records/systemfields/has_draftcheck.py +0 -47
  94. oarepo_runtime/records/systemfields/icu.py +0 -371
  95. oarepo_runtime/records/systemfields/owner.py +0 -115
  96. oarepo_runtime/records/systemfields/record_status.py +0 -35
  97. oarepo_runtime/records/systemfields/selectors.py +0 -98
  98. oarepo_runtime/records/systemfields/synthetic.py +0 -130
  99. oarepo_runtime/resources/__init__.py +0 -4
  100. oarepo_runtime/resources/config.py +0 -12
  101. oarepo_runtime/resources/file_resource.py +0 -15
  102. oarepo_runtime/resources/json_serializer.py +0 -27
  103. oarepo_runtime/resources/localized_ui_json_serializer.py +0 -54
  104. oarepo_runtime/resources/resource.py +0 -53
  105. oarepo_runtime/resources/responses.py +0 -20
  106. oarepo_runtime/services/components.py +0 -429
  107. oarepo_runtime/services/config/draft_link.py +0 -23
  108. oarepo_runtime/services/config/permissions_presets.py +0 -174
  109. oarepo_runtime/services/config/service.py +0 -117
  110. oarepo_runtime/services/custom_fields/__init__.py +0 -80
  111. oarepo_runtime/services/custom_fields/mappings.py +0 -188
  112. oarepo_runtime/services/entity/__init__.py +0 -0
  113. oarepo_runtime/services/entity/config.py +0 -14
  114. oarepo_runtime/services/entity/schema.py +0 -9
  115. oarepo_runtime/services/entity/service.py +0 -48
  116. oarepo_runtime/services/expansions/__init__.py +0 -0
  117. oarepo_runtime/services/expansions/expandable_fields.py +0 -21
  118. oarepo_runtime/services/expansions/service.py +0 -4
  119. oarepo_runtime/services/facets/__init__.py +0 -33
  120. oarepo_runtime/services/facets/base.py +0 -12
  121. oarepo_runtime/services/facets/date.py +0 -72
  122. oarepo_runtime/services/facets/enum.py +0 -11
  123. oarepo_runtime/services/facets/facet_groups_names.py +0 -17
  124. oarepo_runtime/services/facets/max_facet.py +0 -13
  125. oarepo_runtime/services/facets/multilingual_facet.py +0 -33
  126. oarepo_runtime/services/facets/nested_facet.py +0 -32
  127. oarepo_runtime/services/facets/params.py +0 -192
  128. oarepo_runtime/services/facets/year_histogram.py +0 -200
  129. oarepo_runtime/services/files/__init__.py +0 -8
  130. oarepo_runtime/services/files/components.py +0 -62
  131. oarepo_runtime/services/files/service.py +0 -16
  132. oarepo_runtime/services/generators.py +0 -10
  133. oarepo_runtime/services/permissions/__init__.py +0 -3
  134. oarepo_runtime/services/permissions/generators.py +0 -103
  135. oarepo_runtime/services/relations/__init__.py +0 -0
  136. oarepo_runtime/services/relations/components.py +0 -15
  137. oarepo_runtime/services/relations/errors.py +0 -18
  138. oarepo_runtime/services/relations/mapping.py +0 -38
  139. oarepo_runtime/services/schema/cf.py +0 -13
  140. oarepo_runtime/services/schema/i18n_validation.py +0 -7
  141. oarepo_runtime/services/schema/marshmallow.py +0 -44
  142. oarepo_runtime/services/schema/marshmallow_to_json_schema.py +0 -72
  143. oarepo_runtime/services/schema/oneofschema.py +0 -192
  144. oarepo_runtime/services/schema/polymorphic.py +0 -21
  145. oarepo_runtime/services/schema/rdm.py +0 -75
  146. oarepo_runtime/services/schema/rdm_ui.py +0 -156
  147. oarepo_runtime/services/schema/ui.py +0 -251
  148. oarepo_runtime/services/schema/validation.py +0 -70
  149. oarepo_runtime/services/search.py +0 -282
  150. oarepo_runtime/services/service.py +0 -61
  151. oarepo_runtime/tasks.py +0 -6
  152. oarepo_runtime/translations/cs/LC_MESSAGES/messages.mo +0 -0
  153. oarepo_runtime/translations/cs/LC_MESSAGES/messages.po +0 -85
  154. oarepo_runtime/translations/default_translations.py +0 -6
  155. oarepo_runtime/translations/en/LC_MESSAGES/messages.mo +0 -0
  156. oarepo_runtime/translations/en/LC_MESSAGES/messages.po +0 -89
  157. oarepo_runtime/translations/messages.pot +0 -91
  158. oarepo_runtime/uow.py +0 -146
  159. oarepo_runtime/utils/__init__.py +0 -0
  160. oarepo_runtime/utils/functools.py +0 -37
  161. oarepo_runtime/utils/identity_utils.py +0 -35
  162. oarepo_runtime/utils/index.py +0 -11
  163. oarepo_runtime/utils/path.py +0 -97
  164. oarepo_runtime-1.10.2.dist-info/RECORD +0 -163
  165. oarepo_runtime-1.10.2.dist-info/entry_points.txt +0 -16
  166. oarepo_runtime-1.10.2.dist-info/top_level.txt +0 -2
  167. tests/marshmallow_to_json/__init__.py +0 -0
  168. tests/marshmallow_to_json/test_datacite_ui_schema.py +0 -1410
  169. tests/marshmallow_to_json/test_simple_schema.py +0 -52
  170. tests/pkg_data/__init__.py +0 -0
  171. {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,84 @@
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 collections.abc import Iterable
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ from invenio_search import current_search_client
18
+ from invenio_search.engine import dsl
19
+ from invenio_search.utils import build_alias_name
20
+
21
+ from oarepo_runtime.records.systemfields.mapping import MappingSystemFieldMixin
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Iterable
25
+
26
+ from invenio_records.api import RecordBase
27
+
28
+
29
+ def prefixed_index(index: dsl.Index) -> dsl.Index:
30
+ """Return a prefixed index for the given index."""
31
+ return dsl.Index(
32
+ build_alias_name(
33
+ index._name, # type: ignore[attr-defined] # noqa: SLF001
34
+ ),
35
+ using=current_search_client, # pyright: ignore[reportArgumentType]
36
+ )
37
+
38
+
39
+ def update_record_system_fields_mapping(record_class: type[RecordBase]) -> None:
40
+ """Update mapping for system fields in the record class.
41
+
42
+ :param record_class: The record class which index mapping should be updated.
43
+ :raise search.RequestError: If there is an error while updating the mapping.
44
+ """
45
+ index = getattr(record_class, "index", None)
46
+ if not index:
47
+ return
48
+
49
+ for fld in get_mapping_fields(record_class):
50
+ # get mapping
51
+ mapping = fld.mapping
52
+ settings = fld.mapping_settings
53
+ dynamic_templates = fld.dynamic_templates
54
+
55
+ # upload mapping
56
+ update_record_index(prefixed_index(index), settings, mapping, dynamic_templates)
57
+
58
+
59
+ def update_record_index(
60
+ record_index: dsl.Index,
61
+ settings: dict,
62
+ mapping: dict,
63
+ dynamic_templates: list | None = None,
64
+ ) -> None:
65
+ """Update the index mapping for the given record index."""
66
+ if settings:
67
+ record_index.close()
68
+ record_index.put_settings(body=settings)
69
+ record_index.open()
70
+
71
+ body: dict[str, Any] = {}
72
+ if mapping:
73
+ body["properties"] = mapping
74
+ if dynamic_templates:
75
+ body["dynamic_templates"] = dynamic_templates
76
+ if body:
77
+ record_index.put_mapping(body=body)
78
+
79
+
80
+ def get_mapping_fields(
81
+ record_class: type[RecordBase],
82
+ ) -> Iterable[MappingSystemFieldMixin]:
83
+ """Get all mapping fields from the record class."""
84
+ return (attr for _, attr in inspect.getmembers(record_class, lambda x: isinstance(x, MappingSystemFieldMixin)))
@@ -1,17 +1,53 @@
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
+ """PID providers."""
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING, Any, Self, cast
14
+
1
15
  from invenio_pidstore.models import PersistentIdentifier, PIDStatus
2
16
 
17
+ if TYPE_CHECKING:
18
+ from invenio_pidstore.providers.recordid_v2 import RecordIdProviderV2
19
+ else:
20
+ RecordIdProviderV2 = object
21
+
22
+
23
+ class UniversalPIDMixin(RecordIdProviderV2):
24
+ """Mixin class to handle creation and management of universal PIDs for records."""
3
25
 
4
- class UniversalPIDMixin:
5
26
  unpid_pid_type = "unpid"
6
27
  unpid_default_status = PIDStatus.REGISTERED
7
28
 
8
29
  @classmethod
9
- def create(cls, object_type=None, object_uuid=None, options=None, **kwargs):
10
- pid = super().create(
11
- object_type=object_type, object_uuid=object_uuid, options=options, **kwargs
30
+ def create( # type: ignore[override]
31
+ cls,
32
+ object_type: str | None = None,
33
+ object_uuid: str | None = None,
34
+ options: dict | None = None,
35
+ **kwargs: Any,
36
+ ) -> Self:
37
+ """Create PID for a given object and store it."""
38
+ pid = cast(
39
+ "Self",
40
+ super().create(
41
+ object_type=object_type,
42
+ object_uuid=object_uuid,
43
+ options=options,
44
+ **kwargs,
45
+ ),
12
46
  )
13
- assert pid.pid.pid_value is not None
14
- control_pid = PersistentIdentifier.create(
47
+ if pid.pid.pid_value is None:
48
+ raise ValueError("PID value cannot be None.") # pragma: no cover
49
+
50
+ PersistentIdentifier.create(
15
51
  cls.unpid_pid_type,
16
52
  pid.pid.pid_value,
17
53
  pid_provider=None,
@@ -19,4 +55,4 @@ class UniversalPIDMixin:
19
55
  object_uuid=object_uuid,
20
56
  status=cls.unpid_default_status,
21
57
  )
22
- return pid
58
+ return pid
@@ -1,34 +1,16 @@
1
- from .icu import (
2
- FulltextIndexField,
3
- ICUField,
4
- ICUSearchField,
5
- ICUSortField,
6
- ICUSuggestField,
7
- TermIndexField,
8
- )
9
- from .mapping import MappingSystemFieldMixin, SystemFieldDumperExt
10
- from .selectors import (
11
- FilteredSelector,
12
- FirstItemSelector,
13
- MultiSelector,
14
- PathSelector,
15
- Selector,
16
- )
17
- from .synthetic import SyntheticSystemField
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
+ """Records system fields."""
18
10
 
19
- __all__ = (
20
- "ICUField",
21
- "ICUSuggestField",
22
- "ICUSortField",
23
- "ICUSearchField",
24
- "FulltextIndexField",
25
- "MappingSystemFieldMixin",
26
- "SystemFieldDumperExt",
27
- "SyntheticSystemField",
28
- "PathSelector",
29
- "Selector",
30
- "FirstItemSelector",
31
- "FilteredSelector",
32
- "MultiSelector",
33
- "TermIndexField",
34
- )
11
+ from __future__ import annotations
12
+
13
+ from .mapping import MappingSystemFieldMixin
14
+ from .publication_status import PublicationStatusSystemField
15
+
16
+ __all__ = ("MappingSystemFieldMixin", "PublicationStatusSystemField")
@@ -1,39 +1,56 @@
1
- import inspect
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
+ """System fields mapping."""
2
10
 
3
- from invenio_records.dumpers import SearchDumperExt
11
+ from __future__ import annotations
4
12
 
13
+ from typing import TYPE_CHECKING, override
14
+
15
+ if TYPE_CHECKING:
16
+ from invenio_records.api import RecordBase
17
+ from invenio_records.dumpers import Dumper
18
+ from invenio_records.systemfields import SystemField
19
+ else:
20
+ SystemField = object
21
+
22
+
23
+ class MappingSystemFieldMixin(SystemField):
24
+ """Mixin class that provides default mapping, mapping settings, and dynamic templates for system fields."""
5
25
 
6
- class MappingSystemFieldMixin:
7
26
  @property
8
- def mapping(self):
27
+ def mapping(self) -> dict:
28
+ """Return the default mapping for the system field."""
9
29
  return {}
10
30
 
11
31
  @property
12
- def mapping_settings(self):
32
+ def mapping_settings(self) -> dict:
33
+ """Return the default mapping settings for the system field."""
13
34
  return {}
14
35
 
15
36
  @property
16
- def dynamic_templates(self):
37
+ def dynamic_templates(self) -> list:
38
+ """Return the default dynamic templates for the system field."""
17
39
  return []
18
40
 
19
- def search_dump(self, data, record):
20
- """Dump custom field."""
21
-
22
- def search_load(self, data, record_cls):
23
- """Load custom field."""
41
+ # The following methods are added just for typing purposes.
42
+ @override
43
+ def pre_dump(self, record: RecordBase, data: dict, dumper: Dumper | None = None) -> None: # type: ignore[misc]
44
+ """Dump record to the data - pre-dump phase."""
24
45
 
46
+ @override
47
+ def post_dump(self, record: RecordBase, data: dict, dumper: Dumper | None = None) -> None: # type: ignore[misc]
48
+ """Dump record to the data - post-dump phase."""
25
49
 
26
- class SystemFieldDumperExt(SearchDumperExt):
27
- def dump(self, record, data):
28
- """Dump custom fields."""
29
- for cf in inspect.getmembers(
30
- type(record), lambda x: isinstance(x, MappingSystemFieldMixin)
31
- ):
32
- cf[1].search_dump(data, record=record)
50
+ @override
51
+ def pre_load(self, data: dict, loader: Dumper | None = None) -> None: # type: ignore[misc]
52
+ """Load record from the data - pre-load phase."""
33
53
 
34
- def load(self, data, record_cls):
35
- """Load custom fields."""
36
- for cf in inspect.getmembers(
37
- record_cls, lambda x: isinstance(x, MappingSystemFieldMixin)
38
- ):
39
- cf[1].search_load(data, record_cls=record_cls)
54
+ @override
55
+ def post_load(self, record: RecordBase, data: dict, loader: Dumper | None = None) -> None: # type: ignore[misc]
56
+ """Load record from the data - post-load phase."""
@@ -0,0 +1,59 @@
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
+ """Record status module."""
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING, Any, override
14
+
15
+ from invenio_records.systemfields import SystemField
16
+
17
+ from .mapping import MappingSystemFieldMixin
18
+
19
+ if TYPE_CHECKING:
20
+ from invenio_records.api import RecordBase
21
+ from invenio_records.dumpers import Dumper
22
+
23
+
24
+ class PublicationStatusSystemField(MappingSystemFieldMixin, SystemField):
25
+ """A system field to track the status of a record (either 'draft' or 'published').
26
+
27
+ The default key for this field is 'publication_status', but it can be customized.
28
+ """
29
+
30
+ def __init__(self, key: str | None = "publication_status"):
31
+ """Initialize the system field with an optional key."""
32
+ super().__init__(key)
33
+
34
+ @property
35
+ def mapping(self) -> dict:
36
+ """Return the mapping for the field in the search index."""
37
+ return {
38
+ self.key: {
39
+ "type": "keyword",
40
+ },
41
+ }
42
+
43
+ @override
44
+ def post_load(self, record: RecordBase, data: dict, loader: Dumper | None = None) -> None:
45
+ data.pop(self.key, None)
46
+
47
+ @override
48
+ def post_dump(self, record: RecordBase, data: dict, dumper: Dumper | None = None) -> None:
49
+ if self.key is None:
50
+ return
51
+ if not self.attr_name:
52
+ raise ValueError("attr_name must be set for PublicationStatusSystemField")
53
+ data[self.key] = getattr(record, self.attr_name)
54
+
55
+ def __get__(self, record: RecordBase | None, owner: Any = None) -> Any:
56
+ """Access the attribute."""
57
+ if record is None:
58
+ return self
59
+ return "draft" if getattr(record, "is_draft", False) else "published"
@@ -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
+ """Services module."""
11
+
12
+ from __future__ import annotations
@@ -1,35 +1,29 @@
1
- from .draft_link import DraftLink
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
+ """Service config module."""
10
+
11
+ from __future__ import annotations
12
+
2
13
  from .link_conditions import (
3
14
  has_draft,
4
15
  has_draft_permission,
5
- has_file_permission,
6
16
  has_permission,
7
- has_permission_file_service,
8
17
  has_published_record,
9
- is_draft_record,
10
18
  is_published_record,
11
19
  )
12
- from .permissions_presets import (
13
- AuthenticatedPermissionPolicy,
14
- EveryonePermissionPolicy,
15
- OaiHarvesterPermissionPolicy,
16
- ReadOnlyPermissionPolicy,
17
- )
18
- from .service import PermissionsPresetsConfigMixin
20
+ from .permissions import EveryonePermissionPolicy
19
21
 
20
22
  __all__ = (
21
- "PermissionsPresetsConfigMixin",
22
- "OaiHarvesterPermissionPolicy",
23
- "ReadOnlyPermissionPolicy",
24
23
  "EveryonePermissionPolicy",
25
- "AuthenticatedPermissionPolicy",
26
- "is_published_record",
27
- "is_draft_record",
28
24
  "has_draft",
25
+ "has_draft_permission",
29
26
  "has_permission",
30
- "has_permission_file_service",
31
- "has_file_permission",
32
27
  "has_published_record",
33
- "has_draft_permission",
34
- "DraftLink",
28
+ "is_published_record",
35
29
  )
@@ -1,126 +1,120 @@
1
- import warnings
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
+ """Link conditions module."""
11
+
12
+ from __future__ import annotations
13
+
2
14
  from abc import abstractmethod
3
15
  from logging import getLogger
16
+ from typing import Any
4
17
 
5
18
  from invenio_pidstore.errors import PIDDoesNotExistError, PIDUnregistered
6
- from invenio_records.api import RecordBase
7
- from invenio_records_resources.records.api import FileRecord, Record
19
+ from invenio_records_resources.records.api import FileRecord, RecordBase
8
20
 
9
- from ...datastreams.utils import (
10
- get_file_service_for_record_class,
11
- get_record_service_for_record,
12
- )
13
- from ...records.drafts import get_draft
21
+ from oarepo_runtime.proxies import current_runtime
22
+ from oarepo_runtime.records.drafts import get_draft
14
23
 
15
24
  log = getLogger(__name__)
16
25
 
17
26
 
18
27
  class Condition:
28
+ """Base class for defining conditions with callable logic."""
29
+
19
30
  @abstractmethod
20
- def __call__(self, obj, ctx: dict):
21
- raise NotImplementedError
31
+ def __call__(self, obj: RecordBase, ctx: dict):
32
+ """Abstract method to be implemented in subclasses to define a condition."""
33
+ raise NotImplementedError # pragma: no cover
22
34
 
23
- def __and__(self, other):
35
+ def __and__(self, other: Any):
36
+ """Combine two conditions using a logical AND."""
24
37
  return type(
25
- "CompositeCondition",
38
+ "And",
26
39
  (Condition,),
27
40
  {"__call__": lambda _, obj, ctx: self(obj, ctx) and other(obj, ctx)},
28
41
  )()
29
42
 
30
- def __or__(self, other):
43
+ def __or__(self, other: Any):
44
+ """Combine two conditions using a logical OR."""
31
45
  return type(
32
- "CompositeCondition",
46
+ "Or",
33
47
  (Condition,),
34
48
  {"__call__": lambda _, obj, ctx: self(obj, ctx) or other(obj, ctx)},
35
49
  )()
36
50
 
37
51
 
38
- class is_published_record(Condition):
39
- """Shortcut for links to determine if record is a published record."""
40
-
41
- def __call__(self, obj: Record, ctx: dict):
42
- return not getattr(obj, "is_draft", False)
43
-
44
-
45
- class is_draft_record(Condition):
46
- """Shortcut for links to determine if record is a draft record."""
47
-
48
- def __call__(self, obj: Record, ctx: dict):
49
- return getattr(obj, "is_draft", False)
50
-
51
-
52
- class has_draft(Condition):
53
- """Shortcut for links to determine if record is either a draft or a published one with a draft associated."""
54
-
55
- def __call__(self, obj: Record, ctx: dict):
56
- if getattr(obj, "is_draft", False):
57
- return True
58
- if getattr(obj, "has_draft", False):
59
- return True
60
- return False
61
-
52
+ class has_permission(Condition): # noqa: N801
53
+ """A condition to check if a user has the specified permission for a given record."""
62
54
 
63
- class has_permission(Condition):
64
- def __init__(self, action_name):
55
+ def __init__(self, action_name: str):
56
+ """Initialize the condition with the specified action name."""
65
57
  self.action_name = action_name
66
58
 
67
59
  def __call__(self, obj: RecordBase, ctx: dict):
60
+ """Evaluate the condition by checking the permission for a given record."""
68
61
  if isinstance(obj, FileRecord):
69
62
  obj = obj.record
70
- service = get_record_service_for_record(obj)
63
+ service = current_runtime.get_record_service_for_record(obj)
71
64
  try:
72
- return service.check_permission(
73
- action_name=self.action_name, record=obj, **ctx
74
- )
75
- except Exception as e:
76
- log.exception(f"Unexpected exception {e}.")
65
+ return service.check_permission(action_name=self.action_name, record=obj, **ctx)
66
+ except Exception: # pragma: no cover
67
+ log.exception("Unexpected exception.")
68
+
77
69
 
70
+ class has_draft_permission(Condition): # noqa: N801
71
+ """A condition to check if a user has the specified permission for a draft record."""
78
72
 
79
- class has_draft_permission(Condition):
80
- def __init__(self, action_name):
73
+ def __init__(self, action_name: str):
74
+ """Initialize the condition with the specified action name."""
81
75
  self.action_name = action_name
82
76
 
83
77
  def __call__(self, obj: RecordBase, ctx: dict):
78
+ """Valuates the condition by checking the permission for a draft record."""
79
+ _ = ctx
84
80
  draft_record = get_draft(obj)
85
81
  if not draft_record:
86
82
  return False
87
- service = get_record_service_for_record(obj)
83
+ service = current_runtime.get_record_service_for_record(obj)
88
84
  try:
89
- return service.check_permission(
90
- action_name=self.action_name, record=draft_record, **ctx
91
- )
92
- except Exception as e:
93
- log.exception(f"Unexpected exception {e}.")
85
+ return service.check_permission(action_name=self.action_name, record=draft_record, **ctx)
86
+ except Exception: # pragma: no cover
87
+ log.exception("Unexpected exception.")
94
88
  return False
95
89
 
96
90
 
97
- class has_file_permission(has_permission):
98
- def __call__(self, obj: RecordBase, ctx: dict):
99
- if isinstance(obj, FileRecord):
100
- obj = obj.record
101
- service = get_file_service_for_record_class(type(obj))
102
- try:
103
- return service.check_permission(
104
- action_name=self.action_name, record=obj, **ctx
105
- )
106
- except Exception as e:
107
- log.exception(f"Unexpected exception {e}.")
91
+ class has_draft(Condition): # noqa: N801
92
+ """Shortcut for links to determine if record is either a draft or a published one with a draft associated."""
108
93
 
94
+ def __call__(self, obj: RecordBase, ctx: dict):
95
+ """Check if the given record has draft."""
96
+ _ = ctx
97
+ return bool(getattr(obj, "is_draft", False)) or bool(getattr(obj, "has_draft", False))
109
98
 
110
- class has_permission_file_service(has_file_permission):
111
- def __init__(self, action_name):
112
- warnings.warn(
113
- "has_permission_file_service is deprecated, use has_file_permission instead",
114
- DeprecationWarning,
115
- )
116
- super().__init__(action_name)
117
99
 
100
+ class has_published_record(Condition): # noqa: N801
101
+ """Shortcut for links to determine if the given record has a published PID."""
118
102
 
119
- class has_published_record(Condition):
120
- def __call__(self, obj: Record, ctx: dict):
121
- service = get_record_service_for_record(obj)
103
+ def __call__(self, obj: RecordBase, ctx: dict):
104
+ """Check if the given record has a published PID."""
105
+ _ = ctx
106
+ service = current_runtime.get_record_service_for_record(obj)
122
107
  try:
123
108
  service.record_cls.pid.resolve(obj["id"])
124
109
  except (PIDUnregistered, PIDDoesNotExistError):
125
110
  return False
126
111
  return True
112
+
113
+
114
+ class is_published_record(Condition): # noqa: N801
115
+ """Shortcut for links to determine if record is a published record."""
116
+
117
+ def __call__(self, obj: RecordBase, ctx: dict):
118
+ """Check if the given record is draft."""
119
+ _ = ctx
120
+ return not getattr(obj, "is_draft", False)
@@ -0,0 +1,62 @@
1
+ # mypy: disable-error-code="assignment"
2
+ #
3
+ # Copyright (c) 2025 CESNET z.s.p.o.
4
+ #
5
+ # This file is a part of oarepo-runtime (see http://github.com/oarepo/oarepo-runtime).
6
+ #
7
+ # oarepo-runtime is free software; you can redistribute it and/or modify it
8
+ # under the terms of the MIT License; see LICENSE file for more details.
9
+ #
10
+
11
+ """Everyone permissions."""
12
+
13
+ from __future__ import annotations
14
+
15
+ from invenio_records_permissions import RecordPermissionPolicy
16
+ from invenio_records_permissions.generators import AnyUser, Generator, SystemProcess
17
+
18
+ type GeneratorList = tuple[Generator, ...]
19
+
20
+
21
+ class EveryonePermissionPolicy(RecordPermissionPolicy):
22
+ """Record policy for read-only repository."""
23
+
24
+ can_search: GeneratorList = (SystemProcess(), AnyUser())
25
+ can_read: GeneratorList = (SystemProcess(), AnyUser())
26
+ can_create: GeneratorList = (SystemProcess(), AnyUser())
27
+ can_update: GeneratorList = (SystemProcess(), AnyUser())
28
+ can_delete: GeneratorList = (SystemProcess(), AnyUser())
29
+ can_manage: GeneratorList = (SystemProcess(), AnyUser())
30
+
31
+ can_create_files: GeneratorList = (SystemProcess(), AnyUser())
32
+ can_set_content_files: GeneratorList = (SystemProcess(), AnyUser())
33
+ can_get_content_files: GeneratorList = (SystemProcess(), AnyUser())
34
+ can_commit_files: GeneratorList = (SystemProcess(), AnyUser())
35
+ can_read_files: GeneratorList = (SystemProcess(), AnyUser())
36
+ can_update_files: GeneratorList = (SystemProcess(), AnyUser())
37
+ can_delete_files: GeneratorList = (SystemProcess(), AnyUser())
38
+ can_list_files: GeneratorList = (SystemProcess(), AnyUser())
39
+ can_manage_files: GeneratorList = (SystemProcess(), AnyUser())
40
+
41
+ can_edit: GeneratorList = (SystemProcess(), AnyUser())
42
+ can_new_version: GeneratorList = (SystemProcess(), AnyUser())
43
+ can_search_drafts: GeneratorList = (SystemProcess(), AnyUser())
44
+ can_read_draft: GeneratorList = (SystemProcess(), AnyUser())
45
+ can_search_versions: GeneratorList = (SystemProcess(), AnyUser())
46
+ can_update_draft: GeneratorList = (SystemProcess(), AnyUser())
47
+ can_delete_draft: GeneratorList = (SystemProcess(), AnyUser())
48
+ can_publish: GeneratorList = (SystemProcess(), AnyUser())
49
+ can_draft_create_files: GeneratorList = (SystemProcess(), AnyUser())
50
+ can_draft_set_content_files: GeneratorList = (SystemProcess(), AnyUser())
51
+ can_draft_get_content_files: GeneratorList = (SystemProcess(), AnyUser())
52
+ can_draft_commit_files: GeneratorList = (SystemProcess(), AnyUser())
53
+ can_draft_read_files: GeneratorList = (SystemProcess(), AnyUser())
54
+ can_draft_update_files: GeneratorList = (SystemProcess(), AnyUser())
55
+ can_draft_delete_files: GeneratorList = (SystemProcess(), AnyUser())
56
+
57
+ can_add_community: GeneratorList = (SystemProcess(), AnyUser())
58
+ can_remove_community: GeneratorList = (SystemProcess(), AnyUser())
59
+
60
+ can_read_deleted: GeneratorList = (SystemProcess(), AnyUser())
61
+ can_manage_record_access: GeneratorList = (SystemProcess(), AnyUser())
62
+ can_lift_embargo: GeneratorList = (SystemProcess(), AnyUser())