folio-migration-tools 1.9.0rc7__py3-none-any.whl → 1.9.0rc8__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 (25) hide show
  1. folio_migration_tools/__main__.py +17 -5
  2. folio_migration_tools/folder_structure.py +5 -0
  3. folio_migration_tools/library_configuration.py +41 -3
  4. folio_migration_tools/mapper_base.py +11 -38
  5. folio_migration_tools/mapping_file_transformation/courses_mapper.py +1 -1
  6. folio_migration_tools/mapping_file_transformation/item_mapper.py +0 -4
  7. folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +1 -1
  8. folio_migration_tools/mapping_file_transformation/user_mapper.py +1 -1
  9. folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +1 -1
  10. folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +11 -6
  11. folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +1 -1
  12. folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +52 -1
  13. folio_migration_tools/migration_tasks/holdings_csv_transformer.py +1 -13
  14. folio_migration_tools/migration_tasks/holdings_marc_transformer.py +12 -3
  15. folio_migration_tools/migration_tasks/items_transformer.py +22 -17
  16. folio_migration_tools/migration_tasks/loans_migrator.py +2 -9
  17. folio_migration_tools/migration_tasks/migration_task_base.py +50 -6
  18. folio_migration_tools/migration_tasks/orders_transformer.py +1 -1
  19. folio_migration_tools/migration_tasks/user_transformer.py +2 -10
  20. folio_migration_tools/test_infrastructure/mocked_classes.py +63 -0
  21. {folio_migration_tools-1.9.0rc7.dist-info → folio_migration_tools-1.9.0rc8.dist-info}/METADATA +2 -1
  22. {folio_migration_tools-1.9.0rc7.dist-info → folio_migration_tools-1.9.0rc8.dist-info}/RECORD +25 -25
  23. {folio_migration_tools-1.9.0rc7.dist-info → folio_migration_tools-1.9.0rc8.dist-info}/LICENSE +0 -0
  24. {folio_migration_tools-1.9.0rc7.dist-info → folio_migration_tools-1.9.0rc8.dist-info}/WHEEL +0 -0
  25. {folio_migration_tools-1.9.0rc7.dist-info → folio_migration_tools-1.9.0rc8.dist-info}/entry_points.txt +0 -0
@@ -62,6 +62,22 @@ def parse_args(args):
62
62
  )
63
63
  return parser.parse_args(args)
64
64
 
65
+ def prep_library_config(args):
66
+ config_file_humped = merge_load(args.configuration_path)
67
+ config_file_humped["libraryInformation"]["okapiPassword"] = args.okapi_password
68
+ config_file_humped["libraryInformation"]["baseFolder"] = args.base_folder_path
69
+ config_file = humps.decamelize(config_file_humped)
70
+ library_config = LibraryConfiguration(**config_file["library_information"])
71
+ if library_config.ecs_tenant_id:
72
+ library_config.is_ecs = True
73
+ if library_config.ecs_tenant_id and not library_config.ecs_central_iteration_identifier:
74
+ print(
75
+ "ECS tenant ID is set, but no central iteration identifier is provided. "
76
+ "Please provide the central iteration identifier in the configuration file."
77
+ )
78
+ sys.exit("ECS Central Iteration Identifier Not Found")
79
+ return config_file, library_config
80
+
65
81
 
66
82
  def main():
67
83
  try:
@@ -79,11 +95,7 @@ def main():
79
95
  except i18n.I18nFileLoadError:
80
96
  i18n.load_config(Path(__file__).parent / "i18n_config.py")
81
97
  i18n.set("locale", args.report_language)
82
- config_file_humped = merge_load(args.configuration_path)
83
- config_file_humped["libraryInformation"]["okapiPassword"] = args.okapi_password
84
- config_file_humped["libraryInformation"]["baseFolder"] = args.base_folder_path
85
- config_file = humps.decamelize(config_file_humped)
86
- library_config = LibraryConfiguration(**config_file["library_information"])
98
+ config_file, library_config = prep_library_config(args)
87
99
  try:
88
100
  migration_task_config = next(
89
101
  t for t in config_file["migration_tasks"] if t["name"] == args.task_name
@@ -120,6 +120,9 @@ class FolderStructure:
120
120
  self.id_map_path = (
121
121
  self.results_folder / f"{str(self.object_type.name).lower()}_id_map.json"
122
122
  )
123
+ self.boundwith_relationships_map_path = (
124
+ self.results_folder / "boundwith_relationships_map.json"
125
+ )
123
126
  # Mapping files
124
127
  self.material_type_map_path = self.mapping_files_folder / "material_types.tsv"
125
128
  self.loan_type_map_path = self.mapping_files_folder / "loan_types.tsv"
@@ -139,6 +142,8 @@ class FolderStructure:
139
142
  def verify_git_ignore(gitignore: Path):
140
143
  with open(gitignore, "r+") as f:
141
144
  contents = f.read()
145
+ if "reports/" not in contents:
146
+ f.write("reports/\n")
142
147
  if "results/" not in contents:
143
148
  f.write("results/\n")
144
149
  if "archive/" not in contents:
@@ -90,13 +90,20 @@ class LibraryConfiguration(BaseModel):
90
90
  )
91
91
  multi_field_delimiter: Optional[str] = "<delimiter>"
92
92
  failed_records_threshold: Annotated[
93
- int, Field(description=("Number of failed records until the process shuts down"))
93
+ int,
94
+ Field(description=("Number of failed records until the process shuts down")),
94
95
  ] = 5000
95
96
  failed_percentage_threshold: Annotated[
96
- int, Field(description=("Percentage of failed records until the process shuts down"))
97
+ int,
98
+ Field(
99
+ description=("Percentage of failed records until the process shuts down")
100
+ ),
97
101
  ] = 20
98
102
  generic_exception_threshold: Annotated[
99
- int, Field(description=("Number of generic exceptions until the process shuts down"))
103
+ int,
104
+ Field(
105
+ description=("Number of generic exceptions until the process shuts down")
106
+ ),
100
107
  ] = 50
101
108
  library_name: str
102
109
  log_level_debug: bool
@@ -111,3 +118,34 @@ class LibraryConfiguration(BaseModel):
111
118
  add_time_stamp_to_file_names: Annotated[
112
119
  bool, Field(title="Add time stamp to file names")
113
120
  ] = False
