oarepo-runtime 1.5.88__py3-none-any.whl → 1.5.90__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.
@@ -1,17 +1,29 @@
1
+ from __future__ import annotations
2
+
1
3
  import inspect
2
4
  from collections import defaultdict
5
+ from dataclasses import dataclass, field
3
6
  from typing import Type
4
7
 
5
8
  from flask import current_app
6
9
  from invenio_accounts.models import User
10
+ from invenio_drafts_resources.services.records.config import (
11
+ RecordServiceConfig as DraftsRecordServiceConfig,
12
+ )
13
+ from invenio_rdm_records.services.config import RDMRecordServiceConfig
7
14
  from invenio_records import Record
15
+ from invenio_records_resources.services import FileServiceConfig
16
+ from invenio_records_resources.services.records.config import (
17
+ RecordServiceConfig as RecordsRecordServiceConfig,
18
+ )
8
19
 
9
- from oarepo_runtime.services.custom_fields import CustomFieldsMixin, CustomFields, InlinedCustomFields
20
+ from oarepo_runtime.services.custom_fields import (
21
+ CustomFields,
22
+ CustomFieldsMixin,
23
+ InlinedCustomFields,
24
+ )
10
25
  from oarepo_runtime.services.generators import RecordOwners
11
- from invenio_rdm_records.services.config import RDMRecordServiceConfig
12
- from invenio_drafts_resources.services.records.config import RecordServiceConfig as DraftsRecordServiceConfig
13
- from invenio_records_resources.services.records.config import RecordServiceConfig as RecordsRecordServiceConfig
14
- from invenio_records_resources.services import FileServiceConfig
26
+
15
27
  try:
16
28
  from invenio_drafts_resources.services.records.uow import ParentRecordCommitOp
17
29
  except ImportError:
@@ -62,18 +74,23 @@ class DateIssuedComponent(ServiceComponent):
62
74
  if "dateIssued" not in record["metadata"]:
63
75
  record["metadata"]["dateIssued"] = datetime.today().strftime("%Y-%m-%d")
64
76
 
77
+
65
78
  class CFRegistry:
66
79
  def __init__(self):
67
80
  self.custom_field_names = defaultdict(list)
68
81
 
69
82
  def lookup(self, record_type: Type[Record]):
70
83
  if record_type not in self.custom_field_names:
71
- for fld in inspect.getmembers(record_type, lambda x: isinstance(x, CustomFieldsMixin)):
84
+ for fld in inspect.getmembers(
85
+ record_type, lambda x: isinstance(x, CustomFieldsMixin)
86
+ ):
72
87
  self.custom_field_names[record_type].append(fld[1])
73
88
  return self.custom_field_names[record_type]
74
89
 
90
+
75
91
  cf_registry = CFRegistry()
76
92
 
93
+
77
94
  class CustomFieldsComponent(ServiceComponent):
78
95
  def create(self, identity, data=None, record=None, **kwargs):
79
96
  """Create a new record."""
@@ -90,16 +107,16 @@ class CustomFieldsComponent(ServiceComponent):
90
107
  elif isinstance(cf, InlinedCustomFields):
91
108
  config = current_app.config.get(cf.config_key, {})
92
109
  for c in config:
93
- record[c.name] =data.get(c.name)
110
+ record[c.name] = data.get(c.name)
94
111
 
95
- def process_service_configs(service_config):
96
112
 
113
+ def process_service_configs(service_config, *additional_components):
97
114
  processed_components = []
98
115
  target_classes = {
99
116
  RDMRecordServiceConfig,
100
117
  DraftsRecordServiceConfig,
101
118
  RecordsRecordServiceConfig,
102
- FileServiceConfig
119
+ FileServiceConfig,
103
120
  }
104
121
 
105
122
  for end_index, cls in enumerate(type(service_config).mro()):
@@ -110,19 +127,150 @@ def process_service_configs(service_config):
110
127
  # there are two service_config instances in the MRO (Method Resolution Order) output.
111
128
  start_index = 2 if hasattr(service_config, "build") else 1
112
129
 
113
- service_configs = type(service_config).mro()[start_index:end_index + 1]
130
+ service_configs = type(service_config).mro()[start_index : end_index + 1]
114
131
  for config in service_configs:
