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.
- oarepo_runtime/__init__.py +24 -0
- oarepo_runtime/api.py +111 -0
- oarepo_runtime/cli/__init__.py +10 -21
- oarepo_runtime/cli/search.py +34 -0
- oarepo_runtime/config.py +86 -13
- oarepo_runtime/ext.py +64 -82
- oarepo_runtime/proxies.py +21 -5
- oarepo_runtime/records/__init__.py +11 -50
- oarepo_runtime/records/drafts.py +24 -18
- oarepo_runtime/records/mapping.py +84 -0
- oarepo_runtime/records/pid_providers.py +43 -7
- oarepo_runtime/records/systemfields/__init__.py +15 -33
- oarepo_runtime/records/systemfields/mapping.py +41 -24
- oarepo_runtime/records/systemfields/publication_status.py +59 -0
- oarepo_runtime/services/__init__.py +12 -0
- oarepo_runtime/services/config/__init__.py +15 -21
- oarepo_runtime/services/config/link_conditions.py +69 -75
- oarepo_runtime/services/config/permissions.py +62 -0
- oarepo_runtime/services/records/__init__.py +14 -1
- oarepo_runtime/services/records/links.py +21 -11
- oarepo_runtime/services/records/mapping.py +42 -0
- oarepo_runtime/services/results.py +98 -109
- oarepo_runtime/services/schema/__init__.py +12 -44
- oarepo_runtime/services/schema/i18n.py +47 -22
- oarepo_runtime/services/schema/i18n_ui.py +61 -24
- {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/METADATA +9 -21
- oarepo_runtime-2.0.0.dev3.dist-info/RECORD +30 -0
- {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/WHEEL +1 -2
- oarepo_runtime-2.0.0.dev3.dist-info/entry_points.txt +5 -0
- oarepo_runtime/cli/assets.py +0 -145
- oarepo_runtime/cli/base.py +0 -25
- oarepo_runtime/cli/cf.py +0 -15
- oarepo_runtime/cli/check.py +0 -167
- oarepo_runtime/cli/configuration.py +0 -51
- oarepo_runtime/cli/fixtures.py +0 -167
- oarepo_runtime/cli/index.py +0 -272
- oarepo_runtime/cli/permissions/__init__.py +0 -6
- oarepo_runtime/cli/permissions/base.py +0 -26
- oarepo_runtime/cli/permissions/evaluate.py +0 -63
- oarepo_runtime/cli/permissions/list.py +0 -239
- oarepo_runtime/cli/permissions/search.py +0 -121
- oarepo_runtime/cli/validate.py +0 -150
- oarepo_runtime/datastreams/__init__.py +0 -38
- oarepo_runtime/datastreams/asynchronous.py +0 -247
- oarepo_runtime/datastreams/catalogue.py +0 -150
- oarepo_runtime/datastreams/datastreams.py +0 -152
- oarepo_runtime/datastreams/errors.py +0 -54
- oarepo_runtime/datastreams/ext.py +0 -41
- oarepo_runtime/datastreams/fixtures.py +0 -265
- oarepo_runtime/datastreams/json.py +0 -4
- oarepo_runtime/datastreams/readers/__init__.py +0 -39
- oarepo_runtime/datastreams/readers/attachments.py +0 -51
- oarepo_runtime/datastreams/readers/excel.py +0 -123
- oarepo_runtime/datastreams/readers/json.py +0 -27
- oarepo_runtime/datastreams/readers/service.py +0 -54
- oarepo_runtime/datastreams/readers/yaml.py +0 -14
- oarepo_runtime/datastreams/semi_asynchronous.py +0 -91
- oarepo_runtime/datastreams/synchronous.py +0 -70
- oarepo_runtime/datastreams/transformers.py +0 -18
- oarepo_runtime/datastreams/types.py +0 -323
- oarepo_runtime/datastreams/utils.py +0 -131
- oarepo_runtime/datastreams/writers/__init__.py +0 -21
- oarepo_runtime/datastreams/writers/attachments_file.py +0 -92
- oarepo_runtime/datastreams/writers/attachments_service.py +0 -118
- oarepo_runtime/datastreams/writers/publish.py +0 -70
- oarepo_runtime/datastreams/writers/service.py +0 -175
- oarepo_runtime/datastreams/writers/utils.py +0 -30
- oarepo_runtime/datastreams/writers/validation_errors.py +0 -20
- oarepo_runtime/datastreams/writers/yaml.py +0 -56
- oarepo_runtime/ext_config.py +0 -67
- oarepo_runtime/i18n/__init__.py +0 -3
- oarepo_runtime/info/__init__.py +0 -0
- oarepo_runtime/info/check.py +0 -95
- oarepo_runtime/info/permissions/__init__.py +0 -0
- oarepo_runtime/info/permissions/debug.py +0 -191
- oarepo_runtime/info/views.py +0 -586
- oarepo_runtime/profile.py +0 -60
- oarepo_runtime/records/dumpers/__init__.py +0 -8
- oarepo_runtime/records/dumpers/edtf_interval.py +0 -38
- oarepo_runtime/records/dumpers/multilingual_dumper.py +0 -34
- oarepo_runtime/records/entity_resolvers/__init__.py +0 -13
- oarepo_runtime/records/entity_resolvers/proxies.py +0 -57
- oarepo_runtime/records/mappings/__init__.py +0 -0
- oarepo_runtime/records/mappings/rdm_parent_mapping.json +0 -483
- oarepo_runtime/records/owners/__init__.py +0 -3
- oarepo_runtime/records/owners/registry.py +0 -22
- oarepo_runtime/records/relations/__init__.py +0 -22
- oarepo_runtime/records/relations/base.py +0 -296
- oarepo_runtime/records/relations/internal.py +0 -46
- oarepo_runtime/records/relations/lookup.py +0 -28
- oarepo_runtime/records/relations/pid_relation.py +0 -102
- oarepo_runtime/records/systemfields/featured_file.py +0 -45
- oarepo_runtime/records/systemfields/has_draftcheck.py +0 -47
- oarepo_runtime/records/systemfields/icu.py +0 -371
- oarepo_runtime/records/systemfields/owner.py +0 -115
- oarepo_runtime/records/systemfields/record_status.py +0 -35
- oarepo_runtime/records/systemfields/selectors.py +0 -98
- oarepo_runtime/records/systemfields/synthetic.py +0 -130
- oarepo_runtime/resources/__init__.py +0 -4
- oarepo_runtime/resources/config.py +0 -12
- oarepo_runtime/resources/file_resource.py +0 -15
- oarepo_runtime/resources/json_serializer.py +0 -27
- oarepo_runtime/resources/localized_ui_json_serializer.py +0 -54
- oarepo_runtime/resources/resource.py +0 -53
- oarepo_runtime/resources/responses.py +0 -20
- oarepo_runtime/services/components.py +0 -429
- oarepo_runtime/services/config/draft_link.py +0 -23
- oarepo_runtime/services/config/permissions_presets.py +0 -174
- oarepo_runtime/services/config/service.py +0 -117
- oarepo_runtime/services/custom_fields/__init__.py +0 -80
- oarepo_runtime/services/custom_fields/mappings.py +0 -188
- oarepo_runtime/services/entity/__init__.py +0 -0
- oarepo_runtime/services/entity/config.py +0 -14
- oarepo_runtime/services/entity/schema.py +0 -9
- oarepo_runtime/services/entity/service.py +0 -48
- oarepo_runtime/services/expansions/__init__.py +0 -0
- oarepo_runtime/services/expansions/expandable_fields.py +0 -21
- oarepo_runtime/services/expansions/service.py +0 -4
- oarepo_runtime/services/facets/__init__.py +0 -33
- oarepo_runtime/services/facets/base.py +0 -12
- oarepo_runtime/services/facets/date.py +0 -72
- oarepo_runtime/services/facets/enum.py +0 -11
- oarepo_runtime/services/facets/facet_groups_names.py +0 -17
- oarepo_runtime/services/facets/max_facet.py +0 -13
- oarepo_runtime/services/facets/multilingual_facet.py +0 -33
- oarepo_runtime/services/facets/nested_facet.py +0 -32
- oarepo_runtime/services/facets/params.py +0 -192
- oarepo_runtime/services/facets/year_histogram.py +0 -200
- oarepo_runtime/services/files/__init__.py +0 -8
- oarepo_runtime/services/files/components.py +0 -62
- oarepo_runtime/services/files/service.py +0 -16
- oarepo_runtime/services/generators.py +0 -10
- oarepo_runtime/services/permissions/__init__.py +0 -3
- oarepo_runtime/services/permissions/generators.py +0 -103
- oarepo_runtime/services/relations/__init__.py +0 -0
- oarepo_runtime/services/relations/components.py +0 -15
- oarepo_runtime/services/relations/errors.py +0 -18
- oarepo_runtime/services/relations/mapping.py +0 -38
- oarepo_runtime/services/schema/cf.py +0 -13
- oarepo_runtime/services/schema/i18n_validation.py +0 -7
- oarepo_runtime/services/schema/marshmallow.py +0 -44
- oarepo_runtime/services/schema/marshmallow_to_json_schema.py +0 -72
- oarepo_runtime/services/schema/oneofschema.py +0 -192
- oarepo_runtime/services/schema/polymorphic.py +0 -21
- oarepo_runtime/services/schema/rdm.py +0 -75
- oarepo_runtime/services/schema/rdm_ui.py +0 -156
- oarepo_runtime/services/schema/ui.py +0 -251
- oarepo_runtime/services/schema/validation.py +0 -70
- oarepo_runtime/services/search.py +0 -282
- oarepo_runtime/services/service.py +0 -61
- oarepo_runtime/tasks.py +0 -6
- oarepo_runtime/translations/cs/LC_MESSAGES/messages.mo +0 -0
- oarepo_runtime/translations/cs/LC_MESSAGES/messages.po +0 -85
- oarepo_runtime/translations/default_translations.py +0 -6
- oarepo_runtime/translations/en/LC_MESSAGES/messages.mo +0 -0
- oarepo_runtime/translations/en/LC_MESSAGES/messages.po +0 -89
- oarepo_runtime/translations/messages.pot +0 -91
- oarepo_runtime/uow.py +0 -146
- oarepo_runtime/utils/__init__.py +0 -0
- oarepo_runtime/utils/functools.py +0 -37
- oarepo_runtime/utils/identity_utils.py +0 -35
- oarepo_runtime/utils/index.py +0 -11
- oarepo_runtime/utils/path.py +0 -97
- oarepo_runtime-1.10.2.dist-info/RECORD +0 -163
- oarepo_runtime-1.10.2.dist-info/entry_points.txt +0 -16
- oarepo_runtime-1.10.2.dist-info/top_level.txt +0 -2
- tests/marshmallow_to_json/__init__.py +0 -0
- tests/marshmallow_to_json/test_datacite_ui_schema.py +0 -1410
- tests/marshmallow_to_json/test_simple_schema.py +0 -52
- tests/pkg_data/__init__.py +0 -0
- {oarepo_runtime-1.10.2.dist-info → oarepo_runtime-2.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
@@ -1,371 +0,0 @@
|
|
1
|
-
from abc import abstractmethod, abstractproperty
|
2
|
-
from functools import cached_property
|
3
|
-
from typing import Dict
|
4
|
-
|
5
|
-
from flask import current_app
|
6
|
-
from invenio_records.systemfields import SystemField
|
7
|
-
|
8
|
-
from oarepo_runtime.records.relations.lookup import lookup_key
|
9
|
-
from oarepo_runtime.records.systemfields.mapping import MappingSystemFieldMixin
|
10
|
-
|
11
|
-
|
12
|
-
class ICUBase(MappingSystemFieldMixin, SystemField):
|
13
|
-
"""
|
14
|
-
Base class for ICU system fields.
|
15
|
-
It provides the basic functionality for ICU fields, such as
|
16
|
-
getting the attribute name and handling the key.
|
17
|
-
"""
|
18
|
-
|
19
|
-
def __init__(self, source_field=None, key=None):
|
20
|
-
super().__init__(key=key)
|
21
|
-
self._attr_name = key or self.__class__.__name__.lower()
|
22
|
-
self.source_field = source_field
|
23
|
-
|
24
|
-
@cached_property
|
25
|
-
def languages(self) -> Dict[str, Dict]:
|
26
|
-
icu_languages = current_app.config.get("OAREPO_ICU_LANGUAGES", {})
|
27
|
-
if icu_languages:
|
28
|
-
return icu_languages
|
29
|
-
|
30
|
-
primary_language = current_app.config.get("BABEL_DEFAULT_LOCALE", "en")
|
31
|
-
# list of tuples [lang, title], just take lang
|
32
|
-
babel_languages = [x[0] for x in current_app.config.get("I18N_LANGUAGES", [])]
|
33
|
-
|
34
|
-
return {primary_language: {}, **{k: {} for k in babel_languages}}
|
35
|
-
|
36
|
-
def get_values(self, data, language):
|
37
|
-
ret = []
|
38
|
-
for l in lookup_key(data, f"{self.source_field}"):
|
39
|
-
if isinstance(l.value, str):
|
40
|
-
# take single value as being always the the language provided
|
41
|
-
ret.append(l.value)
|
42
|
-
elif isinstance(l.value, dict):
|
43
|
-
# expected to be {"cs": "", "en": ""}
|
44
|
-
val = l.value.get(language)
|
45
|
-
if val:
|
46
|
-
ret.append(val)
|
47
|
-
elif "lang" in l.value:
|
48
|
-
# for [{"lang": "", "value": ""}, ...] we get each item separately
|
49
|
-
# that's why we do not iterate over l.value
|
50
|
-
if l.value["lang"] == language:
|
51
|
-
ret.append(l.value["value"])
|
52
|
-
return ret
|
53
|
-
|
54
|
-
@abstractproperty
|
55
|
-
def mapping(self):
|
56
|
-
"""
|
57
|
-
The mapping for the field. It should return a dictionary with the
|
58
|
-
mapping for the field, based on the current configuration of the application.
|
59
|
-
"""
|
60
|
-
raise NotImplementedError("Subclasses must implement the mapping property.")
|
61
|
-
|
62
|
-
@abstractmethod
|
63
|
-
def search_dump(self, data, record):
|
64
|
-
"""
|
65
|
-
Dump custom field. This method should be implemented by subclasses
|
66
|
-
to provide the functionality for dumping the field data into the
|
67
|
-
OpenSearch data structure.
|
68
|
-
"""
|
69
|
-
raise NotImplementedError("Subclasses must implement the search_dump method.")
|
70
|
-
|
71
|
-
def search_load(self, data, record_cls):
|
72
|
-
"""
|
73
|
-
Just remove the field from the data on load.
|
74
|
-
"""
|
75
|
-
data.pop(self.attr_name, None)
|
76
|
-
|
77
|
-
def __get__(self, instance, owner):
|
78
|
-
return self
|
79
|
-
|
80
|
-
|
81
|
-
class ICUField(ICUBase):
|
82
|
-
"""
|
83
|
-
A system field that acts as an opensearch "proxy" to another field.
|
84
|
-
It creates a top-level mapping field with the same name and copies
|
85
|
-
content of {another field}.language into {mapping field}.language.
|
86
|
-
|
87
|
-
The language accessor can be modified by overriding get_values method.
|
88
|
-
"""
|
89
|
-
|
90
|
-
def __init__(self, *, source_field, key=None):
|
91
|
-
super().__init__(source_field=source_field, key=key)
|
92
|
-
|
93
|
-
def search_dump(self, data, record):
|
94
|
-
ret = {}
|
95
|
-
for lang in self.languages:
|
96
|
-
r = self.get_values(data, lang)
|
97
|
-
if r:
|
98
|
-
# if the language is not empty, add it to the result
|
99
|
-
# otherwise do not add it at all to safe transport
|
100
|
-
ret[lang] = r
|
101
|
-
if ret:
|
102
|
-
data[self.attr_name] = ret
|
103
|
-
|
104
|
-
|
105
|
-
class ICUSortField(ICUField):
|
106
|
-
"""
|
107
|
-
A field that adds icu sorting field
|
108
|
-
"""
|
109
|
-
|
110
|
-
def __init__(self, *, source_field, key=None):
|
111
|
-
super().__init__(source_field=source_field, key=key)
|
112
|
-
|
113
|
-
@property
|
114
|
-
def mapping(self):
|
115
|
-
return {
|
116
|
-
self.attr_name: {
|
117
|
-
"type": "object",
|
118
|
-
"properties": {
|
119
|
-
lang: {
|
120
|
-
"type": "icu_collation_keyword",
|
121
|
-
"index": False,
|
122
|
-
"language": lang,
|
123
|
-
**setting.get("collation", {}),
|
124
|
-
}
|
125
|
-
for lang, setting in self.languages.items()
|
126
|
-
},
|
127
|
-
},
|
128
|
-
}
|
129
|
-
|
130
|
-
|
131
|
-
class ICUSuggestField(ICUField):
|
132
|
-
"""
|
133
|
-
A field that adds icu-aware suggestion field
|
134
|
-
"""
|
135
|
-
|
136
|
-
def __init__(self, source_field, key=None):
|
137
|
-
super().__init__(source_field=source_field, key=key)
|
138
|
-
|
139
|
-
@property
|
140
|
-
def mapping(self):
|
141
|
-
return {
|
142
|
-
self.attr_name: {
|
143
|
-
"type": "object",
|
144
|
-
"properties": {
|
145
|
-
lang: setting.get(
|
146
|
-
"suggest",
|
147
|
-
{
|
148
|
-
"type": "text",
|
149
|
-
"fields": {
|
150
|
-
"original": {
|
151
|
-
"type": "search_as_you_type",
|
152
|
-
},
|
153
|
-
"no_accent": {
|
154
|
-
"type": "search_as_you_type",
|
155
|
-
"analyzer": "accent_removal_analyzer",
|
156
|
-
},
|
157
|
-
},
|
158
|
-
},
|
159
|
-
)
|
160
|
-
for lang, setting in self.languages.items()
|
161
|
-
},
|
162
|
-
},
|
163
|
-
}
|
164
|
-
|
165
|
-
@property
|
166
|
-
def mapping_settings(self):
|
167
|
-
return {
|
168
|
-
"analysis": {
|
169
|
-
"analyzer": {
|
170
|
-
"accent_removal_analyzer": {
|
171
|
-
"type": "custom",
|
172
|
-
"tokenizer": "standard",
|
173
|
-
"filter": ["lowercase", "asciifolding"],
|
174
|
-
}
|
175
|
-
}
|
176
|
-
}
|
177
|
-
}
|
178
|
-
|
179
|
-
|
180
|
-
class ICUSearchAnalyzerMixin:
|
181
|
-
|
182
|
-
default_stemming_analyzers = {
|
183
|
-
"stemming_analyzer_cs": {
|
184
|
-
"tokenizer": "standard",
|
185
|
-
"filter": ["stemming_filter_cs", "lowercase"],
|
186
|
-
},
|
187
|
-
"stemming_analyzer_en": {
|
188
|
-
"tokenizer": "standard",
|
189
|
-
"filter": ["stemming_filter_en", "lowercase"],
|
190
|
-
},
|
191
|
-
"ascii_folding_analyzer": {
|
192
|
-
"tokenizer": "standard",
|
193
|
-
"filter": ["ascii_folding_filter", "lowercase"],
|
194
|
-
},
|
195
|
-
"lowercase_analyzer": {
|
196
|
-
"tokenizer": "standard",
|
197
|
-
"filter": ["lowercase"],
|
198
|
-
},
|
199
|
-
}
|
200
|
-
|
201
|
-
default_stemming_filters = {
|
202
|
-
"stemming_filter_cs": {
|
203
|
-
"type": "stemmer",
|
204
|
-
"name": "czech",
|
205
|
-
"language": "czech",
|
206
|
-
},
|
207
|
-
"stemming_filter_en": {
|
208
|
-
"type": "stemmer",
|
209
|
-
"name": "english",
|
210
|
-
"language": "english",
|
211
|
-
},
|
212
|
-
"ascii_folding_filter": {"type": "asciifolding", "preserve_original": True},
|
213
|
-
}
|
214
|
-
|
215
|
-
@property
|
216
|
-
def mapping_settings(self):
|
217
|
-
return {
|
218
|
-
"analysis": {
|
219
|
-
"analyzer": current_app.config.get(
|
220
|
-
"OAREPO_ICU_SEARCH_ANALYZERS", self.default_stemming_analyzers
|
221
|
-
),
|
222
|
-
"filter": current_app.config.get(
|
223
|
-
"OAREPO_ICU_SEARCH_FILTERS", self.default_stemming_filters
|
224
|
-
),
|
225
|
-
}
|
226
|
-
}
|
227
|
-
|
228
|
-
|
229
|
-
class ICUSearchField(ICUSearchAnalyzerMixin, ICUField):
|
230
|
-
"""
|
231
|
-
A field that adds stemming-aware search field for multilingual data (
|
232
|
-
e.g. data that contains {"cs": "...", "en": "..."}
|
233
|
-
or [{"lang": "cs", "value": "..."}, ...]
|
234
|
-
)
|
235
|
-
"""
|
236
|
-
|
237
|
-
def __init__(self, source_field, key=None, boost=1):
|
238
|
-
super().__init__(source_field=source_field, key=key)
|
239
|
-
self.boost = boost
|
240
|
-
|
241
|
-
@property
|
242
|
-
def mapping(self):
|
243
|
-
return {
|
244
|
-
self.attr_name: {
|
245
|
-
"type": "object",
|
246
|
-
"properties": {
|
247
|
-
# normal stemming
|
248
|
-
lang: setting.get(
|
249
|
-
"search",
|
250
|
-
{
|
251
|
-
"type": "text",
|
252
|
-
"boost": 1 * self.boost,
|
253
|
-
"fields": {
|
254
|
-
"stemmed": {
|
255
|
-
"type": "text",
|
256
|
-
"analyzer": f"stemming_analyzer_{lang}",
|
257
|
-
"boost": 0.5 * self.boost,
|
258
|
-
},
|
259
|
-
"lowercase": {
|
260
|
-
"type": "text",
|
261
|
-
"boost": 0.8 * self.boost,
|
262
|
-
"analyzer": "lowercase_analyzer",
|
263
|
-
},
|
264
|
-
"ascii_folded": {
|
265
|
-
"type": "text",
|
266
|
-
"analyzer": "ascii_folding_analyzer",
|
267
|
-
"boost": 0.3 * self.boost,
|
268
|
-
},
|
269
|
-
},
|
270
|
-
},
|
271
|
-
)
|
272
|
-
for lang, setting in self.languages.items()
|
273
|
-
},
|
274
|
-
},
|
275
|
-
}
|
276
|
-
|
277
|
-
def get_values(self, data, language):
|
278
|
-
return super().get_values(data, language=language)
|
279
|
-
|
280
|
-
|
281
|
-
class SingleLanguageSearchField(ICUSearchAnalyzerMixin, ICUBase):
|
282
|
-
"""
|
283
|
-
A base class for single-language search fields - that is, data contain a text
|
284
|
-
value in a pre-defined, single language.
|
285
|
-
"""
|
286
|
-
|
287
|
-
def __init__(self, *, source_field, key=None, language=None, boost=1):
|
288
|
-
super().__init__(source_field=source_field, key=key)
|
289
|
-
self.language = language
|
290
|
-
self.boost = boost
|
291
|
-
|
292
|
-
def search_dump(self, data, record):
|
293
|
-
"""Dump custom field."""
|
294
|
-
ret = self.get_values(data, language=self.language)
|
295
|
-
if ret:
|
296
|
-
data[self.attr_name] = ret
|
297
|
-
|
298
|
-
|
299
|
-
class FulltextIndexField(SingleLanguageSearchField):
|
300
|
-
"""
|
301
|
-
A system field that makes the field searchable in OpenSearch,
|
302
|
-
regardless if it is indexed/analyzed, embedded in Nested or not.
|
303
|
-
|
304
|
-
It creates a top-level mapping field and copies
|
305
|
-
content of {source_field} into it. It also provides the correct mapping
|
306
|
-
for the field based on the current configuration of the application.
|
307
|
-
|
308
|
-
Unlike the ICU, this field is a single-language and the language should
|
309
|
-
be provided when initializing the field.
|
310
|
-
It defaults to the BABEL_DEFAULT_LOCALE if not provided.
|
311
|
-
"""
|
312
|
-
|
313
|
-
@property
|
314
|
-
def mapping(self):
|
315
|
-
language = self.language or current_app.config.get("BABEL_DEFAULT_LOCALE", "en")
|
316
|
-
mapping_settings = self.languages.get(language, None)
|
317
|
-
if mapping_settings:
|
318
|
-
mapping_settings = mapping_settings.get("search")
|
319
|
-
if not mapping_settings:
|
320
|
-
mapping_settings = {
|
321
|
-
"type": "text",
|
322
|
-
"boost": 1 * self.boost,
|
323
|
-
"fields": {
|
324
|
-
"stemmed": {
|
325
|
-
"type": "text",
|
326
|
-
"analyzer": f"stemming_analyzer_{language}",
|
327
|
-
"boost": 0.5 * self.boost,
|
328
|
-
},
|
329
|
-
"lowercase": {
|
330
|
-
"type": "text",
|
331
|
-
"boost": 0.8 * self.boost,
|
332
|
-
"analyzer": "lowercase_analyzer",
|
333
|
-
},
|
334
|
-
"ascii_folded": {
|
335
|
-
"type": "text",
|
336
|
-
"analyzer": "ascii_folding_analyzer",
|
337
|
-
"boost": 0.3 * self.boost,
|
338
|
-
},
|
339
|
-
},
|
340
|
-
}
|
341
|
-
|
342
|
-
return {self.attr_name: mapping_settings}
|
343
|
-
|
344
|
-
def search_load(self, data, record_cls):
|
345
|
-
"""Load custom field."""
|
346
|
-
data.pop(self.attr_name, None)
|
347
|
-
|
348
|
-
|
349
|
-
class TermIndexField(SingleLanguageSearchField):
|
350
|
-
"""
|
351
|
-
A system field that makes the field searchable in OpenSearch,
|
352
|
-
regardless if it is indexed/analyzed, embedded in Nested or not.
|
353
|
-
|
354
|
-
It creates a top-level mapping field and copies
|
355
|
-
content of {source_field} into it. It also provides the correct mapping
|
356
|
-
for the field based on the current configuration of the application.
|
357
|
-
|
358
|
-
Unlike the ICU, this field is a single-language and the language should
|
359
|
-
be provided when initializing the field.
|
360
|
-
It defaults to the BABEL_DEFAULT_LOCALE if not provided.
|
361
|
-
"""
|
362
|
-
|
363
|
-
@property
|
364
|
-
def mapping(self):
|
365
|
-
mapping_settings = {
|
366
|
-
"type": "keyword",
|
367
|
-
"boost": 1 * self.boost,
|
368
|
-
"ignore_above": 256,
|
369
|
-
}
|
370
|
-
|
371
|
-
return {self.attr_name: mapping_settings}
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Copyright (C) 2021 CERN.
|
4
|
-
#
|
5
|
-
# Invenio-Communities is free software; you can redistribute it and/or
|
6
|
-
# modify it under the terms of the MIT License; see LICENSE file for more
|
7
|
-
# details.
|
8
|
-
|
9
|
-
"""Communities system field."""
|
10
|
-
|
11
|
-
from invenio_records.systemfields import SystemField
|
12
|
-
|
13
|
-
from oarepo_runtime.records.owners import OwnerEntityResolverRegistry
|
14
|
-
from oarepo_runtime.records.systemfields import MappingSystemFieldMixin
|
15
|
-
|
16
|
-
|
17
|
-
class OwnerRelationManager:
|
18
|
-
def __init__(self, record_id, serialized_owners):
|
19
|
-
self._serialized_owners = serialized_owners
|
20
|
-
self._deserialized_owners = None
|
21
|
-
|
22
|
-
# from oarepo_requests utils, dependancy on that would be wrong here, right?
|
23
|
-
# invenio_requests is ok<
|
24
|
-
|
25
|
-
#
|
26
|
-
# API
|
27
|
-
#
|
28
|
-
|
29
|
-
def to_dict(self):
|
30
|
-
if self._serialized_owners is None:
|
31
|
-
deserialized_owners = []
|
32
|
-
for deserialized_owner in self._deserialized_owners or []:
|
33
|
-
serialized_owner = OwnerEntityResolverRegistry.reference_entity(
|
34
|
-
deserialized_owner
|
35
|
-
)
|
36
|
-
if serialized_owner is None:
|
37
|
-
raise ValueError(
|
38
|
-
f"failed serialize owner; owner - {deserialized_owner}"
|
39
|
-
)
|
40
|
-
deserialized_owners.append(serialized_owner)
|
41
|
-
self._serialized_owners = deserialized_owners
|
42
|
-
return self._serialized_owners
|
43
|
-
|
44
|
-
def _resolve(self):
|
45
|
-
if self._deserialized_owners is None:
|
46
|
-
self._deserialized_owners = set()
|
47
|
-
for ref in self._serialized_owners or []:
|
48
|
-
self._deserialized_owners.add(
|
49
|
-
OwnerEntityResolverRegistry.resolve_reference(ref)
|
50
|
-
)
|
51
|
-
self._serialized_owners = None
|
52
|
-
|
53
|
-
def add(self, owner):
|
54
|
-
if owner is None:
|
55
|
-
return
|
56
|
-
self._resolve()
|
57
|
-
self._deserialized_owners.add(owner)
|
58
|
-
|
59
|
-
def remove(self, owner):
|
60
|
-
if owner is None:
|
61
|
-
return
|
62
|
-
self._resolve()
|
63
|
-
self._deserialized_owners.remove(owner)
|
64
|
-
|
65
|
-
def __iter__(self):
|
66
|
-
self._resolve()
|
67
|
-
return iter(self._deserialized_owners)
|
68
|
-
|
69
|
-
|
70
|
-
class OwnersField(MappingSystemFieldMixin, SystemField):
|
71
|
-
"""Communites system field for managing relations to communities."""
|
72
|
-
|
73
|
-
def __init__(self, key="owners", manager_cls=None):
|
74
|
-
"""Constructor."""
|
75
|
-
self._manager_cls = manager_cls or OwnerRelationManager
|
76
|
-
super().__init__(key=key)
|
77
|
-
|
78
|
-
@property
|
79
|
-
def mapping(self):
|
80
|
-
return {
|
81
|
-
self.attr_name: {
|
82
|
-
"type": "object",
|
83
|
-
"properties": {"user": {"type": "keyword", "ignore_above": 256}},
|
84
|
-
},
|
85
|
-
}
|
86
|
-
|
87
|
-
def pre_commit(self, record):
|
88
|
-
"""Commit the communities field."""
|
89
|
-
manager = self.obj(record)
|
90
|
-
self.set_dictkey(record, manager.to_dict())
|
91
|
-
|
92
|
-
def pre_dump(self, record, data, dumper=None):
|
93
|
-
"""Called before a record is dumped."""
|
94
|
-
# parent record commit op is not called during update, resulting in the parent not being converted correctly into 'dict', ie. the dict() function in invenio_records.dumpers.base #36 works incorrectly
|
95
|
-
manager = self.obj(record)
|
96
|
-
self.set_dictkey(record, manager.to_dict())
|
97
|
-
|
98
|
-
def obj(self, record):
|
99
|
-
"""Get or crate the communities manager."""
|
100
|
-
# Check cache
|
101
|
-
obj = self._get_cache(record)
|
102
|
-
if obj is not None:
|
103
|
-
return obj
|
104
|
-
|
105
|
-
serialized_owners = self.get_dictkey(record)
|
106
|
-
# Create manager
|
107
|
-
obj = self._manager_cls(record.id, serialized_owners)
|
108
|
-
self._set_cache(record, obj)
|
109
|
-
return obj
|
110
|
-
|
111
|
-
def __get__(self, record, owner=None):
|
112
|
-
"""Get the persistent identifier."""
|
113
|
-
if record is None:
|
114
|
-
return self
|
115
|
-
return self.obj(record)
|
@@ -1,35 +0,0 @@
|
|
1
|
-
from invenio_records.systemfields import SystemField
|
2
|
-
|
3
|
-
from .mapping import MappingSystemFieldMixin
|
4
|
-
|
5
|
-
|
6
|
-
class RecordStatusResult:
|
7
|
-
def __init__(self, record, attr_name):
|
8
|
-
self.record = record
|
9
|
-
self.attr_name = attr_name
|
10
|
-
|
11
|
-
|
12
|
-
class RecordStatusSystemField(MappingSystemFieldMixin, SystemField):
|
13
|
-
@property
|
14
|
-
def mapping(self):
|
15
|
-
return {
|
16
|
-
self.attr_name: {
|
17
|
-
"type": "keyword",
|
18
|
-
},
|
19
|
-
}
|
20
|
-
|
21
|
-
def search_load(self, data, record_cls):
|
22
|
-
data.pop(self.attr_name, None)
|
23
|
-
|
24
|
-
def search_dump(self, data, record):
|
25
|
-
if getattr(record, "is_draft"):
|
26
|
-
data[self.attr_name] = "draft"
|
27
|
-
else:
|
28
|
-
data[self.attr_name] = "published"
|
29
|
-
|
30
|
-
def __get__(self, record, owner=None):
|
31
|
-
"""Accessing the attribute."""
|
32
|
-
# Class access
|
33
|
-
if record is None:
|
34
|
-
return self
|
35
|
-
return RecordStatusResult(record, self.attr_name)
|
@@ -1,98 +0,0 @@
|
|
1
|
-
import dataclasses
|
2
|
-
from typing import Any, Callable, List, Protocol, Tuple
|
3
|
-
|
4
|
-
from oarepo_runtime.records.relations.lookup import lookup_key
|
5
|
-
|
6
|
-
|
7
|
-
class Selector(Protocol):
|
8
|
-
def select(self, record) -> List[Any]:
|
9
|
-
return []
|
10
|
-
|
11
|
-
|
12
|
-
class PathSelector(Selector):
|
13
|
-
def __init__(self, *paths):
|
14
|
-
self.paths = [x.split(".") for x in paths]
|
15
|
-
|
16
|
-
def select(self, record):
|
17
|
-
ret = []
|
18
|
-
for path in self.paths:
|
19
|
-
for rec in getter(record, path):
|
20
|
-
ret.append(rec)
|
21
|
-
return ret
|
22
|
-
|
23
|
-
|
24
|
-
class FirstItemSelector(PathSelector):
|
25
|
-
def select(self, record):
|
26
|
-
for rec in super().select(record):
|
27
|
-
return [rec]
|
28
|
-
return []
|
29
|
-
|
30
|
-
|
31
|
-
@dataclasses.dataclass
|
32
|
-
class FilteredSelector(Selector):
|
33
|
-
"""
|
34
|
-
Selector which filters output of another selector
|
35
|
-
Example:
|
36
|
-
FilteredSelector(PathSelector("metadata.creators", "metadata.contributors"),
|
37
|
-
filter=lambda x: x["nameType"] == "personal", projection="affiliations")
|
38
|
-
|
39
|
-
selects affiliations of creators with nameType personal from following data
|
40
|
-
|
41
|
-
data = {
|
42
|
-
"metadata": {
|
43
|
-
"creators": [
|
44
|
-
{"name": "hugo", "affiliations": ["uni1", "uni2"], "nameType": "personal"},
|
45
|
-
{"name": "uni3", "nameType": "organizational"},
|
46
|
-
]
|
47
|
-
}
|
48
|
-
}
|
49
|
-
"""
|
50
|
-
selector: Selector
|
51
|
-
filter: Callable[[Any], bool]
|
52
|
-
projection: Callable[[Any], Any] | str = None
|
53
|
-
|
54
|
-
def select(self, record):
|
55
|
-
selected = self.selector.select(record)
|
56
|
-
selected = filter(self.filter, selected)
|
57
|
-
if self.projection:
|
58
|
-
ret = []
|
59
|
-
for select_element in selected:
|
60
|
-
if isinstance(self.projection, str):
|
61
|
-
result = [x.value for x in lookup_key(select_element, self.projection)]
|
62
|
-
else:
|
63
|
-
result = self.projection(select_element)
|
64
|
-
if isinstance(result, list):
|
65
|
-
ret += result
|
66
|
-
else:
|
67
|
-
ret.append(result)
|
68
|
-
else:
|
69
|
-
ret = list(selected)
|
70
|
-
return ret
|
71
|
-
|
72
|
-
|
73
|
-
@dataclasses.dataclass
|
74
|
-
class MultiSelector(Selector):
|
75
|
-
"""Selector concatenating outputs of multiple selectors"""
|
76
|
-
|
77
|
-
def __init__(self, *selectors: Selector):
|
78
|
-
self.selectors = selectors
|
79
|
-
|
80
|
-
def select(self, record):
|
81
|
-
ret = []
|
82
|
-
for selector in self.selectors:
|
83
|
-
ret += selector.select(record)
|
84
|
-
return ret
|
85
|
-
|
86
|
-
|
87
|
-
def getter(data, path: List):
|
88
|
-
if len(path) == 0:
|
89
|
-
if isinstance(data, list):
|
90
|
-
yield from data
|
91
|
-
else:
|
92
|
-
yield data
|
93
|
-
elif isinstance(data, dict):
|
94
|
-
if path[0] in data:
|
95
|
-
yield from getter(data[path[0]], path[1:])
|
96
|
-
elif isinstance(data, list):
|
97
|
-
for item in data:
|
98
|
-
yield from getter(item, path)
|