folio-migration-tools 1.10.1__py3-none-any.whl → 1.10.3__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 +10 -2
- folio_migration_tools/__main__.py +7 -0
- folio_migration_tools/circulation_helper.py +23 -8
- folio_migration_tools/colors.py +7 -0
- folio_migration_tools/config_file_load.py +7 -0
- folio_migration_tools/custom_dict.py +17 -0
- folio_migration_tools/custom_exceptions.py +40 -4
- folio_migration_tools/extradata_writer.py +12 -0
- folio_migration_tools/folder_structure.py +16 -0
- folio_migration_tools/helper.py +7 -0
- folio_migration_tools/holdings_helper.py +11 -5
- folio_migration_tools/i18n_config.py +6 -0
- folio_migration_tools/library_configuration.py +19 -5
- folio_migration_tools/mapper_base.py +15 -0
- folio_migration_tools/mapping_file_transformation/__init__.py +1 -0
- folio_migration_tools/mapping_file_transformation/courses_mapper.py +17 -0
- folio_migration_tools/mapping_file_transformation/holdings_mapper.py +19 -0
- folio_migration_tools/mapping_file_transformation/item_mapper.py +24 -0
- folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +18 -0
- folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +26 -9
- folio_migration_tools/mapping_file_transformation/notes_mapper.py +16 -0
- folio_migration_tools/mapping_file_transformation/order_mapper.py +40 -27
- folio_migration_tools/mapping_file_transformation/organization_mapper.py +40 -33
- folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +17 -0
- folio_migration_tools/mapping_file_transformation/user_mapper.py +16 -0
- folio_migration_tools/marc_rules_transformation/__init__.py +1 -0
- folio_migration_tools/marc_rules_transformation/conditions.py +49 -36
- folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +9 -3
- folio_migration_tools/marc_rules_transformation/hrid_handler.py +16 -1
- folio_migration_tools/marc_rules_transformation/marc_file_processor.py +15 -1
- folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +7 -0
- folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +35 -29
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +23 -18
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +46 -27
- folio_migration_tools/migration_report.py +14 -6
- folio_migration_tools/migration_tasks/__init__.py +2 -0
- folio_migration_tools/migration_tasks/batch_poster.py +41 -19
- folio_migration_tools/migration_tasks/bibs_transformer.py +16 -0
- folio_migration_tools/migration_tasks/courses_migrator.py +15 -0
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py +18 -3
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py +17 -0
- folio_migration_tools/migration_tasks/inventory_batch_poster.py +424 -0
- folio_migration_tools/migration_tasks/items_transformer.py +16 -0
- folio_migration_tools/migration_tasks/loans_migrator.py +17 -2
- folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +16 -0
- folio_migration_tools/migration_tasks/marc_import.py +407 -0
- folio_migration_tools/migration_tasks/migration_task_base.py +49 -17
- folio_migration_tools/migration_tasks/orders_transformer.py +16 -0
- folio_migration_tools/migration_tasks/organization_transformer.py +17 -2
- folio_migration_tools/migration_tasks/requests_migrator.py +15 -0
- folio_migration_tools/migration_tasks/reserves_migrator.py +15 -0
- folio_migration_tools/migration_tasks/user_importer.py +347 -0
- folio_migration_tools/migration_tasks/user_transformer.py +16 -0
- folio_migration_tools/task_configuration.py +7 -0
- folio_migration_tools/transaction_migration/__init__.py +1 -0
- folio_migration_tools/transaction_migration/legacy_loan.py +16 -0
- folio_migration_tools/transaction_migration/legacy_request.py +14 -0
- folio_migration_tools/transaction_migration/legacy_reserve.py +14 -0
- folio_migration_tools/transaction_migration/transaction_result.py +16 -0
- {folio_migration_tools-1.10.1.dist-info → folio_migration_tools-1.10.3.dist-info}/METADATA +1 -1
- folio_migration_tools-1.10.3.dist-info/RECORD +66 -0
- folio_migration_tools-1.10.1.dist-info/RECORD +0 -63
- {folio_migration_tools-1.10.1.dist-info → folio_migration_tools-1.10.3.dist-info}/WHEEL +0 -0
- {folio_migration_tools-1.10.1.dist-info → folio_migration_tools-1.10.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""FOLIO Migration Tools package for migrating library data to FOLIO LSP."""
|
|
2
|
+
|
|
1
3
|
import importlib.metadata
|
|
2
4
|
from typing import Protocol
|
|
3
5
|
|
|
@@ -5,6 +7,12 @@ __version__ = importlib.metadata.version("folio_migration_tools")
|
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class StrCoercible(Protocol):
|
|
8
|
-
|
|
10
|
+
"""Protocol for objects that can be coerced to string."""
|
|
11
|
+
|
|
12
|
+
def __repr__(self) -> str:
|
|
13
|
+
"""Return repr(self)."""
|
|
14
|
+
...
|
|
9
15
|
|
|
10
|
-
def __str__(self) -> str:
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
"""Return str(self)."""
|
|
18
|
+
...
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Main entry point for the folio_migration_tools CLI.
|
|
2
|
+
|
|
3
|
+
Provides the command-line interface for running migration tasks. Loads configuration
|
|
4
|
+
files, validates parameters, instantiates appropriate task classes, and executes
|
|
5
|
+
the migration workflow. Supports all transformation and loading tasks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
from importlib import metadata
|
|
2
9
|
import json
|
|
3
10
|
import logging
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Helper utilities for circulation transactions migration.
|
|
2
|
+
|
|
3
|
+
Provides the CirculationHelper class with methods for validating and posting
|
|
4
|
+
circulation transactions (loans, requests). Handles patron and item lookups,
|
|
5
|
+
loan policy validation, and error handling for circulation operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import copy
|
|
2
9
|
import json
|
|
3
10
|
import logging
|
|
@@ -29,6 +36,13 @@ class CirculationHelper:
|
|
|
29
36
|
service_point_id,
|
|
30
37
|
migration_report: MigrationReport,
|
|
31
38
|
):
|
|
39
|
+
"""Initialize CirculationHelper with FOLIO client and service point.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
folio_client (FolioClient): FOLIO API client for circulation operations.
|
|
43
|
+
service_point_id: ID of the service point for check-outs and check-ins.
|
|
44
|
+
migration_report (MigrationReport): Report object for tracking statistics.
|
|
45
|
+
"""
|
|
32
46
|
self.folio_client = folio_client
|
|
33
47
|
self.service_point_id = service_point_id
|
|
34
48
|
self.missing_patron_barcodes: Set[str] = set()
|
|
@@ -72,15 +86,16 @@ class CirculationHelper:
|
|
|
72
86
|
return {}
|
|
73
87
|
|
|
74
88
|
def is_checked_out(self, legacy_loan: LegacyLoan) -> bool:
|
|
75
|
-
"""Makes a deeper check to find out if the loan is already
|
|
89
|
+
"""Makes a deeper check to find out if the loan is already checked out in FOLIO.
|
|
90
|
+
|
|
76
91
|
Looks up the item id, and then searches Loan Storage for any open loans.
|
|
77
92
|
If there are open loans, returns True. Else False.
|
|
78
93
|
|
|
79
94
|
Args:
|
|
80
|
-
legacy_loan (LegacyLoan):
|
|
95
|
+
legacy_loan (LegacyLoan): The legacy loan object to check.
|
|
81
96
|
|
|
82
97
|
Returns:
|
|
83
|
-
bool:
|
|
98
|
+
bool: True if the loan is already checked out, False otherwise.
|
|
84
99
|
"""
|
|
85
100
|
if item := self.get_item_by_barcode(legacy_loan.item_barcode):
|
|
86
101
|
if self.get_active_loan_by_item_id(item["id"]):
|
|
@@ -113,16 +128,16 @@ class CirculationHelper:
|
|
|
113
128
|
return {}
|
|
114
129
|
|
|
115
130
|
def check_out_by_barcode(self, legacy_loan: LegacyLoan) -> TransactionResult:
|
|
116
|
-
"""Checks out a legacy loan using the Endpoint /circulation/check-out-by-barcode
|
|
117
|
-
|
|
131
|
+
"""Checks out a legacy loan using the Endpoint /circulation/check-out-by-barcode.
|
|
132
|
+
|
|
133
|
+
Adds all possible overrides in order to make the transaction go through.
|
|
118
134
|
|
|
119
135
|
Args:
|
|
120
|
-
legacy_loan (LegacyLoan):
|
|
136
|
+
legacy_loan (LegacyLoan): The legacy loan object to check.
|
|
121
137
|
|
|
122
138
|
Returns:
|
|
123
|
-
TransactionResult:
|
|
139
|
+
TransactionResult: The result of the check-out transaction.
|
|
124
140
|
"""
|
|
125
|
-
|
|
126
141
|
t0_function = time.time()
|
|
127
142
|
data = {
|
|
128
143
|
"itemBarcode": legacy_loan.item_barcode,
|
folio_migration_tools/colors.py
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Configuration file loading and merging utilities.
|
|
2
|
+
|
|
3
|
+
Provides functions for loading JSON configuration files with support for
|
|
4
|
+
inheriting from parent configurations. Handles deep merging of nested
|
|
5
|
+
dictionaries and lists while preserving unique entries.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import json
|
|
2
9
|
from pathlib import Path
|
|
3
10
|
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
"""Custom CSV DictReader with case-insensitive field names.
|
|
2
|
+
|
|
3
|
+
Provides the InsensitiveDictReader class that normalizes CSV field names to
|
|
4
|
+
lowercase and strips whitespace, making CSV parsing more forgiving of
|
|
5
|
+
inconsistent header formatting.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import csv
|
|
2
9
|
|
|
3
10
|
|
|
4
11
|
class InsensitiveDictReader(csv.DictReader):
|
|
12
|
+
"""CSV DictReader with case-insensitive field names.
|
|
13
|
+
|
|
14
|
+
This class extends csv.DictReader to normalize field names by stripping
|
|
15
|
+
leading/trailing whitespace and converting them to lowercase. This allows
|
|
16
|
+
for more forgiving CSV parsing where header formatting may vary.
|
|
17
|
+
"""
|
|
18
|
+
|
|
5
19
|
# This class overrides the csv.fieldnames property, which converts all
|
|
6
20
|
# fieldnames without leading and trailing
|
|
7
21
|
# spaces and to lower case.
|
|
@@ -14,7 +28,10 @@ class InsensitiveDictReader(csv.DictReader):
|
|
|
14
28
|
|
|
15
29
|
|
|
16
30
|
class InsensitiveDict(dict):
|
|
31
|
+
"""Dictionary with case-insensitive key lookup."""
|
|
32
|
+
|
|
17
33
|
# This class overrides the __getitem__ method to automatically strip()
|
|
18
34
|
# and lower() the input key
|
|
19
35
|
def __getitem__(self, key):
|
|
36
|
+
"""Get item by key, normalizing to lowercase and stripped."""
|
|
20
37
|
return dict.__getitem__(self, key.strip().lower())
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
"""Custom exception classes for migration transformations.
|
|
2
|
+
|
|
3
|
+
Defines specialized exception types for different transformation error scenarios:
|
|
4
|
+
- TransformationFieldMappingError: Non-critical data issues
|
|
5
|
+
- TransformationRecordFailedError: Critical record-level failures
|
|
6
|
+
- TransformationProcessError: Fatal configuration or process errors
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
import logging
|
|
2
10
|
import i18n
|
|
3
11
|
|
|
@@ -10,15 +18,25 @@ class TransformationError(Exception):
|
|
|
10
18
|
|
|
11
19
|
class TransformationFieldMappingError(TransformationError):
|
|
12
20
|
"""Raised when the field mapping fails, but the error is not critical.
|
|
13
|
-
|
|
21
|
+
|
|
22
|
+
The issue should be logged for the library to act upon it
|
|
23
|
+
"""
|
|
14
24
|
|
|
15
25
|
def __init__(self, index_or_id="", message="", data_value: str | StrCoercible = ""):
|
|
26
|
+
"""Initialize field mapping error with index, message, and data value.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
index_or_id: Record identifier or row index from source data.
|
|
30
|
+
message: Descriptive error message.
|
|
31
|
+
data_value: The problematic data value.
|
|
32
|
+
"""
|
|
16
33
|
self.index_or_id = index_or_id or ""
|
|
17
34
|
self.message = message
|
|
18
35
|
self.data_value: str | StrCoercible = data_value
|
|
19
36
|
super().__init__(self.message)
|
|
20
37
|
|
|
21
38
|
def __str__(self):
|
|
39
|
+
"""Return formatted error message with record context."""
|
|
22
40
|
return (
|
|
23
41
|
i18n.t("Data issue. Consider fixing the record. ")
|
|
24
42
|
+ f"\t{self.index_or_id}\t{self.message}\t{self.data_value}"
|
|
@@ -35,9 +53,16 @@ class TransformationFieldMappingError(TransformationError):
|
|
|
35
53
|
|
|
36
54
|
|
|
37
55
|
class TransformationRecordFailedError(TransformationError):
|
|
38
|
-
"""Raised when the field mapping fails, Error is critical and means transformation fails"""
|
|
56
|
+
"""Raised when the field mapping fails, Error is critical and means transformation fails."""
|
|
39
57
|
|
|
40
58
|
def __init__(self, index_or_id, message="", data_value=""):
|
|
59
|
+
"""Initialize record failure error with context information.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
index_or_id: Record identifier or row index from source data.
|
|
63
|
+
message: Descriptive error message about the failure.
|
|
64
|
+
data_value: The problematic data value.
|
|
65
|
+
"""
|
|
41
66
|
self.index_or_id = index_or_id
|
|
42
67
|
self.message = message
|
|
43
68
|
self.data_value: str | StrCoercible = data_value
|
|
@@ -45,6 +70,7 @@ class TransformationRecordFailedError(TransformationError):
|
|
|
45
70
|
super().__init__(self.message)
|
|
46
71
|
|
|
47
72
|
def __str__(self):
|
|
73
|
+
"""Return formatted error message with record context."""
|
|
48
74
|
return (
|
|
49
75
|
f"Critical data issue. Record needs fixing"
|
|
50
76
|
f"\t{self.index_or_id}\t{self.message}\t{self.data_value}"
|
|
@@ -61,8 +87,10 @@ class TransformationRecordFailedError(TransformationError):
|
|
|
61
87
|
|
|
62
88
|
|
|
63
89
|
class TransformationProcessError(TransformationError):
|
|
64
|
-
"""Raised when the transformation fails due to incorrect configuration
|
|
65
|
-
|
|
90
|
+
"""Raised when the transformation fails due to incorrect configuration.
|
|
91
|
+
|
|
92
|
+
This error should take the process to a halt when mapping or reference data is incorrect.
|
|
93
|
+
"""
|
|
66
94
|
|
|
67
95
|
def __init__(
|
|
68
96
|
self,
|
|
@@ -71,12 +99,20 @@ class TransformationProcessError(TransformationError):
|
|
|
71
99
|
" Check configuration, mapping files and reference data",
|
|
72
100
|
data_value: str | StrCoercible = "",
|
|
73
101
|
):
|
|
102
|
+
"""Initialize process error with configuration or reference data context.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
index_or_id: Record identifier or context for where error occurred.
|
|
106
|
+
message: Descriptive error message about the process failure.
|
|
107
|
+
data_value: The problematic data or configuration value.
|
|
108
|
+
"""
|
|
74
109
|
self.index_or_id = index_or_id
|
|
75
110
|
self.message = message
|
|
76
111
|
self.data_value = data_value
|
|
77
112
|
super().__init__(self.message)
|
|
78
113
|
|
|
79
114
|
def __str__(self):
|
|
115
|
+
"""Return formatted error message with process context."""
|
|
80
116
|
return (
|
|
81
117
|
f"Critical Process issue. Check configuration, mapping files and reference data"
|
|
82
118
|
f"\t{self.index_or_id}\t{self.message}\t{self.data_value}"
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Writer for extradata objects generated during transformation.
|
|
2
|
+
|
|
3
|
+
Provides the ExtradataWriter class for buffering and writing extradata objects
|
|
4
|
+
(e.g., organization interfaces, bound-with parts) to separate files during
|
|
5
|
+
migration. Handles buffering, flushing, and file management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import json
|
|
2
9
|
import logging
|
|
3
10
|
import os
|
|
@@ -17,6 +24,11 @@ class ExtradataWriter:
|
|
|
17
24
|
return cls.__instance
|
|
18
25
|
|
|
19
26
|
def __init__(self, path_to_file: Path) -> None:
|
|
27
|
+
"""Initialize the singleton extradata writer.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
path_to_file (Path): Path to the file where extradata will be written.
|
|
31
|
+
"""
|
|
20
32
|
if type(self).__inited:
|
|
21
33
|
return
|
|
22
34
|
self.cache: List[str] = []
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Folder structure management for migration tasks.
|
|
2
|
+
|
|
3
|
+
Defines the FolderStructure class that manages the standardized directory layout
|
|
4
|
+
for migration tasks. Creates and manages folders for source data, results, reports,
|
|
5
|
+
logs, and mapping files. Handles path generation and validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import logging
|
|
2
9
|
import sys
|
|
3
10
|
import time
|
|
@@ -15,6 +22,15 @@ class FolderStructure:
|
|
|
15
22
|
iteration_identifier: str,
|
|
16
23
|
add_time_stamp_to_file_names: bool,
|
|
17
24
|
):
|
|
25
|
+
"""Initialize folder structure for a migration task iteration.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
base_path (Path): Root folder for the migration.
|
|
29
|
+
object_type (FOLIONamespaces): Type of FOLIO object being migrated.
|
|
30
|
+
migration_task_name (str): Name of the migration task.
|
|
31
|
+
iteration_identifier (str): Identifier for this migration iteration.
|
|
32
|
+
add_time_stamp_to_file_names (bool): Whether to add timestamps to output files.
|
|
33
|
+
"""
|
|
18
34
|
logging.info("Validating folder structure")
|
|
19
35
|
|
|
20
36
|
self.object_type: FOLIONamespaces = object_type
|
folio_migration_tools/helper.py
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""General helper utilities for migration tasks.
|
|
2
|
+
|
|
3
|
+
Provides the Helper class with static utility methods for data validation,
|
|
4
|
+
mapping report generation, and common transformation operations used across
|
|
5
|
+
multiple migration tasks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import json
|
|
2
9
|
import logging
|
|
3
10
|
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Helper utilities specific to holdings transformations.
|
|
2
|
+
|
|
3
|
+
Provides the HoldingsHelper class with methods for holdings-specific validations
|
|
4
|
+
and transformations including location handling, call number processing, and
|
|
5
|
+
holdings notes management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import json
|
|
2
9
|
import logging
|
|
3
10
|
import i18n
|
|
@@ -17,14 +24,13 @@ class HoldingsHelper:
|
|
|
17
24
|
migration_report: MigrationReport,
|
|
18
25
|
holdings_type_id_to_exclude_from_merging: str = "Not set",
|
|
19
26
|
) -> str:
|
|
20
|
-
"""
|
|
21
|
-
record to determine uniquenes
|
|
27
|
+
"""Create a unique key from holding field values for merging.
|
|
22
28
|
|
|
23
29
|
fields_criterias are limited to the strings and UUID properties on the first level of
|
|
24
|
-
the object. If the property is not found, or empty, it will be ignored
|
|
30
|
+
the object. If the property is not found, or empty, it will be ignored.
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
the key will be added with a uuid to prevent merging of this holding
|
|
32
|
+
If the holdings type id is matched to holdings_type_id_to_exclude_from_merging,
|
|
33
|
+
the key will be added with a uuid to prevent merging of this holding.
|
|
28
34
|
|
|
29
35
|
Args:
|
|
30
36
|
holdings_record (dict): The Holdingsrecord
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Library and task configuration models.
|
|
2
|
+
|
|
3
|
+
Defines Pydantic models for library-wide configuration (LibraryConfiguration) and
|
|
4
|
+
file definitions (FileDefinition). Handles configuration validation, folder structure
|
|
5
|
+
setup, and migration parameters like HRID handling and iteration identifiers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
from enum import Enum
|
|
2
9
|
from typing import Annotated
|
|
3
10
|
|
|
@@ -7,12 +14,14 @@ from pydantic.types import DirectoryPath
|
|
|
7
14
|
|
|
8
15
|
class HridHandling(str, Enum):
|
|
9
16
|
"""Enum determining how the HRID generation should be handled.
|
|
17
|
+
|
|
18
|
+
Options:
|
|
10
19
|
- default: Enumerates the HRID, building on the current value in the HRID settings
|
|
11
20
|
- preserve001: Takes the 001 and uses this as the HRID.
|
|
12
21
|
|
|
13
22
|
Args:
|
|
14
|
-
str (_type_):
|
|
15
|
-
Enum (_type_):
|
|
23
|
+
str (_type_): The type of HRID handling.
|
|
24
|
+
Enum (_type_): The enumeration of HRID handling options.
|
|
16
25
|
"""
|
|
17
26
|
|
|
18
27
|
default = "default"
|
|
@@ -77,7 +86,7 @@ class FileDefinition(BaseModel):
|
|
|
77
86
|
|
|
78
87
|
|
|
79
88
|
class IlsFlavour(str, Enum):
|
|
80
|
-
""" """
|
|
89
|
+
"""Enum representing different ILS flavours for migration."""
|
|
81
90
|
|
|
82
91
|
aleph = "aleph"
|
|
83
92
|
voyager = "voyager"
|
|
@@ -92,6 +101,8 @@ class IlsFlavour(str, Enum):
|
|
|
92
101
|
|
|
93
102
|
|
|
94
103
|
class FolioRelease(str, Enum):
|
|
104
|
+
"""Enum representing different FOLIO releases."""
|
|
105
|
+
|
|
95
106
|
ramsons = "ramsons"
|
|
96
107
|
sunflower = "sunflower"
|
|
97
108
|
trillium = "trillium"
|
|
@@ -99,6 +110,8 @@ class FolioRelease(str, Enum):
|
|
|
99
110
|
|
|
100
111
|
|
|
101
112
|
class LibraryConfiguration(BaseModel):
|
|
113
|
+
"""Pydantic model for tenant-level configuration."""
|
|
114
|
+
|
|
102
115
|
gateway_url: Annotated[
|
|
103
116
|
str,
|
|
104
117
|
Field(
|
|
@@ -249,8 +262,9 @@ class LibraryConfiguration(BaseModel):
|
|
|
249
262
|
@model_validator(mode="before")
|
|
250
263
|
@classmethod
|
|
251
264
|
def set_error_thresholds_for_debug(cls, values):
|
|
252
|
-
"""If log_level_debug is true, set error thresholds to very high values
|
|
253
|
-
|
|
265
|
+
"""If log_level_debug is true, set error thresholds to very high values.
|
|
266
|
+
|
|
267
|
+
This avoids process shutdown during debugging.
|
|
254
268
|
"""
|
|
255
269
|
if values.get("log_level_debug", False):
|
|
256
270
|
values["failed_records_threshold"] = 10_000_000
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Base class for all data mappers.
|
|
2
|
+
|
|
3
|
+
Provides the MapperBase class with common functionality for transforming legacy
|
|
4
|
+
data to FOLIO format. Handles reference data mapping, field validation, error
|
|
5
|
+
handling, bound-with processing, and statistical code mapping.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import ast
|
|
2
9
|
import copy
|
|
3
10
|
import json
|
|
@@ -41,6 +48,14 @@ class MapperBase:
|
|
|
41
48
|
folio_client: FolioClient,
|
|
42
49
|
parent_id_map: Dict[str, Tuple] | None = None,
|
|
43
50
|
):
|
|
51
|
+
"""Initialize the mapper base with configuration and FOLIO client.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
library_configuration (LibraryConfiguration): Library-specific configuration.
|
|
55
|
+
task_configuration (AbstractTaskConfiguration): Task-specific transformation settings.
|
|
56
|
+
folio_client (FolioClient): FOLIO API client for reference data and posting.
|
|
57
|
+
parent_id_map (Dict[str, Tuple] | None): Optional parent id map from prior transform.
|
|
58
|
+
"""
|
|
44
59
|
logging.info("MapperBase initiating")
|
|
45
60
|
self.parent_id_map: dict[str, tuple] = parent_id_map or {}
|
|
46
61
|
self.extradata_writer: ExtradataWriter = ExtradataWriter(Path(""))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Mappers for transforming CSV/JSON data using mapping configuration files."""
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Mapper for transforming course data to FOLIO Course Reserves format.
|
|
2
|
+
|
|
3
|
+
Provides the CoursesMapper class for mapping legacy course data to FOLIO course
|
|
4
|
+
records using configured mapping files. Handles course listings, instructors, and
|
|
5
|
+
term/department references.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import i18n
|
|
2
9
|
from typing import Any
|
|
3
10
|
from typing import Dict
|
|
@@ -27,6 +34,16 @@ class CoursesMapper(MappingFileMapperBase):
|
|
|
27
34
|
library_configuration: LibraryConfiguration,
|
|
28
35
|
task_configuration,
|
|
29
36
|
):
|
|
37
|
+
"""Initialize CoursesMapper with mapping configurations.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
folio_client (FolioClient): FOLIO API client.
|
|
41
|
+
course_map: Course mapping configuration.
|
|
42
|
+
terms_map: Academic terms mapping configuration.
|
|
43
|
+
departments_map: Department mapping configuration.
|
|
44
|
+
library_configuration (LibraryConfiguration): Library configuration.
|
|
45
|
+
task_configuration: Task configuration for course migration.
|
|
46
|
+
"""
|
|
30
47
|
self.folio_client: FolioClient = folio_client
|
|
31
48
|
self.user_cache: dict = {}
|
|
32
49
|
self.notes_mapper: NotesMapper = NotesMapper(
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Mapper for transforming holdings data to FOLIO Holdings format.
|
|
2
|
+
|
|
3
|
+
Provides the HoldingsMapper class for mapping legacy holdings data to FOLIO Holdings
|
|
4
|
+
records using configured mapping files. Handles locations, call numbers, notes, and
|
|
5
|
+
bound-with relationships.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import ast
|
|
2
9
|
import json
|
|
3
10
|
import logging
|
|
@@ -35,6 +42,18 @@ class HoldingsMapper(MappingFileMapperBase):
|
|
|
35
42
|
task_config: AbstractTaskConfiguration,
|
|
36
43
|
statistical_codes_map=None,
|
|
37
44
|
):
|
|
45
|
+
"""Initialize HoldingsMapper for holdings transformations.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
folio_client (FolioClient): FOLIO API client.
|
|
49
|
+
holdings_map: Mapping configuration for holdings fields.
|
|
50
|
+
location_map: Mapping of legacy to FOLIO locations.
|
|
51
|
+
call_number_type_map: Mapping of legacy to FOLIO call number types.
|
|
52
|
+
instance_id_map: Mapping of legacy to FOLIO instance IDs.
|
|
53
|
+
library_configuration (LibraryConfiguration): Library configuration.
|
|
54
|
+
task_config (AbstractTaskConfiguration): Task configuration.
|
|
55
|
+
statistical_codes_map: Mapping of legacy to FOLIO statistical codes.
|
|
56
|
+
"""
|
|
38
57
|
holdings_schema = folio_client.get_holdings_schema()
|
|
39
58
|
self.instance_id_map = instance_id_map
|
|
40
59
|
super().__init__(
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Mapper for transforming item data to FOLIO Items format.
|
|
2
|
+
|
|
3
|
+
Provides the ItemMapper class for mapping legacy item data to FOLIO Item records
|
|
4
|
+
using configured mapping files. Handles material types, loan types, statuses, and
|
|
5
|
+
circulation notes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import json
|
|
2
9
|
import logging
|
|
3
10
|
import sys
|
|
@@ -44,6 +51,23 @@ class ItemMapper(MappingFileMapperBase):
|
|
|
44
51
|
library_configuration: LibraryConfiguration,
|
|
45
52
|
task_configuration: AbstractTaskConfiguration,
|
|
46
53
|
):
|
|
54
|
+
"""Initialize ItemMapper for item transformations.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
folio_client (FolioClient): FOLIO API client.
|
|
58
|
+
items_map: Mapping configuration for item fields.
|
|
59
|
+
material_type_map: Mapping of legacy to FOLIO material types.
|
|
60
|
+
loan_type_map: Mapping of legacy to FOLIO loan types.
|
|
61
|
+
location_map: Mapping of legacy to FOLIO locations.
|
|
62
|
+
call_number_type_map: Mapping of legacy to FOLIO call number types.
|
|
63
|
+
holdings_id_map: Mapping of legacy to FOLIO holdings IDs.
|
|
64
|
+
statistical_codes_map: Mapping of legacy to FOLIO statistical codes.
|
|
65
|
+
item_statuses_map: Mapping of legacy to FOLIO item statuses.
|
|
66
|
+
temporary_loan_type_mapping: Mapping for temporary loan types.
|
|
67
|
+
temporary_location_mapping: Mapping for temporary locations.
|
|
68
|
+
library_configuration (LibraryConfiguration): Library configuration.
|
|
69
|
+
task_configuration (AbstractTaskConfiguration): Task configuration.
|
|
70
|
+
"""
|
|
47
71
|
item_schema = folio_client.get_item_schema()
|
|
48
72
|
super().__init__(
|
|
49
73
|
folio_client,
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
"""Mapper for transforming manual fee/fine data to FOLIO Accounts format.
|
|
2
|
+
|
|
3
|
+
Provides the ManualFeeFinesMapper class for mapping legacy fee and fine data to FOLIO
|
|
4
|
+
Account records. Handles fee/fine types, owners, amounts, and payment status.
|
|
5
|
+
"""
|
|
6
|
+
|
|
1
7
|
import json
|
|
2
8
|
import logging
|
|
3
9
|
import uuid
|
|
@@ -34,6 +40,18 @@ class ManualFeeFinesMapper(MappingFileMapperBase):
|
|
|
34
40
|
service_point_map,
|
|
35
41
|
ignore_legacy_identifier: bool = True,
|
|
36
42
|
):
|
|
43
|
+
"""Initialize ManualFeeFinesMapper for fee/fine transformations.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
folio_client (FolioClient): FOLIO API client.
|
|
47
|
+
library_configuration (LibraryConfiguration): Library configuration.
|
|
48
|
+
task_configuration: Task configuration for fee fines migration.
|
|
49
|
+
feefines_map: Mapping configuration for fee/fine fields.
|
|
50
|
+
feefines_owner_map: Mapping of legacy to FOLIO fee/fine owners.
|
|
51
|
+
feefines_type_map: Mapping of legacy to FOLIO fee/fine types.
|
|
52
|
+
service_point_map: Mapping of legacy to FOLIO service points.
|
|
53
|
+
ignore_legacy_identifier (bool): Whether to ignore legacy identifiers.
|
|
54
|
+
"""
|
|
37
55
|
self.folio_client: FolioClient = folio_client
|
|
38
56
|
self.composite_feefine_schema = self.get_composite_feefine_schema()
|
|
39
57
|
self.task_configuration = task_configuration
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Base class for all mapping file-based data transformations.
|
|
2
|
+
|
|
3
|
+
Provides the MappingFileMapperBase abstract class that all mapper classes inherit
|
|
4
|
+
from. Handles common functionality for loading mapping files, validating mappings,
|
|
5
|
+
and performing data transformations based on configured field mappings.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import csv
|
|
2
9
|
import itertools
|
|
3
10
|
import json
|
|
@@ -38,6 +45,18 @@ class MappingFileMapperBase(MapperBase):
|
|
|
38
45
|
task_config: AbstractTaskConfiguration,
|
|
39
46
|
ignore_legacy_identifier=False,
|
|
40
47
|
):
|
|
48
|
+
"""Initialize the base mapper for mapping file transformations.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
folio_client (FolioClient): FOLIO API client.
|
|
52
|
+
schema: JSON schema for validation.
|
|
53
|
+
record_map: Mapping configuration from legacy to FOLIO fields.
|
|
54
|
+
statistical_codes_map: Mapping for statistical codes.
|
|
55
|
+
uuid_namespace (UUID): UUID namespace for deterministic IDs.
|
|
56
|
+
library_configuration (LibraryConfiguration): Library configuration.
|
|
57
|
+
task_config (AbstractTaskConfiguration): Task configuration.
|
|
58
|
+
ignore_legacy_identifier (bool): Whether to ignore legacy identifiers.
|
|
59
|
+
"""
|
|
41
60
|
super().__init__(library_configuration, task_config, folio_client)
|
|
42
61
|
self.uuid_namespace = uuid_namespace
|
|
43
62
|
self.ignore_legacy_identifier = ignore_legacy_identifier
|
|
@@ -685,12 +704,10 @@ class MappingFileMapperBase(MapperBase):
|
|
|
685
704
|
|
|
686
705
|
@staticmethod
|
|
687
706
|
def _get_delimited_file_reader(source_file, file_name: Path):
|
|
688
|
-
"""
|
|
689
|
-
First, let's count:
|
|
690
|
-
* The total number of rows in the source file
|
|
691
|
-
* The total number of empty rows in the source file
|
|
707
|
+
"""Count rows in source file and return counts with a DictReader.
|
|
692
708
|
|
|
693
|
-
|
|
709
|
+
Counts total rows and empty rows, then returns those counts
|
|
710
|
+
along with a csv.DictReader.
|
|
694
711
|
|
|
695
712
|
Args:
|
|
696
713
|
source_file (_type_): _description_
|
|
@@ -884,7 +901,7 @@ def weird_division(number, divisor):
|
|
|
884
901
|
|
|
885
902
|
|
|
886
903
|
def set_deep(dictionary, key, value):
|
|
887
|
-
"""
|
|
904
|
+
"""Sets a nested property in a dict given a dot notated address.
|
|
888
905
|
|
|
889
906
|
Args:
|
|
890
907
|
dictionary (_type_): a python dictionary ({"a":{"b":{"c":"value"}}})
|
|
@@ -901,7 +918,7 @@ def set_deep(dictionary, key, value):
|
|
|
901
918
|
|
|
902
919
|
|
|
903
920
|
def set_deep2(dictionary, key, value):
|
|
904
|
-
"""
|
|
921
|
+
"""Sets a nested property in a dict given a dot notated address.
|
|
905
922
|
|
|
906
923
|
Args:
|
|
907
924
|
dictionary (_type_): a python dictionary ({"a":{"b":{"c":"value"}}})
|
|
@@ -938,7 +955,7 @@ def set_deep2(dictionary, key, value):
|
|
|
938
955
|
|
|
939
956
|
|
|
940
957
|
def get_deep(dictionary, keys, default=None):
|
|
941
|
-
"""
|
|
958
|
+
"""Returns a nested property in a dict given a dot notated address.
|
|
942
959
|
|
|
943
960
|
Args:
|
|
944
961
|
dictionary (_type_): a python dictionary ({"a":{"b":{"c":"value"}}})
|
|
@@ -956,7 +973,7 @@ def get_deep(dictionary, keys, default=None):
|
|
|
956
973
|
|
|
957
974
|
|
|
958
975
|
def in_deep(dictionary, keys):
|
|
959
|
-
"""Checks if a property exists given a dot notated address
|
|
976
|
+
"""Checks if a property exists given a dot notated address.
|
|
960
977
|
|
|
961
978
|
Args:
|
|
962
979
|
dictionary (_type_): a python dictionary ({"a":{"b":{"c":"value"}}})
|