115
-
116
132
  if hasattr(config, "build"):
117
133
  config = config.build(current_app)
118
134
 
119
- if hasattr(config, 'components'):
135
+ if hasattr(config, "components"):
120
136
  component_property = config.components
121
137
  if isinstance(component_property, list):
122
138
  processed_components.extend(component_property)
123
139
  elif isinstance(component_property, tuple):
124
- processed_components.extend(list (component_property))
140
+ processed_components.extend(list(component_property))
125
141
  else:
126
142
  raise ValueError(f"{config} component's definition is not supported")
127
143
 
128
- return processed_components
144
+ processed_components.extend(additional_components)
145
+ processed_components = _sort_components(processed_components)
146
+ return processed_components
147
+
148
+
149
+ @dataclass
150
+ class ComponentPlacement:
151
+ """Component placement in the list of components.
152
+
153
+ This is a helper class used in the component ordering algorithm.
154
+ """
155
+
156
+ component: Type[ServiceComponent]
157
+ """Component to be ordered."""
158
+
159
+ depends_on: list[ComponentPlacement] = field(default_factory=list)
160
+ """List of components this one depends on.
161
+
162
+ The components must be classes of ServiceComponent or '*' to denote
163
+ that this component depends on all other components and should be placed last.
164
+ """
165
+
166
+ affects: list[ComponentPlacement] = field(default_factory=list)
167
+ """List of components that depend on this one.
168
+
169
+ This is a temporary list used for evaluation of '*' dependencies
170
+ but does not take part in the sorting algorithm."""
171
+
172
+ def __hash__(self) -> int:
173
+ return id(self.component)
174
+
175
+ def __eq__(self, other: ComponentPlacement) -> bool:
176
+ return self.component is other.component
177
+
178
+
179
+ def _sort_components(components):
180
+ """Sort components based on their dependencies while trying to
181
+ keep the initial order as far as possible."""
182
+
183
+ placements: list[ComponentPlacement] = _prepare_component_placement(components)
184
+ placements = _propagate_dependencies(placements)
185
+
186
+ ret = []
187
+ while placements:
188
+ without_dependencies = [p for p in placements if not p.depends_on]
189
+ if not without_dependencies:
190
+ raise ValueError("Circular dependency detected in components.")
191
+ for p in without_dependencies:
192
+ ret.append(p.component)
193
+ placements.remove(p)
194
+ for p2 in placements:
195
+ if p in p2.depends_on:
196
+ p2.depends_on.remove(p)
197
+ return ret
198
+
199
+
200
+ def _matching_placements(placements, dep_class_or_factory):
201
+ for pl in placements:
202
+ pl_component = pl.component
203
+ if not inspect.isclass(pl_component):
204
+ pl_component = type(pl_component(service=object()))
205
+ if issubclass(pl_component, dep_class_or_factory):
206
+ yield pl
207
+
208
+
209
+ def _prepare_component_placement(components) -> list[ComponentPlacement]:
210
+ """Convert components to ComponentPlacement instances and resolve dependencies."""
211
+ placements = []
212
+ for idx, c in enumerate(components):
213
+ placement = ComponentPlacement(component=c)
214
+ placements.append(placement)
215
+
216
+ # direct dependencies
217
+ for idx, placement in enumerate(placements):
218
+ placements_without_this = placements[:idx] + placements[idx + 1 :]
219
+ for dep in getattr(placement.component, "depends_on", []):
220
+ if dep == "*":
221
+ continue
222
+ for pl in _matching_placements(placements_without_this, dep):
223
+ placement.depends_on.append(pl)
224
+ pl.affects.append(placement)
225
+
226
+ for dep in getattr(placement.component, "affects", []):
227
+ if dep == "*":
228
+ continue
229
+ for pl in _matching_placements(placements_without_this, dep):
230
+ placement.affects.append(pl)
231
+ pl.depends_on.append(placement)
232
+
233
+ # star dependencies
234
+ for idx, placement in enumerate(placements):
235
+ placements_without_this = placements[:idx] + placements[idx + 1 :]
236
+ if "*" in getattr(placement.component, "depends_on", []):
237
+ for pl in placements_without_this:
238
+ # if this placement is not in placements that pl depends on
239
+ # (added via direct dependencies above), add it
240
+ if placement not in pl.depends_on:
241
+ placement.depends_on.append(pl)
242
+ pl.affects.append(placement)
243
+
244
+ if "*" in getattr(placement.component, "affects", []):
245
+ for pl in placements_without_this:
246
+ # if this placement is not in placements that pl affects
247
+ # (added via direct dependencies above), add it
248
+ if placement not in pl.affects:
249
+ placement.affects.append(pl)
250
+ pl.depends_on.append(placement)
251
+ return placements
252
+
253
+
254
+ def _propagate_dependencies(
255
+ placements: list[ComponentPlacement],
256
+ ) -> list[ComponentPlacement]:
257
+ # now propagate dependencies
258
+ dependency_propagated = True
259
+ while dependency_propagated:
260
+ dependency_propagated = False
261
+ for placement in placements:
262
+ for dep in placement.depends_on:
263
+ for dep_of_dep in dep.depends_on:
264
+ if dep_of_dep not in placement.depends_on:
265
+ placement.depends_on.append(dep_of_dep)
266
+ dep_of_dep.affects.append(placement)
267
+ dependency_propagated = True
268
+
269
+ for dep in placement.affects:
270
+ for dep_of_dep in dep.affects:
271
+ if dep_of_dep not in placement.affects:
272
+ placement.affects.append(dep_of_dep)
273
+ dep_of_dep.depends_on.append(placement)
274
+ dependency_propagated = True
275
+
276
+ return placements
@@ -2,6 +2,7 @@ import functools
2
2
  import re
