folio-migration-tools 1.9.9__py3-none-any.whl → 1.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. folio_migration_tools/__init__.py +3 -4
  2. folio_migration_tools/__main__.py +53 -31
  3. folio_migration_tools/circulation_helper.py +118 -108
  4. folio_migration_tools/custom_dict.py +2 -2
  5. folio_migration_tools/custom_exceptions.py +4 -5
  6. folio_migration_tools/folder_structure.py +17 -7
  7. folio_migration_tools/helper.py +8 -7
  8. folio_migration_tools/holdings_helper.py +4 -3
  9. folio_migration_tools/i18n_cache.py +79 -0
  10. folio_migration_tools/library_configuration.py +77 -37
  11. folio_migration_tools/mapper_base.py +45 -31
  12. folio_migration_tools/mapping_file_transformation/courses_mapper.py +1 -1
  13. folio_migration_tools/mapping_file_transformation/holdings_mapper.py +7 -3
  14. folio_migration_tools/mapping_file_transformation/item_mapper.py +13 -26
  15. folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +1 -2
  16. folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +13 -11
  17. folio_migration_tools/mapping_file_transformation/order_mapper.py +23 -5
  18. folio_migration_tools/mapping_file_transformation/organization_mapper.py +3 -3
  19. folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +3 -0
  20. folio_migration_tools/mapping_file_transformation/user_mapper.py +47 -28
  21. folio_migration_tools/marc_rules_transformation/conditions.py +82 -97
  22. folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +13 -5
  23. folio_migration_tools/marc_rules_transformation/hrid_handler.py +3 -2
  24. folio_migration_tools/marc_rules_transformation/marc_file_processor.py +26 -24
  25. folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +56 -51
  26. folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +28 -17
  27. folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +68 -37
  28. folio_migration_tools/migration_report.py +18 -7
  29. folio_migration_tools/migration_tasks/batch_poster.py +285 -354
  30. folio_migration_tools/migration_tasks/bibs_transformer.py +14 -9
  31. folio_migration_tools/migration_tasks/courses_migrator.py +2 -3
  32. folio_migration_tools/migration_tasks/holdings_csv_transformer.py +23 -24
  33. folio_migration_tools/migration_tasks/holdings_marc_transformer.py +14 -24
  34. folio_migration_tools/migration_tasks/items_transformer.py +23 -34
  35. folio_migration_tools/migration_tasks/loans_migrator.py +67 -144
  36. folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +3 -3
  37. folio_migration_tools/migration_tasks/migration_task_base.py +43 -52
  38. folio_migration_tools/migration_tasks/orders_transformer.py +25 -41
  39. folio_migration_tools/migration_tasks/organization_transformer.py +9 -18
  40. folio_migration_tools/migration_tasks/requests_migrator.py +21 -24
  41. folio_migration_tools/migration_tasks/reserves_migrator.py +6 -5
  42. folio_migration_tools/migration_tasks/user_transformer.py +25 -20
  43. folio_migration_tools/task_configuration.py +6 -7
  44. folio_migration_tools/transaction_migration/legacy_loan.py +15 -27
  45. folio_migration_tools/transaction_migration/legacy_request.py +1 -1
  46. folio_migration_tools/translations/en.json +3 -8
  47. {folio_migration_tools-1.9.9.dist-info → folio_migration_tools-1.10.0.dist-info}/METADATA +19 -28
  48. folio_migration_tools-1.10.0.dist-info/RECORD +63 -0
  49. folio_migration_tools-1.10.0.dist-info/WHEEL +4 -0
  50. folio_migration_tools-1.10.0.dist-info/entry_points.txt +3 -0
  51. folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +0 -241
  52. folio_migration_tools/migration_tasks/authority_transformer.py +0 -119
  53. folio_migration_tools/test_infrastructure/__init__.py +0 -0
  54. folio_migration_tools/test_infrastructure/mocked_classes.py +0 -406
  55. folio_migration_tools-1.9.9.dist-info/RECORD +0 -67
  56. folio_migration_tools-1.9.9.dist-info/WHEEL +0 -4
  57. folio_migration_tools-1.9.9.dist-info/entry_points.txt +0 -3
  58. folio_migration_tools-1.9.9.dist-info/licenses/LICENSE +0 -21
@@ -12,6 +12,7 @@ from pymarc import Optional
12
12
  from pymarc.field import Field
13
13
  from pymarc.record import Record
14
14
 
