oarepo-runtime 1.10.3__py3-none-any.whl → 2.0.0.dev4__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 +210 -0
  3. oarepo_runtime/cli/__init__.py +10 -21
  4. oarepo_runtime/cli/search.py +34 -0
  5. oarepo_runtime/config.py +98 -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 +61 -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/facets/__init__.py +12 -33
  20. oarepo_runtime/services/facets/params.py +45 -110
  21. oarepo_runtime/services/records/__init__.py +14 -1
  22. oarepo_runtime/services/records/links.py +21 -11
  23. oarepo_runtime/services/records/mapping.py +42 -0
  24. oarepo_runtime/services/results.py +98 -109
  25. oarepo_runtime/services/schema/__init__.py +12 -44
  26. oarepo_runtime/services/schema/i18n.py +47 -22
  27. oarepo_runtime/services/schema/i18n_ui.py +61 -24
  28. {oarepo_runtime-1.10.3.dist-info → oarepo_runtime-2.0.0.dev4.dist-info}/METADATA +10 -21
  29. oarepo_runtime-2.0.0.dev4.dist-info/RECORD +32 -0
  30. {oarepo_runtime-1.10.3.dist-info → oarepo_runtime-2.0.0.dev4.dist-info}/WHEEL +1 -2
  31. oarepo_runtime-2.0.0.dev4.dist-info/entry_points.txt +5 -0
  32. oarepo_runtime/cli/assets.py +0 -145
  33. oarepo_runtime/cli/base.py +0 -25
  34. oarepo_runtime/cli/cf.py +0 -15
  35. oarepo_runtime/cli/check.py +0 -167
  36. oarepo_runtime/cli/configuration.py +0 -51
  37. oarepo_runtime/cli/fixtures.py +0 -167
  38. oarepo_runtime/cli/index.py +0 -272
  39. oarepo_runtime/cli/permissions/__init__.py +0 -6
  40. oarepo_runtime/cli/permissions/base.py +0 -26
  41. oarepo_runtime/cli/permissions/evaluate.py +0 -63
  42. oarepo_runtime/cli/permissions/list.py +0 -239
  43. oarepo_runtime/cli/permissions/search.py +0 -121
  44. oarepo_runtime/cli/validate.py +0 -150
  45. oarepo_runtime/datastreams/__init__.py +0 -38
  46. oarepo_runtime/datastreams/asynchronous.py +0 -247
  47. oarepo_runtime/datastreams/catalogue.py +0 -150
  48. oarepo_runtime/datastreams/datastreams.py +0 -152
  49. oarepo_runtime/datastreams/errors.py +0 -54
  50. oarepo_runtime/datastreams/ext.py +0 -41
  51. oarepo_runtime/datastreams/fixtures.py +0 -265
  52. oarepo_runtime/datastreams/json.py +0 -4
  53. oarepo_runtime/datastreams/readers/__init__.py +0 -39
  54. oarepo_runtime/datastreams/readers/attachments.py +0 -51
  55. oarepo_runtime/datastreams/readers/excel.py +0 -123
  56. oarepo_runtime/datastreams/readers/json.py +0 -27
  57. oarepo_runtime/datastreams/readers/service.py +0 -54
  58. oarepo_runtime/datastreams/readers/yaml.py +0 -14
  59. oarepo_runtime/datastreams/semi_asynchronous.py +0 -91
  60. oarepo_runtime/datastreams/synchronous.py +0 -70
  61. oarepo_runtime/datastreams/transformers.py +0 -18
  62. oarepo_runtime/datastreams/types.py +0 -323
  63. oarepo_runtime/datastreams/utils.py +0 -131
  64. oarepo_runtime/datastreams/writers/__init__.py +0 -21
  65. oarepo_runtime/datastreams/writers/attachments_file.py +0 -92
  66. oarepo_runtime/datastreams/writers/attachments_service.py +0 -118
  67. oarepo_runtime/datastreams/writers/publish.py +0 -70
  68. oarepo_runtime/datastreams/writers/service.py +0 -175
  69. oarepo_runtime/datastreams/writers/utils.py +0 -30
  70. oarepo_runtime/datastreams/writers/validation_errors.py +0 -20
  71. oarepo_runtime/datastreams/writers/yaml.py +0 -56
  72. oarepo_runtime/ext_config.py +0 -67
  73. oarepo_runtime/i18n/__init__.py +0 -3
  74. oarepo_runtime/info/__init__.py +0 -0
  75. oarepo_runtime/info/check.py +0 -95
  76. oarepo_runtime/info/permissions/__init__.py +0 -0
  77. oarepo_runtime/info/permissions/debug.py +0 -191
  78. oarepo_runtime/info/views.py +0 -586
  79. oarepo_runtime/profile.py +0 -60
  80. oarepo_runtime/records/dumpers/__init__.py +0 -8
  81. oarepo_runtime/records/dumpers/edtf_interval.py +0 -38
  82. oarepo_runtime/records/dumpers/multilingual_dumper.py +0 -34
  83. oarepo_runtime/records/entity_resolvers/__init__.py +0 -13
  84. oarepo_runtime/records/entity_resolvers/proxies.py +0 -57
  85. oarepo_runtime/records/mappings/__init__.py +0 -0
  86. oarepo_runtime/records/mappings/rdm_parent_mapping.json +0 -483
  87. oarepo_runtime/records/owners/__init__.py +0 -3
  88. oarepo_runtime/records/owners/registry.py +0 -22
  89. oarepo_runtime/records/relations/__init__.py +0 -22
  90. oarepo_runtime/records/relations/base.py +0 -296
  91. oarepo_runtime/records/relations/internal.py +0 -46
  92. oarepo_runtime/records/relations/lookup.py +0 -28
  93. oarepo_runtime/records/relations/pid_relation.py +0 -102
  94. oarepo_runtime/records/systemfields/featured_file.py +0 -45
  95. oarepo_runtime/records/systemfields/has_draftcheck.py +0 -47
  96. oarepo_runtime/records/systemfields/icu.py +0 -371
  97. oarepo_runtime/records/systemfields/owner.py +0 -115
  98. oarepo_runtime/records/systemfields/record_status.py +0 -35
  99. oarepo_runtime/records/systemfields/selectors.py +0 -98
  100. oarepo_runtime/records/systemfields/synthetic.py +0 -130
  101. oarepo_runtime/resources/__init__.py +0 -4
  102. oarepo_runtime/resources/config.py +0 -12
  103. oarepo_runtime/resources/file_resource.py +0 -15
  104. oarepo_runtime/resources/json_serializer.py +0 -27
  105. oarepo_runtime/resources/localized_ui_json_serializer.py +0 -54
  106. oarepo_runtime/resources/resource.py +0 -53
  107. oarepo_runtime/resources/responses.py +0 -20
  108. oarepo_runtime/services/components.py +0 -429
  109. oarepo_runtime/services/config/draft_link.py +0 -23
  110. oarepo_runtime/services/config/permissions_presets.py +0 -174
  111. oarepo_runtime/services/config/service.py +0 -117
  112. oarepo_runtime/services/custom_fields/__init__.py +0 -80
  113. oarepo_runtime/services/custom_fields/mappings.py +0 -188
  114. oarepo_runtime/services/entity/__init__.py +0 -0
  115. oarepo_runtime/services/entity/config.py +0 -14
  116. oarepo_runtime/services/entity/schema.py +0 -9
  117. oarepo_runtime/services/entity/service.py +0 -48
  118. oarepo_runtime/services/expansions/__init__.py +0 -0
  119. oarepo_runtime/services/expansions/expandable_fields.py +0 -21
  120. oarepo_runtime/services/expansions/service.py +0 -4
  121. oarepo_runtime/services/facets/base.py +0 -12
  122. oarepo_runtime/services/facets/date.py +0 -72
  123. oarepo_runtime/services/facets/enum.py +0 -11
  124. oarepo_runtime/services/facets/facet_groups_names.py +0 -17
  125. oarepo_runtime/services/facets/max_facet.py +0 -13
  126. oarepo_runtime/services/facets/multilingual_facet.py +0 -33
  127. oarepo_runtime/services/facets/nested_facet.py +0 -32
  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 -146
  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 -95
  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 -97
  157. oarepo_runtime/translations/messages.pot +0 -100
  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.3.dist-info/RECORD +0 -163
  165. oarepo_runtime-1.10.3.dist-info/entry_points.txt +0 -16
  166. oarepo_runtime-1.10.3.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.3.dist-info → oarepo_runtime-2.0.0.dev4.dist-info}/licenses/LICENSE +0 -0
