folio-migration-tools 1.9.10__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.
- folio_migration_tools/__init__.py +3 -4
- folio_migration_tools/__main__.py +53 -31
- folio_migration_tools/circulation_helper.py +118 -108
- folio_migration_tools/custom_dict.py +2 -2
- folio_migration_tools/custom_exceptions.py +4 -5
- folio_migration_tools/folder_structure.py +17 -7
- folio_migration_tools/helper.py +8 -7
- folio_migration_tools/holdings_helper.py +4 -3
- folio_migration_tools/i18n_cache.py +79 -0
- folio_migration_tools/library_configuration.py +77 -37
- folio_migration_tools/mapper_base.py +45 -31
- folio_migration_tools/mapping_file_transformation/courses_mapper.py +1 -1
- folio_migration_tools/mapping_file_transformation/holdings_mapper.py +7 -3
- folio_migration_tools/mapping_file_transformation/item_mapper.py +13 -26
- folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +1 -2
- folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +13 -11
- folio_migration_tools/mapping_file_transformation/order_mapper.py +6 -5
- folio_migration_tools/mapping_file_transformation/organization_mapper.py +3 -3
- folio_migration_tools/mapping_file_transformation/user_mapper.py +47 -28
- folio_migration_tools/marc_rules_transformation/conditions.py +82 -97
- folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +13 -5
- folio_migration_tools/marc_rules_transformation/hrid_handler.py +3 -2
- folio_migration_tools/marc_rules_transformation/marc_file_processor.py +26 -24
- folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +56 -51
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +28 -17
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +68 -37
- folio_migration_tools/migration_report.py +18 -7
- folio_migration_tools/migration_tasks/batch_poster.py +285 -354
- folio_migration_tools/migration_tasks/bibs_transformer.py +14 -9
- folio_migration_tools/migration_tasks/courses_migrator.py +2 -3
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py +23 -24
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py +14 -24
- folio_migration_tools/migration_tasks/items_transformer.py +23 -34
- folio_migration_tools/migration_tasks/loans_migrator.py +67 -144
- folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +3 -3
- folio_migration_tools/migration_tasks/migration_task_base.py +47 -60
- folio_migration_tools/migration_tasks/orders_transformer.py +25 -42
- folio_migration_tools/migration_tasks/organization_transformer.py +9 -18
- folio_migration_tools/migration_tasks/requests_migrator.py +21 -24
- folio_migration_tools/migration_tasks/reserves_migrator.py +6 -5
- folio_migration_tools/migration_tasks/user_transformer.py +25 -20
- folio_migration_tools/task_configuration.py +6 -7
- folio_migration_tools/transaction_migration/legacy_loan.py +15 -27
- folio_migration_tools/transaction_migration/legacy_request.py +1 -1
- folio_migration_tools/translations/en.json +0 -7
- {folio_migration_tools-1.9.10.dist-info → folio_migration_tools-1.10.0.dist-info}/METADATA +19 -28
- folio_migration_tools-1.10.0.dist-info/RECORD +63 -0
- folio_migration_tools-1.10.0.dist-info/WHEEL +4 -0
- folio_migration_tools-1.10.0.dist-info/entry_points.txt +3 -0
- folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +0 -241
- folio_migration_tools/migration_tasks/authority_transformer.py +0 -119
- folio_migration_tools/test_infrastructure/__init__.py +0 -0
- folio_migration_tools/test_infrastructure/mocked_classes.py +0 -406
- folio_migration_tools-1.9.10.dist-info/RECORD +0 -67
- folio_migration_tools-1.9.10.dist-info/WHEEL +0 -4
- folio_migration_tools-1.9.10.dist-info/entry_points.txt +0 -3
- folio_migration_tools-1.9.10.dist-info/licenses/LICENSE +0 -21
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
"""The default mapper, responsible for parsing MARC21 records acording to the
|
|
2
|
-
FOLIO community specifications"""
|
|
3
|
-
import logging
|
|
4
|
-
import re
|
|
5
|
-
import time
|
|
6
|
-
import uuid
|
|
7
|
-
from typing import List
|
|
8
|
-
|
|
9
|
-
import i18n
|
|
10
|
-
import pymarc
|
|
11
|
-
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
12
|
-
from folio_uuid.folio_uuid import FolioUUID
|
|
13
|
-
from folioclient import FolioClient
|
|
14
|
-
from pymarc import Leader, Record
|
|
15
|
-
|
|
16
|
-
from folio_migration_tools.custom_exceptions import TransformationProcessError
|
|
17
|
-
from folio_migration_tools.helper import Helper
|
|
18
|
-
from folio_migration_tools.library_configuration import (
|
|
19
|
-
FileDefinition,
|
|
20
|
-
IlsFlavour,
|
|
21
|
-
LibraryConfiguration,
|
|
22
|
-
)
|
|
23
|
-
from folio_migration_tools.marc_rules_transformation.conditions import Conditions
|
|
24
|
-
from folio_migration_tools.marc_rules_transformation.hrid_handler import HRIDHandler
|
|
25
|
-
from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
|
|
26
|
-
RulesMapperBase,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class AuthorityMapper(RulesMapperBase):
|
|
31
|
-
non_repatable_fields = [
|
|
32
|
-
"100",
|
|
33
|
-
"110",
|
|
34
|
-
"111",
|
|
35
|
-
"130",
|
|
36
|
-
"147",
|
|
37
|
-
"148",
|
|
38
|
-
"150",
|
|
39
|
-
"151",
|
|
40
|
-
"155",
|
|
41
|
-
"162",
|
|
42
|
-
"180",
|
|
43
|
-
"181",
|
|
44
|
-
"182",
|
|
45
|
-
"185",
|
|
46
|
-
"378",
|
|
47
|
-
"384",
|
|
48
|
-
]
|
|
49
|
-
"""_summary_
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
RulesMapperBase (_type_): _description_
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
def __init__(
|
|
56
|
-
self,
|
|
57
|
-
folio_client,
|
|
58
|
-
library_configuration: LibraryConfiguration,
|
|
59
|
-
task_configuration,
|
|
60
|
-
):
|
|
61
|
-
super().__init__(
|
|
62
|
-
folio_client,
|
|
63
|
-
library_configuration,
|
|
64
|
-
task_configuration,
|
|
65
|
-
None,
|
|
66
|
-
self.get_authority_json_schema(folio_client, library_configuration),
|
|
67
|
-
Conditions(folio_client, self, "auth", library_configuration.folio_release),
|
|
68
|
-
)
|
|
69
|
-
self.srs_recs: list = []
|
|
70
|
-
logging.info("Fetching mapping rules from the tenant")
|
|
71
|
-
rules_endpoint = "/mapping-rules/marc-authority"
|
|
72
|
-
self.mappings = self.folio_client.folio_get_single_object(rules_endpoint)
|
|
73
|
-
self.source_file_mapping: dict = {}
|
|
74
|
-
self.setup_source_file_mapping()
|
|
75
|
-
self.start = time.time()
|
|
76
|
-
|
|
77
|
-
def get_legacy_ids(self, marc_record: Record, idx: int) -> List[str]:
|
|
78
|
-
ils_flavour: IlsFlavour = self.task_configuration.ils_flavour
|
|
79
|
-
if ils_flavour in {IlsFlavour.sierra, IlsFlavour.millennium}:
|
|
80
|
-
raise TransformationProcessError("", f"ILS {ils_flavour} not configured")
|
|
81
|
-
elif ils_flavour == IlsFlavour.tag907y:
|
|
82
|
-
return RulesMapperBase.get_bib_id_from_907y(marc_record, idx)
|
|
83
|
-
elif ils_flavour == IlsFlavour.tagf990a:
|
|
84
|
-
return RulesMapperBase.get_bib_id_from_990a(marc_record, idx)
|
|
85
|
-
elif ils_flavour == IlsFlavour.aleph:
|
|
86
|
-
raise TransformationProcessError("", f"ILS {ils_flavour} not configured")
|
|
87
|
-
elif ils_flavour in {IlsFlavour.voyager, "voyager", IlsFlavour.tag001}:
|
|
88
|
-
return RulesMapperBase.get_bib_id_from_001(marc_record, idx)
|
|
89
|
-
elif ils_flavour == IlsFlavour.koha:
|
|
90
|
-
raise TransformationProcessError("", f"ILS {ils_flavour} not configured")
|
|
91
|
-
elif ils_flavour == IlsFlavour.none:
|
|
92
|
-
return [str(uuid.uuid4())]
|
|
93
|
-
else:
|
|
94
|
-
raise TransformationProcessError("", f"ILS {ils_flavour} not configured")
|
|
95
|
-
|
|
96
|
-
def parse_record(
|
|
97
|
-
self, marc_record: pymarc.Record, file_def: FileDefinition, legacy_ids: List[str]
|
|
98
|
-
) -> list[dict]:
|
|
99
|
-
"""Parses an auth recod into a FOLIO Authority object
|
|
100
|
-
This is the main function
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
legacy_ids (_type_): _description_
|
|
104
|
-
marc_record (Record): _description_
|
|
105
|
-
file_def (FileDefinition): _description_
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
dict: _description_
|
|
109
|
-
"""
|
|
110
|
-
self.print_progress()
|
|
111
|
-
ignored_subsequent_fields: set = set()
|
|
112
|
-
bad_tags = set(self.task_configuration.tags_to_delete) # "907"
|
|
113
|
-
folio_authority = self.perform_initial_preparation(marc_record, legacy_ids)
|
|
114
|
-
for marc_field in marc_record:
|
|
115
|
-
self.report_marc_stats(marc_field, bad_tags, legacy_ids, ignored_subsequent_fields)
|
|
116
|
-
if marc_field.tag not in ignored_subsequent_fields:
|
|
117
|
-
self.process_marc_field(
|
|
118
|
-
folio_authority,
|
|
119
|
-
marc_field,
|
|
120
|
-
ignored_subsequent_fields,
|
|
121
|
-
legacy_ids,
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
self.perform_additional_parsing(folio_authority)
|
|
125
|
-
clean_folio_authority = self.validate_required_properties(
|
|
126
|
-
"-".join(legacy_ids), folio_authority, self.schema, FOLIONamespaces.instances
|
|
127
|
-
)
|
|
128
|
-
self.dedupe_rec(clean_folio_authority)
|
|
129
|
-
marc_record.remove_fields(*list(bad_tags))
|
|
130
|
-
self.report_folio_mapping(clean_folio_authority, self.schema)
|
|
131
|
-
return [clean_folio_authority]
|
|
132
|
-
|
|
133
|
-
def perform_initial_preparation(self, marc_record: pymarc.Record, legacy_ids):
|
|
134
|
-
folio_authority = {}
|
|
135
|
-
folio_authority["id"] = str(
|
|
136
|
-
FolioUUID(
|
|
137
|
-
self.base_string_for_folio_uuid,
|
|
138
|
-
FOLIONamespaces.authorities,
|
|
139
|
-
str(legacy_ids[-1]),
|
|
140
|
-
)
|
|
141
|
-
)
|
|
142
|
-
HRIDHandler.handle_035_generation(
|
|
143
|
-
marc_record, legacy_ids, self.migration_report, False, False
|
|
144
|
-
)
|
|
145
|
-
self.map_source_file_and_natural_id(marc_record, folio_authority)
|
|
146
|
-
self.handle_leader_17(marc_record, legacy_ids)
|
|
147
|
-
return folio_authority
|
|
148
|
-
|
|
149
|
-
def map_source_file_and_natural_id(self, marc_record, folio_authority):
|
|
150
|
-
"""Implement source file and natural ID mappings according to MODDICORE-283"""
|
|
151
|
-
match_prefix_patt = re.compile("^[A-Za-z]+")
|
|
152
|
-
natural_id = None
|
|
153
|
-
source_file_id = None
|
|
154
|
-
has_010 = marc_record.get("010")
|
|
155
|
-
if has_010 and (has_010a := has_010.get_subfields("a")):
|
|
156
|
-
for a_subfield in has_010a:
|
|
157
|
-
natural_id_prefix = match_prefix_patt.match(a_subfield)
|
|
158
|
-
if natural_id_prefix and (
|
|
159
|
-
source_file := self.source_file_mapping.get(natural_id_prefix.group(0), None)
|
|
160
|
-
):
|
|
161
|
-
natural_id = "".join(a_subfield.split())
|
|
162
|
-
source_file_id = source_file["id"]
|
|
163
|
-
self.migration_report.add_general_statistics(
|
|
164
|
-
i18n.t("naturalId mapped from %{fro}", fro="010$a")
|
|
165
|
-
)
|
|
166
|
-
self.migration_report.add(
|
|
167
|
-
"AuthoritySourceFileMapping",
|
|
168
|
-
f"{source_file['name']} -- {natural_id_prefix.group(0)} -- 010$a",
|
|
169
|
-
number=1,
|
|
170
|
-
)
|
|
171
|
-
break
|
|
172
|
-
if not source_file_id:
|
|
173
|
-
natural_id = "".join(marc_record["001"].data.split())
|
|
174
|
-
self.migration_report.add_general_statistics(
|
|
175
|
-
i18n.t("naturalId mapped from %{fro}", fro="001")
|
|
176
|
-
)
|
|
177
|
-
natural_id_prefix = match_prefix_patt.match(natural_id)
|
|
178
|
-
if natural_id_prefix:
|
|
179
|
-
if source_file := self.source_file_mapping.get(natural_id_prefix.group(0), None):
|
|
180
|
-
source_file_id = source_file["id"]
|
|
181
|
-
self.migration_report.add(
|
|
182
|
-
"AuthoritySourceFileMapping",
|
|
183
|
-
f"{source_file['name']} -- {natural_id_prefix.group(0)} -- 001",
|
|
184
|
-
number=1,
|
|
185
|
-
)
|
|
186
|
-
folio_authority["naturalId"] = natural_id
|
|
187
|
-
if source_file_id:
|
|
188
|
-
folio_authority["sourceFileId"] = source_file_id
|
|
189
|
-
|
|
190
|
-
def setup_source_file_mapping(self):
|
|
191
|
-
if self.folio_client.authority_source_files:
|
|
192
|
-
logging.info(
|
|
193
|
-
f"{len(self.folio_client.authority_source_files)} \tAuthority source files"
|
|
194
|
-
)
|
|
195
|
-
for source_file in self.folio_client.authority_source_files:
|
|
196
|
-
for sf_code in source_file.get("codes", []):
|
|
197
|
-
self.source_file_mapping[sf_code] = source_file
|
|
198
|
-
|
|
199
|
-
def handle_leader_17(self, marc_record, legacy_ids):
|
|
200
|
-
leader_17 = marc_record.leader[17] or "Empty"
|
|
201
|
-
self.migration_report.add(
|
|
202
|
-
"AuthorityEncodingLevel", i18n.t("Original value") + f": {leader_17}"
|
|
203
|
-
)
|
|
204
|
-
if leader_17 not in ["n", "o"]:
|
|
205
|
-
Helper.log_data_issue(
|
|
206
|
-
legacy_ids,
|
|
207
|
-
f"LDR pos. 17 is '{leader_17}'. Is this correct? Value has been changed to 'n'.",
|
|
208
|
-
marc_record.leader,
|
|
209
|
-
)
|
|
210
|
-
marc_record.leader = Leader(f"{marc_record.leader[:17]}n{marc_record.leader[18:]}")
|
|
211
|
-
self.migration_report.add(
|
|
212
|
-
"AuthorityEncodingLevel", i18n.t("Changed %{a} to %{b}", a=leader_17, b="n")
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
def perform_additional_parsing(
|
|
216
|
-
self,
|
|
217
|
-
folio_authority: dict,
|
|
218
|
-
) -> None:
|
|
219
|
-
"""Do stuff not easily captured by the mapping rules
|
|
220
|
-
|
|
221
|
-
Args:
|
|
222
|
-
folio_authority (dict): _description_
|
|
223
|
-
"""
|
|
224
|
-
folio_authority["source"] = "MARC"
|
|
225
|
-
|
|
226
|
-
def get_authority_json_schema(self, folio_client: FolioClient, library_configuration):
|
|
227
|
-
"""Fetches the JSON Schema for autorities"""
|
|
228
|
-
if library_configuration.folio_release.name.lower()[0] < "p":
|
|
229
|
-
schema = folio_client.get_from_github(
|
|
230
|
-
"folio-org", "mod-inventory-storage", "/ramls/authorities/authority.json"
|
|
231
|
-
)
|
|
232
|
-
else:
|
|
233
|
-
schema = folio_client.get_from_github(
|
|
234
|
-
"folio-org",
|
|
235
|
-
"mod-entities-links",
|
|
236
|
-
"/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml",
|
|
237
|
-
)
|
|
238
|
-
return schema
|
|
239
|
-
|
|
240
|
-
def wrap_up(self):
|
|
241
|
-
logging.info("Mapper wrapping up")
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Annotated
|
|
3
|
-
from typing import List
|
|
4
|
-
import i18n
|
|
5
|
-
|
|
6
|
-
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
7
|
-
from pydantic import Field
|
|
8
|
-
|
|
9
|
-
from folio_migration_tools.helper import Helper
|
|
10
|
-
from folio_migration_tools.library_configuration import FileDefinition
|
|
11
|
-
from folio_migration_tools.library_configuration import IlsFlavour
|
|
12
|
-
from folio_migration_tools.library_configuration import LibraryConfiguration
|
|
13
|
-
from folio_migration_tools.marc_rules_transformation.marc_file_processor import (
|
|
14
|
-
MarcFileProcessor,
|
|
15
|
-
)
|
|
16
|
-
from folio_migration_tools.marc_rules_transformation.rules_mapper_authorities import (
|
|
17
|
-
AuthorityMapper,
|
|
18
|
-
)
|
|
19
|
-
from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
|
|
20
|
-
from folio_migration_tools.task_configuration import AbstractTaskConfiguration
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class AuthorityTransformer(MigrationTaskBase):
|
|
24
|
-
class TaskConfiguration(AbstractTaskConfiguration):
|
|
25
|
-
name: Annotated[
|
|
26
|
-
str,
|
|
27
|
-
Field(
|
|
28
|
-
description=(
|
|
29
|
-
"Name of this migration task. The name is being used to call the specific "
|
|
30
|
-
"task, and to distinguish tasks of similar types"
|
|
31
|
-
)
|
|
32
|
-
),
|
|
33
|
-
]
|
|
34
|
-
migration_task_type: Annotated[
|
|
35
|
-
str,
|
|
36
|
-
Field(
|
|
37
|
-
title="Migration task type",
|
|
38
|
-
description=("The type of migration task you want to perform"),
|
|
39
|
-
),
|
|
40
|
-
]
|
|
41
|
-
files: Annotated[
|
|
42
|
-
List[FileDefinition],
|
|
43
|
-
Field(
|
|
44
|
-
title="Source files", description=("List of MARC21 files with authority records")
|
|
45
|
-
),
|
|
46
|
-
]
|
|
47
|
-
ils_flavour: Annotated[
|
|
48
|
-
IlsFlavour,
|
|
49
|
-
Field(
|
|
50
|
-
title="ILS flavour", description="The type of ILS you are migrating records from."
|
|
51
|
-
),
|
|
52
|
-
]
|
|
53
|
-
tags_to_delete: Annotated[
|
|
54
|
-
List[str],
|
|
55
|
-
Field(
|
|
56
|
-
title="Tags to delete from MARC record",
|
|
57
|
-
description=(
|
|
58
|
-
"Tags in the incoming MARC authority that the process should remove "
|
|
59
|
-
"before adding them into FOLIO. These tags will be used in the "
|
|
60
|
-
"transformation before getting removed."
|
|
61
|
-
),
|
|
62
|
-
),
|
|
63
|
-
] = []
|
|
64
|
-
create_source_records: Annotated[
|
|
65
|
-
bool,
|
|
66
|
-
Field(
|
|
67
|
-
title="Create source records",
|
|
68
|
-
description=(
|
|
69
|
-
"Controls wheter or not to retain the MARC records in "
|
|
70
|
-
"Source Record Storage."
|
|
71
|
-
),
|
|
72
|
-
),
|
|
73
|
-
] = True
|
|
74
|
-
|
|
75
|
-
@staticmethod
|
|
76
|
-
def get_object_type() -> FOLIONamespaces:
|
|
77
|
-
return FOLIONamespaces.authorities
|
|
78
|
-
|
|
79
|
-
def __init__(
|
|
80
|
-
self,
|
|
81
|
-
task_config: TaskConfiguration,
|
|
82
|
-
library_config: LibraryConfiguration,
|
|
83
|
-
use_logging: bool = True,
|
|
84
|
-
):
|
|
85
|
-
super().__init__(library_config, task_config, use_logging)
|
|
86
|
-
self.processor: MarcFileProcessor
|
|
87
|
-
self.check_source_files(
|
|
88
|
-
self.folder_structure.legacy_records_folder, self.task_configuration.files
|
|
89
|
-
)
|
|
90
|
-
self.mapper: AuthorityMapper = AuthorityMapper(
|
|
91
|
-
self.folio_client, library_config, task_config
|
|
92
|
-
)
|
|
93
|
-
self.auth_ids: set = set()
|
|
94
|
-
logging.info("Init done")
|
|
95
|
-
|
|
96
|
-
def do_work(self):
|
|
97
|
-
self.do_work_marc_transformer()
|
|
98
|
-
|
|
99
|
-
def wrap_up(self):
|
|
100
|
-
logging.info("Done. Transformer Wrapping up...")
|
|
101
|
-
self.extradata_writer.flush()
|
|
102
|
-
self.processor.wrap_up()
|
|
103
|
-
with open(self.folder_structure.migration_reports_file, "w+") as report_file:
|
|
104
|
-
self.mapper.migration_report.write_migration_report(
|
|
105
|
-
i18n.t("Authority records transformation report"),
|
|
106
|
-
report_file,
|
|
107
|
-
self.start_datetime,
|
|
108
|
-
)
|
|
109
|
-
Helper.print_mapping_report(
|
|
110
|
-
report_file,
|
|
111
|
-
self.mapper.parsed_records,
|
|
112
|
-
self.mapper.mapped_folio_fields,
|
|
113
|
-
self.mapper.mapped_legacy_fields,
|
|
114
|
-
)
|
|
115
|
-
logging.info(
|
|
116
|
-
"Done. Transformation report written to %s",
|
|
117
|
-
self.folder_structure.migration_reports_file.name,
|
|
118
|
-
)
|
|
119
|
-
self.clean_out_empty_logs()
|
|
File without changes
|