15
+ from folio_migration_tools.i18n_cache import i18n_t
15
16
  from folio_migration_tools.custom_exceptions import (
16
17
  TransformationFieldMappingError,
17
18
  TransformationProcessError,
@@ -95,25 +96,21 @@ class RulesMapperHoldings(RulesMapperBase):
95
96
  )
96
97
  self.mappings["852"] = new_852_mapping
97
98
 
98
- def integrate_supplemental_mfhd_mappings(self, new_rules={}):
99
+ def integrate_supplemental_mfhd_mappings(self, new_rules=None):
99
100
  try:
100
- self.mappings.update(new_rules)
101
+ self.mappings.update(new_rules or {})
101
102
  self.fix_853_bug_in_rules()
102
103
  except Exception as e:
103
104
  raise TransformationProcessError(
104
105
  "",
105
106
  "Failed to integrate supplemental mfhd mappings",
106
107
  str(e),
107
- )
108
+ ) from e
108
109
 
109
110
  def prep_852_notes(self, marc_record: Record):
110
111
  for field in marc_record.get_fields("852"):
111
112
  field.subfields.sort(key=lambda x: x[0])
112
- new_952 = Field(
113
- tag="952",
114
- indicators=["f", "f"],
115
- subfields=field.subfields
116
- )
113
+ new_952 = Field(tag="952", indicators=["f", "f"], subfields=field.subfields)
117
114
  marc_record.add_ordered_field(new_952)
118
115
 
119
116
  def parse_record(
@@ -257,7 +254,7 @@ class RulesMapperHoldings(RulesMapperBase):
257
254
  ignored_subsequent_fields (_type_): _description_
258
255
  index_or_legacy_ids (_type_): _description_
259
256
  """
260
- self.migration_report.add("Trivia", i18n.t("Total number of Tags processed"))
257
+ self.migration_report.add("Trivia", i18n_t("Total number of Tags processed"))
261
258
  if marc_field.tag not in self.mappings:
262
259
  self.report_legacy_mapping(marc_field.tag, True, False)
263
260
  elif marc_field.tag not in ignored_subsequent_fields:
@@ -270,7 +267,11 @@ class RulesMapperHoldings(RulesMapperBase):
270
267
  ignored_subsequent_fields.add(marc_field.tag)
271
268
 
272
269
  def perform_additional_mapping(
273
- self, marc_record: Record, folio_holding: Dict, legacy_ids: List[str], file_def: FileDefinition
270
+ self,
271
+ marc_record: Record,
272
+ folio_holding: Dict,
273
+ legacy_ids: List[str],
274
+ file_def: FileDefinition,
274
275
  ):
275
276
  """_summary_
276
277
 
@@ -306,11 +307,13 @@ class RulesMapperHoldings(RulesMapperBase):
306
307
  "",
307
308
  )
308
309
  self.handle_suppression(folio_holding, file_def, True)
309
- # First, map statistical codes from MARC fields and FileDefinitions to FOLIO statistical codes.
310
- # Then, convert the mapped statistical codes to their corresponding code IDs.
310
+ # First, map statistical codes from MARC fields and FileDefinitions to FOLIO statistical
311
+ # codes. Then, convert the mapped statistical codes to their corresponding code IDs.
311
312
  self.map_statistical_codes(folio_holding, file_def, marc_record)
312
313
  self.map_statistical_code_ids(legacy_ids, folio_holding)
313
- self.set_source_id(self.create_source_records, folio_holding, self.holdingssources, file_def)
314
+ self.set_source_id(
315
+ self.create_source_records, folio_holding, self.holdingssources, file_def
316
+ )
314
317
 
315
318
  def pick_first_location_if_many(self, folio_holding: Dict, legacy_ids: List[str]):
316
319
  if " " in folio_holding.get("permanentLocationId", ""):
@@ -324,7 +327,12 @@ class RulesMapperHoldings(RulesMapperBase):
324
327
  ]
325
328
 
326
329
  @staticmethod
327
- def set_source_id(create_source_records: bool, folio_rec: Dict, holdingssources: Dict, file_def: FileDefinition):
330
+ def set_source_id(
331
+ create_source_records: bool,
332
+ folio_rec: Dict,
333
+ holdingssources: Dict,
334
+ file_def: FileDefinition,
335
+ ):
328
336
  if file_def.create_source_records and create_source_records:
329
337
  folio_rec["sourceId"] = holdingssources.get("MARC")
330
338
  else:
@@ -371,10 +379,14 @@ class RulesMapperHoldings(RulesMapperBase):
371
379
  """
372
380
  if self.task_configuration.include_mrk_statements:
373
381
  mrk_statement_notes = []
374
- for field in marc_record.get_fields("853", "854", "855", "863", "864", "865", "866", "867", "868"):
382
+ for field in marc_record.get_fields(
383
+ "853", "854", "855", "863", "864", "865", "866", "867", "868"
384
+ ):
375
385
  mrk_statement_notes.append(str(field))
376
386
  if mrk_statement_notes:
377
- folio_holding["notes"] = folio_holding.get("notes", []) + self.add_mrk_statements_note(mrk_statement_notes, legacy_ids)
387
+ folio_holding["notes"] = folio_holding.get(
388
+ "notes", []
389
+ ) + self.add_mrk_statements_note(mrk_statement_notes, legacy_ids)
378
390
 
379
391
  def add_mrk_statements_note(self, mrk_statement_notes: List[str], legacy_ids) -> List[Dict]:
380
392
  """Creates a note from the MRK statements
@@ -386,7 +398,9 @@ class RulesMapperHoldings(RulesMapperBase):
386
398
  List: A list containing the FOLIO holdings note object (Dict)
387
399
  """
388
400
  holdings_note_type_tuple = self.conditions.get_ref_data_tuple_by_name(
389
- self.folio.holding_note_types, "holding_note_types", self.task_configuration.mrk_holdings_note_type
401
+ self.folio.holding_note_types,
402
+ "holding_note_types",
403
+ self.task_configuration.mrk_holdings_note_type,
390
404
  )
391
405
  try:
392
406
  holdings_note_type_id = holdings_note_type_tuple[0]
@@ -394,7 +408,8 @@ class RulesMapperHoldings(RulesMapperBase):
394
408
  logging.error(ee)
395
409
  raise TransformationRecordFailedError(
396
410
  legacy_ids,
397
- f'Holdings note type mapping error.\tNote type name: {self.task_configuration.mrk_holdings_note_type}\t'
411
+ f"Holdings note type mapping error.\tNote type name: "
412
+ f"{self.task_configuration.mrk_holdings_note_type}\t"
398
413
  f"MFHD holdings statement note type not found in FOLIO.",
399
414
  self.task_configuration.mrk_holdings_note_type,
400
415
  ) from ee
@@ -403,7 +418,8 @@ class RulesMapperHoldings(RulesMapperBase):
403
418
  "note": chunk,
404
419
  "holdingsNoteTypeId": holdings_note_type_id,
405
420
  "staffOnly": True,
406
- } for chunk in self.split_mrk_by_max_note_size("\n".join(mrk_statement_notes))
421
+ }
422
+ for chunk in self.split_mrk_by_max_note_size("\n".join(mrk_statement_notes))
407
423
  ]
408
424
 
409
425
  @staticmethod
@@ -423,7 +439,9 @@ class RulesMapperHoldings(RulesMapperBase):
423
439
  chunks.append(current_chunk)
424
440
  return chunks
425
441
 
426
- def add_mfhd_as_mrk_note(self, marc_record: Record, folio_holding: Dict, legacy_ids: List[str]):
442
+ def add_mfhd_as_mrk_note(
443
+ self, marc_record: Record, folio_holding: Dict, legacy_ids: List[str]
444
+ ):
427
445
  """Adds the MFHD as a note to the holdings record
428
446
 
429
447
  This is done to preserve the information in the MARC record for future reference.