@@ -1,251 +0,0 @@
1
- import datetime
2
- import re
3
-
4
- import marshmallow as ma
5
- from babel.dates import format_date
6
- from babel_edtf import format_edtf
7
- from flask import current_app
8
- from idutils import to_url
9
- from invenio_rdm_records.records.systemfields.access.field.record import (
10
- AccessStatusEnum,
11
- )
12
- from invenio_rdm_records.resources.serializers.ui.fields import (
13
- UIObjectAccessStatus as InvenioUIObjectAccessStatus,
14
- )
15
- from invenio_rdm_records.services.schemas.parent import RDMParentSchema
16
- from invenio_rdm_records.services.schemas.pids import PIDSchema
17
- from invenio_rdm_records.services.schemas.record import validate_scheme
18
- from invenio_rdm_records.services.schemas.versions import VersionsSchema
19
- from marshmallow.fields import Dict, Nested, Raw
20
- from marshmallow_utils.fields import (
21
- BabelGettextDictField,
22
- FormatDate,
23
- FormatDatetime,
24
- FormatEDTF,
25
- FormatTime,
26
- SanitizedUnicode,
27
- )
28
- from marshmallow_utils.fields.babel import BabelFormatField
29
-
30
- from oarepo_runtime.i18n import gettext
31
- from oarepo_runtime.i18n import lazy_gettext as _
32
-
33
- from .marshmallow import RDMBaseRecordSchema
34
-
35
-
36
- def current_default_locale():
37
- """Get the Flask app's default locale."""
38
- if current_app:
39
- return current_app.config.get("BABEL_DEFAULT_LOCALE", "en")
40
- # Use english by default if not specified
41
- return "en"
42
-
43
-
44
- class LocalizedMixin:
45
- def __init__(self, *args, locale=None, **kwargs):
46
- super().__init__(*args, locale=locale, **kwargs)
47
-
48
- @property
49
- def locale(self):
50
- if self._locale:
51
- return self._locale
52
- if self.parent:
53
- if "locale" in self.context:
54
- return self.context["locale"]
55
- return current_default_locale()
56
-
57
- def format_value(self, value):
58
- """Format the value, gracefully handling exceptions."""
59
- try:
60
- return super().format_value(value)
61
- except Exception as e:
62
- # Handle the exception gracefully
63
- current_app.logger.error(f"Error formatting value '{value}': {e}")
64
- return f"«Error formatting value '{value}'»"
65
-
66
-
67
- # localized date field
68
- class LocalizedDate(LocalizedMixin, FormatDate):
69
- pass
70
-
71
-
72
- class FormatTimeString(FormatTime):
73
- def parse(self, value, as_time=False, as_date=False, as_datetime=False):
74
- if value and isinstance(value, str) and as_time == True:
75
- match = re.match(
76
- r"^(\d|0\d|1\d|2[0-3]):(\d|[0-5]\d|60)(:(\d|[0-5]\d|60))?$", value
77
- )
78
- if match:
79
- value = datetime.time(
80
- hour=int(match.group(1)),
81
- minute=int(match.group(2)),
82
- second=int(match.group(4)) if match.group(4) else 0,
83
- )
84
-
85
- return super().parse(value, as_time, as_date, as_datetime)
86
-
87
-
88
- class MultilayerFormatEDTF(BabelFormatField):
89
- def format_value(self, value):
90
- try:
91
- return format_date(
92
- self.parse(value, as_date=True), format=self._format, locale=self.locale
93
- )
94
- except:
95
- return format_edtf(value, format=self._format, locale=self.locale)
96
-
97
- def parse(self, value, **kwargs):
98
- # standard parsing is too lenient, for example returns "2000-01-01" for input "2000"
99
- if re.match("^[0-9]+-[0-9]+-[0-9]+", value):
100
- return super().parse(value, **kwargs)
101
- raise ValueError("Not a valid date")
102
-
103
- class TimezoneMixin: #i'm not sure about where this should be used
104
- @property
105
- def tzinfo(self):
106
- from oarepo_runtime.proxies import current_timezone
107
- try:
108
- return current_timezone.get()
109
- except LookupError:
110
- return
111
-
112
- class LocalizedDateTime(TimezoneMixin, LocalizedMixin, FormatDatetime):
113
- pass
114
-
115
- class LocalizedTime(LocalizedMixin, FormatTimeString):
116
- pass
117
-
118
-
119
- class LocalizedEDTF(LocalizedMixin, MultilayerFormatEDTF):
120
- pass
121
-
122
-
123
- class LocalizedEDTFTime(LocalizedMixin, MultilayerFormatEDTF):
124
- pass
125
-
126
-
127
- class LocalizedEDTFInterval(LocalizedMixin, FormatEDTF):
128
- pass
129
-
130
-
131
- class LocalizedEDTFTimeInterval(LocalizedMixin, FormatEDTF):
132
- pass
133
-
134
-
135
- class PrefixedGettextField(BabelGettextDictField):
136
- def __init__(self, *, value_prefix, locale, default_locale, **kwargs):
137
- super().__init__(locale, default_locale, **kwargs)
138
- self.value_prefix = value_prefix
139
-
140
- def _serialize(self, value, attr, obj, **kwargs):
141
- if value:
142
- value = f"{self.value_prefix}{value}"
143
- return gettext(value)
144
-
145
-
146
- class LocalizedEnum(LocalizedMixin, PrefixedGettextField):
147
- pass
148
-
149
- def __init__(self, **kwargs):
150
- super().__init__(default_locale=current_default_locale, **kwargs)
151
-
152
-
153
- if False: # NOSONAR
154
- # just for the makemessages to pick up the translations
155
- translations = [_("True"), _("False")]
156
-
157
-
158
- class InvenioUISchema(ma.Schema):
159
- _schema = ma.fields.Str(attribute="$schema", data_key="$schema")
160
- id = ma.fields.Str()
161
- created = LocalizedDateTime(dump_only=True)
162
- updated = LocalizedDateTime(dump_only=True)
163
- links = ma.fields.Raw(dump_only=True)
164
- revision_id = ma.fields.Integer(dump_only=True)
165
- expanded = ma.fields.Raw(dump_only=True)
166
-
167
-
168
- # seems not possible to avoid, as they have this hardcoded in their object,
169
- # and translation keys are i.e. open, which gets translated to otevret
170
- class UIObjectAccessStatus(InvenioUIObjectAccessStatus):
171
- @property
172
- def title(self):
173
- """Access status title."""
174
- return {
175
- AccessStatusEnum.OPEN: _("access.status.open"),
176
- AccessStatusEnum.EMBARGOED: _("access.status.embargoed"),
177
- AccessStatusEnum.RESTRICTED: _("access.status.restricted"),
178
- AccessStatusEnum.METADATA_ONLY: _("access.status.metadata-only"),
179
- }.get(self.access_status)
180
-
181
-
182
- class AccessStatusField(ma.fields.Field):
183
- """Record access status."""
184
-
185
- def _serialize(self, value, attr, obj, **kwargs):
186
- """Serialise access status."""
187
- record_access_dict = obj.get("access")
188
- _files = obj.get("files", {})
189
- has_files = _files is not None and _files.get("enabled", False)
190
- if record_access_dict:
191
- record_access_status_ui = UIObjectAccessStatus(
192
- record_access_dict, has_files
193
- )
194
- return {
195
- "id": record_access_status_ui.id,
196
- "title_l10n": record_access_status_ui.title,
197
- "description_l10n": record_access_status_ui.description,
198
- "icon": record_access_status_ui.icon,
199
- "embargo_date_l10n": record_access_status_ui.embargo_date,
200
- "message_class": record_access_status_ui.message_class,
201
- }
202
-
203
-
204
- # to be able to have access to entire pids object
205
- class PIDsField(Dict):
206
- """Custom Dict field for PIDs that adds URLs after serialization."""
207
-
208
- def _serialize(self, value, attr, obj, **kwargs):
209
- """Serialize the PIDs and add URLs to them."""
210
- serialized = super()._serialize(value, attr, obj, **kwargs)
211
-
212
- if serialized:
213
- for scheme, pid in serialized.items():
214
- if scheme and pid and isinstance(pid, dict) and pid.get("identifier"):
215
- url = to_url(pid["identifier"], scheme.lower(), url_scheme="https")
216
- if url:
217
- pid["url"] = url
218
-
219
- return serialized
220
-
221
-
222
- class InvenioRDMParentUISchema(RDMParentSchema):
223
- """Parent schema."""
224
-
225
- pids = PIDsField(
226
- keys=SanitizedUnicode(validate=validate_scheme),
227
- values=Nested(PIDSchema),
228
- )
229
-
230
-
231
- class InvenioRDMUISchema(InvenioUISchema, RDMBaseRecordSchema):
232
- """RDM UI schema."""
233
-
234
- is_draft = ma.fields.Boolean(dump_only=True)
235
- access_status = AccessStatusField(attribute="access", dump_only=True)
236
- versions = ma.fields.Nested(VersionsSchema, dump_only=True)
237
- pids = PIDsField(
238
- keys=SanitizedUnicode(validate=validate_scheme),
239
- values=Nested(PIDSchema),
240
- )
241
- parent = ma.fields.Nested(InvenioRDMParentUISchema)
242
- access = ma.fields.Raw(attribute="access", data_key="access", dump_only=True)
243
- files = ma.fields.Raw(attribute="files", data_key="files", dump_only=True)
244
-
245
- def hide_tombstone(self, data):
246
- """Hide tombstone info if the record isn't deleted and metadata if it is."""
247
- return data
248
-
249
- def default_nested(self, data):
250
- """Serialize fields as empty dict for partial drafts."""
251
- return data
@@ -1,70 +0,0 @@
1
- import functools
2
- import re
3
- from datetime import datetime
4
- from idutils import normalize_pid
5
- from isbnlib import canonical, mask
6
-
7
- from marshmallow.exceptions import ValidationError
8
- from marshmallow_utils.fields.edtfdatestring import EDTFValidator
9
-
10
- from invenio_i18n import gettext as _
11
-
12
-
13
- def validate_identifier(value):
14
- try:
15
- original_identifier = (value["identifier"] or '').strip()
16
- normalized_identifier = normalize_pid(
17
- value["identifier"], value["scheme"].lower()
18
- )
19
- if original_identifier and not normalized_identifier:
20
- # the normalize_pid library has problems with isbn - does not raise an exception
21
- # but returns an empty string
22
- raise ValueError()
23
-
24
- # normalized_pid is changing from 10 length ISBN to 13 length ISBN
25
- if value["scheme"].lower() == "isbn":
26
- canonical_isbn = canonical(value["identifier"])
27
- if original_identifier and not canonical_isbn: # just check in case it returns empty string
28
- raise ValueError()
29
- value["identifier"] = mask(canonical_isbn)
30
- return value
31
-
32
- value["identifier"] = normalized_identifier
33
- except:
34
- raise ValidationError({
35
- "identifier": _("Invalid value %(identifier)s of identifier type %(type)s") % {"identifier": value['identifier'], "type": value['scheme']}
36
- })
37
- return value
38
-
39
-
40
- def validate_date(date_format):
41
- def validate(value):
42
- try:
43
- datetime.strptime(value, date_format)
44
- except Exception as e:
45
- raise ValidationError(
46
- f"Invalid date/time format, expecting {date_format}, got {value}"
47
- ) from e
48
-
49
- return validate
50
-
51
-
52
- def validate_datetime(value):
53
- try:
54
- datetime.fromisoformat(value)
55
- except Exception as e:
56
- raise ValidationError(
57
- f"Invalid datetime format, expecting iso format, got {value}"
58
- ) from e
59
-
60
-
61
- class CachedMultilayerEDTFValidator(EDTFValidator):
62
- @functools.lru_cache(maxsize=1024)
63
- def __call__(self, value):
64
- if re.match(r"^\d{4}$", value):
65
- return value
66
- try:
67
- datetime.strptime(value, "%Y-%m-%d")
68
- return value
69
- except:
70
- return super().__call__(value)
@@ -1,282 +0,0 @@
1
- import dataclasses
2
- import inspect
3
- from typing import List
4
-
5
- from invenio_rdm_records.services.config import (
6
- RDMSearchDraftsOptions as BaseRDMSearchDraftsOptions,
7
- )
8
- from invenio_rdm_records.services.config import RDMSearchOptions as BaseRDMSearchOptions
9
- from invenio_records_resources.proxies import current_service_registry
10
- from invenio_records_resources.services.records import (
11
- SearchOptions as InvenioSearchOptions,
12
- )
13
- from invenio_drafts_resources.services.records.config import SearchDraftsOptions as InvenioSearchDraftsOptions
14
- from invenio_records_resources.services.records.params import (
15
- FacetsParam,
16
- PaginationParam,
17
- QueryStrParam,
18
- SortParam,
19
- )
20
- from invenio_records_resources.services.records.queryparser import SuggestQueryParser
21
- from invenio_search.engine import dsl
22
- from invenio_drafts_resources.services.records.search_params import AllVersionsParam
23
- # TODO: integrate this to invenio_records_resources.services.records and remove SearchOptions class
24
- from oarepo_runtime.i18n import lazy_gettext as _
25
- from oarepo_runtime.records.systemfields.icu import ICUSuggestField
26
- from oarepo_runtime.utils.functools import class_property
27
-
28
- from .facets.params import GroupedFacetsParam, OARepoAllVersionsParam, OARepoPublishedRecordsParam
29
- from invenio_drafts_resources.services.records.search_params import AllVersionsParam
30
- from invenio_rdm_records.services.search_params import PublishedRecordsParam
31
- from functools import partial
32
- try:
33
- from invenio_i18n import get_locale
34
- except ImportError:
35
- from invenio_i18n.babel import get_locale
36
-
37
-
38
- class FuzzySuggestQueryParser(SuggestQueryParser):
39
- def __init__(self, identity=None, extra_params=None, **kwargs):
40
- """Constructor."""
41
- super().__init__(identity=identity, extra_params=extra_params)
42
- self.fields = self.extra_params.get("fields", [])
43
- self.extra_params.setdefault("type", "bool_prefix")
44
-
45
- def parse(self, query_str):
46
- """Parse the query."""
47
- # default behavior
48
- multi_match_with_bool_prefix = dsl.Q(
49
- "multi_match", query=query_str, **self.extra_params
50
- )
51
- # fuzziness does not seem to work with bool_prefix multimatch query, so we turn this into
52
- # should multi match query with two clauses
53
- multi_match_fuzzy = dsl.Q(
54
- "multi_match", query=query_str, fields=self.fields, fuzziness="AUTO"
55
- )
56
- return dsl.Q("bool", should=[multi_match_with_bool_prefix, multi_match_fuzzy])
57
-
58
-
59
- class SearchOptionsMixin:
60
- @class_property
61
- def params_interpreters_cls(cls):
62
- """Replaces FacetsParam with GroupedFacetsParam."""
63
- params_replace_map = {FacetsParam: GroupedFacetsParam, AllVersionsParam:
64
- OARepoAllVersionsParam.factory(["versions.is_latest", "versions.is_latest_draft"]),
65
- PublishedRecordsParam: OARepoPublishedRecordsParam}
66
-
67
- param_interpreters = [*super(SearchOptionsMixin, cls).params_interpreters_cls]
68
- # replace FacetsParam with GroupedFacetsParam
69
- for idx, interpreter in enumerate(param_interpreters):
70
- if interpreter in params_replace_map:
71
- param_interpreters[idx] = params_replace_map[interpreter]
72
- elif isinstance(interpreter, partial):
73
- fn = interpreter.func
74
- if fn in params_replace_map:
75
- param_interpreters[idx] = params_replace_map[fn]
76
- return param_interpreters
77
-
78
- sort_options = {
79
- "title": dict(
80
- title=_("By Title"),
81
- fields=["metadata.title"], # ES defaults to desc on `_score` field
82
- ),
83
- "bestmatch": dict(
84
- title=_("Best match"),
85
- fields=["_score"], # ES defaults to desc on `_score` field
86
- ),
87
- "newest": dict(
88
- title=_("Newest"),
89
- fields=["-created"],
90
- ),
91
- "oldest": dict(
92
- title=_("Oldest"),
93
- fields=["created"],
94
- ),
95
- }
96
-
97
-
98
- class SearchOptionsDraftMixin(SearchOptionsMixin):
99
- sort_options = {
100
- "bestmatch": dict(
101
- title=_("Best match"),
102
- fields=["_score"], # search defaults to desc on `_score` field
103
- ),
104
- "updated-desc": dict(
105
- title=_("Recently updated"),
106
- fields=["-updated"],
107
- ),
108
- "updated-asc": dict(
109
- title=_("Least recently updated"),
110
- fields=["updated"],
111
- ),
112
- "newest": dict(
113
- title=_("Newest"),
114
- fields=["-created"],
115
- ),
116
- "oldest": dict(
117
- title=_("Oldest"),
118
- fields=["created"],
119
- ),
120
- "version": dict(
121
- title=_("Version"),
122
- fields=["-versions.index"],
123
- ),
124
- }
125
-
126
-
127
-
128
- class SearchOptions(SearchOptionsMixin, InvenioSearchOptions):
129
- # TODO: should be changed
130
- params_interpreters_cls = [
131
- QueryStrParam,
132
- PaginationParam,
133
- SortParam,
134
- GroupedFacetsParam,
135
- ]
136
-
137
- class SearchDraftsOptions(SearchOptionsMixin, InvenioSearchDraftsOptions):
138
- # TODO: should be changed
139
- params_interpreters_cls = [
140
- QueryStrParam,
141
- PaginationParam,
142
- SortParam,
143
- GroupedFacetsParam,
144
- AllVersionsParam.factory("versions.is_latest_draft")
145
- ]
146
-
147
-
148
- class RDMSearchOptions(SearchOptionsMixin, BaseRDMSearchOptions):
149
- pass
150
-
151
-
152
- class RDMSearchDraftsOptions(SearchOptionsDraftMixin, BaseRDMSearchDraftsOptions):
153
- pass
154
-
155
-
156
- @dataclasses.dataclass
157
- class SuggestField:
158
- field: str
159
- boost: int
160
- use_ngrams: bool = True
161
- boost_exact: float = 5
162
- boost_2gram: float = 1
163
- boost_3gram: float = 1
164
- boost_prefix: float = 1
165
-
166
-
167
- class ICUSuggestParser:
168
- def __init__(
169
- self,
170
- record_cls_or_service_name,
171
- extra_fields: List[SuggestField] = None,
172
- default_fields: List[SuggestField] = None,
173
- ):
174
- self.record_cls_or_service_name = record_cls_or_service_name
175
- self.extra_fields = extra_fields or []
176
- self.default_fields = default_fields or []
177
-
178
- @property
179
- def record_cls(self):
180
- if not isinstance(self.record_cls_or_service_name, str):
181
- return self.record_cls_or_service_name
182
- return current_service_registry.get(
183
- self.record_cls_or_service_name
184
- ).config.record_cls
185
-
186
- def __get__(self, instance, owner):
187
- search_as_you_type_fields: List[SuggestField] = []
188
- locale = get_locale()
189
- if locale:
190
- language = locale.language
191
-
192
- for fld_name, fld in inspect.getmembers(
193
- self.record_cls, lambda x: isinstance(x, ICUSuggestField)
194
- ):
195
- search_as_you_type_fields.append(
196
- SuggestField(f"{fld_name}.{language}.original", 2)
197
- )
198
- search_as_you_type_fields.append(
199
- SuggestField(f"{fld_name}.{language}.no_accent", 1)
200
- )
201
-
202
- if not search_as_you_type_fields:
203
- search_as_you_type_fields.extend(self.default_fields)
204
-
205
- search_as_you_type_fields.extend(self.extra_fields)
206
-
207
- fields = []
208
- for fld in search_as_you_type_fields:
209
- fields.append(f"{fld.field}^{fld.boost * fld.boost_exact}")
210
- if fld.use_ngrams:
211
- fields.append(f"{fld.field}._2gram^{fld.boost * fld.boost_2gram}")
212
- fields.append(f"{fld.field}._3gram^{fld.boost * fld.boost_3gram}")
213
- fields.append(
214
- f"{fld.field}._index_prefix^{fld.boost * fld.boost_prefix}"
215
- )
216
-
217
- return FuzzySuggestQueryParser.factory(fields=fields)
218
-
219
-
220
- @dataclasses.dataclass
221
- class SortField:
222
- option_name: str = "title"
223
- icu_sort_field: str = "sort"
224
- title: str = _("By Title")
225
-
226
-
227
- class ICUSortOptions:
228
- def __init__(self, record_cls_or_service_name, fields: List[SortField] = None):
229
- self.record_cls_or_service_name = record_cls_or_service_name
230
- self.fields = fields or [SortField()]
231
-
232
- @property
233
- def record_cls(self):
234
- if not isinstance(self.record_cls_or_service_name, str):
235
- return self.record_cls_or_service_name
236
- return current_service_registry.get(
237
- self.record_cls_or_service_name
238
- ).config.record_cls
239
-
240
- def __get__(self, instance, owner):
241
- if not inspect.isclass(owner):
242
- owner = type(owner)
243
- super_options = {}
244
- for mro in list(owner.mro())[1:]:
245
- if hasattr(mro, "sort_options"):
246
- super_options = mro.sort_options
247
- break
248
-
249
- ret = {
250
- **super_options,
251
- **getattr(owner, "extra_sort_options", {}),
252
- }
253
-
254
- # transform the sort options by the current language
255
- locale = get_locale()
256
- if not locale:
257
- return ret
258
-
259
- language = locale.language
260
-
261
- for sort_field in self.fields:
262
- icu_field = getattr(self.record_cls, sort_field.icu_sort_field)
263
- ret[sort_field.option_name] = {
264
- "title": sort_field.title,
265
- "fields": [f"{icu_field.key}.{language}"],
266
- }
267
- return ret
268
-
269
-
270
- class I18nSearchOptions(SearchOptions):
271
- extra_sort_options = {}
272
- record_cls = None
273
-
274
-
275
- class I18nRDMSearchOptions(RDMSearchOptions):
276
- extra_sort_options = {}
277
- record_cls = None
278
-
279
-
280
- class I18nRDMDraftsSearchOptions(RDMSearchDraftsOptions):
281
- extra_sort_options = {}
282
- record_cls = None
@@ -1,61 +0,0 @@
1
- """An extension to invenio RDM service that includes a search across all records
2
- (published, draft and deleted).
3
- without any filtering. Permission `can_read_all_records` and `search_all_records`
4
- were added and must be used to filter the results so that no information is leaked.
5
- """
6
-
7
- from invenio_rdm_records.services import RDMRecordService
8
- from invenio_records_resources.services import LinksTemplate
9
-
10
-
11
- class SearchAllRecordsService(RDMRecordService):
12
- def search_all_records(
13
- self,
14
- identity,
15
- params=None,
16
- search_preference=None,
17
- expand=False,
18
- extra_filter=None,
19
- **kwargs,
20
- ):
21
- """Search for drafts records matching the querystring."""
22
- self.require_permission(
23
- identity,
24
- "search_all_records",
25
- params=params,
26
- extra_filter=extra_filter,
27
- **kwargs,
28
- )
29
- # Prepare and execute the search
30
- params = params or {}
31
-
32
- search_opts = (
33
- getattr(self.config, "search_all", None) or self.config.search
34
- )
35
-
36
- search_result = self._search(
37
- "search_all",
38
- identity,
39
- params,
40
- search_preference,
41
- record_cls=self.draft_cls,
42
- search_opts=search_opts,
43
- extra_filter=extra_filter,
44
- permission_action="read_all_records",
45
- **kwargs,
46
- ).execute()
47
-
48
- return self.result_list(
49
- self,
50
- identity,
51
- search_result,
52
- params,
53
- links_tpl=LinksTemplate(
54
- getattr(self.config, "links_search_drafts")
55
- or self.config.links_search_drafts,
56
- context={"args": params},
57
- ),
58
- links_item_tpl=self.links_item_tpl,
59
- expandable_fields=self.expandable_fields,
60
- expand=expand,
61
- )
oarepo_runtime/tasks.py DELETED
@@ -1,6 +0,0 @@
1
- from celery import shared_task
2
-
3
- # TODO priority if something big is in queue already
4
- @shared_task(name='ping')
5
- def ping():
6
- return 'pong'