121
+ use_gateway_url_for_uuids: Annotated[
122
+ bool,
123
+ Field(
124
+ title="Use gateway URL for UUIDs",
125
+ description=(
126
+ "If set to true, folio_uuid will use the gateway URL when generating deterministic UUIDs for FOLIO records. "
127
+ "If set to false (default), the UUIDs will be generated using the tenant_id (or ecs_tenant_id)."
128
+ ),
129
+ ),
130
+ ] = False
131
+ is_ecs: Annotated[
132
+ bool,
133
+ Field(
134
+ title="Library is running ECS FOLIO",
135
+ description=(
136
+ "If set to true, the migration is running in an ECS environment. "
137
+ "If set to false (default), the migration is running in a non-ECS environment. "
138
+ "If ecs_tenant_id is set, this will be set to true, regardless of the value here."
139
+ ),
140
+ ),
141
+ ] = False
142
+ ecs_central_iteration_identifier: Annotated[
143
+ str,
144
+ Field(
145
+ title="ECS central iteration identifier",
146
+ description=(
147
+ "The iteration_identifier value from the central tenant configuration that corresponds "
148
+ "to this configuration's iteration_identifier. Used to access the central instances_id_map."
149
+ ),
150
+ ),
151
+ ] = ""
@@ -293,42 +293,6 @@ class MapperBase:
293
293
  )
294
294
  sys.exit(1)
295
295
 
296
- def setup_boundwith_relationship_map(self, boundwith_relationship_map):
297
- new_map = {}
298
- for entry in boundwith_relationship_map:
299
- if "MFHD_ID" not in entry or not entry.get("MFHD_ID", ""):
300
- raise TransformationProcessError(
301
- "", "Column MFHD_ID missing from Boundwith relationship map", ""
302
- )
303
- if "BIB_ID" not in entry or not entry.get("BIB_ID", ""):
304
- raise TransformationProcessError(
305
- "", "Column BIB_ID missing from Boundwith relationship map", ""
306
- )
307
- instance_uuid = str(
308
- FolioUUID(
309
- str(self.folio_client.okapi_url),
310
- FOLIONamespaces.instances,
311
- entry["BIB_ID"],
312
- )
313
- )
314
- mfhd_uuid = str(
315
- FolioUUID(
316
- str(self.folio_client.okapi_url),
317
- FOLIONamespaces.holdings,
318
- entry["MFHD_ID"],
319
- )
320
- )
321
- if entry["BIB_ID"] in self.parent_id_map:
322
- new_map[mfhd_uuid] = new_map.get(mfhd_uuid, []) + [instance_uuid]
323
- else:
324
- raise TransformationRecordFailedError(
325
- entry["MFHD_ID"],
326
- "Boundwith relationship map contains a BIB_ID id not in the instance id map. No boundwith holdings created.",
327
- entry["BIB_ID"],
328
- )
329
-
330
- return new_map
331
-
332
296
  def save_id_map_file(self, path, legacy_map: dict):
333
297
  with open(path, "w") as legacy_map_file:
334
298
  for id_string in legacy_map.values():
@@ -417,7 +381,7 @@ class MapperBase:
417
381
  "holdingsRecordId": bound_with_holding_uuid,
418
382
  "itemId": str(
419
383
  FolioUUID(
420
- self.folio_client.okapi_url,
384
+ self.base_string_for_folio_uuid,
421
385
  FOLIONamespaces.items,
422
386
  legacy_item_id,
423
387
  )
@@ -470,12 +434,21 @@ class MapperBase:
470
434
  def generate_boundwith_holding_uuid(self, holding_uuid, instance_uuid):
471
435
  return str(
472
436
  FolioUUID(
473
- self.folio_client.okapi_url,
437
+ self.base_string_for_folio_uuid,
474
438
  FOLIONamespaces.holdings,
475
439
  f"{holding_uuid}-{instance_uuid}",
476
440
  )
477
441
  )
478
442
 
443
+ @property
444
+ def base_string_for_folio_uuid(self):
445
+ if self.library_configuration.use_gateway_url_for_uuids and not self.library_configuration.is_ecs:
446
+ return str(self.folio_client.okapi_url)
447
+ elif self.library_configuration.ecs_tenant_id:
448
+ return str(self.library_configuration.ecs_tenant_id)
449
+ else:
450
+ return str(self.library_configuration.tenant_id)
451
+
479
452
  @staticmethod
480
453
  def validate_location_map(location_map: List[Dict], locations: List[Dict]) -> List[Dict]:
481
454
  mapped_codes = [x['folio_code'] for x in location_map]
@@ -121,7 +121,7 @@ class CoursesMapper(MappingFileMapperBase):
121
121
  def get_uuid(self, composite_course, object_type: FOLIONamespaces, idx: int = 0):
122
122
  return str(
123
123
  FolioUUID(
124
- self.folio_client.okapi_url,
124
+ self.base_string_for_folio_uuid,
125
125
  object_type,
126
126
  composite_course[1] if idx == 0 else f"{composite_course[1]}_{idx}",
127
127
  )
@@ -42,7 +42,6 @@ class ItemMapper(MappingFileMapperBase):
42
42
  temporary_loan_type_mapping,
43
43
  temporary_location_mapping,
44
44
  library_configuration: LibraryConfiguration,
45
- boundwith_relationship_map,
46
45
  task_configuration: AbstractTaskConfiguration,
47
46
  ):
48
47
  item_schema = folio_client.get_item_schema()
@@ -99,9 +98,6 @@ class ItemMapper(MappingFileMapperBase):
99
98
  "name",
100
99
  "PermanentLoanTypeMapping",
101
100
  )
102
- self.boundwith_relationship_map = self.setup_boundwith_relationship_map(
103
- boundwith_relationship_map
104
- )
105
101
 
106
102
  self.material_type_mapping = RefDataMapping(
107
103
  self.folio_client,
@@ -208,7 +208,7 @@ class MappingFileMapperBase(MapperBase):
208
208
  )
209
209
  generated_id = str(
210
210
  FolioUUID(
211
- self.folio_client.okapi_url,
211
+ self.base_string_for_folio_uuid,
212
212
  object_type,
213
213
  legacy_id,
214
214
  )
@@ -158,7 +158,7 @@ class UserMapper(MappingFileMapperBase):
158
158
  self.departments_mapping,
159
159
  legacy_user,
160
160
  index_or_id,
161
- False,
161
+ True,
162
162
  )
163
163
  elif folio_prop_name in ["expirationDate", "enrollmentDate", "personal.dateOfBirth"]:
164
164
  return self.get_parsed_date(mapped_value, folio_prop_name)
@@ -133,7 +133,7 @@ class AuthorityMapper(RulesMapperBase):
133
133
  folio_authority = {}
134
134
  folio_authority["id"] = str(
135
135
  FolioUUID(
136
- str(self.folio_client.okapi_url),
136
+ self.base_string_for_folio_uuid,
137
137
  FOLIONamespaces.authorities,
138
138
  str(legacy_ids[-1]),
139
139
  )
@@ -810,8 +810,8 @@ class RulesMapperBase(MapperBase):
810
810
  )