@@ -434,7 +452,9 @@ class RulesMapperHoldings(RulesMapperBase):
434
452
  """
435
453
  if self.task_configuration.include_mfhd_mrk_as_note:
436
454
  holdings_note_type_tuple = self.conditions.get_ref_data_tuple_by_name(
437
- self.folio.holding_note_types, "holding_note_types", self.task_configuration.mfhd_mrk_note_type
455
+ self.folio.holding_note_types,
456
+ "holding_note_types",
457
+ self.task_configuration.mfhd_mrk_note_type,
438
458
  )
439
459
  try:
440
460
  holdings_note_type_id = holdings_note_type_tuple[0]
@@ -442,7 +462,8 @@ class RulesMapperHoldings(RulesMapperBase):
442
462
  logging.error(ee)
443
463
  raise TransformationRecordFailedError(
444
464
  legacy_ids,
445
- f'Holdings note type mapping error.\tNote type name: {self.task_configuration.mfhd_mrk_note_type}\t'
465
+ f"Holdings note type mapping error.\tNote type name: "
466
+ f"{self.task_configuration.mfhd_mrk_note_type}\t"
446
467
  f"Note type not found in FOLIO.",
447
468
  self.task_configuration.mfhd_mrk_note_type,
448
469
  ) from ee
@@ -451,13 +472,16 @@ class RulesMapperHoldings(RulesMapperBase):
451
472
  "note": chunk,
452
473
  "holdingsNoteTypeId": holdings_note_type_id,
453
474
  "staffOnly": True,
454
- } for chunk in self.split_mrk_by_max_note_size(str(marc_record))
475
+ }
476
+ for chunk in self.split_mrk_by_max_note_size(str(marc_record))
455
477
  ]
456
478
 
457
479
  @staticmethod
458
- def split_mrc_by_max_note_size(data: bytes, sep: bytes = b"\x1e", max_chunk_size: int = 32000) -> List[bytes]:
480
+ def split_mrc_by_max_note_size(
481
+ data: bytes, sep: bytes = b"\x1e", max_chunk_size: int = 32000
482
+ ) -> List[bytes]:
459
483
  # Split data into segments, each ending with the separator (except possibly the last)
460
- pattern = re.compile(b'(.*?' + re.escape(sep) + b'|.+?$)', re.DOTALL)
484
+ pattern = re.compile(b"(.*?" + re.escape(sep) + b"|.+?$)", re.DOTALL)
461
485
  parts = [m.group(0) for m in pattern.finditer(data) if m.group(0)]
462
486
  chunks = []
463
487
  current_chunk = b""
@@ -471,7 +495,9 @@ class RulesMapperHoldings(RulesMapperBase):
471
495
  chunks.append(current_chunk)
472
496
  return chunks
473
497
 
474
- def add_mfhd_as_mrc_note(self, marc_record: Record, folio_holding: Dict, legacy_ids: List[str]):
498
+ def add_mfhd_as_mrc_note(
499
+ self, marc_record: Record, folio_holding: Dict, legacy_ids: List[str]
500
+ ):
475
501
  """Adds the MFHD as a note to the holdings record
476
502
 
477
503
  This is done to preserve the information in the MARC record for future reference.
@@ -482,7 +508,9 @@ class RulesMapperHoldings(RulesMapperBase):
482
508
  """
483
509
  if self.task_configuration.include_mfhd_mrc_as_note:
484
510
  holdings_note_type_tuple = self.conditions.get_ref_data_tuple_by_name(
485
- self.folio.holding_note_types, "holding_note_types", self.task_configuration.mfhd_mrc_note_type
511
+ self.folio.holding_note_types,
512
+ "holding_note_types",
513
+ self.task_configuration.mfhd_mrc_note_type,
486
514
  )
487
515
  try:
488
516
  holdings_note_type_id = holdings_note_type_tuple[0]
@@ -490,7 +518,8 @@ class RulesMapperHoldings(RulesMapperBase):
490
518
  logging.error(ee)
491
519
  raise TransformationRecordFailedError(
492
520
  legacy_ids,
493
- f'Holdings note type mapping error.\tNote type name: {self.task_configuration.mfhd_mrc_note_type}\t'
521
+ f"Holdings note type mapping error.\tNote type name: "
522
+ f"{self.task_configuration.mfhd_mrc_note_type}\t"
494
523
  f"Note type not found in FOLIO.",
495
524
  self.task_configuration.mfhd_mrc_note_type,
496
525
  ) from ee
@@ -499,7 +528,8 @@ class RulesMapperHoldings(RulesMapperBase):
499
528
  "note": chunk.decode("utf-8"),
500
529
  "holdingsNoteTypeId": holdings_note_type_id,
501
530
  "staffOnly": True,
502
- } for chunk in self.split_mrc_by_max_note_size(marc_record.as_marc())
531
+ }
532
+ for chunk in self.split_mrc_by_max_note_size(marc_record.as_marc())
503
533
  ]
504
534
 
505
535
  def wrap_up(self):
@@ -555,7 +585,7 @@ class RulesMapperHoldings(RulesMapperBase):
555
585
  Helper.log_data_issue(
556
586
  legacy_ids,
557
587
  (
558
- i18n.t("blurbs.HoldingsTypeMapping.title") + " is 'unknown'. "
588
+ i18n_t("blurbs.HoldingsTypeMapping.title") + " is 'unknown'. "
559
589
  "(leader 06 is set to 'u') Check if this is correct"
560
590
  ),
561
591
  ldr06,
@@ -569,14 +599,14 @@ class RulesMapperHoldings(RulesMapperBase):
569
599
  folio_holding["holdingsTypeId"] = self.fallback_holdings_type_id
570
600
  self.migration_report.add(
571
601
  "HoldingsTypeMapping",
572
- i18n.t("An Unmapped")
602
+ i18n_t("An Unmapped")
573
603
  + f" {ldr06} -> {holdings_type} -> "
574
- + i18n.t("Unmapped"),
604
+ + i18n_t("Unmapped"),
575
605
  )
576
606
  Helper.log_data_issue(
577
607
  legacy_ids,
578
608
  (
579
- i18n.t("blurbs.HoldingsTypeMapping.title", locale="en")
609
+ i18n_t("blurbs.HoldingsTypeMapping.title", locale="en")
580
610
  + ". leader 06 was unmapped."
581
611
  ),
582
612
  ldr06,
@@ -642,7 +672,7 @@ class RulesMapperHoldings(RulesMapperBase):
642
672
  Raises:
643
673
  TransformationProcessError: If MFHD_ID or BIB_ID is missing from the entry or if the instance_uuid is not in the parent_id_map.
644
674
  TransformationRecordFailedError: If BIB_ID is not in the instance id map.
645
- """
675
+ """ # noqa: E501
646
676
  new_map = {}
647
677
  for idx, entry in enumerate(boundwith_relationship_map_list):
648
678
  self.verity_boundwith_map_entry(entry)
@@ -663,9 +693,10 @@ class RulesMapperHoldings(RulesMapperBase):
663
693
  def get_bw_instance_id_map_tuple(self, entry: Dict):
664
694
  try:
665
695
  return self.parent_id_map[entry["BIB_ID"]]
666
- except KeyError:
696
+ except KeyError as e:
667
697
  raise TransformationRecordFailedError(
668
698
  entry["MFHD_ID"],
669
- "Boundwith relationship map contains a BIB_ID id not in the instance id map. No boundwith holdings created for this BIB_ID.",
699
+ "Boundwith relationship map contains a BIB_ID id not in the instance id map. "
700
+ "No boundwith holdings created for this BIB_ID.",
670
701
  entry["BIB_ID"],
671
- )
702
+ ) from e
@@ -1,8 +1,11 @@
1
1
  import logging
2
+ import json
2
3
  import i18n
3
4
  from datetime import datetime
4
5
  from datetime import timezone
5
6
 
7
+ from folio_migration_tools.i18n_cache import i18n_t
8
+
6
9
 
7
10
  class MigrationReport:
8
11
  """Class responsible for handling the migration report"""
@@ -47,6 +50,14 @@ class MigrationReport:
47
50
  """
