oarepo-runtime 2.0.0.dev42__tar.gz → 2.0.0.dev44__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/PKG-INFO +1 -1
  2. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/__init__.py +1 -1
  3. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/api.py +32 -0
  4. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/ext.py +116 -1
  5. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/resources/signposting/__init__.py +17 -16
  6. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/.gitignore +0 -0
  7. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/LICENSE +0 -0
  8. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/README.md +0 -0
  9. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/cli/__init__.py +0 -0
  10. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/cli/search.py +0 -0
  11. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/config.py +0 -0
  12. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/info/__init__.py +0 -0
  13. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/info/views.py +0 -0
  14. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/proxies.py +0 -0
  15. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/py.typed +0 -0
  16. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/__init__.py +0 -0
  17. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/drafts.py +0 -0
  18. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/mapping.py +0 -0
  19. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/pid_providers.py +0 -0
  20. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/systemfields/__init__.py +0 -0
  21. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/systemfields/base.py +0 -0
  22. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/systemfields/custom_fields.py +0 -0
  23. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/systemfields/mapping.py +0 -0
  24. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/systemfields/publication_status.py +0 -0
  25. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/systemfields/relations.py +0 -0
  26. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/records/systemfields/selectors.py +0 -0
  27. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/resources/__init__.py +0 -0
  28. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/resources/config.py +0 -0
  29. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/__init__.py +0 -0
  30. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/config/__init__.py +0 -0
  31. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/config/components.py +0 -0
  32. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/config/link_conditions.py +0 -0
  33. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/config/permissions.py +0 -0
  34. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/facets/__init__.py +0 -0
  35. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/facets/base.py +0 -0
  36. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/facets/date.py +0 -0
  37. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/facets/nested_facet.py +0 -0
  38. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/facets/params.py +0 -0
  39. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/facets/utils.py +0 -0
  40. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/generators.py +0 -0
  41. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/records/__init__.py +0 -0
  42. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/records/custom_fields.py +0 -0
  43. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/records/links.py +0 -0
  44. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/records/mapping.py +0 -0
  45. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/results.py +0 -0
  46. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/schema/__init__.py +0 -0
  47. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/schema/i18n.py +0 -0
  48. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/schema/i18n_ui.py +0 -0
  49. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/services/schema/ui.py +0 -0
  50. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/oarepo_runtime/typing.py +0 -0
  51. {oarepo_runtime-2.0.0.dev42 → oarepo_runtime-2.0.0.dev44}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oarepo-runtime
3
- Version: 2.0.0.dev42
3
+ Version: 2.0.0.dev44
4
4
  Summary: A set of runtime extensions of Invenio repository
5
5
  Project-URL: Homepage, https://github.com/oarepo/oarepo-runtime
6
6
  License-Expression: MIT
@@ -19,6 +19,6 @@ from .api import Model
19
19
  from .ext import OARepoRuntime
20
20
  from .proxies import current_runtime
21
21
 
22
- __version__ = "2.0.0dev42"
22
+ __version__ = "2.0.0dev44"
23
23
 
24
24
  __all__ = ("Model", "OARepoRuntime", "__version__", "current_runtime")
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  import dataclasses
15
15
  from functools import cached_property
16
+ from mimetypes import guess_extension
16
17
  from typing import TYPE_CHECKING, Any, cast
17
18
 
18
19
  from flask import current_app
@@ -22,6 +23,7 @@ from invenio_records_resources.proxies import current_service_registry
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from collections.abc import Mapping
26
+ from types import SimpleNamespace
25
27
 
26
28
  from flask_babel.speaklater import LazyString
27
29
  from flask_resources.deserializers import DeserializerMixin
@@ -92,6 +94,21 @@ class Export:
92
94
  description: LazyString | None = None
93
95
  """Description of the export format, human readable."""
94
96
 
97
+ extension: str | None = None
98
+ """Ext."""
99
+
100
+ def __post_init__(self):
101
+ """Post init with extension guessing."""
102
+ if self.extension is None:
103
+ extension = guess_extension(self.mimetype)
104
+ if not extension:
105
+ first, second = self.mimetype.rsplit("/", maxsplit=1)
106
+ _, second = second.rsplit("+", maxsplit=1)
107
+ extension = guess_extension(f"{first}/{second}")
108
+ self.extension = extension
109
+ if not self.extension:
110
+ self.extension = ".bin"
111
+
95
112
 