811
811
  data_import_marc_file.write(marc_record.as_marc())
812
812
 
813
- @staticmethod
814
813
  def save_source_record(
814
+ self,
815
815
  srs_records_file,
816
816
  record_type: FOLIONamespaces,
817
817
  folio_client: FolioClient,
@@ -831,7 +831,7 @@ class RulesMapperBase(MapperBase):
831
831
  legacy_ids (List[str]): _description_
832
832
  suppress (bool): _description_
833
833
  """
834
- srs_id = RulesMapperBase.create_srs_id(record_type, folio_client.okapi_url, legacy_ids[-1])
834
+ srs_id = self.create_srs_id(record_type, legacy_ids[-1])
835
835
 
836
836
  marc_record.add_ordered_field(
837
837
  Field(
@@ -850,7 +850,7 @@ class RulesMapperBase(MapperBase):
850
850
  logging.exception(
851
851
  "Something is wrong with the marc record's leader: %s, %s", marc_record.leader, ee
852
852
  )
853
- srs_record_string = RulesMapperBase.get_srs_string(
853
+ srs_record_string = self.get_srs_string(
854
854
  marc_record,
855
855
  folio_record,
856
856
  srs_id,
@@ -859,8 +859,7 @@ class RulesMapperBase(MapperBase):
859
859
  )
860
860
  srs_records_file.write(f"{srs_record_string}\n")
861
861
 
862
- @staticmethod
863
- def create_srs_id(record_type, okapi_url: str, legacy_id: str):
862
+ def create_srs_id(self, record_type, legacy_id: str):
864
863
  srs_types = {
865
864
  FOLIONamespaces.holdings: FOLIONamespaces.srs_records_holdingsrecord,
866
865
  FOLIONamespaces.instances: FOLIONamespaces.srs_records_bib,
@@ -868,7 +867,13 @@ class RulesMapperBase(MapperBase):
868
867
  FOLIONamespaces.edifact: FOLIONamespaces.srs_records_edifact,
869
868
  }
870
869
 
871
- return str(FolioUUID(okapi_url, srs_types.get(record_type), legacy_id))
870
+ return str(
871
+ FolioUUID(
872
+ self.base_string_for_folio_uuid,
873
+ srs_types.get(record_type),
874
+ legacy_id
875
+ )
876
+ )
872
877
 
873
878
  @staticmethod
874
879
  def get_bib_id_from_907y(marc_record: Record, index_or_legacy_id):
@@ -71,7 +71,7 @@ class BibsRulesMapper(RulesMapperBase):
71
71
  folio_instance = {}
72
72
  folio_instance["id"] = str(
73
73
  FolioUUID(
74
- str(self.folio_client.okapi_url),
74
+ self.base_string_for_folio_uuid,
75
75
  FOLIONamespaces.instances,
76
76
  str(legacy_ids[-1]),
77
77
  )
@@ -211,7 +211,7 @@ class RulesMapperHoldings(RulesMapperBase):
211
211
  folio_holding: dict = {}
212
212
  folio_holding["id"] = str(
213
213
  FolioUUID(
214
- str(self.folio_client.okapi_url),
214
+ self.base_string_for_folio_uuid,
215
215
  FOLIONamespaces.holdings,
216
216
  str(legacy_ids[0]),
217
217
  )
@@ -461,3 +461,54 @@ class RulesMapperHoldings(RulesMapperBase):
461
461
  idx, f"No legacy id found in record from {marc_path}", ""
462
462
  )
463
463
  return results
464
+
465
+ def verity_boundwith_map_entry(self, entry):
466
+ if "MFHD_ID" not in entry or not entry.get("MFHD_ID", ""):
467
+ raise TransformationProcessError(
468
+ "", "Column MFHD_ID missing from Boundwith relationship map", ""
469
+ )
470
+ if "BIB_ID" not in entry or not entry.get("BIB_ID", ""):
471
+ raise TransformationProcessError(
472
+ "", "Column BIB_ID missing from Boundwith relationship map", ""
473
+ )
474
+
475
+ def setup_boundwith_relationship_map(self, boundwith_relationship_map):
476
+ """
477
+ Creates a map of MFHD_ID to BIB_ID for boundwith relationships.
478
+
479
+ Arguments:
480
+ boundwith_relationship_map: A list of dictionaries containing the MFHD_ID and BIB_ID.
481
+
482
+ Returns:
483
+ A dictionary mapping MFHD_ID to a list of BIB_IDs.
484
+
485
+ Raises:
486
+ TransformationProcessError: If MFHD_ID or BIB_ID is missing from the entry or if the instance_uuid is not in the parent_id_map.
487
+ TransformationRecordFailedError: If BIB_ID is not in the instance id map.
488
+ """
489
+ new_map = {}
490
+ for idx, entry in enumerate(boundwith_relationship_map):
491
+ self.verity_boundwith_map_entry(entry)
492
+ mfhd_uuid = str(
493
+ FolioUUID(
494
+ self.base_string_for_folio_uuid,
495
+ FOLIONamespaces.holdings,
496
+ entry["MFHD_ID"],
497
+ )
498
+ )
499
+ try:
500
+ parent_id_tuple = self.get_bw_instance_id_map_tuple(entry)
501
+ new_map[mfhd_uuid] = new_map.get(mfhd_uuid, []) + [parent_id_tuple[1]]
502
+ except TransformationRecordFailedError as trfe:
503
+ self.handle_transformation_record_failed_error(idx, trfe)
504
+ return new_map
505
+
506
+ def get_bw_instance_id_map_tuple(self, entry):
507
+ try:
508
+ return self.parent_id_map[entry["BIB_ID"]]
509
+ except KeyError:
510
+ raise TransformationRecordFailedError(
511
+ entry["MFHD_ID"],
512
+ "Boundwith relationship map contains a BIB_ID id not in the instance id map. No boundwith holdings created for this BIB_ID.",
513
+ entry["BIB_ID"],
514
+ )
@@ -182,7 +182,7 @@ class HoldingsCsvTransformer(MigrationTaskBase):
182
182
  self.load_mapped_fields(),
183
183
  self.load_location_map(),
184
184
  self.load_call_number_type_map(),
185
- self.load_id_map(self.folder_structure.instance_id_map_path, True),
185
+ self.load_instance_id_map(True),
186
186
  library_config,
187
187
  )
188
188
  self.holdings = {}
@@ -296,18 +296,6 @@ class HoldingsCsvTransformer(MigrationTaskBase):
296
296
  )
297
297
  return holdings_map
298
298
 
299
- def load_instance_id_map(self):
300
- res = {}
301
- with open(self.folder_structure.instance_id_map_path, "r") as instance_id_map_file:
302
- for index, json_string in enumerate(instance_id_map_file):
303
- # Format:{"legacy_id", "folio_id","instanceLevelCallNumber"}
304
- if index % 500000 == 0:
305
- print(f"{index} instance ids loaded to map", end="\r")
306
- map_object = json.loads(json_string)
307
- res[map_object["legacy_id"]] = map_object
308
- logging.info("Loaded %s migrated instance IDs", (index + 1))
309
- return res
310
-
311
299
  def do_work(self):
312
300
  logging.info("Starting....")
313
301
  for file_def in self.task_config.files:
@@ -229,9 +229,7 @@ class HoldingsMarcTransformer(MigrationTaskBase):
229
229
  self.check_source_files(
230
230
  self.folder_structure.legacy_records_folder, self.task_config.files
231
231
  )
232
- self.instance_id_map = self.load_id_map(
233
- self.folder_structure.instance_id_map_path, True
234
- )
232
+ self.instance_id_map = self.load_instance_id_map(True)
235
233
  self.mapper = RulesMapperHoldings(
236
234
  self.folio_client,
237
235
  self.location_map,
@@ -283,6 +281,17 @@ class HoldingsMarcTransformer(MigrationTaskBase):
283
281
  logging.info("Done. Transformer Wrapping up...")
284
282
  self.extradata_writer.flush()
285
283
  self.processor.wrap_up()
284
+ if self.mapper.boundwith_relationship_map:
285
+ with open(
286
+ self.folder_structure.boundwith_relationships_map_path, "w+"
287
+ ) as boundwith_relationship_file:
288
+ logging.info(
289
+ "Writing boundwiths relationship map to %s",
290
+ boundwith_relationship_file.name,
291
+ )
292
+ for key, val in self.mapper.boundwith_relationship_map.items():
293
+ boundwith_relationship_file.write(json.dumps((key, val)) + "\n")
294
+
286
295
  with open(self.folder_structure.migration_reports_file, "w+") as report_file:
287
296
  self.mapper.migration_report.write_migration_report(
288
297
  i18n.t("Bibliographic records transformation report"),
@@ -233,7 +233,8 @@ class ItemsTransformer(MigrationTaskBase):
233
233
  ).is_file():
234
234
  temporary_loan_type_mapping = self.load_ref_data_mapping_file(
235
235
  "temporaryLoanTypeId",
236
- self.folder_structure.temp_loan_type_map_path,
236
+ self.folder_structure.mapping_files_folder
237
+ / self.task_config.temp_loan_types_map_file_name,
237
238
  self.folio_keys,
238
239
  )
239
240
  else:
@@ -243,20 +244,9 @@ class ItemsTransformer(MigrationTaskBase):
243
244
  )
244
245
  temporary_loan_type_mapping = None
245
246
  # Load Boundwith relationship map
246
- self.boundwith_relationship_map = []
247
+ self.boundwith_relationship_map = {}
247
248
  if self.task_config.boundwith_relationship_file_path:
248
- with open(
249
- self.folder_structure.data_folder
250
- / FOLIONamespaces.holdings.name
251
- / self.task_config.boundwith_relationship_file_path
252
- ) as boundwith_relationship_file:
253
- self.boundwith_relationship_map = list(
254
- csv.DictReader(boundwith_relationship_file, dialect="tsv")
255
- )
256
- logging.info(
257
- "Rows in Bound with relationship map: %s", len(self.boundwith_relationship_map)
258
- )
259
-
249
+ self.load_boundwith_relationships()
260
250
  if (
261
251
  self.folder_structure.mapping_files_folder
262
252
  / self.task_config.temp_location_map_file_name
@@ -312,7 +302,6 @@ class ItemsTransformer(MigrationTaskBase):
312
302
  temporary_loan_type_mapping,
313
303
  temporary_location_mapping,
314
304
  self.library_configuration,
315
- self.boundwith_relationship_map,
316
305
  self.task_configuration
317
306
  )
318
307
  if (
@@ -367,9 +356,9 @@ class ItemsTransformer(MigrationTaskBase):
367
356
  self.mapper.perform_additional_mappings(folio_rec, file_def)
368
357
  self.handle_circiulation_notes(folio_rec, self.folio_client.current_user)
369
358
  self.handle_notes(folio_rec)
370
- if folio_rec["holdingsRecordId"] in self.mapper.boundwith_relationship_map:
359
+ if folio_rec["holdingsRecordId"] in self.boundwith_relationship_map:
371
360
  for idx_, instance_id in enumerate(
372
- self.mapper.boundwith_relationship_map.get(
361
+ self.boundwith_relationship_map.get(
373
362
  folio_rec["holdingsRecordId"]
374
363
  )
375
364
  ):
@@ -456,6 +445,22 @@ class ItemsTransformer(MigrationTaskBase):
456
445
  else:
457
446
  del folio_rec["circulationNotes"]
458
447
 
448
+ def load_boundwith_relationships(self):
449
+ try:
450
+ with open(
451
+ self.folder_structure.boundwith_relationships_map_path
452
+ ) as boundwith_relationship_file:
453
+ self.boundwith_relationship_map = dict(
454
+ json.loads(x) for x in boundwith_relationship_file
455
+ )
456
+ logging.info(
457
+ "Rows in Bound with relationship map: %s", len(self.boundwith_relationship_map)
458
+ )
459
+ except FileNotFoundError:
460
+ raise TransformationProcessError(
461
+ "", "Boundwith relationship file specified, but relationships file from holdings transformation not found. ", self.folder_structure.boundwith_relationships_map_path
462
+ )
463
+
459
464
  def wrap_up(self):
460
465
  logging.info("Done. Transformer wrapping up...")
461
466
  self.extradata_writer.flush()
@@ -14,12 +14,12 @@ from pydantic import Field
14
14
  import i18n
15
15
  from dateutil import parser as du_parser
16
16
  from folio_uuid.folio_namespaces import FOLIONamespaces
17
+ from art import tprint
17
18
 
18
19
  from folio_migration_tools.circulation_helper import CirculationHelper
19
20
  from folio_migration_tools.helper import Helper
20
21
  from folio_migration_tools.library_configuration import (
21
22
  FileDefinition,
22
- FolioRelease,
23
23
  LibraryConfiguration,
24
24
  )
25
25
  from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
@@ -781,11 +781,4 @@ def timings(t0, t0func, num_objects):
781
781
 
782
782
 
783
783
  def print_smtp_warning():
784
- s = r"""
785
- _____ __ __ _____ ______ ___
786
- / ____| | \/ | |_ _| | __ | |__ \\
787
- | (___ | \ / | | | | |__|_| ) |
788
- \___ \ | |\/| | | | | | / /
789
- |_____/ |_| |_| |_| |_| (_)
790
- """ # noqa: E501, W605
791
- print(s)
784
+ tprint("\nSMTP?\n", space=2)
@@ -9,6 +9,7 @@ from abc import abstractmethod
9
9
  from datetime import datetime, timezone
10
10
  from genericpath import isfile
11
11
  from pathlib import Path
12
+ from typing import Optional
12
13
 
13
14
  import folioclient
14
15
  from folio_uuid.folio_namespaces import FOLIONamespaces
@@ -54,6 +55,15 @@ class MigrationTaskBase:
54
55
  {"x-okapi-tenant": self.ecs_tenant_id} if self.ecs_tenant_id else {}
55
56
  )
56
57
  self.folio_client.okapi_headers.update(self.ecs_tenant_header)
58
+ self.central_folder_structure: Optional[FolderStructure] = None
59
+ if library_configuration.is_ecs and library_configuration.ecs_central_iteration_identifier:
60
+ self.central_folder_structure = FolderStructure(
61
+ library_configuration.base_folder,
62
+ FOLIONamespaces.instances,
63
+ task_configuration.name,
64
+ library_configuration.ecs_central_iteration_identifier,
65
+ library_configuration.add_time_stamp_to_file_names,
66
+ )
57
67
  self.folder_structure: FolderStructure = FolderStructure(
58
68
  library_configuration.base_folder,
59
69
  self.get_object_type(),
@@ -66,6 +76,8 @@ class MigrationTaskBase:
66
76
  self.object_type = self.get_object_type()
67
77
  try:
68
78
  self.folder_structure.setup_migration_file_structure()
79
+ if self.central_folder_structure:
80
+ self.central_folder_structure.setup_migration_file_structure()
69
81
  # Initiate Worker
70
82
  except FileNotFoundError as fne:
71
83
  logging.error(fne)
@@ -143,15 +155,47 @@ class MigrationTaskBase:
143
155
  for filename in files:
144
156
  logging.info("\t%s", filename)
145
157
 
158
+ def load_instance_id_map(self, raise_if_empty=True) -> dict:
159
+ """
160
+ This method handles loading instance id maps for holdings and other transformations that require it.
161
+ This is in the base class because multiple tasks need it. It exists because instances in an ECS environment
162
+ are transformed for the central and data tenants separately, but the data tenants need to know about
163
+ the central tenant instance ids. This is a bit of a hack, but it works for now.
164
+ """
165
+ map_files = []
166
+ if self.library_configuration.is_ecs and self.central_folder_structure:
167
+ logging.info(
168
+ "Loading ECS central tenant instance id map from %s", self.central_folder_structure.instance_id_map_path
169
+ )
170
+ instance_id_map = self.load_id_map(
171
+ self.central_folder_structure.instance_id_map_path,
172
+ raise_if_empty=False,
173
+ )
174
+ map_files.append(str(self.central_folder_structure.instance_id_map_path))
175
+ logging.info(
176
+ "Loading member tenant isntance id map from %s",
177
+ self.folder_structure.instance_id_map_path
178
+ )
179
+ instance_id_map = self.load_id_map(
180
+ self.folder_structure.instance_id_map_path,
181
+ raise_if_empty=False,
182
+ existing_id_map=instance_id_map,
183
+ )
184
+ map_files.append(str(self.folder_structure.instance_id_map_path))
185
+ if not any(instance_id_map) and raise_if_empty:
186
+ map_file_paths = ", ".join(map_files)
187
+ raise TransformationProcessError("", "Instance id map is empty", map_file_paths)
188
+ return instance_id_map
189
+
146
190
  @staticmethod
147
- def load_id_map(map_path, raise_if_empty=False):
191
+ def load_id_map(map_path, raise_if_empty=False, existing_id_map={}):
148
192
  if not isfile(map_path):
149
- logging.warn(
193
+ logging.warning(
150
194
  "No legacy id map found at %s. Will build one from scratch", map_path
151
195
  )
152
196
  return {}
153
- id_map = {}
154
- loaded_rows = 0
197
+ id_map = existing_id_map
198
+ loaded_rows = len(id_map)
155
199
  with open(map_path) as id_map_file:
156
200
  for index, json_string in enumerate(id_map_file, start=1):
157
201
  loaded_rows = index
@@ -159,12 +203,12 @@ class MigrationTaskBase:
159
203
  map_tuple = json.loads(json_string)
160
204
  if loaded_rows % 500000 == 0:
161
205
  print(
162
- f"{loaded_rows + 1} ids loaded to map. Last Id: {map_tuple[0]}",
206
+ f"{loaded_rows + 1} ids loaded to map. Last Id: {map_tuple[0]} ",
163
207
  end="\r",
164
208
  )
165
209
 
166
210
  id_map[map_tuple[0]] = map_tuple
167
- logging.info("Loaded %s migrated IDs", loaded_rows)
211
+ logging.info("Loaded %s migrated IDs from %s", loaded_rows, id_map_file.name)
168
212
  if not any(id_map) and raise_if_empty:
169
213
  raise TransformationProcessError("", "Legacy id map is empty", map_path)
170
214
  return id_map
@@ -177,7 +177,7 @@ class OrdersTransformer(MigrationTaskBase):
177
177
  self.library_configuration,
178
178
  self.orders_map,
179
179
  self.load_id_map(self.folder_structure.organizations_id_map_path, True),
180
- self.load_id_map(self.folder_structure.instance_id_map_path, True),
180
+ self.load_instance_id_map(True),
181
181
  self.load_ref_data_mapping_file(
182
182
  "acquisitionMethod",
183
183
  self.folder_structure.mapping_files_folder
@@ -6,6 +6,7 @@ from pydantic import Field
6
6
 
7
7
  import i18n
8
8
  from folio_uuid.folio_namespaces import FOLIONamespaces
9
+ from art import tprint
9
10
 
10
11
  from folio_migration_tools.custom_exceptions import (
11
12
  TransformationProcessError,
@@ -282,16 +283,7 @@ class UserTransformer(MigrationTaskBase):
282
283
 
283
284
 
284
285
  def print_email_warning():
285
- s = (
286
- " ______ __ __ _____ _ _____ ___ \n" # noqa: E501, W605
287
- " | ____| | \\/ | /\\ |_ _| | | / ____| |__ \\ \n" # noqa: E501, W605
288
- " | |__ | \\ / | / \\ | | | | | (___ ) |\n" # noqa: E501, W605
289
- " | __| | |\\/| | / /\\ \\ | | | | \\___ \\ / / \n" # noqa: E501, W605
290
- " |______| |_| |_| /_/ \\_\\ |_____| |______| |_____/ (_) \n" # noqa: E501, W605
291
- " \n" # noqa: E501, W605
292
- " \n"
293
- )
294
- print(s)
286
+ tprint("\nEMAILS?\n", space=2)
295
287
 
296
288
 
297
289
  def remove_empty_addresses(folio_user):
@@ -12,6 +12,10 @@ from folio_migration_tools.mapping_file_transformation.holdings_mapper import (
12
12
  HoldingsMapper,
13
13
  )
14
14
  from folio_migration_tools.migration_report import MigrationReport
15
+ from folio_migration_tools.library_configuration import (
16
+ LibraryConfiguration,
17
+ FolioRelease,
18
+ )
15
19
 
16
20
 
17
21
  def mocked_holdings_mapper() -> Mock:
@@ -242,3 +246,62 @@ def folio_get_single_object_mocked(*args, **kwargs):
242
246
 
243
247
  def folio_get_from_github(owner, repo, file_path):
244
248
  return FolioClient.get_latest_from_github(owner, repo, file_path, "")
249
+
250
+ OKAPI_URL = "http://localhost:9130"
251
+ LIBRARY_NAME = "Test Library"
252
+
253
+ def get_mocked_library_config():
254
+ return LibraryConfiguration(
255
+ okapi_url=OKAPI_URL,
256
+ tenant_id="test_tenant",
257
+ okapi_username="test_user",
258
+ okapi_password="test_password",
259
+ base_folder=Path("."),
260
+ library_name=LIBRARY_NAME,
261
+ log_level_debug=False,
262
+ folio_release=FolioRelease.sunflower,
263
+ iteration_identifier="test_iteration"
264
+ )
265
+
266
+ def get_mocked_ecs_central_libarary_config():
267
+ return LibraryConfiguration(
268
+ okapi_url=OKAPI_URL,
269
+ tenant_id="test_tenant",
270
+ okapi_username="test_user",
271
+ okapi_password="test_password",
272
+ base_folder=Path("."),
273
+ library_name=LIBRARY_NAME,
274
+ log_level_debug=False,
275
+ folio_release=FolioRelease.sunflower,
276
+ iteration_identifier="central_iteration",
277
+ is_ecs=True,
278
+ )
279
+
280
+ def get_mocked_ecs_member_libarary_config():
281
+ return LibraryConfiguration(
282
+ okapi_url=OKAPI_URL,
283
+ tenant_id="test_tenant",
284
+ ecs_tenant_id="test_ecs_tenant",
285
+ okapi_username="test_user",
286
+ okapi_password="test_password",
287
+ base_folder=Path("."),
288
+ library_name=LIBRARY_NAME,
289
+ log_level_debug=False,
290
+ folio_release=FolioRelease.sunflower,
291
+ iteration_identifier="member_iteration",
292
+ ecs_central_iteration_identifier="central_iteration",
293
+ is_ecs=True,
294
+ )
295
+
296
+ def get_mocked_folder_structure():
297
+ mock_fs = MagicMock()
298
+ mock_fs.mapping_files = Path("mapping_files")
299
+ mock_fs.results_folder = Path("results")
300
+ mock_fs.legacy_records_folder = Path("source_files")
301
+ mock_fs.logs_folder = Path("logs")
302
+ mock_fs.migration_reports_file = Path("/dev/null")
303
+ mock_fs.transformation_extra_data_path = Path("transformation_extra_data")
304
+ mock_fs.transformation_log_path = Path("/dev/null")
305
+ mock_fs.data_issue_file_path = Path("/dev/null")
306
+ mock_fs.failed_marc_recs_file = Path("failed_marc_recs.txt")
307
+ return mock_fs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: folio_migration_tools
3
- Version: 1.9.0rc7
3
+ Version: 1.9.0rc8
4
4
  Summary: A tool allowing you to migrate data from legacy ILS:s (Library systems) into FOLIO LSP
5
5
  License: MIT
6
6
  Keywords: FOLIO,ILS,LSP,Library Systems,MARC21,Library data
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
17
  Provides-Extra: docs
18
18
  Requires-Dist: argparse-prompt (>=0.0.5,<0.0.6)
19
+ Requires-Dist: art (>=6.5,<7.0)
19
20
  Requires-Dist: deepdiff (>=6.2.3,<7.0.0)
20
21
  Requires-Dist: defusedxml (>=0.7.1,<0.8.0)
21
22
  Requires-Dist: folio-uuid (>=0.2.8,<0.3.0)
@@ -1,28 +1,28 @@
1
1
  folio_migration_tools/__init__.py,sha256=DXvzUKFSpSZjflFWaNm0L8yhFk0u7RVIvQMskwMmKFc,238
2
- folio_migration_tools/__main__.py,sha256=0rbCmTq4HTxj8M3UjvX3rOEkq6-YqvaSGEMmuCORbho,7282
2
+ folio_migration_tools/__main__.py,sha256=_0el5EyJhG8lPj--gM5zMfVJgTt9RhrJo7rmuOY20sM,7883
3
3
  folio_migration_tools/circulation_helper.py,sha256=2kAkLM6caPiep0ZtBkMICbRDh53KdfdH21oEX1eMRDI,14193
4
4
  folio_migration_tools/colors.py,sha256=GP0wdI_GZ2WD5SjrbPN-S3u8vvN_u6rGQIBBcWv_0ZM,227
5
5
  folio_migration_tools/config_file_load.py,sha256=zHHa6NDkN6EJiQE4DgjrFQPVKsd70POsfbGkB8308jg,2822
6
6
  folio_migration_tools/custom_dict.py,sha256=-FUnhKp90Dg8EHlY6twx-PYQxBUWEO7FgxL2b7pf-xk,678
7
7
  folio_migration_tools/custom_exceptions.py,sha256=1zgOKy3NBUVGG6i9YxK6w2Hntlea8MHmm7mdnjBtzvQ,2687
8
8
  folio_migration_tools/extradata_writer.py,sha256=fuchNcMc6BYb9IyfAcvXg7X4J2TfX6YiROfT2hr0JMw,1678
9
- folio_migration_tools/folder_structure.py,sha256=yyVvbkM9PbczSHNI8vK0Ru7i0x4nbYGzrRriXrnIh38,6715
9
+ folio_migration_tools/folder_structure.py,sha256=bZlmKGtxdytWcqjnM2lE4Vpx4nHyYRk7CNL1tZhLtXY,6917
10
10
  folio_migration_tools/helper.py,sha256=KkOkNAGO_fuYqxdLrsbLzCJLQHUrFZG1NzD4RmpQ-KM,2804
11
11
  folio_migration_tools/holdings_helper.py,sha256=yJpz6aJrKRBiJ1MtT5bs2vXAc88uJuGh2_KDuCySOKc,7559
12
12
  folio_migration_tools/i18n_config.py,sha256=3AH_2b9zTsxE4XTe4isM_zYtPJSlK0ix6eBmV7kAYUM,228
13
- folio_migration_tools/library_configuration.py,sha256=JE23VSUKDeCxzj_fOeQwkXF4fwd_y9s_hNy91YtEy7A,3514
14
- folio_migration_tools/mapper_base.py,sha256=ftYQJ6UDsP96EvCr5t3yMJemsmSNXsSH5zChDLu4Pp8,21224
13
+ folio_migration_tools/library_configuration.py,sha256=UhHNiz9SI2nEnm6XME2ESD33LNwqdRzIgCU9kjYPHQQ,4863
14
+ folio_migration_tools/mapper_base.py,sha256=WnUA2KBJrvAWRuq7KsTPi9YXD76pXfX7lyI5pExEwLI,20139
15
15
  folio_migration_tools/mapping_file_transformation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- folio_migration_tools/mapping_file_transformation/courses_mapper.py,sha256=mJQxxeTn1bCYb2zwFYyXJ6EGZpJ0DsmwOY3nED7D_gQ,8091
16
+ folio_migration_tools/mapping_file_transformation/courses_mapper.py,sha256=RuNkdG9XumpgPO3Zvcx_JYzZ598Xle_AMNf18zLR2UM,8095
17
17
  folio_migration_tools/mapping_file_transformation/holdings_mapper.py,sha256=nJS-xx1LszvbYfw0qdTUHX9xXHlxS7wP5mYmixFMh8A,7221
18
- folio_migration_tools/mapping_file_transformation/item_mapper.py,sha256=KiE-y6ZcKAV2XMqrgax3mx3KHB_oRxBfCBSanIBs95E,10853
18
+ folio_migration_tools/mapping_file_transformation/item_mapper.py,sha256=YYZFgNoDVuSO_mRuaDNZ6-6bYbEtYFtfbIZ1MFPBAgc,10687
19
19
  folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py,sha256=nCkqbxaDHKxMuqQHh_afxQp48YrVD-SeCZ0L1iGvnkk,13402
20
- folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py,sha256=vu-5dw20NbxpDTDNq2zIgbto8KkWMf6ImXj2U0XbHPg,37812
20
+ folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py,sha256=bLL6tTqqv2MOjZlowjL8lngYP09F_iwfFikEpjB4nmI,37816
21
21
  folio_migration_tools/mapping_file_transformation/notes_mapper.py,sha256=auLQZqa4rSJo_MIV4Lc5-LG8RcBpp2bnKH243qNYq_0,3470
22
22
  folio_migration_tools/mapping_file_transformation/order_mapper.py,sha256=k-kIuf2ceXrPWe3oVnfhuQlE7eglcx6PDLVJtddkeiM,17680
23
23
  folio_migration_tools/mapping_file_transformation/organization_mapper.py,sha256=0zjw0-C-qTYH9GC6FDBElucWCZWdoOiTHOY7q9_4NQg,14571
24
24
  folio_migration_tools/mapping_file_transformation/ref_data_mapping.py,sha256=qFsn_LwKZeKFdOudfEQnNA3DEHOdNQVKzTPdZAlDPX0,8864
25
- folio_migration_tools/mapping_file_transformation/user_mapper.py,sha256=oWuIPRQL0anF_qTVFibHtc1oOaqyKCBH4O1hX5rQAZQ,7806
25
+ folio_migration_tools/mapping_file_transformation/user_mapper.py,sha256=Q8418BdXdCuEDxfoXqLCjWy1lUxhQNLRwSE5Gi1lqoA,7805
26
26
  folio_migration_tools/marc_rules_transformation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  folio_migration_tools/marc_rules_transformation/conditions.py,sha256=ttTZISieqveu3YpvpnawHh3In1_DNQMTziI5yasfmWU,39142
28
28
  folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py,sha256=lTb5QWEAgwyFHy5vdSK6oDl1Q5v2GnzuV04xWV3p4rc,12401
@@ -30,38 +30,38 @@ folio_migration_tools/marc_rules_transformation/hrid_handler.py,sha256=Ihdv0_1q7
30
30
  folio_migration_tools/marc_rules_transformation/loc_language_codes.xml,sha256=ztn2_yKws6qySL4oSsZh7sOjxq5bCC1PhAnXJdtgmJ0,382912
31
31
  folio_migration_tools/marc_rules_transformation/marc_file_processor.py,sha256=WkOQRDi7f4PZ5qmVH3Q-1_zdGEKYSvOGC6jixDwDp98,12349
32
32
  folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py,sha256=9ATjYMRAjy0QcXtmNZaHVhHLJ5hE1WUgOcF6KMJjbgo,5309
33
- folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py,sha256=GFw8j9UtCxnUdLShmPzJa1MpCK8a0NkQIN5C3jyouRs,9604
34
- folio_migration_tools/marc_rules_transformation/rules_mapper_base.py,sha256=-vJDMNZe-7JbARgybVj6lLtZM79PgWU9V_k23330uLM,41195
35
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py,sha256=ckVeysbpW9s19pmHvogdRFOCouzz17Y6oKJD0_QfQAk,28924
36
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py,sha256=p_WIsn-6VphLNVCeDyvTEv-tdYFU5QuV4GhnM4gvwt8,19046
33
+ folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py,sha256=e-wwJs8s8qEgIp8NvQgjx9lEyv7uvt08Fp6fPsy1GK8,9603
34
+ folio_migration_tools/marc_rules_transformation/rules_mapper_base.py,sha256=C2jTzdXGkGjE3EWHxUh8jJeqE9tVk0qwRWVxFPZUj-Y,41223
35
+ folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py,sha256=ZZHsuxlrHRcxkWPeiTjze0SahkNW_rhY3vkOQKnm1cU,28923
36
+ folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py,sha256=wTZ2x8VIvCuLuNJLsbUAMCVjbMN7SS1teq0G6LAcOhU,21240
37
37
  folio_migration_tools/migration_report.py,sha256=BkRspM1hwTBnWeqsHamf7yVEofzLj560Q-9G--O00hw,4258
38
38
  folio_migration_tools/migration_tasks/__init__.py,sha256=ZkbY_yGyB84Ke8OMlYUzyyBj4cxxNrhMTwQlu_GbdDs,211
39
39
  folio_migration_tools/migration_tasks/authority_transformer.py,sha256=AoXg9s-GLO3yEEDCrQV7hc4YVXxwxsdxDdpj1zhHydE,4251
40
40
  folio_migration_tools/migration_tasks/batch_poster.py,sha256=wI4lCXU5BQDbKErF6pQxT6srq_Wf_nfFAJc4f1sRCoo,36388
41
41
  folio_migration_tools/migration_tasks/bibs_transformer.py,sha256=XzlPo-0uuugJA4SM80xOlOj5nDK6OMDXFnAYg80hOBc,7791
42
42
  folio_migration_tools/migration_tasks/courses_migrator.py,sha256=CzXnsu-KGP7B4zcINJzLYUqz47D16NuFfzu_DPqRlTQ,7061
43
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py,sha256=WT-RlDRm2ILr2-2shfG3TZ3nlSfqxEXT3TklZSqtJCM,22311
44
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py,sha256=8dtrhxyA9hbISISzpvMJGYaMaDbtZ1MOZeoJJF5lk24,11164
45
- folio_migration_tools/migration_tasks/items_transformer.py,sha256=NNG7AdS_iuWurdySnWZ0o8kw3KNWPAvkRaHpW-18nV0,18779
46
- folio_migration_tools/migration_tasks/loans_migrator.py,sha256=4n7zbwljX_jgj9ltnxZAegjN3e8QJMjr6JJa5XfVueY,34771
43
+ folio_migration_tools/migration_tasks/holdings_csv_transformer.py,sha256=NtysoayEIqQ8c_GNcRi6LXDYR-7OLmqFCfciMwzsyT4,21668
44
+ folio_migration_tools/migration_tasks/holdings_marc_transformer.py,sha256=gL2LoXgavVQDpIH-t2vF2za04W8IjBul7MiVifuzvD8,11637
45
+ folio_migration_tools/migration_tasks/items_transformer.py,sha256=qk0sLPBxE5MtPnpLzO_gEhVVe1BqHHnpn2Zaz_vo1RY,19083
46
+ folio_migration_tools/migration_tasks/loans_migrator.py,sha256=JE1e0i2HFzhYl05SqEkg79p9KzwSq_hPboVT9mJhgmk,34510
47
47
  folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py,sha256=CnmlTge7nChUJ10EiUkriQtJlVxWqglgfhjgneh2_yM,7247
48
- folio_migration_tools/migration_tasks/migration_task_base.py,sha256=8enHPNrgOHZs5sDGsz0yMPXap0HBprz8-1HVr9udvf0,16704
49
- folio_migration_tools/migration_tasks/orders_transformer.py,sha256=Q9YU8DUtVBqXfmvwa2LFxsO09h3IRRXXh3xWF5hUNg8,14249
48
+ folio_migration_tools/migration_tasks/migration_task_base.py,sha256=WEKsO8fBsbtA5jkXe_tn1tP9QaVtSzP1_AJR5m41bII,19148
49
+ folio_migration_tools/migration_tasks/orders_transformer.py,sha256=ry3oUUVQTFKCDUbGF5Zjo5ppa6AseKQwpF-wb1sb5UY,14214
50
50
  folio_migration_tools/migration_tasks/organization_transformer.py,sha256=vcCjhN1sS55c_a0LXi1Yw1eq3zpDn5E4BGbm2zDQ_Z4,16885
51
51
  folio_migration_tools/migration_tasks/requests_migrator.py,sha256=QP9OBezC3FfcKpI78oMmydxcPaUIYAgHyKevyLwC-WQ,14841
52
52
  folio_migration_tools/migration_tasks/reserves_migrator.py,sha256=SA3b7FQWHMHb7bEO8ZqOlblQ9m65zWUMH71uRk-zOKw,9950
53
- folio_migration_tools/migration_tasks/user_transformer.py,sha256=g-0etM5MpW3gjOrJ4pHotJHfubH8nxsZqUI8ZDi_TC0,12912
53
+ folio_migration_tools/migration_tasks/user_transformer.py,sha256=cNBT-wn_xx1OQXiB-vMLZmvyzkg1X562AJXUcYfThaE,12279
54
54
  folio_migration_tools/task_configuration.py,sha256=C5-OQtZLH7b4lVeyj5v8OXsqKNN4tzfp9F3b4vhthN4,632
55
55
  folio_migration_tools/test_infrastructure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- folio_migration_tools/test_infrastructure/mocked_classes.py,sha256=rNes6UlRqIWGwPurfiQK97IvgB5OPwnZTbv1T28jHzk,9150
56
+ folio_migration_tools/test_infrastructure/mocked_classes.py,sha256=trK1ZvxTdebc8qHtFtLtc-6SLlNdGDtX2z4zhP8GMcI,11278
57
57
  folio_migration_tools/transaction_migration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  folio_migration_tools/transaction_migration/legacy_loan.py,sha256=3PDyC1wbJzF0CcNWelvZ0tC8hjl3p5hbLVJHrz78ORM,6006
59
59
  folio_migration_tools/transaction_migration/legacy_request.py,sha256=1ulyFzPQw_InOjyPzkWpGnNptgXdQ18nmri0J8Nlpkc,6124
60
60
  folio_migration_tools/transaction_migration/legacy_reserve.py,sha256=d0qbh2fWpwlVSYRL6wZyZG20__NAYNxh7sPSsB-LAes,1804
61
61
  folio_migration_tools/transaction_migration/transaction_result.py,sha256=cTdCN0BnlI9_ZJB2Z3Fdkl9gpymIi-9mGZsRFlQcmDk,656
62
62
  folio_migration_tools/translations/en.json,sha256=HOVpkb_T-SN_x0NpDp8gyvV1hMLCui3SsG7ByyIv0OU,38669
63
- folio_migration_tools-1.9.0rc7.dist-info/LICENSE,sha256=PhIEkitVi3ejgq56tt6sWoJIG_zmv82cjjd_aYPPGdI,1072
64
- folio_migration_tools-1.9.0rc7.dist-info/METADATA,sha256=ZBN-JMKfGXNzGGKEgecmOsbOS49pl09CF7KtyfVBhKs,7415
65
- folio_migration_tools-1.9.0rc7.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
66
- folio_migration_tools-1.9.0rc7.dist-info/entry_points.txt,sha256=Hbe-HjqMcU8FwVshVIkeWyZd9XwgT1CCMNf06EpHQu8,77
67
- folio_migration_tools-1.9.0rc7.dist-info/RECORD,,
63
+ folio_migration_tools-1.9.0rc8.dist-info/LICENSE,sha256=PhIEkitVi3ejgq56tt6sWoJIG_zmv82cjjd_aYPPGdI,1072
64
+ folio_migration_tools-1.9.0rc8.dist-info/METADATA,sha256=BelPrPeeo24CEE-5O1AmPhHODf5NOMUAmQWe1TXZf3U,7447
65
+ folio_migration_tools-1.9.0rc8.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
66
+ folio_migration_tools-1.9.0rc8.dist-info/entry_points.txt,sha256=Hbe-HjqMcU8FwVshVIkeWyZd9XwgT1CCMNf06EpHQu8,77
67
+ folio_migration_tools-1.9.0rc8.dist-info/RECORD,,