48
51
  self.add("GeneralStatistics", measure_to_add)
49
52
 
53
+ def _write_json_report(self, report_file):
54
+ """Writes the raw migration report data to a JSON file.
55
+
56
+ Args:
57
+ report_file: An open file object to write the JSON data to
58
+ """
59
+ json.dump(self.report, report_file, indent=2)
60
+
50
61
  def write_migration_report(
51
62
  self,
52
63
  report_title,
@@ -66,17 +77,17 @@ class MigrationReport:
66
77
  [
67
78
  "# " + report_title,
68
79
  i18n.t("blurbs.Introduction.description"),
69
- "## " + i18n.t("Timings"),
80
+ "## " + i18n_t("Timings"),
70
81
  "",
71
- i18n.t("Measure") + " | " + i18n.t("Value"),
82
+ i18n_t("Measure") + " | " + i18n_t("Value"),
72
83
  "--- | ---:",
73
- i18n.t("Time Started:") + " | " + datetime.isoformat(time_started),
74
- i18n.t("Time Finished:") + " | " + datetime.isoformat(time_finished),
75
- i18n.t("Elapsed time:") + " | " + str(time_finished - time_started),
84
+ i18n_t("Time Started:") + " | " + datetime.isoformat(time_started),
85
+ i18n_t("Time Finished:") + " | " + datetime.isoformat(time_finished),
86
+ i18n_t("Elapsed time:") + " | " + str(time_finished - time_started),
76
87
  ]
77
88
  )
78
89
  )
79
- logging.info(f"Elapsed time: {time_finished-time_started}")
90
+ logging.info(f"Elapsed time: {time_finished - time_started}")
80
91
  for a in self.report:
81
92
  blurb_id = self.report[a].get("blurb_id") or ""
82
93
  report_file.write(
@@ -89,7 +100,7 @@ class MigrationReport:
89
100
  + i18n.t("Click to expand all %{count} things", count=len(self.report[a]))
90
101
  + "</summary>",
91
102
  "",
92
- i18n.t("Measure") + " | " + i18n.t("Count"),
103
+ i18n_t("Measure") + " | " + i18n_t("Count"),
93
104
  "--- | ---:",
94
105
  ]
95
106
  + [