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.
Files changed (73) hide show
  1. folio_migration_tools/__init__.py +11 -0
  2. folio_migration_tools/__main__.py +169 -85
  3. folio_migration_tools/circulation_helper.py +96 -59
  4. folio_migration_tools/config_file_load.py +66 -0
  5. folio_migration_tools/custom_dict.py +6 -4
  6. folio_migration_tools/custom_exceptions.py +21 -19
  7. folio_migration_tools/extradata_writer.py +46 -0
  8. folio_migration_tools/folder_structure.py +63 -66
  9. folio_migration_tools/helper.py +29 -21
  10. folio_migration_tools/holdings_helper.py +57 -34
  11. folio_migration_tools/i18n_config.py +9 -0
  12. folio_migration_tools/library_configuration.py +173 -13
  13. folio_migration_tools/mapper_base.py +317 -106
  14. folio_migration_tools/mapping_file_transformation/courses_mapper.py +203 -0
  15. folio_migration_tools/mapping_file_transformation/holdings_mapper.py +83 -69
  16. folio_migration_tools/mapping_file_transformation/item_mapper.py +98 -94
  17. folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +352 -0
  18. folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +702 -223
  19. folio_migration_tools/mapping_file_transformation/notes_mapper.py +90 -0
  20. folio_migration_tools/mapping_file_transformation/order_mapper.py +492 -0
  21. folio_migration_tools/mapping_file_transformation/organization_mapper.py +389 -0
  22. folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +38 -27
  23. folio_migration_tools/mapping_file_transformation/user_mapper.py +149 -361
  24. folio_migration_tools/marc_rules_transformation/conditions.py +650 -246
  25. folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +292 -130
  26. folio_migration_tools/marc_rules_transformation/hrid_handler.py +244 -0
  27. folio_migration_tools/marc_rules_transformation/loc_language_codes.xml +20846 -0
  28. folio_migration_tools/marc_rules_transformation/marc_file_processor.py +300 -0
  29. folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +136 -0
  30. folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +241 -0
  31. folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +681 -201
  32. folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +395 -429
  33. folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +531 -100
  34. folio_migration_tools/migration_report.py +85 -38
  35. folio_migration_tools/migration_tasks/__init__.py +1 -3
  36. folio_migration_tools/migration_tasks/authority_transformer.py +119 -0
  37. folio_migration_tools/migration_tasks/batch_poster.py +911 -198
  38. folio_migration_tools/migration_tasks/bibs_transformer.py +121 -116
  39. folio_migration_tools/migration_tasks/courses_migrator.py +192 -0
  40. folio_migration_tools/migration_tasks/holdings_csv_transformer.py +252 -247
  41. folio_migration_tools/migration_tasks/holdings_marc_transformer.py +321 -115
  42. folio_migration_tools/migration_tasks/items_transformer.py +264 -84
  43. folio_migration_tools/migration_tasks/loans_migrator.py +506 -195
  44. folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +187 -0
  45. folio_migration_tools/migration_tasks/migration_task_base.py +364 -74
  46. folio_migration_tools/migration_tasks/orders_transformer.py +373 -0
  47. folio_migration_tools/migration_tasks/organization_transformer.py +451 -0
  48. folio_migration_tools/migration_tasks/requests_migrator.py +130 -62
  49. folio_migration_tools/migration_tasks/reserves_migrator.py +253 -0
  50. folio_migration_tools/migration_tasks/user_transformer.py +180 -139
  51. folio_migration_tools/task_configuration.py +46 -0
  52. folio_migration_tools/test_infrastructure/__init__.py +0 -0
  53. folio_migration_tools/test_infrastructure/mocked_classes.py +406 -0
  54. folio_migration_tools/transaction_migration/legacy_loan.py +148 -34
  55. folio_migration_tools/transaction_migration/legacy_request.py +65 -25
  56. folio_migration_tools/transaction_migration/legacy_reserve.py +47 -0
  57. folio_migration_tools/transaction_migration/transaction_result.py +12 -1
  58. folio_migration_tools/translations/en.json +476 -0
  59. folio_migration_tools-1.9.10.dist-info/METADATA +169 -0
  60. folio_migration_tools-1.9.10.dist-info/RECORD +67 -0
  61. {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info}/WHEEL +1 -2
  62. folio_migration_tools-1.9.10.dist-info/entry_points.txt +3 -0
  63. folio_migration_tools/generate_schemas.py +0 -46
  64. folio_migration_tools/mapping_file_transformation/mapping_file_mapping_base_impl.py +0 -44
  65. folio_migration_tools/mapping_file_transformation/user_mapper_base.py +0 -212
  66. folio_migration_tools/marc_rules_transformation/bibs_processor.py +0 -163
  67. folio_migration_tools/marc_rules_transformation/holdings_processor.py +0 -284
  68. folio_migration_tools/report_blurbs.py +0 -219
  69. folio_migration_tools/transaction_migration/legacy_fee_fine.py +0 -36
  70. folio_migration_tools-1.2.1.dist-info/METADATA +0 -134
  71. folio_migration_tools-1.2.1.dist-info/RECORD +0 -50
  72. folio_migration_tools-1.2.1.dist-info/top_level.txt +0 -1
  73. {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