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
@@ -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