folio-migration-tools 1.2.1__py3-none-any.whl → 1.9.10__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 +11 -0
- folio_migration_tools/__main__.py +169 -85
- folio_migration_tools/circulation_helper.py +96 -59
- folio_migration_tools/config_file_load.py +66 -0
- folio_migration_tools/custom_dict.py +6 -4
- folio_migration_tools/custom_exceptions.py +21 -19
- folio_migration_tools/extradata_writer.py +46 -0
- folio_migration_tools/folder_structure.py +63 -66
- folio_migration_tools/helper.py +29 -21
- folio_migration_tools/holdings_helper.py +57 -34
- folio_migration_tools/i18n_config.py +9 -0
- folio_migration_tools/library_configuration.py +173 -13
- folio_migration_tools/mapper_base.py +317 -106
- folio_migration_tools/mapping_file_transformation/courses_mapper.py +203 -0
- folio_migration_tools/mapping_file_transformation/holdings_mapper.py +83 -69
- folio_migration_tools/mapping_file_transformation/item_mapper.py +98 -94
- folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +352 -0
- folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +702 -223
- folio_migration_tools/mapping_file_transformation/notes_mapper.py +90 -0
- folio_migration_tools/mapping_file_transformation/order_mapper.py +492 -0
- folio_migration_tools/mapping_file_transformation/organization_mapper.py +389 -0
- folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +38 -27
- folio_migration_tools/mapping_file_transformation/user_mapper.py +149 -361
- folio_migration_tools/marc_rules_transformation/conditions.py +650 -246
- folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +292 -130
- folio_migration_tools/marc_rules_transformation/hrid_handler.py +244 -0
- folio_migration_tools/marc_rules_transformation/loc_language_codes.xml +20846 -0
- folio_migration_tools/marc_rules_transformation/marc_file_processor.py +300 -0
- folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +136 -0
- folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +241 -0
- folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +681 -201
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +395 -429
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +531 -100
- folio_migration_tools/migration_report.py +85 -38
- folio_migration_tools/migration_tasks/__init__.py +1 -3
- folio_migration_tools/migration_tasks/authority_transformer.py +119 -0
- folio_migration_tools/migration_tasks/batch_poster.py +911 -198
- folio_migration_tools/migration_tasks/bibs_transformer.py +121 -116
- folio_migration_tools/migration_tasks/courses_migrator.py +192 -0
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py +252 -247
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py +321 -115
- folio_migration_tools/migration_tasks/items_transformer.py +264 -84
- folio_migration_tools/migration_tasks/loans_migrator.py +506 -195
- folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +187 -0
- folio_migration_tools/migration_tasks/migration_task_base.py +364 -74
- folio_migration_tools/migration_tasks/orders_transformer.py +373 -0
- folio_migration_tools/migration_tasks/organization_transformer.py +451 -0
- folio_migration_tools/migration_tasks/requests_migrator.py +130 -62
- folio_migration_tools/migration_tasks/reserves_migrator.py +253 -0
- folio_migration_tools/migration_tasks/user_transformer.py +180 -139
- folio_migration_tools/task_configuration.py +46 -0
- folio_migration_tools/test_infrastructure/__init__.py +0 -0
- folio_migration_tools/test_infrastructure/mocked_classes.py +406 -0
- folio_migration_tools/transaction_migration/legacy_loan.py +148 -34
- folio_migration_tools/transaction_migration/legacy_request.py +65 -25
- folio_migration_tools/transaction_migration/legacy_reserve.py +47 -0
- folio_migration_tools/transaction_migration/transaction_result.py +12 -1
- folio_migration_tools/translations/en.json +476 -0
- folio_migration_tools-1.9.10.dist-info/METADATA +169 -0
- folio_migration_tools-1.9.10.dist-info/RECORD +67 -0
- {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info}/WHEEL +1 -2
- folio_migration_tools-1.9.10.dist-info/entry_points.txt +3 -0
- folio_migration_tools/generate_schemas.py +0 -46
- folio_migration_tools/mapping_file_transformation/mapping_file_mapping_base_impl.py +0 -44
- folio_migration_tools/mapping_file_transformation/user_mapper_base.py +0 -212
- folio_migration_tools/marc_rules_transformation/bibs_processor.py +0 -163
- folio_migration_tools/marc_rules_transformation/holdings_processor.py +0 -284
- folio_migration_tools/report_blurbs.py +0 -219
- folio_migration_tools/transaction_migration/legacy_fee_fine.py +0 -36
- folio_migration_tools-1.2.1.dist-info/METADATA +0 -134
- folio_migration_tools-1.2.1.dist-info/RECORD +0 -50
- folio_migration_tools-1.2.1.dist-info/top_level.txt +0 -1
- {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Set
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import i18n
|
|
7
|
+
from folio_uuid import FOLIONamespaces
|
|
8
|
+
from folioclient import FolioClient
|
|
9
|
+
from pymarc import Field, Record, Subfield
|
|
10
|
+
|
|
11
|
+
from folio_migration_tools.custom_exceptions import TransformationProcessError
|
|
12
|
+
from folio_migration_tools.helper import Helper
|
|
13
|
+
from folio_migration_tools.library_configuration import HridHandling
|
|
14
|
+
from folio_migration_tools.migration_report import MigrationReport
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HRIDHandler:
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
folio_client: FolioClient,
|
|
21
|
+
handling: HridHandling,
|
|
22
|
+
migration_report: MigrationReport,
|
|
23
|
+
deactivate035_from001: bool,
|
|
24
|
+
):
|
|
25
|
+
self.unique_001s: Set[str] = set()
|
|
26
|
+
self.deactivate035_from001: bool = deactivate035_from001
|
|
27
|
+
self.hrid_path = "/hrid-settings-storage/hrid-settings"
|
|
28
|
+
self.folio_client: FolioClient = folio_client
|
|
29
|
+
self.handling: HridHandling = handling
|
|
30
|
+
self.migration_report: MigrationReport = migration_report
|
|
31
|
+
self.hrid_settings = self.folio_client.folio_get_single_object(self.hrid_path)
|
|
32
|
+
self.instance_hrid_prefix = self.hrid_settings["instances"].get("prefix", "")
|
|
33
|
+
self.instance_hrid_counter = self.hrid_settings["instances"]["startNumber"]
|
|
34
|
+
self.holdings_hrid_prefix = self.hrid_settings["holdings"].get("prefix", "")
|
|
35
|
+
self.holdings_hrid_counter = self.hrid_settings["holdings"]["startNumber"]
|
|
36
|
+
self.items_hrid_prefix = self.hrid_settings["items"].get("prefix", "")
|
|
37
|
+
self.items_hrid_counter = self.hrid_settings["items"]["startNumber"]
|
|
38
|
+
self.common_retain_leading_zeroes: bool = self.hrid_settings["commonRetainLeadingZeroes"]
|
|
39
|
+
logging.info(f"HRID handling is set to: '{self.handling}'")
|
|
40
|
+
|
|
41
|
+
def handle_hrid(
|
|
42
|
+
self,
|
|
43
|
+
namespace: FOLIONamespaces,
|
|
44
|
+
folio_record: dict,
|
|
45
|
+
marc_record: Record,
|
|
46
|
+
legacy_ids: list[str],
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Create HRID if not mapped. Add hrid as MARC record 001
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
namespace (FOLIONamespaces): determening the type of hrid setting to update
|
|
52
|
+
folio_record (dict): _description_
|
|
53
|
+
marc_record (Record): _description_
|
|
54
|
+
legacy_ids (list[str]): _description_
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
TransformationProcessError: _description_
|
|
58
|
+
"""
|
|
59
|
+
if self.enumerate_hrid(marc_record):
|
|
60
|
+
self.generate_enumerated_hrid(folio_record, marc_record, legacy_ids, namespace)
|
|
61
|
+
elif self.handling == HridHandling.preserve001:
|
|
62
|
+
self.preserve_001_as_hrid(folio_record, marc_record, legacy_ids, namespace)
|
|
63
|
+
else:
|
|
64
|
+
raise TransformationProcessError("", f"Unknown HRID handling: {self.handling}")
|
|
65
|
+
|
|
66
|
+
def generate_enumerated_hrid(
|
|
67
|
+
self,
|
|
68
|
+
folio_record: dict,
|
|
69
|
+
marc_record: Record,
|
|
70
|
+
legacy_ids: list[str],
|
|
71
|
+
namespace: FOLIONamespaces,
|
|
72
|
+
):
|
|
73
|
+
folio_record["hrid"] = self.get_next_hrid(namespace)
|
|
74
|
+
new_001 = Field(tag="001", data=folio_record["hrid"])
|
|
75
|
+
self.handle_035_generation(
|
|
76
|
+
marc_record, legacy_ids, self.migration_report, self.deactivate035_from001
|
|
77
|
+
)
|
|
78
|
+
marc_record.add_ordered_field(new_001)
|
|
79
|
+
self.migration_report.add("HridHandling", i18n.t("Created HRID using default settings"))
|
|
80
|
+
|
|
81
|
+
def enumerate_hrid(self, marc_record):
|
|
82
|
+
return self.handling == HridHandling.default or "001" not in marc_record
|
|
83
|
+
|
|
84
|
+
def get_next_hrid(self, namespace: FOLIONamespaces):
|
|
85
|
+
hrid = ""
|
|
86
|
+
if namespace == FOLIONamespaces.instances:
|
|
87
|
+
hrid = (
|
|
88
|
+
f"{self.instance_hrid_prefix}"
|
|
89
|
+
f"{self.generate_numeric_part(self.instance_hrid_counter)}"
|
|
90
|
+
)
|
|
91
|
+
self.instance_hrid_counter += 1
|
|
92
|
+
elif namespace == FOLIONamespaces.holdings:
|
|
93
|
+
hrid = (
|
|
94
|
+
f"{self.holdings_hrid_prefix}"
|
|
95
|
+
f"{self.generate_numeric_part(self.holdings_hrid_counter)}"
|
|
96
|
+
)
|
|
97
|
+
self.holdings_hrid_counter += 1
|
|
98
|
+
else:
|
|
99
|
+
raise TransformationProcessError("", "Unimplemented namespace")
|
|
100
|
+
return hrid
|
|
101
|
+
|
|
102
|
+
def generate_numeric_part(self, counter):
|
|
103
|
+
return str(counter).zfill(11) if self.common_retain_leading_zeroes else str(counter)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def handle_035_generation(
|
|
107
|
+
marc_record: Record,
|
|
108
|
+
legacy_ids,
|
|
109
|
+
migration_report: MigrationReport,
|
|
110
|
+
deactivate035_from001: bool,
|
|
111
|
+
remove_001: bool = True,
|
|
112
|
+
):
|
|
113
|
+
try:
|
|
114
|
+
f_001 = marc_record["001"].value()
|
|
115
|
+
f_003 = marc_record["003"].value().strip() if "003" in marc_record else ""
|
|
116
|
+
migration_report.add(
|
|
117
|
+
"HridHandling", i18n.t("Values in %{field}", field="003") + f': {f_003 or "Empty"}'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if deactivate035_from001:
|
|
121
|
+
migration_report.add("HridHandling", i18n.t("035 generation from 001 turned off"))
|
|
122
|
+
else:
|
|
123
|
+
str_035 = f"({f_003}){f_001}" if f_003 else f"{f_001}"
|
|
124
|
+
new_035 = Field(
|
|
125
|
+
tag="035",
|
|
126
|
+
indicators=[" ", " "],
|
|
127
|
+
subfields=[Subfield(code="a", value=str_035)],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Don't add the 035 if an identical field already exists
|
|
131
|
+
existing_035 = marc_record.get_fields("035")
|
|
132
|
+
if not any(compare_fields(new_035, e) for e in existing_035):
|
|
133
|
+
marc_record.add_ordered_field(new_035)
|
|
134
|
+
migration_report.add("HridHandling", i18n.t("Added 035 from 001"))
|
|
135
|
+
if remove_001:
|
|
136
|
+
marc_record.remove_fields("001", "003")
|
|
137
|
+
|
|
138
|
+
except Exception:
|
|
139
|
+
if "001" in marc_record:
|
|
140
|
+
s = i18n.n("Failed to create %{to} from %{fro}", to="001", fro="035")
|
|
141
|
+
migration_report.add("HridHandling", s)
|
|
142
|
+
Helper.log_data_issue(legacy_ids, s, marc_record["001"])
|
|
143
|
+
else:
|
|
144
|
+
migration_report.add("HridHandling", i18n.t("Legacy bib records without 001"))
|
|
145
|
+
|
|
146
|
+
def hrids_not_updated(self):
|
|
147
|
+
return (
|
|
148
|
+
self.hrid_settings["instances"]["startNumber"] == self.instance_hrid_counter
|
|
149
|
+
and self.hrid_settings["holdings"]["startNumber"] == self.holdings_hrid_counter
|
|
150
|
+
and self.hrid_settings["items"]["startNumber"] == self.items_hrid_counter
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def store_hrid_settings(self):
|
|
154
|
+
logging.info("Setting HRID counter to current")
|
|
155
|
+
try:
|
|
156
|
+
if self.hrids_not_updated():
|
|
157
|
+
logging.info("NOT POSTing HRID settings, since did not change.")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
self.hrid_settings["instances"]["startNumber"] = self.instance_hrid_counter
|
|
161
|
+
self.hrid_settings["holdings"]["startNumber"] = self.holdings_hrid_counter
|
|
162
|
+
self.hrid_settings["items"]["startNumber"] = self.items_hrid_counter
|
|
163
|
+
url = self.folio_client.gateway_url + self.hrid_path
|
|
164
|
+
resp = httpx.put(
|
|
165
|
+
url,
|
|
166
|
+
json=self.hrid_settings,
|
|
167
|
+
headers=self.folio_client.okapi_headers,
|
|
168
|
+
)
|
|
169
|
+
resp.raise_for_status()
|
|
170
|
+
logging.info("%s Successfully set HRID settings.", resp.status_code)
|
|
171
|
+
a = self.folio_client.folio_get_single_object(self.hrid_path)
|
|
172
|
+
logging.info("Current hrid settings: %s", json.dumps(a, indent=4))
|
|
173
|
+
except Exception:
|
|
174
|
+
logging.exception(
|
|
175
|
+
f"Something went wrong when setting the HRID settings. "
|
|
176
|
+
f"Update them manually. {json.dumps(self.hrid_settings)}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def reset_instance_hrid_counter(self):
|
|
180
|
+
logging.info("Resetting Instances HRID settings to 1")
|
|
181
|
+
self.instance_hrid_counter = 1
|
|
182
|
+
self.migration_report.set(
|
|
183
|
+
"GeneralStatistics",
|
|
184
|
+
i18n.t("Instances HRID starting number"),
|
|
185
|
+
self.instance_hrid_counter,
|
|
186
|
+
)
|
|
187
|
+
self.store_hrid_settings()
|
|
188
|
+
|
|
189
|
+
def reset_holdings_hrid_counter(self):
|
|
190
|
+
logging.info("Resetting Holdings HRID settings to 1")
|
|
191
|
+
self.holdings_hrid_counter = 1
|
|
192
|
+
self.migration_report.set(
|
|
193
|
+
"GeneralStatistics", "Holdings HRID starting number", self.holdings_hrid_counter
|
|
194
|
+
)
|
|
195
|
+
self.store_hrid_settings()
|
|
196
|
+
|
|
197
|
+
def reset_item_hrid_counter(self):
|
|
198
|
+
logging.info("Resetting Items HRID settings to 1")
|
|
199
|
+
self.items_hrid_counter = 1
|
|
200
|
+
self.migration_report.set(
|
|
201
|
+
"GeneralStatistics", "Items HRID starting number", self.items_hrid_counter
|
|
202
|
+
)
|
|
203
|
+
self.store_hrid_settings()
|
|
204
|
+
|
|
205
|
+
def preserve_001_as_hrid(
|
|
206
|
+
self,
|
|
207
|
+
folio_record: dict,
|
|
208
|
+
marc_record: Record,
|
|
209
|
+
legacy_ids: list[str],
|
|
210
|
+
namespace: FOLIONamespaces,
|
|
211
|
+
):
|
|
212
|
+
value = marc_record["001"].value()
|
|
213
|
+
if value in self.unique_001s:
|
|
214
|
+
self.migration_report.add(
|
|
215
|
+
"HridHandling",
|
|
216
|
+
i18n.t(
|
|
217
|
+
"Duplicate 001. Creating HRID instead.\n Previous 001 will be stored in a new 035 field"
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
self.handle_035_generation(
|
|
221
|
+
marc_record, legacy_ids, self.migration_report, self.deactivate035_from001, False
|
|
222
|
+
)
|
|
223
|
+
Helper.log_data_issue(
|
|
224
|
+
legacy_ids,
|
|
225
|
+
"Duplicate 001 for record. HRID created for record",
|
|
226
|
+
value,
|
|
227
|
+
)
|
|
228
|
+
folio_record["hrid"] = self.get_next_hrid(namespace)
|
|
229
|
+
new_001 = Field(tag="001", data=folio_record["hrid"])
|
|
230
|
+
marc_record.add_ordered_field(new_001)
|
|
231
|
+
self.instance_hrid_counter += 1
|
|
232
|
+
else:
|
|
233
|
+
self.unique_001s.add(value)
|
|
234
|
+
folio_record["hrid"] = value
|
|
235
|
+
self.migration_report.add("HridHandling", i18n.t("Took HRID from 001"))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def compare_fields(field1: Field, field2: Field) -> bool:
|
|
239
|
+
bool_compare = (
|
|
240
|
+
field1.tag == field2.tag
|
|
241
|
+
and field1.indicators == field2.indicators
|
|
242
|
+
and field1.subfields == field2.subfields
|
|
243
|
+
)
|
|
244
|
+
return bool_compare
|