folio-migration-tools 1.9.10__py3-none-any.whl → 1.10.0__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.
- folio_migration_tools/__init__.py +3 -4
- folio_migration_tools/__main__.py +53 -31
- folio_migration_tools/circulation_helper.py +118 -108
- folio_migration_tools/custom_dict.py +2 -2
- folio_migration_tools/custom_exceptions.py +4 -5
- folio_migration_tools/folder_structure.py +17 -7
- folio_migration_tools/helper.py +8 -7
- folio_migration_tools/holdings_helper.py +4 -3
- folio_migration_tools/i18n_cache.py +79 -0
- folio_migration_tools/library_configuration.py +77 -37
- folio_migration_tools/mapper_base.py +45 -31
- folio_migration_tools/mapping_file_transformation/courses_mapper.py +1 -1
- folio_migration_tools/mapping_file_transformation/holdings_mapper.py +7 -3
- folio_migration_tools/mapping_file_transformation/item_mapper.py +13 -26
- folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +1 -2
- folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +13 -11
- folio_migration_tools/mapping_file_transformation/order_mapper.py +6 -5
- folio_migration_tools/mapping_file_transformation/organization_mapper.py +3 -3
- folio_migration_tools/mapping_file_transformation/user_mapper.py +47 -28
- folio_migration_tools/marc_rules_transformation/conditions.py +82 -97
- folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +13 -5
- folio_migration_tools/marc_rules_transformation/hrid_handler.py +3 -2
- folio_migration_tools/marc_rules_transformation/marc_file_processor.py +26 -24
- folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +56 -51
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +28 -17
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +68 -37
- folio_migration_tools/migration_report.py +18 -7
- folio_migration_tools/migration_tasks/batch_poster.py +285 -354
- folio_migration_tools/migration_tasks/bibs_transformer.py +14 -9
- folio_migration_tools/migration_tasks/courses_migrator.py +2 -3
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py +23 -24
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py +14 -24
- folio_migration_tools/migration_tasks/items_transformer.py +23 -34
- folio_migration_tools/migration_tasks/loans_migrator.py +67 -144
- folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +3 -3
- folio_migration_tools/migration_tasks/migration_task_base.py +47 -60
- folio_migration_tools/migration_tasks/orders_transformer.py +25 -42
- folio_migration_tools/migration_tasks/organization_transformer.py +9 -18
- folio_migration_tools/migration_tasks/requests_migrator.py +21 -24
- folio_migration_tools/migration_tasks/reserves_migrator.py +6 -5
- folio_migration_tools/migration_tasks/user_transformer.py +25 -20
- folio_migration_tools/task_configuration.py +6 -7
- folio_migration_tools/transaction_migration/legacy_loan.py +15 -27
- folio_migration_tools/transaction_migration/legacy_request.py +1 -1
- folio_migration_tools/translations/en.json +0 -7
- {folio_migration_tools-1.9.10.dist-info → folio_migration_tools-1.10.0.dist-info}/METADATA +19 -28
- folio_migration_tools-1.10.0.dist-info/RECORD +63 -0
- folio_migration_tools-1.10.0.dist-info/WHEEL +4 -0
- folio_migration_tools-1.10.0.dist-info/entry_points.txt +3 -0
- folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +0 -241
- folio_migration_tools/migration_tasks/authority_transformer.py +0 -119
- folio_migration_tools/test_infrastructure/__init__.py +0 -0
- folio_migration_tools/test_infrastructure/mocked_classes.py +0 -406
- folio_migration_tools-1.9.10.dist-info/RECORD +0 -67
- folio_migration_tools-1.9.10.dist-info/WHEEL +0 -4
- folio_migration_tools-1.9.10.dist-info/entry_points.txt +0 -3
- folio_migration_tools-1.9.10.dist-info/licenses/LICENSE +0 -21
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
import sys
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
|
-
from typing import Dict, List, Set
|
|
5
|
+
from typing import Dict, List, Set
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
8
|
import i18n
|
|
@@ -117,7 +117,9 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
117
117
|
"LocationMapping",
|
|
118
118
|
)
|
|
119
119
|
|
|
120
|
-
def perform_additional_mappings(
|
|
120
|
+
def perform_additional_mappings(
|
|
121
|
+
self, legacy_ids: List[str] | str, folio_rec: Dict, file_def: FileDefinition
|
|
122
|
+
):
|
|
121
123
|
self.handle_suppression(folio_rec, file_def)
|
|
122
124
|
self.map_statistical_codes(folio_rec, file_def)
|
|
123
125
|
self.map_statistical_code_ids(legacy_ids, folio_rec)
|
|
@@ -126,24 +128,17 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
126
128
|
folio_record["discoverySuppress"] = file_def.discovery_suppressed
|
|
127
129
|
self.migration_report.add(
|
|
128
130
|
"Suppression",
|
|
129
|
-
i18n.t("Suppressed from discovery")
|
|
130
|
-
+ f" = {folio_record['discoverySuppress']}",
|
|
131
|
+
i18n.t("Suppressed from discovery") + f" = {folio_record['discoverySuppress']}",
|
|
131
132
|
)
|
|
132
133
|
|
|
133
134
|
def setup_status_mapping(self, item_statuses_map):
|
|
134
|
-
statuses = self.item_schema["properties"]["status"]["properties"]["name"][
|
|
135
|
-
"enum"
|
|
136
|
-
]
|
|
135
|
+
statuses = self.item_schema["properties"]["status"]["properties"]["name"]["enum"]
|
|
137
136
|
for mapping in item_statuses_map:
|
|
138
137
|
if "folio_name" not in mapping:
|
|
139
|
-
logging.critical(
|
|
140
|
-
"folio_name is not a column in the status mapping file"
|
|
141
|
-
)
|
|
138
|
+
logging.critical("folio_name is not a column in the status mapping file")
|
|
142
139
|
sys.exit(1)
|
|
143
140
|
elif "legacy_code" not in mapping:
|
|
144
|
-
logging.critical(
|
|
145
|
-
"legacy_code is not a column in the status mapping file"
|
|
146
|
-
)
|
|
141
|
+
logging.critical("legacy_code is not a column in the status mapping file")
|
|
147
142
|
sys.exit(1)
|
|
148
143
|
elif mapping["folio_name"] not in statuses:
|
|
149
144
|
logging.critical(
|
|
@@ -158,9 +153,7 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
158
153
|
)
|
|
159
154
|
sys.exit(1)
|
|
160
155
|
elif not all(mapping.values()):
|
|
161
|
-
logging.critical(
|
|
162
|
-
"empty value in mapping %s. Check mapping file", mapping.values()
|
|
163
|
-
)
|
|
156
|
+
logging.critical("empty value in mapping %s. Check mapping file", mapping.values())
|
|
164
157
|
sys.exit(1)
|
|
165
158
|
else:
|
|
166
159
|
self.status_mapping = {
|
|
@@ -168,7 +161,7 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
168
161
|
}
|
|
169
162
|
logging.info(json.dumps(statuses, indent=True))
|
|
170
163
|
|
|
171
|
-
def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_value):
|
|
164
|
+
def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_value): # noqa: C901
|
|
172
165
|
if folio_prop_name == "permanentLocationId":
|
|
173
166
|
return self.get_mapped_ref_data_value(
|
|
174
167
|
self.location_mapping,
|
|
@@ -213,9 +206,7 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
213
206
|
index_or_id,
|
|
214
207
|
True,
|
|
215
208
|
)
|
|
216
|
-
self.migration_report.add(
|
|
217
|
-
"TemporaryLoanTypeMapping", f"{folio_prop_name} -> {ltid}"
|
|
218
|
-
)
|
|
209
|
+
self.migration_report.add("TemporaryLoanTypeMapping", f"{folio_prop_name} -> {ltid}")
|
|
219
210
|
return ltid
|
|
220
211
|
elif folio_prop_name == "permanentLoanTypeId":
|
|
221
212
|
return self.get_mapped_ref_data_value(
|
|
@@ -232,9 +223,7 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
232
223
|
normalized_barcode = barcode.strip().lower()
|
|
233
224
|
if normalized_barcode and normalized_barcode in self.unique_barcodes:
|
|
234
225
|
Helper.log_data_issue(index_or_id, "Duplicate barcode", mapped_value)
|
|
235
|
-
self.migration_report.add_general_statistics(
|
|
236
|
-
i18n.t("Duplicate barcodes")
|
|
237
|
-
)
|
|
226
|
+
self.migration_report.add_general_statistics(i18n.t("Duplicate barcodes"))
|
|
238
227
|
return f"{barcode}-{uuid4()}"
|
|
239
228
|
else:
|
|
240
229
|
if normalized_barcode:
|
|
@@ -259,9 +248,7 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
259
248
|
self.migration_report.add("UnmappedProperties", f"{folio_prop_name}")
|
|
260
249
|
return ""
|
|
261
250
|
|
|
262
|
-
def get_item_level_call_number_type_id(
|
|
263
|
-
self, legacy_item, folio_prop_name: str, index_or_id
|
|
264
|
-
):
|
|
251
|
+
def get_item_level_call_number_type_id(self, legacy_item, folio_prop_name: str, index_or_id):
|
|
265
252
|
if self.call_number_mapping:
|
|
266
253
|
return self.get_mapped_ref_data_value(
|
|
267
254
|
self.call_number_mapping, legacy_item, index_or_id, folio_prop_name
|
|
@@ -172,8 +172,7 @@ class ManualFeeFinesMapper(MappingFileMapperBase):
|
|
|
172
172
|
except TypeError as te:
|
|
173
173
|
raise TransformationProcessError(
|
|
174
174
|
"",
|
|
175
|
-
"Failed to fetch Tenant Locale Settings. "
|
|
176
|
-
"Is your library configuration correct?",
|
|
175
|
+
"Failed to fetch Tenant Locale Settings. Is your library configuration correct?",
|
|
177
176
|
) from te
|
|
178
177
|
except KeyError as ke:
|
|
179
178
|
raise TransformationProcessError(
|
|
@@ -133,7 +133,7 @@ class MappingFileMapperBase(MapperBase):
|
|
|
133
133
|
raise TransformationProcessError(
|
|
134
134
|
"",
|
|
135
135
|
f"property legacyIdentifier not setup in map: "
|
|
136
|
-
f"{field_map.get('legacyIdentifier', '')
|
|
136
|
+
f"{field_map.get('legacyIdentifier', '')({exception})}",
|
|
137
137
|
) from exception
|
|
138
138
|
del field_map["legacyIdentifier"]
|
|
139
139
|
return field_map
|
|
@@ -213,12 +213,10 @@ class MappingFileMapperBase(MapperBase):
|
|
|
213
213
|
}
|
|
214
214
|
)
|
|
215
215
|
if object_type == FOLIONamespaces.holdings and hasattr(self, "holdings_sources"):
|
|
216
|
-
folio_object[
|
|
216
|
+
folio_object["sourceId"] = self.holdings_sources.get("FOLIO")
|
|
217
217
|
elif object_type == FOLIONamespaces.holdings and not hasattr(self, "holdings_sources"):
|
|
218
218
|
raise TransformationProcessError(
|
|
219
|
-
index_or_id,
|
|
220
|
-
"Holdings source not set in the mapper",
|
|
221
|
-
None
|
|
219
|
+
index_or_id, "Holdings source not set in the mapper", None
|
|
222
220
|
)
|
|
223
221
|
return folio_object, legacy_id
|
|
224
222
|
|
|
@@ -400,7 +398,7 @@ class MappingFileMapperBase(MapperBase):
|
|
|
400
398
|
value = replaced_val
|
|
401
399
|
if value and mapping_file_entry.get("rules", {}).get("regexGetFirstMatchOrEmpty", ""):
|
|
402
400
|
my_pattern = (
|
|
403
|
-
f
|
|
401
|
+
f"{mapping_file_entry.get('rules', {}).get('regexGetFirstMatchOrEmpty')}|$"
|
|
404
402
|
)
|
|
405
403
|
value = re.findall(my_pattern, value)[0]
|
|
406
404
|
if not value and mapping_file_entry.get("fallback_legacy_field", ""):
|
|
@@ -498,7 +496,7 @@ class MappingFileMapperBase(MapperBase):
|
|
|
498
496
|
set_deep(folio_object, schema_property_name, temp_object)
|
|
499
497
|
# folio_object[schema_property_name] = temp_object
|
|
500
498
|
|
|
501
|
-
def map_objects_array_props(
|
|
499
|
+
def map_objects_array_props( # noqa: C901
|
|
502
500
|
self,
|
|
503
501
|
legacy_object,
|
|
504
502
|
prop_name: str,
|
|
@@ -553,7 +551,9 @@ class MappingFileMapperBase(MapperBase):
|
|
|
553
551
|
)
|
|
554
552
|
multi_field_props.append(sub_prop_name)
|
|
555
553
|
else:
|
|
556
|
-
self.validate_enums(
|
|
554
|
+
self.validate_enums(
|
|
555
|
+
res, sub_prop, sub_prop_name, index_or_id, required
|
|
556
|
+
)
|
|
557
557
|
|
|
558
558
|
if res or isinstance(res, bool):
|
|
559
559
|
temp_object[sub_prop_name] = res
|
|
@@ -619,8 +619,8 @@ class MappingFileMapperBase(MapperBase):
|
|
|
619
619
|
@staticmethod
|
|
620
620
|
def split_obj_by_delim(delimiter: str, folio_obj: dict, delimited_props: List[str]):
|
|
621
621
|
non_split_props = [(k, v) for k, v in folio_obj.items() if k not in delimited_props]
|
|
622
|
-
delimited_props =
|
|
623
|
-
zipped = list(zip(*delimited_props))
|
|
622
|
+
delimited_props = ([x, *folio_obj[x].split(delimiter)] for x in delimited_props)
|
|
623
|
+
zipped = list(zip(*delimited_props, strict=False))
|
|
624
624
|
res = []
|
|
625
625
|
for (prop_name_idx, prop_name), (value_idx, ra) in itertools.product(
|
|
626
626
|
enumerate(zipped[0]), enumerate(zipped[1:])
|
|
@@ -973,4 +973,6 @@ def in_deep(dictionary, keys):
|
|
|
973
973
|
|
|
974
974
|
|
|
975
975
|
def is_set_or_bool_or_numeric(any_value):
|
|
976
|
-
return (isinstance(any_value, str) and (any_value.strip() not in empty_vals)) or isinstance(
|
|
976
|
+
return (isinstance(any_value, str) and (any_value.strip() not in empty_vals)) or isinstance(
|
|
977
|
+
any_value, (int, float, complex)
|
|
978
|
+
)
|
|
@@ -25,7 +25,6 @@ from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class CompositeOrderMapper(MappingFileMapperBase):
|
|
28
|
-
|
|
29
28
|
def __init__(
|
|
30
29
|
self,
|
|
31
30
|
folio_client: FolioClient,
|
|
@@ -291,7 +290,9 @@ class CompositeOrderMapper(MappingFileMapperBase):
|
|
|
291
290
|
):
|
|
292
291
|
object_schema["properties"] = CompositeOrderMapper.inject_schema_by_ref(
|
|
293
292
|
submodule_path, github_headers, object_schema
|
|
294
|
-
).get(
|
|
293
|
+
).get(
|
|
294
|
+
"properties", {}
|
|
295
|
+
) # TODO: Investigate new CustomFields schema and figure out how to actually handle it # noqa: E501
|
|
295
296
|
|
|
296
297
|
for property_name_level1, property_level1 in object_schema.get(
|
|
297
298
|
"properties", {}
|
|
@@ -400,9 +401,9 @@ class CompositeOrderMapper(MappingFileMapperBase):
|
|
|
400
401
|
return composite_order
|
|
401
402
|
|
|
402
403
|
def validate_po_number(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
404
|
+
self,
|
|
405
|
+
index_or_id: str,
|
|
406
|
+
po_number: str,
|
|
406
407
|
):
|
|
407
408
|
if not self.is_valid_po_number(po_number):
|
|
408
409
|
self.migration_report.add(
|
|
@@ -330,9 +330,9 @@ class OrganizationMapper(MappingFileMapperBase):
|
|
|
330
330
|
["username", "password", "interfaceId"],
|
|
331
331
|
)
|
|
332
332
|
|
|
333
|
-
interface_schema["properties"][
|
|
334
|
-
|
|
335
|
-
|
|
333
|
+
interface_schema["properties"]["interfaceCredential"] = (
|
|
334
|
+
interface_credential_schema
|
|
335
|
+
)
|
|
336
336
|
|
|
337
337
|
property_level1["items"] = interface_schema
|
|
338
338
|
|
|
@@ -47,7 +47,7 @@ class UserMapper(MappingFileMapperBase):
|
|
|
47
47
|
None,
|
|
48
48
|
FOLIONamespaces.users,
|
|
49
49
|
library_config,
|
|
50
|
-
task_config
|
|
50
|
+
task_config,
|
|
51
51
|
)
|
|
52
52
|
self.task_config = self.task_configuration
|
|
53
53
|
self.notes_mapper: NotesMapper = NotesMapper(
|
|
@@ -115,6 +115,10 @@ class UserMapper(MappingFileMapperBase):
|
|
|
115
115
|
|
|
116
116
|
if self.task_config.remove_request_preferences:
|
|
117
117
|
del clean_folio_object["requestPreference"]
|
|
118
|
+
|
|
119
|
+
if self.task_config.remove_username:
|
|
120
|
+
del clean_folio_object["username"]
|
|
121
|
+
|
|
118
122
|
self.report_folio_mapping_no_schema(clean_folio_object)
|
|
119
123
|
self.report_legacy_mapping_no_schema(legacy_user)
|
|
120
124
|
|
|
@@ -157,18 +161,25 @@ class UserMapper(MappingFileMapperBase):
|
|
|
157
161
|
"No Departments mapping set up. Set up a departments mapping file "
|
|
158
162
|
" or remove the mapping of the Departments field",
|
|
159
163
|
)
|
|
160
|
-
if len(
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
if len(
|
|
165
|
+
self.departments_mapping.mapped_legacy_keys
|
|
166
|
+
) == 1 and self.library_configuration.multi_field_delimiter in legacy_user.get(
|
|
167
|
+
self.departments_mapping.mapped_legacy_keys[0], ""
|
|
168
|
+
):
|
|
169
|
+
split_departments = legacy_user.get(
|
|
170
|
+
self.departments_mapping.mapped_legacy_keys[0], ""
|
|
171
|
+
).split(self.library_configuration.multi_field_delimiter)
|
|
172
|
+
return self.library_configuration.multi_field_delimiter.join(
|
|
173
|
+
[
|
|
174
|
+
self.get_mapped_name(
|
|
175
|
+
self.departments_mapping,
|
|
176
|
+
{self.departments_mapping.mapped_legacy_keys[0]: dept},
|
|
177
|
+
index_or_id,
|
|
178
|
+
True,
|
|
179
|
+
)
|
|
180
|
+
for dept in split_departments
|
|
181
|
+
]
|
|
163
182
|
)
|
|
164
|
-
return self.library_configuration.multi_field_delimiter.join([
|
|
165
|
-
self.get_mapped_name(
|
|
166
|
-
self.departments_mapping,
|
|
167
|
-
{self.departments_mapping.mapped_legacy_keys[0]: dept},
|
|
168
|
-
index_or_id,
|
|
169
|
-
True,
|
|
170
|
-
) for dept in split_departments
|
|
171
|
-
])
|
|
172
183
|
else:
|
|
173
184
|
return self.get_mapped_name(
|
|
174
185
|
self.departments_mapping,
|
|
@@ -198,21 +209,29 @@ class UserMapper(MappingFileMapperBase):
|
|
|
198
209
|
return ""
|
|
199
210
|
|
|
200
211
|
def setup_groups_mapping(self, groups_map):
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
return (
|
|
213
|
+
RefDataMapping(
|
|
214
|
+
self.folio_client,
|
|
215
|
+
"/groups",
|
|
216
|
+
"usergroups",
|
|
217
|
+
groups_map,
|
|
218
|
+
"group",
|
|
219
|
+
"UserGroupMapping",
|
|
220
|
+
)
|
|
221
|
+
if groups_map
|
|
222
|
+
else None
|
|
223
|
+
)
|
|
209
224
|
|
|
210
225
|
def setup_departments_mapping(self, departments_mapping):
|
|
211
|
-
return
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
226
|
+
return (
|
|
227
|
+
RefDataMapping(
|
|
228
|
+
self.folio_client,
|
|
229
|
+
"/departments",
|
|
230
|
+
"departments",
|
|
231
|
+
departments_mapping,
|
|
232
|
+
"name",
|
|
233
|
+
"DepartmentsMapping",
|
|
234
|
+
)
|
|
235
|
+
if departments_mapping
|
|
236
|
+
else None
|
|
237
|
+
)
|