folio-migration-tools 1.9.0a2__py3-none-any.whl → 1.9.0a4__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 -0
- folio_migration_tools/__main__.py +16 -6
- folio_migration_tools/folder_structure.py +3 -0
- folio_migration_tools/library_configuration.py +8 -7
- folio_migration_tools/mapper_base.py +26 -17
- folio_migration_tools/mapping_file_transformation/holdings_mapper.py +23 -11
- folio_migration_tools/mapping_file_transformation/item_mapper.py +13 -11
- folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +9 -10
- folio_migration_tools/mapping_file_transformation/order_mapper.py +2 -2
- folio_migration_tools/mapping_file_transformation/organization_mapper.py +1 -1
- folio_migration_tools/mapping_file_transformation/user_mapper.py +6 -4
- folio_migration_tools/marc_rules_transformation/conditions.py +23 -7
- folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +21 -11
- folio_migration_tools/marc_rules_transformation/marc_file_processor.py +36 -9
- folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +15 -11
- folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +7 -5
- folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +98 -45
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +53 -27
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +13 -11
- folio_migration_tools/migration_tasks/batch_poster.py +78 -38
- folio_migration_tools/migration_tasks/bibs_transformer.py +21 -8
- folio_migration_tools/migration_tasks/courses_migrator.py +11 -6
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py +22 -16
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py +9 -7
- folio_migration_tools/migration_tasks/items_transformer.py +13 -10
- folio_migration_tools/migration_tasks/loans_migrator.py +10 -9
- folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +13 -9
- folio_migration_tools/migration_tasks/migration_task_base.py +18 -18
- folio_migration_tools/migration_tasks/orders_transformer.py +14 -10
- folio_migration_tools/migration_tasks/organization_transformer.py +12 -8
- folio_migration_tools/migration_tasks/requests_migrator.py +8 -5
- folio_migration_tools/migration_tasks/reserves_migrator.py +7 -4
- folio_migration_tools/migration_tasks/user_transformer.py +46 -17
- folio_migration_tools/task_configuration.py +3 -3
- folio_migration_tools/translations/en.json +2 -1
- {folio_migration_tools-1.9.0a2.dist-info → folio_migration_tools-1.9.0a4.dist-info}/METADATA +6 -5
- {folio_migration_tools-1.9.0a2.dist-info → folio_migration_tools-1.9.0a4.dist-info}/RECORD +39 -39
- {folio_migration_tools-1.9.0a2.dist-info → folio_migration_tools-1.9.0a4.dist-info}/WHEEL +1 -1
- {folio_migration_tools-1.9.0a2.dist-info → folio_migration_tools-1.9.0a4.dist-info}/LICENSE +0 -0
|
@@ -8,6 +8,7 @@ import httpx
|
|
|
8
8
|
import humps
|
|
9
9
|
import i18n
|
|
10
10
|
from argparse_prompt import PromptParser
|
|
11
|
+
from folioclient import FolioClient
|
|
11
12
|
from pydantic import ValidationError
|
|
12
13
|
|
|
13
14
|
from folio_migration_tools.config_file_load import merge_load
|
|
@@ -38,14 +39,16 @@ def parse_args(args):
|
|
|
38
39
|
)
|
|
39
40
|
parser.add_argument(
|
|
40
41
|
"--okapi_password",
|
|
41
|
-
help="
|
|
42
|
+
help="password for the tenant in the configuration file",
|
|
42
43
|
prompt="FOLIO_MIGRATION_TOOLS_OKAPI_PASSWORD" not in environ,
|
|
43
44
|
default=environ.get("FOLIO_MIGRATION_TOOLS_OKAPI_PASSWORD"),
|
|
44
45
|
secure=True,
|
|
45
46
|
)
|
|
46
47
|
parser.add_argument(
|
|
47
48
|
"--base_folder_path",
|
|
48
|
-
help=(
|
|
49
|
+
help=(
|
|
50
|
+
"path to the base folder for this library. Built on migration_repo_template"
|
|
51
|
+
),
|
|
49
52
|
prompt="FOLIO_MIGRATION_TOOLS_BASE_FOLDER_PATH" not in environ,
|
|
50
53
|
default=environ.get("FOLIO_MIGRATION_TOOLS_BASE_FOLDER_PATH"),
|
|
51
54
|
)
|
|
@@ -107,10 +110,17 @@ def main():
|
|
|
107
110
|
)
|
|
108
111
|
sys.exit("Task Type Not Found")
|
|
109
112
|
try:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
logging.getLogger("httpx").setLevel(logging.WARNING) # Exclude info messages from httpx
|
|
114
|
+
with FolioClient(
|
|
115
|
+
library_config.okapi_url,
|
|
116
|
+
library_config.tenant_id,
|
|
117
|
+
library_config.okapi_username,
|
|
118
|
+
library_config.okapi_password,
|
|
119
|
+
) as folio_client:
|
|
120
|
+
task_config = task_class.TaskConfiguration(**migration_task_config)
|
|
121
|
+
task_obj = task_class(task_config, library_config, folio_client)
|
|
122
|
+
task_obj.do_work()
|
|
123
|
+
task_obj.wrap_up()
|
|
114
124
|
except TransformationProcessError as tpe:
|
|
115
125
|
logging.critical(tpe.message)
|
|
116
126
|
print(f"\n{tpe.message}: {tpe.data_value}")
|
|
@@ -101,6 +101,9 @@ class FolderStructure:
|
|
|
101
101
|
self.srs_records_path = (
|
|
102
102
|
self.results_folder / f"folio_srs_{object_type_string}{self.file_template}.json"
|
|
103
103
|
)
|
|
104
|
+
self.data_import_marc_path = (
|
|
105
|
+
self.results_folder / f"folio_marc_{object_type_string}{self.file_template}.mrc"
|
|
106
|
+
)
|
|
104
107
|
self.organizations_id_map_path = (
|
|
105
108
|
self.results_folder / f"{str(FOLIONamespaces.organizations.name).lower()}_id_map.json"
|
|
106
109
|
)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import Annotated
|
|
3
|
-
from typing import Optional
|
|
2
|
+
from typing import Annotated, Optional
|
|
4
3
|
|
|
5
|
-
from pydantic import BaseModel
|
|
6
|
-
from pydantic import Field
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
7
5
|
from pydantic.types import DirectoryPath
|
|
8
6
|
|
|
9
7
|
|
|
@@ -102,6 +100,9 @@ class LibraryConfiguration(BaseModel):
|
|
|
102
100
|
failed_percentage_threshold: Annotated[
|
|
103
101
|
int, Field(description=("Percentage of failed records until the process shuts down"))
|
|
104
102
|
] = 20
|
|
103
|
+
generic_exception_threshold: Annotated[
|
|
104
|
+
int, Field(description=("Number of generic exceptions until the process shuts down"))
|
|
105
|
+
] = 50
|
|
105
106
|
library_name: str
|
|
106
107
|
log_level_debug: bool
|
|
107
108
|
folio_release: FolioRelease = Field(
|
|
@@ -112,6 +113,6 @@ class LibraryConfiguration(BaseModel):
|
|
|
112
113
|
)
|
|
113
114
|
)
|
|
114
115
|
iteration_identifier: str
|
|
115
|
-
add_time_stamp_to_file_names: Annotated[
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
add_time_stamp_to_file_names: Annotated[
|
|
117
|
+
bool, Field(title="Add time stamp to file names")
|
|
118
|
+
] = False
|
|
@@ -4,18 +4,20 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
import sys
|
|
6
6
|
import uuid
|
|
7
|
-
import
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
from datetime import timezone
|
|
7
|
+
from datetime import datetime, timezone
|
|
10
8
|
from pathlib import Path
|
|
9
|
+
from typing import List
|
|
11
10
|
|
|
11
|
+
import i18n
|
|
12
12
|
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
13
13
|
from folio_uuid.folio_uuid import FolioUUID
|
|
14
14
|
from folioclient import FolioClient
|
|
15
15
|
|
|
16
|
-
from folio_migration_tools.custom_exceptions import
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
from folio_migration_tools.custom_exceptions import (
|
|
17
|
+
TransformationFieldMappingError,
|
|
18
|
+
TransformationProcessError,
|
|
19
|
+
TransformationRecordFailedError,
|
|
20
|
+
)
|
|
19
21
|
from folio_migration_tools.extradata_writer import ExtradataWriter
|
|
20
22
|
from folio_migration_tools.library_configuration import LibraryConfiguration
|
|
21
23
|
from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
|
|
@@ -44,7 +46,7 @@ class MapperBase:
|
|
|
44
46
|
self.mapped_folio_fields: dict = {}
|
|
45
47
|
self.migration_report: MigrationReport = MigrationReport()
|
|
46
48
|
self.num_criticalerrors = 0
|
|
47
|
-
self.
|
|
49
|
+
self.num_exceptions = 0
|
|
48
50
|
self.mapped_legacy_fields: dict = {}
|
|
49
51
|
self.schema_properties = None
|
|
50
52
|
|
|
@@ -269,18 +271,18 @@ class MapperBase:
|
|
|
269
271
|
return (legacy_id, folio_record["id"], folio_record["hrid"])
|
|
270
272
|
return (legacy_id, folio_record["id"])
|
|
271
273
|
|
|
272
|
-
def handle_generic_exception(self, idx,
|
|
273
|
-
self.
|
|
274
|
+
def handle_generic_exception(self, idx, exception: Exception):
|
|
275
|
+
self.num_exceptions += 1
|
|
274
276
|
print("\n=======ERROR===========")
|
|
275
277
|
print(
|
|
276
|
-
f"Row {idx:,} failed with the following unhandled Exception: {
|
|
277
|
-
f"of type {type(
|
|
278
|
+
f"Row {idx:,} failed with the following unhandled Exception: {exception} "
|
|
279
|
+
f"of type {type(exception).__name__}"
|
|
278
280
|
)
|
|
279
|
-
logging.error(
|
|
280
|
-
if self.
|
|
281
|
+
logging.error(exception, exc_info=True)
|
|
282
|
+
if self.num_exceptions > self.library_configuration.generic_exception_threshold:
|
|
281
283
|
logging.fatal(
|
|
282
284
|
"Stopping. More than %s unhandled exceptions. Code needs fixing",
|
|
283
|
-
self.
|
|
285
|
+
self.num_exceptions,
|
|
284
286
|
)
|
|
285
287
|
sys.exit(1)
|
|
286
288
|
|
|
@@ -345,7 +347,8 @@ class MapperBase:
|
|
|
345
347
|
"One or many required properties empty",
|
|
346
348
|
f"{json.dumps(missing)}",
|
|
347
349
|
)
|
|
348
|
-
|
|
350
|
+
if object_type not in [FOLIONamespaces.users]:
|
|
351
|
+
cleaned_folio_object.pop("type", None)
|
|
349
352
|
return cleaned_folio_object
|
|
350
353
|
|
|
351
354
|
@staticmethod
|
|
@@ -428,8 +431,14 @@ class MapperBase:
|
|
|
428
431
|
|
|
429
432
|
if call_number := folio_holding.get("callNumber", None):
|
|
430
433
|
if "[" in call_number:
|
|
431
|
-
|
|
432
|
-
|
|
434
|
+
try:
|
|
435
|
+
call_numbers: List = ast.literal_eval(str(folio_holding["callNumber"]))
|
|
436
|
+
bound_with_holding["callNumber"] = call_numbers[bwidx]
|
|
437
|
+
except IndexError:
|
|
438
|
+
if call_numbers:
|
|
439
|
+
bound_with_holding["callNumber"] = call_numbers[0]
|
|
440
|
+
except SyntaxError:
|
|
441
|
+
bound_with_holding["callNumber"] = call_number
|
|
433
442
|
else:
|
|
434
443
|
bound_with_holding["callNumber"] = call_number
|
|
435
444
|
bound_with_holding["holdingsTypeId"] = bound_with_holdings_type_id
|
|
@@ -2,12 +2,13 @@ import ast
|
|
|
2
2
|
|
|
3
3
|
import i18n
|
|
4
4
|
from folio_uuid.folio_uuid import FOLIONamespaces
|
|
5
|
-
from folio_uuid.folio_uuid import FolioUUID
|
|
6
5
|
from folioclient import FolioClient
|
|
7
6
|
|
|
8
7
|
from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
|
|
9
|
-
from folio_migration_tools.library_configuration import
|
|
10
|
-
|
|
8
|
+
from folio_migration_tools.library_configuration import (
|
|
9
|
+
FileDefinition,
|
|
10
|
+
LibraryConfiguration,
|
|
11
|
+
)
|
|
11
12
|
from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
|
|
12
13
|
MappingFileMapperBase,
|
|
13
14
|
)
|
|
@@ -97,6 +98,11 @@ class HoldingsMapper(MappingFileMapperBase):
|
|
|
97
98
|
"BoundWithMappings",
|
|
98
99
|
(f"Number of bib-level callnumbers in record: {len(legacy_value.split(','))}"),
|
|
99
100
|
)
|
|
101
|
+
if legacy_value.startswith("[") and len(legacy_value.split(",")) == 1:
|
|
102
|
+
try:
|
|
103
|
+
legacy_value = ast.literal_eval(str(legacy_value))[0]
|
|
104
|
+
except SyntaxError:
|
|
105
|
+
return legacy_value
|
|
100
106
|
return legacy_value
|
|
101
107
|
|
|
102
108
|
def get_location_id(
|
|
@@ -127,9 +133,21 @@ class HoldingsMapper(MappingFileMapperBase):
|
|
|
127
133
|
i18n.t("Number of bib records referenced in item") + f": {len(legacy_bib_ids)}",
|
|
128
134
|
)
|
|
129
135
|
for legacy_instance_id in legacy_bib_ids:
|
|
136
|
+
new_legacy_value = (
|
|
137
|
+
f".{legacy_instance_id}"
|
|
138
|
+
if legacy_instance_id.startswith("b")
|
|
139
|
+
else legacy_instance_id
|
|
140
|
+
)
|
|
130
141
|
if (
|
|
131
|
-
new_legacy_value
|
|
132
|
-
|
|
142
|
+
new_legacy_value not in self.instance_id_map
|
|
143
|
+
and legacy_instance_id not in self.instance_id_map
|
|
144
|
+
):
|
|
145
|
+
self.migration_report.add_general_statistics(
|
|
146
|
+
i18n.t("Records not matched to Instances")
|
|
147
|
+
)
|
|
148
|
+
s = "Bib id not in instance id map."
|
|
149
|
+
raise TransformationRecordFailedError(index_or_id, s, new_legacy_value)
|
|
150
|
+
else:
|
|
133
151
|
self.migration_report.add_general_statistics(
|
|
134
152
|
i18n.t("Records matched to Instances")
|
|
135
153
|
)
|
|
@@ -137,12 +155,6 @@ class HoldingsMapper(MappingFileMapperBase):
|
|
|
137
155
|
legacy_instance_id
|
|
138
156
|
)
|
|
139
157
|
return_ids.append(entry[1])
|
|
140
|
-
else:
|
|
141
|
-
self.migration_report.add_general_statistics(
|
|
142
|
-
i18n.t("Records not matched to Instances")
|
|
143
|
-
)
|
|
144
|
-
s = "Bib id not in instance id map."
|
|
145
|
-
raise TransformationRecordFailedError(index_or_id, s, new_legacy_value)
|
|
146
158
|
if any(return_ids):
|
|
147
159
|
return return_ids
|
|
148
160
|
else:
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import sys
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from datetime import timezone
|
|
4
|
+
from datetime import datetime, timezone
|
|
6
5
|
from typing import Set
|
|
7
6
|
from uuid import uuid4
|
|
8
7
|
|
|
9
8
|
import i18n
|
|
10
9
|
from folio_uuid.folio_uuid import FOLIONamespaces
|
|
11
|
-
from folio_uuid.folio_uuid import FolioUUID
|
|
12
10
|
from folioclient import FolioClient
|
|
13
11
|
|
|
14
|
-
from folio_migration_tools.custom_exceptions import
|
|
15
|
-
|
|
12
|
+
from folio_migration_tools.custom_exceptions import (
|
|
13
|
+
TransformationProcessError,
|
|
14
|
+
TransformationRecordFailedError,
|
|
15
|
+
)
|
|
16
16
|
from folio_migration_tools.helper import Helper
|
|
17
|
-
from folio_migration_tools.library_configuration import
|
|
18
|
-
|
|
17
|
+
from folio_migration_tools.library_configuration import (
|
|
18
|
+
FileDefinition,
|
|
19
|
+
LibraryConfiguration,
|
|
20
|
+
)
|
|
19
21
|
from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
|
|
20
22
|
MappingFileMapperBase,
|
|
21
23
|
)
|
|
@@ -224,16 +226,16 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
224
226
|
return self.transform_status(mapped_value)
|
|
225
227
|
elif folio_prop_name == "barcode":
|
|
226
228
|
barcode = mapped_value
|
|
227
|
-
|
|
229
|
+
normalized_barcode = barcode.strip().lower()
|
|
230
|
+
if normalized_barcode and normalized_barcode in self.unique_barcodes:
|
|
228
231
|
Helper.log_data_issue(index_or_id, "Duplicate barcode", mapped_value)
|
|
229
232
|
self.migration_report.add_general_statistics(i18n.t("Duplicate barcodes"))
|
|
230
233
|
return f"{barcode}-{uuid4()}"
|
|
231
234
|
else:
|
|
232
|
-
if
|
|
233
|
-
self.unique_barcodes.add(
|
|
235
|
+
if normalized_barcode:
|
|
236
|
+
self.unique_barcodes.add(normalized_barcode)
|
|
234
237
|
return barcode
|
|
235
238
|
elif folio_prop_name == "holdingsRecordId":
|
|
236
|
-
mapped_value = FolioUUID.clean_iii_identifiers(mapped_value)
|
|
237
239
|
if mapped_value in self.holdings_id_map:
|
|
238
240
|
return self.holdings_id_map[mapped_value][1]
|
|
239
241
|
elif f"{self.bib_id_template}{mapped_value}" in self.holdings_id_map:
|
|
@@ -6,19 +6,18 @@ import re
|
|
|
6
6
|
import uuid
|
|
7
7
|
from functools import reduce
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Dict
|
|
10
|
-
from typing import List
|
|
11
|
-
from typing import Set
|
|
9
|
+
from typing import Dict, List, Set
|
|
12
10
|
from uuid import UUID
|
|
13
11
|
|
|
14
12
|
import i18n
|
|
15
|
-
from folio_uuid.folio_uuid import FOLIONamespaces
|
|
16
|
-
from folio_uuid.folio_uuid import FolioUUID
|
|
13
|
+
from folio_uuid.folio_uuid import FOLIONamespaces, FolioUUID
|
|
17
14
|
from folioclient import FolioClient
|
|
18
15
|
|
|
19
|
-
from folio_migration_tools.custom_exceptions import
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
from folio_migration_tools.custom_exceptions import (
|
|
17
|
+
TransformationFieldMappingError,
|
|
18
|
+
TransformationProcessError,
|
|
19
|
+
TransformationRecordFailedError,
|
|
20
|
+
)
|
|
22
21
|
from folio_migration_tools.library_configuration import LibraryConfiguration
|
|
23
22
|
from folio_migration_tools.mapper_base import MapperBase
|
|
24
23
|
from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
|
|
@@ -787,7 +786,7 @@ class MappingFileMapperBase(MapperBase):
|
|
|
787
786
|
missing_keys_in_record,
|
|
788
787
|
)
|
|
789
788
|
else:
|
|
790
|
-
logging.info("All
|
|
789
|
+
logging.info("All mapped legacy fields are in the legacy object")
|
|
791
790
|
|
|
792
791
|
def get_ref_data_tuple_by_code(self, ref_data, ref_name, code):
|
|
793
792
|
return self.get_ref_data_tuple(ref_data, ref_name, code, "code")
|
|
@@ -872,7 +871,7 @@ class MappingFileMapperBase(MapperBase):
|
|
|
872
871
|
|
|
873
872
|
def skip_property(property_name, property):
|
|
874
873
|
return bool(
|
|
875
|
-
property_name in ["metadata", "id", "
|
|
874
|
+
property_name in ["metadata", "id", "lastCheckIn"]
|
|
876
875
|
or property_name.startswith("effective")
|
|
877
876
|
or property.get("folio:isVirtual", False)
|
|
878
877
|
or property.get("description", "") == "Deprecated"
|
|
@@ -5,9 +5,9 @@ import re
|
|
|
5
5
|
import sys
|
|
6
6
|
import urllib.parse
|
|
7
7
|
import uuid
|
|
8
|
-
import i18n
|
|
9
8
|
|
|
10
9
|
import httpx
|
|
10
|
+
import i18n
|
|
11
11
|
from folio_uuid.folio_uuid import FOLIONamespaces
|
|
12
12
|
from folioclient import FolioClient
|
|
13
13
|
from httpx import HTTPError
|
|
@@ -141,7 +141,7 @@ class CompositeOrderMapper(MappingFileMapperBase):
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
if os.environ.get("GITHUB_TOKEN"):
|
|
144
|
-
logging.info("Using
|
|
144
|
+
logging.info("Using GITHUB_TOKEN environment variable for GitHub API Access")
|
|
145
145
|
github_headers["authorization"] = f"token {os.environ.get('GITHUB_TOKEN')}"
|
|
146
146
|
|
|
147
147
|
# Start talkign to GitHub...
|
|
@@ -31,7 +31,7 @@ class OrganizationMapper(MappingFileMapperBase):
|
|
|
31
31
|
):
|
|
32
32
|
# Build composite organization schema
|
|
33
33
|
if os.environ.get("GITHUB_TOKEN"):
|
|
34
|
-
logging.info("Using
|
|
34
|
+
logging.info("Using GITHUB_TOKEN environment variable for GitHub API Access")
|
|
35
35
|
organization_schema = OrganizationMapper.get_latest_acq_schemas_from_github(
|
|
36
36
|
"folio-org", "mod-organizations-storage", "mod-orgs", "organization"
|
|
37
37
|
)
|
|
@@ -2,14 +2,16 @@ import csv
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import sys
|
|
5
|
-
import i18n
|
|
6
5
|
|
|
6
|
+
import i18n
|
|
7
7
|
from dateutil.parser import parse
|
|
8
8
|
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
9
9
|
from folioclient import FolioClient
|
|
10
10
|
|
|
11
|
-
from folio_migration_tools.custom_exceptions import
|
|
12
|
-
|
|
11
|
+
from folio_migration_tools.custom_exceptions import (
|
|
12
|
+
TransformationProcessError,
|
|
13
|
+
TransformationRecordFailedError,
|
|
14
|
+
)
|
|
13
15
|
from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
|
|
14
16
|
MappingFileMapperBase,
|
|
15
17
|
)
|
|
@@ -79,7 +81,7 @@ class UserMapper(MappingFileMapperBase):
|
|
|
79
81
|
)
|
|
80
82
|
if "personal" not in folio_user:
|
|
81
83
|
folio_user["personal"] = {}
|
|
82
|
-
folio_user["personal"]["preferredContactTypeId"] = "
|
|
84
|
+
folio_user["personal"]["preferredContactTypeId"] = "email"
|
|
83
85
|
folio_user["active"] = True
|
|
84
86
|
if folio_user.get("requestPreference"):
|
|
85
87
|
folio_user["requestPreference"].update(
|
|
@@ -6,9 +6,11 @@ import pymarc
|
|
|
6
6
|
from folioclient import FolioClient
|
|
7
7
|
from pymarc import field
|
|
8
8
|
|
|
9
|
-
from folio_migration_tools.custom_exceptions import
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
from folio_migration_tools.custom_exceptions import (
|
|
10
|
+
TransformationFieldMappingError,
|
|
11
|
+
TransformationProcessError,
|
|
12
|
+
TransformationRecordFailedError,
|
|
13
|
+
)
|
|
12
14
|
from folio_migration_tools.helper import Helper
|
|
13
15
|
from folio_migration_tools.library_configuration import FolioRelease
|
|
14
16
|
from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
|
|
@@ -58,6 +60,7 @@ class Conditions:
|
|
|
58
60
|
logging.info("%s\tcontributor_types", len(self.folio.contributor_types))
|
|
59
61
|
logging.info("%s\talt_title_types", len(self.folio.alt_title_types))
|
|
60
62
|
logging.info("%s\tidentifier_types", len(self.folio.identifier_types))
|
|
63
|
+
logging.info("%s\tsubject_types", len(self.folio.subject_types))
|
|
61
64
|
# Raise for empty settings
|
|
62
65
|
if not self.folio.contributor_types:
|
|
63
66
|
raise TransformationProcessError("", "No contributor_types in FOLIO")
|
|
@@ -67,6 +70,8 @@ class Conditions:
|
|
|
67
70
|
raise TransformationProcessError("", "No identifier_types in FOLIO")
|
|
68
71
|
if not self.folio.alt_title_types:
|
|
69
72
|
raise TransformationProcessError("", "No alt_title_types in FOLIO")
|
|
73
|
+
if not self.folio.subject_types:
|
|
74
|
+
raise TransformationProcessError("", "No subject_types in FOLIO")
|
|
70
75
|
|
|
71
76
|
# Set defaults
|
|
72
77
|
logging.info("Setting defaults")
|
|
@@ -113,9 +118,7 @@ class Conditions:
|
|
|
113
118
|
logging.info("Default Callnumber type Name:\t%s", self.default_call_number_type["name"])
|
|
114
119
|
|
|
115
120
|
def setup_and_validate_holdings_types(self):
|
|
116
|
-
self.holdings_types =
|
|
117
|
-
self.folio.folio_get_all("/holdings-types", "holdingsTypes", "", 1000)
|
|
118
|
-
)
|
|
121
|
+
self.holdings_types = self.folio.holdings_types
|
|
119
122
|
if not self.holdings_types:
|
|
120
123
|
raise TransformationProcessError("", "No holdings_types in FOLIO")
|
|
121
124
|
missing_holdings_types = [
|
|
@@ -846,6 +849,19 @@ class Conditions:
|
|
|
846
849
|
+ i18n.t("1 is public, all other values are Staff only")
|
|
847
850
|
+ ")",
|
|
848
851
|
)
|
|
849
|
-
if ind1
|
|
852
|
+
if ind1 == "0":
|
|
850
853
|
return "true"
|
|
851
854
|
return "false"
|
|
855
|
+
|
|
856
|
+
def condition_set_subject_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
|
|
857
|
+
try:
|
|
858
|
+
t = self.get_ref_data_tuple_by_name(
|
|
859
|
+
self.folio.subject_types, "subject_types", parameter["name"]
|
|
860
|
+
)
|
|
861
|
+
self.mapper.migration_report.add("MappedSubjectTypes", t[1])
|
|
862
|
+
return t[0]
|
|
863
|
+
except Exception:
|
|
864
|
+
raise TransformationProcessError(
|
|
865
|
+
legacy_id,
|
|
866
|
+
f"Subject type not found for {parameter['name']} {marc_field}",
|
|
867
|
+
)
|
|
@@ -2,11 +2,10 @@ import calendar
|
|
|
2
2
|
import contextlib
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
5
|
-
import i18n
|
|
6
5
|
from typing import List
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
from pymarc import Record
|
|
7
|
+
import i18n
|
|
8
|
+
from pymarc import Field, Record
|
|
10
9
|
|
|
11
10
|
from folio_migration_tools.custom_exceptions import TransformationFieldMappingError
|
|
12
11
|
|
|
@@ -72,9 +71,20 @@ class HoldingsStatementsParser:
|
|
|
72
71
|
|
|
73
72
|
else:
|
|
74
73
|
for linked_value_field in linked_value_fields:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
try:
|
|
75
|
+
parsed_dict = HoldingsStatementsParser.parse_linked_field(
|
|
76
|
+
pattern_field, linked_value_field
|
|
77
|
+
)
|
|
78
|
+
except KeyError:
|
|
79
|
+
raise TransformationFieldMappingError(
|
|
80
|
+
legacy_ids,
|
|
81
|
+
i18n.t(
|
|
82
|
+
"subfield present in %{linked_value_tag} but not in %{pattern_field}",
|
|
83
|
+
pattern_field=pattern_field,
|
|
84
|
+
linked_value_tag=linked_value_field,
|
|
85
|
+
),
|
|
86
|
+
pattern_field,
|
|
87
|
+
)
|
|
78
88
|
if parsed_dict["hlm_stmt"]:
|
|
79
89
|
return_dict["hlm_stmts"].append(parsed_dict["hlm_stmt"])
|
|
80
90
|
if parsed_dict["statement"]:
|
|
@@ -222,7 +232,7 @@ class HoldingsStatementsParser:
|
|
|
222
232
|
|
|
223
233
|
@staticmethod
|
|
224
234
|
def dedupe_list_of_dict(list_of_dict):
|
|
225
|
-
return [dict(t) for t in {tuple(d.items()) for d in list_of_dict}]
|
|
235
|
+
return [dict(t) for t in {tuple(d.items()): None for d in list_of_dict}]
|
|
226
236
|
|
|
227
237
|
@staticmethod
|
|
228
238
|
def g_m(m: int):
|
|
@@ -254,8 +264,8 @@ class HoldingsStatementsParser:
|
|
|
254
264
|
year = False
|
|
255
265
|
is_span = False
|
|
256
266
|
for chron_level in [cl for cl in "ijkl" if cl in linked_value_field]:
|
|
257
|
-
desc = pattern_field
|
|
258
|
-
if linked_value_field
|
|
267
|
+
desc = pattern_field.get(chron_level, "")
|
|
268
|
+
if linked_value_field.get(chron_level):
|
|
259
269
|
if chron_level == "i" and desc == "(year)":
|
|
260
270
|
hlm_stmt = linked_value_field[chron_level]
|
|
261
271
|
if desc == "(year)":
|
|
@@ -295,9 +305,9 @@ class HoldingsStatementsParser:
|
|
|
295
305
|
_to = ""
|
|
296
306
|
is_span = False
|
|
297
307
|
for enum_level in [el for el in "abcdef" if el in linked_value_field]:
|
|
298
|
-
desc = pattern_field
|
|
308
|
+
desc = pattern_field.get(enum_level, "")
|
|
299
309
|
desc = desc.strip() if "(" not in desc else ""
|
|
300
|
-
if linked_value_field
|
|
310
|
+
if linked_value_field.get(enum_level):
|
|
301
311
|
val, *val_rest = linked_value_field[enum_level].split("-")
|
|
302
312
|
is_span = "-" in linked_value_field[enum_level] or is_span
|
|
303
313
|
_from = f"{_from}{(':' if _from else '')}{desc}{val}"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
import sys
|
|
3
4
|
import time
|
|
4
5
|
import traceback
|
|
@@ -6,17 +7,15 @@ from typing import List
|
|
|
6
7
|
|
|
7
8
|
import i18n
|
|
8
9
|
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
9
|
-
from
|
|
10
|
-
from pymarc import Field
|
|
11
|
-
from pymarc import Record
|
|
12
|
-
from pymarc import Subfield
|
|
10
|
+
from pymarc import Field, Record, Subfield
|
|
13
11
|
|
|
14
|
-
from folio_migration_tools.custom_exceptions import
|
|
15
|
-
|
|
12
|
+
from folio_migration_tools.custom_exceptions import (
|
|
13
|
+
TransformationProcessError,
|
|
14
|
+
TransformationRecordFailedError,
|
|
15
|
+
)
|
|
16
16
|
from folio_migration_tools.folder_structure import FolderStructure
|
|
17
17
|
from folio_migration_tools.helper import Helper
|
|
18
|
-
from folio_migration_tools.library_configuration import FileDefinition
|
|
19
|
-
from folio_migration_tools.library_configuration import HridHandling
|
|
18
|
+
from folio_migration_tools.library_configuration import FileDefinition, HridHandling
|
|
20
19
|
from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
|
|
21
20
|
RulesMapperBase,
|
|
22
21
|
)
|
|
@@ -33,6 +32,8 @@ class MarcFileProcessor:
|
|
|
33
32
|
self.created_objects_file = created_objects_file
|
|
34
33
|
if mapper.task_configuration.create_source_records:
|
|
35
34
|
self.srs_records_file = open(self.folder_structure.srs_records_path, "w+")
|
|
35
|
+
if mapper.task_configuration.data_import_marc:
|
|
36
|
+
self.data_import_marc_file = open(self.folder_structure.data_import_marc_path, "wb+")
|
|
36
37
|
self.unique_001s: set = set()
|
|
37
38
|
self.failed_records_count: int = 0
|
|
38
39
|
self.records_count: int = 0
|
|
@@ -86,6 +87,12 @@ class MarcFileProcessor:
|
|
|
86
87
|
legacy_ids,
|
|
87
88
|
self.object_type,
|
|
88
89
|
)
|
|
90
|
+
if self.mapper.task_configuration.data_import_marc:
|
|
91
|
+
self.save_marc_record(
|
|
92
|
+
marc_record,
|
|
93
|
+
folio_rec,
|
|
94
|
+
self.object_type
|
|
95
|
+
)
|
|
89
96
|
Helper.write_to_file(self.created_objects_file, folio_rec)
|
|
90
97
|
self.mapper.migration_report.add_general_statistics(
|
|
91
98
|
i18n.t("Inventory records written to disk")
|
|
@@ -122,6 +129,19 @@ class MarcFileProcessor:
|
|
|
122
129
|
):
|
|
123
130
|
self.mapper.remove_from_id_map(folio_rec.get("formerIds", []))
|
|
124
131
|
|
|
132
|
+
def save_marc_record(
|
|
133
|
+
self,
|
|
134
|
+
marc_record: Record,
|
|
135
|
+
folio_rec: dict,
|
|
136
|
+
object_type: FOLIONamespaces
|
|
137
|
+
):
|
|
138
|
+
self.mapper.save_data_import_marc_record(
|
|
139
|
+
self.data_import_marc_file,
|
|
140
|
+
object_type,
|
|
141
|
+
marc_record,
|
|
142
|
+
folio_rec,
|
|
143
|
+
)
|
|
144
|
+
|
|
125
145
|
def save_srs_record(
|
|
126
146
|
self,
|
|
127
147
|
marc_record: Record,
|
|
@@ -247,7 +267,15 @@ class MarcFileProcessor:
|
|
|
247
267
|
self.mapper.mapped_legacy_fields,
|
|
248
268
|
)
|
|
249
269
|
if self.mapper.task_configuration.create_source_records:
|
|
270
|
+
self.srs_records_file.seek(0)
|
|
271
|
+
if not self.srs_records_file.seek(0):
|
|
272
|
+
os.remove(self.srs_records_file.name)
|
|
250
273
|
self.srs_records_file.close()
|
|
274
|
+
if self.mapper.task_configuration.data_import_marc:
|
|
275
|
+
self.data_import_marc_file.seek(0)
|
|
276
|
+
if not self.data_import_marc_file.read(1):
|
|
277
|
+
os.remove(self.data_import_marc_file.name)
|
|
278
|
+
self.data_import_marc_file.close()
|
|
251
279
|
self.mapper.wrap_up()
|
|
252
280
|
|
|
253
281
|
logging.info("Transformation report written to %s", report_file.name)
|
|
@@ -255,7 +283,6 @@ class MarcFileProcessor:
|
|
|
255
283
|
|
|
256
284
|
def add_legacy_ids_to_map(self, folio_rec, filtered_legacy_ids):
|
|
257
285
|
for legacy_id in filtered_legacy_ids:
|
|
258
|
-
legacy_id = FolioUUID.clean_iii_identifiers(legacy_id)
|
|
259
286
|
self.legacy_ids.add(legacy_id)
|
|
260
287
|
if legacy_id not in self.mapper.id_map:
|
|
261
288
|
self.mapper.id_map[legacy_id] = self.mapper.get_id_map_tuple(
|