3
3
  from datetime import datetime
4
4
  from idutils import normalize_pid
5
+ from isbnlib import canonical, mask
5
6
 
6
7
  from marshmallow.exceptions import ValidationError
7
8
  from marshmallow_utils.fields.edtfdatestring import EDTFValidator
@@ -11,6 +12,11 @@ from invenio_i18n import gettext as _
11
12
 
12
13
  def validate_identifier(value):
13
14
  try:
15
+ if value["scheme"].lower() == "isbn":
16
+ canonical_isbn = canonical(value["identifier"])
17
+ value["identifier"] = mask(canonical_isbn)
18
+ return value["identifier"]
19
+
14
20
  original_identifier = (value["identifier"] or '').strip()
15
21
  normalized_identifier = normalize_pid(
16
22
  value["identifier"], value["scheme"].lower()
@@ -57,4 +63,4 @@ class CachedMultilayerEDTFValidator(EDTFValidator):
57
63
  datetime.strptime(value, "%Y-%m-%d")
58
64
  return value
59
65
  except:
60
- return super().__call__(value)
66
+ return super().__call__(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: oarepo-runtime
3
- Version: 1.5.88
3
+ Version: 1.5.90
4
4
  Summary: A set of runtime extensions of Invenio repository
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -73,7 +73,7 @@ oarepo_runtime/resources/file_resource.py,sha256=Ta3bFce7l0xwqkkOMOEu9mxbB8BbKj5
73
73
  oarepo_runtime/resources/json_serializer.py,sha256=82_-xQEtxKaPakv8R1oBAFbGnxskF_Ve4tcfcy4PetI,963
74
74
  oarepo_runtime/resources/localized_ui_json_serializer.py,sha256=3V9cJaG_e1PMXKVX_wKfBp1LmbeForwHyBNYdyha4uQ,1878
75
75
  oarepo_runtime/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
- oarepo_runtime/services/components.py,sha256=rCdK3amTM2PAihiRFnaoN_Xkm35W-PreAPjm-gAJFww,4902
76
+ oarepo_runtime/services/components.py,sha256=ZLGaOiaGJfKC_LNvYq4F061CP-sMG29FK4lUypY__Ws,10191
77
77
  oarepo_runtime/services/generators.py,sha256=j87HitHA_w2awsz0C5IAAJ0qjg9JMtvdO3dvh6FQyfg,250
78
78
  oarepo_runtime/services/results.py,sha256=Ap2mUJHl3V4BSduTrBWPuco0inQVq0QsuCbVhez48uY,5705
79
79
  oarepo_runtime/services/search.py,sha256=9xGTN5Yg6eTdptQ9qjO_umbacf9ooMuHYGXWYfla4-M,6227
@@ -121,7 +121,7 @@ oarepo_runtime/services/schema/oneofschema.py,sha256=GnWH4Or_G5M0NgSmCoqMI6PBrJg
121
121
  oarepo_runtime/services/schema/polymorphic.py,sha256=bAbUoTIeDBiJPYPhpLEKKZekEdkHlpqkmNxk1hN3PDw,564
122
122
  oarepo_runtime/services/schema/rdm.py,sha256=4gi44LIMWS9kWcZfEoHaJSq585qfZfMuUYM5IvL1nQo,531
123
123
  oarepo_runtime/services/schema/ui.py,sha256=xQgW-zLyZoHldGw47uVtXQj-5LexVNKTholyq4MiBZo,3777
124
- oarepo_runtime/services/schema/validation.py,sha256=g2lpFwgQygWdLgq5bRvLAjVqXVOJpfVJ7umDp5xEGRU,1849
124
+ oarepo_runtime/services/schema/validation.py,sha256=VFOKSxQLHwFb7bW8BJAFXWe_iTAZFOfqOnb2Ko_Yxxc,2085
125
125
  oarepo_runtime/translations/default_translations.py,sha256=060GBlA1ghWxfeumo6NqxCCZDb-6OezOuF6pr-_GEOQ,104
126
126
  oarepo_runtime/translations/messages.pot,sha256=jyC7mRH5P9uwVVqz9ycvn-T8gcohr62x__O0sC6g2w4,1846
127
127
  oarepo_runtime/translations/cs/LC_MESSAGES/messages.mo,sha256=cSTRnVoi8DxfrXD-ImHYUmxdnNQMnxIn2yFRTObwRzI,801
@@ -135,9 +135,9 @@ tests/marshmallow_to_json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
135
135
  tests/marshmallow_to_json/test_datacite_ui_schema.py,sha256=82iLj8nW45lZOUewpWbLX3mpSkpa9lxo-vK-Qtv_1bU,48552
136
136
  tests/marshmallow_to_json/test_simple_schema.py,sha256=izZN9p0v6kovtSZ6AdxBYmK_c6ZOti2_z_wPT_zXIr0,1500
137
137
  tests/pkg_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
138
- oarepo_runtime-1.5.88.dist-info/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
139
- oarepo_runtime-1.5.88.dist-info/METADATA,sha256=FtYVZcPcE9i3_AG_61zkkHPUNueJDHR6-7da2B8nd_c,4720
140
- oarepo_runtime-1.5.88.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
141
- oarepo_runtime-1.5.88.dist-info/entry_points.txt,sha256=k7O5LZUOGsVeSpB7ulU0txBUNp1CVQG7Q7TJIVTPbzU,491
142
- oarepo_runtime-1.5.88.dist-info/top_level.txt,sha256=bHhlkT1_RQC4IkfTQCqA3iN4KCB6cSFQlsXpQMSP-bE,21
143
- oarepo_runtime-1.5.88.dist-info/RECORD,,
138
+ oarepo_runtime-1.5.90.dist-info/LICENSE,sha256=h2uWz0OaB3EN-J1ImdGJZzc7yvfQjvHVYdUhQ-H7ypY,1064
139
+ oarepo_runtime-1.5.90.dist-info/METADATA,sha256=MjXQ_JQCvfx_q854xrRa73nGe0K-tYLLp-02yGZRwPI,4720
140
+ oarepo_runtime-1.5.90.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
141
+ oarepo_runtime-1.5.90.dist-info/entry_points.txt,sha256=k7O5LZUOGsVeSpB7ulU0txBUNp1CVQG7Q7TJIVTPbzU,491
142
+ oarepo_runtime-1.5.90.dist-info/top_level.txt,sha256=bHhlkT1_RQC4IkfTQCqA3iN4KCB6cSFQlsXpQMSP-bE,21
143
+ oarepo_runtime-1.5.90.dist-info/RECORD,,