96
113
  @dataclasses.dataclass
97
114
  class Import:
@@ -154,6 +171,7 @@ class Model[
154
171
  features: Mapping[str, Any] | None = None,
155
172
  imports: list[Import] | None = None,
156
173
  ui_blueprint_name: str | None = None,
174
+ namespace: SimpleNamespace | None = None,
157
175
  ):
158
176
  """Initialize the model configuration.
159
177
 
@@ -181,6 +199,7 @@ class Model[
181
199
  :param imports: List of import formats that can be used to import the record.
182
200
  If not provided, no imports are available.
183
201
  :param ui_blueprint_name: Name of the UI blueprint
202
+ :param namespace: SimpleNamespace where the model is being created. Used by oarepo-model.
184
203
  """
185
204
  self._code = code
186
205
  self._name = name
@@ -206,6 +225,7 @@ class Model[
206
225
  self._model_metadata = model_metadata
207
226
  self._features = features
208
227
  self._ui_blueprint_name = ui_blueprint_name
228
+ self._namespace = namespace
209
229
 
210
230
  @property
211
231
  def code(self) -> str:
@@ -395,6 +415,13 @@ class Model[
395
415
  return export
396
416
  return None
397
417
 
418
+ def get_export_by_code(self, code: str) -> Export | None:
419
+ """Get an export by code."""
420
+ for export in self._exports:
421
+ if export.code == code:
422
+ return export
423
+ return None
424
+
398
425
  @property
399
426
  def response_handlers(self) -> dict[str, ResponseHandler]:
400
427
  """Get all response handlers from the resource configuration."""
@@ -416,3 +443,8 @@ class Model[
416
443
  if self.records_alias_enabled:
417
444
  return cast("str", self.service.id)
418
445
  raise TypeError("This model does not have associated entity type.")
446
+
447
+ @property
448
+ def namespace(self) -> SimpleNamespace | None:
449
+ """Get the namespace where the model is being created."""
450
+ return self._namespace
@@ -11,8 +11,10 @@
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ import json
15
+ from enum import Enum
14
16
  from functools import cached_property
15
- from typing import TYPE_CHECKING, Any, cast
17
+ from typing import TYPE_CHECKING, Any, Literal, cast, overload
16
18
 
17
19
  from flask import current_app
18
20
  from invenio_db import db
@@ -20,6 +22,7 @@ from invenio_pidstore.errors import PIDDoesNotExistError
20
22
  from invenio_pidstore.models import PersistentIdentifier
21
23
  from invenio_records.api import Record as RecordBase
22
24
  from invenio_records_resources.proxies import current_service_registry
25
+ from lxml.etree import fromstring
23
26
 
24
27
  from . import config
25
28
 
@@ -34,10 +37,19 @@ if TYPE_CHECKING: # pragma: no cover
34
37
  from invenio_records_resources.services.base.service import Service
35
38
  from invenio_records_resources.services.files.service import FileService
36
39
  from invenio_records_resources.services.records import RecordService
40
+ from lxml.etree import Element
37
41
 
38
42
  from .api import Model
39
43
 
40
44
 
45
+ class ExportRepresentation(Enum):
46
+ """Representation of the export, which can be response, dictionary or XML."""
47
+
48
+ RESPONSE = ("response",) # Response
49
+ DICTIONARY = ("dictionary",) # python dictionary
50
+ XML = ("xml",) # XML Element
51
+
52
+
41
53
  class OARepoRuntime:
42
54
  """OARepo base of invenio oarepo client."""
43
55
 
@@ -226,3 +238,106 @@ class OARepoRuntime:
226
238
  if draft_index is not None:
227
239
  indices.add(draft_index.search_alias)
228
240
  return indices
241
+
242
+ @overload
243
+ def get_export_from_serialized_record(
244
+ self,
245
+ record_dict: dict,
246
+ representation: Literal[ExportRepresentation.RESPONSE],
247
+ export_code: str | None = None,
248
+ export_mimetype: str | None = None,
249
+ ) -> tuple[Any, int, dict[str, str]] | None: ...
250
+
251
+ @overload
252
+ def get_export_from_serialized_record(
253
+ self,
254
+ record_dict: dict,
255
+ representation: Literal[ExportRepresentation.DICTIONARY],
256
+ export_code: str | None = None,
257
+ export_mimetype: str | None = None,
258
+ ) -> dict: ...
259
+
260
+ @overload
261
+ def get_export_from_serialized_record(
262
+ self,
263
+ record_dict: dict,
264
+ representation: Literal[ExportRepresentation.XML],
265
+ export_code: str | None = None,
266
+ export_mimetype: str | None = None,
267
+ ) -> Element: ...
268
+
269
+ def get_export_from_serialized_record(
270
+ self,
271
+ record_dict: dict,
272
+ representation: Literal[
273
+ ExportRepresentation.RESPONSE, ExportRepresentation.DICTIONARY, ExportRepresentation.XML
274
+ ],
275
+ export_code: str | None = None,
276
+ export_mimetype: str | None = None,
277
+ ):
278
+ """Retrieve and prepare the export of a record item based on the specified parameters.
279
+
280
+ This function processes the input `record_item` and converts it to the appropriate
281
+ export format based on the `representation` and either `export_code` or `export_mimetype`.
282
+ It ensures strict constraints on input validity and raises errors when conditions
283
+ are violated.
284
+
285
+ Args:
286
+ record_dict: The record item serialized as a dictionary
287
+ representation: The desired output representation of the export. It must be one
288
+ of the defined values of `ExportRepresentation`.
289
+ export_code: The code representing the type of export to retrieve. Only one of
290
+ `export_code` or `export_mimetype` should be provided.
291
+ export_mimetype: The MIME type representing the type of export to retrieve. Only
292
+ one of `export_code` or `export_mimetype` should be provided.
293
+
294
+ Raises:
295
+ ValueError: Raised if both `export_code` and `export_mimetype` are None.
296
+ ValueError: Raised if both `export_code` and `export_mimetype` are provided
297
+ simultaneously.
298
+ ValueError: Raised if no export is found for the given `export_code` or
299
+ `export_mimetype`.
300
+
301
+ Returns:
302
+ Union[Tuple[str, int, dict], dict, etree._Element]: Depending on the `representation`
303
+ specified:
304
+ - If `ExportRepresentation.RESPONSE`, returns a tuple with the serialized export,
305
+ HTTP status code (200), and the HTTP headers containing content type and
306
+ disposition.
307
+ - If `ExportRepresentation.DICTIONARY`, returns a dictionary representation of
308
+ the exported record item.
309
+ - If `ExportRepresentation.XML`, returns an XML element structure of the exported
310
+ record.
311
+
312
+ """
313
+ model = self.models_by_schema[record_dict["$schema"]]
314
+ if export_mimetype:
315
+ if export_code is not None:
316
+ raise ValueError("Only one of the parameters export_code/export_mimetype must be set, both are set")
317
+ model_export = model.get_export_by_mimetype(mimetype=export_mimetype)
318
+ elif export_code:
319
+ model_export = model.get_export_by_code(code=export_code)
320
+ else:
321
+ raise ValueError("One of the parameters export_code/export_mimetype must be set, both are None")
322
+
323
+ if model_export is None:
324
+ raise ValueError("No export found for the given mimetype or code")
325
+
326
+ exported_record = model_export.serializer.serialize_object(record_dict)
327
+
328
+ match representation:
329
+ case ExportRepresentation.RESPONSE:
330
+ filename = f"{record_dict['id']}{model_export.extension}"
331
+ headers = {
332
+ "Content-Type": model_export.mimetype,
333
+ "Content-Disposition": f"attachment; filename={filename}",
334
+ }
335
+ return (exported_record, 200, headers)
336
+
337
+ case ExportRepresentation.DICTIONARY:
338
+ if isinstance(exported_record, str):
339
+ exported_record = json.loads(exported_record)
340
+ return exported_record
341
+
342
+ case ExportRepresentation.XML:
343
+ return fromstring(exported_record)
@@ -35,6 +35,7 @@ from urllib.parse import urljoin
35
35
 
36
36
  from signposting import AbsoluteURI, LinkRel, Signpost
37
37
 
38
+ from oarepo_runtime.ext import ExportRepresentation
38
39
  from oarepo_runtime.proxies import current_runtime
39
40
 
40
41
 
@@ -277,9 +278,7 @@ def landing_page_signpost_links_list(datacite_dict: dict, record_dict: dict, sho
277
278
  model = current_runtime.models_by_schema[record_dict["$schema"]]
278
279
 
279
280
  # author - prvni tri
280
- data = datacite_dict["data"]
281
- attributes = data["attributes"]
282
- creators = attributes.get("creators", [])
281
+ creators = datacite_dict.get("creators", [])
283
282
  if short:
284
283
  creators = creators[:3]
285
284
  for attribute in creators:
@@ -289,7 +288,9 @@ def landing_page_signpost_links_list(datacite_dict: dict, record_dict: dict, sho
289
288
  )
290
289
 
291
290
  # cite-as = DOI
292
- signposting_links.append(Signpost(rel=LinkRel.cite_as, target=urljoin("https://doi.org/", attributes.get("doi"))))
291
+ signposting_links.append(
292
+ Signpost(rel=LinkRel.cite_as, target=urljoin("https://doi.org/", datacite_dict.get("doi")))
293
+ )
293
294
 
294
295
  # describedby
295
296
  for model_export in model.exports:
@@ -319,7 +320,7 @@ def landing_page_signpost_links_list(datacite_dict: dict, record_dict: dict, sho
319
320
  )
320
321
 
321
322
  # license
322
- for attribute in attributes.get("rightsList"):
323
+ for attribute in datacite_dict.get("rightsList", []):
323
324
  # check for schemeUri, rightsIdentifier and 'rightsIdentifierScheme' == SPDX, fallback rightsUri, else nothing
324
325
  license_url = attribute.get("rightsUri")
325
326
  if (
@@ -331,7 +332,7 @@ def landing_page_signpost_links_list(datacite_dict: dict, record_dict: dict, sho
331
332
  signposting_links.append(Signpost(rel=LinkRel.license, target=license_url))
332
333
 
333
334
  # type
334
- schema_org = attributes.get("types", {}).get("schemaOrg")
335
+ schema_org = datacite_dict.get("types", {}).get("schemaOrg")
335
336
  if schema_org:
336
337
  resource_type_url = "https://schema.org/" + schema_org
337
338
  signposting_links.append(Signpost(rel=LinkRel.type, target=resource_type_url))
@@ -342,19 +343,19 @@ def landing_page_signpost_links_list(datacite_dict: dict, record_dict: dict, sho
342
343
 
343
344
  def record_dict_to_linkset(record_dict: dict) -> str:
344
345
  """Create a linkset from the dictionary of a record item. Get datacite to build linkset from model exports."""
345
- model = current_runtime.models_by_schema[record_dict["$schema"]]
346
- datacite_export = model.get_export_by_mimetype("application/vnd.datacite.datacite+json")
347
- if not datacite_export:
348
- return ""
349
- datacite_dict = datacite_export.serializer.serialize_object(record_dict)
346
+ datacite_dict = current_runtime.get_export_from_serialized_record(
347
+ record_dict=record_dict,
348
+ representation=ExportRepresentation.DICTIONARY,
349
+ export_mimetype="application/vnd.datacite.datacite+json",
350
+ )
350
351
  return create_linkset(datacite_dict, record_dict)
351
352
 
352
353
 
353
354
  def record_dict_to_json_linkset(record_dict: dict) -> dict[str, list[dict[str, Any]]]:
354
355
  """Create a JSON linkset from the dictionary of a record item. Get datacite to build linkset from model exports."""
355
- model = current_runtime.models_by_schema[record_dict["$schema"]]
356
- datacite_export = model.get_export_by_mimetype("application/vnd.datacite.datacite+json")
357
- if not datacite_export:
358
- return {}
359
- datacite_dict = datacite_export.serializer.serialize_object(record_dict)
356
+ datacite_dict = current_runtime.get_export_from_serialized_record(
357
+ record_dict=record_dict,
358
+ representation=ExportRepresentation.DICTIONARY,
359
+ export_mimetype="application/vnd.datacite.datacite+json",
360
+ )
360
361
  return create_linkset_json(datacite_dict, record_dict)