folio-migration-tools 1.9.0a2__py3-none-any.whl → 1.9.0a4__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 (39) hide show
  1. folio_migration_tools/__init__.py +3 -0
  2. folio_migration_tools/__main__.py +16 -6
  3. folio_migration_tools/folder_structure.py +3 -0
  4. folio_migration_tools/library_configuration.py +8 -7
  5. folio_migration_tools/mapper_base.py +26 -17
  6. folio_migration_tools/mapping_file_transformation/holdings_mapper.py +23 -11
  7. folio_migration_tools/mapping_file_transformation/item_mapper.py +13 -11
  8. folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +9 -10
  9. folio_migration_tools/mapping_file_transformation/order_mapper.py +2 -2
  10. folio_migration_tools/mapping_file_transformation/organization_mapper.py +1 -1
  11. folio_migration_tools/mapping_file_transformation/user_mapper.py +6 -4
  12. folio_migration_tools/marc_rules_transformation/conditions.py +23 -7
  13. folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +21 -11
  14. folio_migration_tools/marc_rules_transformation/marc_file_processor.py +36 -9
  15. folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +15 -11
  16. folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +7 -5
  17. folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +98 -45
  18. folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +53 -27
  19. folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +13 -11
  20. folio_migration_tools/migration_tasks/batch_poster.py +78 -38
  21. folio_migration_tools/migration_tasks/bibs_transformer.py +21 -8
  22. folio_migration_tools/migration_tasks/courses_migrator.py +11 -6
  23. folio_migration_tools/migration_tasks/holdings_csv_transformer.py +22 -16
  24. folio_migration_tools/migration_tasks/holdings_marc_transformer.py +9 -7
  25. folio_migration_tools/migration_tasks/items_transformer.py +13 -10
  26. folio_migration_tools/migration_tasks/loans_migrator.py +10 -9
  27. folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +13 -9
  28. folio_migration_tools/migration_tasks/migration_task_base.py +18 -18
  29. folio_migration_tools/migration_tasks/orders_transformer.py +14 -10
  30. folio_migration_tools/migration_tasks/organization_transformer.py +12 -8
  31. folio_migration_tools/migration_tasks/requests_migrator.py +8 -5
  32. folio_migration_tools/migration_tasks/reserves_migrator.py +7 -4
  33. folio_migration_tools/migration_tasks/user_transformer.py +46 -17
  34. folio_migration_tools/task_configuration.py +3 -3
  35. folio_migration_tools/translations/en.json +2 -1
  36. {folio_migration_tools-1.9.0a2.dist-info → folio_migration_tools-1.9.0a4.dist-info}/METADATA +6 -5
  37. {folio_migration_tools-1.9.0a2.dist-info → folio_migration_tools-1.9.0a4.dist-info}/RECORD +39 -39
  38. {folio_migration_tools-1.9.0a2.dist-info → folio_migration_tools-1.9.0a4.dist-info}/WHEEL +1 -1
  39. {folio_migration_tools-1.9.0a2.dist-info → folio_migration_tools-1.9.0a4.dist-info}/LICENSE +0 -0
@@ -0,0 +1,3 @@
1
+ import importlib.metadata
2
+
3
+ __version__ = importlib.metadata.version("folio_migration_tools")
@@ -8,6 +8,7 @@ import httpx
8
8
  import humps
9
9
  import i18n
10
10
  from argparse_prompt import PromptParser
11
+ from folioclient import FolioClient
11
12
  from pydantic import ValidationError
12
13
 
13
14
  from folio_migration_tools.config_file_load import merge_load
@@ -38,14 +39,16 @@ def parse_args(args):
38
39
  )
39
40
  parser.add_argument(
40
41
  "--okapi_password",
41
- help="pasword for the tenant in the configuration file",
42
+ help="password for the tenant in the configuration file",
42
43
  prompt="FOLIO_MIGRATION_TOOLS_OKAPI_PASSWORD" not in environ,
43
44
  default=environ.get("FOLIO_MIGRATION_TOOLS_OKAPI_PASSWORD"),
44
45
  secure=True,
45
46
  )
46
47
  parser.add_argument(
47
48
  "--base_folder_path",
48
- help=("path to the base folder for this library. Built on migration_repo_template"),
49
+ help=(
50
+ "path to the base folder for this library. Built on migration_repo_template"
51
+ ),
49
52
  prompt="FOLIO_MIGRATION_TOOLS_BASE_FOLDER_PATH" not in environ,
50
53
  default=environ.get("FOLIO_MIGRATION_TOOLS_BASE_FOLDER_PATH"),
51
54
  )
@@ -107,10 +110,17 @@ def main():
107
110
  )
108
111
  sys.exit("Task Type Not Found")
109
112
  try:
110
- task_config = task_class.TaskConfiguration(**migration_task_config)
111
- task_obj = task_class(task_config, library_config)
112
- task_obj.do_work()
113
- task_obj.wrap_up()
113
+ logging.getLogger("httpx").setLevel(logging.WARNING) # Exclude info messages from httpx
114
+ with FolioClient(
115
+ library_config.okapi_url,
116
+ library_config.tenant_id,
117
+ library_config.okapi_username,
118
+ library_config.okapi_password,
119
+ ) as folio_client:
120
+ task_config = task_class.TaskConfiguration(**migration_task_config)
121
+ task_obj = task_class(task_config, library_config, folio_client)
122
+ task_obj.do_work()
123
+ task_obj.wrap_up()
114
124
  except TransformationProcessError as tpe:
115
125
  logging.critical(tpe.message)
116
126
  print(f"\n{tpe.message}: {tpe.data_value}")
@@ -101,6 +101,9 @@ class FolderStructure:
101
101
  self.srs_records_path = (
102
102
  self.results_folder / f"folio_srs_{object_type_string}{self.file_template}.json"
103
103
  )
104
+ self.data_import_marc_path = (
105
+ self.results_folder / f"folio_marc_{object_type_string}{self.file_template}.mrc"
106
+ )
104
107
  self.organizations_id_map_path = (
105
108
  self.results_folder / f"{str(FOLIONamespaces.organizations.name).lower()}_id_map.json"
106
109
  )
@@ -1,9 +1,7 @@
1
1
  from enum import Enum
2
- from typing import Annotated
3
- from typing import Optional
2
+ from typing import Annotated, Optional
4
3
 
5
- from pydantic import BaseModel
6
- from pydantic import Field
4
+ from pydantic import BaseModel, Field
7
5
  from pydantic.types import DirectoryPath
8
6
 
9
7
 
@@ -102,6 +100,9 @@ class LibraryConfiguration(BaseModel):
102
100
  failed_percentage_threshold: Annotated[
103
101
  int, Field(description=("Percentage of failed records until the process shuts down"))
104
102
  ] = 20
103
+ generic_exception_threshold: Annotated[
104
+ int, Field(description=("Number of generic exceptions until the process shuts down"))
105
+ ] = 50
105
106
  library_name: str
106
107
  log_level_debug: bool
107
108
  folio_release: FolioRelease = Field(
@@ -112,6 +113,6 @@ class LibraryConfiguration(BaseModel):
112
113
  )
113
114
  )
114
115
  iteration_identifier: str
115
- add_time_stamp_to_file_names: Annotated[bool, Field(title="Add time stamp to file names")] = (
116
- False
117
- )
116
+ add_time_stamp_to_file_names: Annotated[
117
+ bool, Field(title="Add time stamp to file names")
118
+ ] = False
@@ -4,18 +4,20 @@ import json
4
4
  import logging
5
5
  import sys
6
6
  import uuid
7
- import i18n
8
- from datetime import datetime
9
- from datetime import timezone
7
+ from datetime import datetime, timezone
10
8
  from pathlib import Path
9
+ from typing import List
11
10
 
11
+ import i18n
12
12
  from folio_uuid.folio_namespaces import FOLIONamespaces
13
13
  from folio_uuid.folio_uuid import FolioUUID
14
14
  from folioclient import FolioClient
15
15
 
16
- from folio_migration_tools.custom_exceptions import TransformationFieldMappingError
17
- from folio_migration_tools.custom_exceptions import TransformationProcessError
18
- from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
16
+ from folio_migration_tools.custom_exceptions import (
17
+ TransformationFieldMappingError,
18
+ TransformationProcessError,
19
+ TransformationRecordFailedError,
20
+ )
19
21
  from folio_migration_tools.extradata_writer import ExtradataWriter
20
22
  from folio_migration_tools.library_configuration import LibraryConfiguration
21
23
  from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
@@ -44,7 +46,7 @@ class MapperBase:
44
46
  self.mapped_folio_fields: dict = {}
45
47
  self.migration_report: MigrationReport = MigrationReport()
46
48
  self.num_criticalerrors = 0
47
- self.num_exeptions = 0
49
+ self.num_exceptions = 0
48
50
  self.mapped_legacy_fields: dict = {}
49
51
  self.schema_properties = None
50
52
 
@@ -269,18 +271,18 @@ class MapperBase:
269
271
  return (legacy_id, folio_record["id"], folio_record["hrid"])
270
272
  return (legacy_id, folio_record["id"])
271
273
 
272
- def handle_generic_exception(self, idx, excepion: Exception):
273
- self.num_exeptions += 1
274
+ def handle_generic_exception(self, idx, exception: Exception):
275
+ self.num_exceptions += 1
274
276
  print("\n=======ERROR===========")
275
277
  print(
276
- f"Row {idx:,} failed with the following unhandled Exception: {excepion} "
277
- f"of type {type(excepion).__name__}"
278
+ f"Row {idx:,} failed with the following unhandled Exception: {exception} "
279
+ f"of type {type(exception).__name__}"
278
280
  )
279
- logging.error(excepion, exc_info=True)
280
- if self.num_exeptions > 50:
281
+ logging.error(exception, exc_info=True)
282
+ if self.num_exceptions > self.library_configuration.generic_exception_threshold:
281
283
  logging.fatal(
282
284
  "Stopping. More than %s unhandled exceptions. Code needs fixing",
283
- self.num_exeptions,
285
+ self.num_exceptions,
284
286
  )
285
287
  sys.exit(1)
286
288
 
@@ -345,7 +347,8 @@ class MapperBase:
345
347
  "One or many required properties empty",
346
348
  f"{json.dumps(missing)}",
347
349
  )
348
- cleaned_folio_object.pop("type", None)
350
+ if object_type not in [FOLIONamespaces.users]:
351
+ cleaned_folio_object.pop("type", None)
349
352
  return cleaned_folio_object
350
353
 
351
354
  @staticmethod
@@ -428,8 +431,14 @@ class MapperBase:
428
431
 
429
432
  if call_number := folio_holding.get("callNumber", None):
430
433
  if "[" in call_number:
431
- call_numbers = ast.literal_eval(str(folio_holding["callNumber"]))
432
- bound_with_holding["callNumber"] = call_numbers[bwidx]
434
+ try:
435
+ call_numbers: List = ast.literal_eval(str(folio_holding["callNumber"]))
436
+ bound_with_holding["callNumber"] = call_numbers[bwidx]
437
+ except IndexError:
438
+ if call_numbers:
439
+ bound_with_holding["callNumber"] = call_numbers[0]
440
+ except SyntaxError:
441
+ bound_with_holding["callNumber"] = call_number
433
442
  else:
434
443
  bound_with_holding["callNumber"] = call_number
435
444
  bound_with_holding["holdingsTypeId"] = bound_with_holdings_type_id
@@ -2,12 +2,13 @@ import ast
2
2
 
3
3
  import i18n
4
4
  from folio_uuid.folio_uuid import FOLIONamespaces
5
- from folio_uuid.folio_uuid import FolioUUID
6
5
  from folioclient import FolioClient
7
6
 
8
7
  from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
9
- from folio_migration_tools.library_configuration import FileDefinition
10
- from folio_migration_tools.library_configuration import LibraryConfiguration
8
+ from folio_migration_tools.library_configuration import (
9
+ FileDefinition,
10
+ LibraryConfiguration,
11
+ )
11
12
  from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
12
13
  MappingFileMapperBase,
13
14
  )
@@ -97,6 +98,11 @@ class HoldingsMapper(MappingFileMapperBase):
97
98
  "BoundWithMappings",
98
99
  (f"Number of bib-level callnumbers in record: {len(legacy_value.split(','))}"),
99
100
  )
101
+ if legacy_value.startswith("[") and len(legacy_value.split(",")) == 1:
102
+ try:
103
+ legacy_value = ast.literal_eval(str(legacy_value))[0]
104
+ except SyntaxError:
105
+ return legacy_value
100
106
  return legacy_value
101
107
 
102
108
  def get_location_id(
@@ -127,9 +133,21 @@ class HoldingsMapper(MappingFileMapperBase):
127
133
  i18n.t("Number of bib records referenced in item") + f": {len(legacy_bib_ids)}",
128
134
  )
129
135
  for legacy_instance_id in legacy_bib_ids:
136
+ new_legacy_value = (
137
+ f".{legacy_instance_id}"
138
+ if legacy_instance_id.startswith("b")
139
+ else legacy_instance_id
140
+ )
130
141
  if (
131
- new_legacy_value := FolioUUID.clean_iii_identifiers(legacy_instance_id)
132
- ) and new_legacy_value in self.instance_id_map:
142
+ new_legacy_value not in self.instance_id_map
143
+ and legacy_instance_id not in self.instance_id_map
144
+ ):
145
+ self.migration_report.add_general_statistics(
146
+ i18n.t("Records not matched to Instances")
147
+ )
148
+ s = "Bib id not in instance id map."
149
+ raise TransformationRecordFailedError(index_or_id, s, new_legacy_value)
150
+ else:
133
151
  self.migration_report.add_general_statistics(
134
152
  i18n.t("Records matched to Instances")
135
153
  )
@@ -137,12 +155,6 @@ class HoldingsMapper(MappingFileMapperBase):
137
155
  legacy_instance_id
138
156
  )
139
157
  return_ids.append(entry[1])
140
- else:
141
- self.migration_report.add_general_statistics(
142
- i18n.t("Records not matched to Instances")
143
- )
144
- s = "Bib id not in instance id map."
145
- raise TransformationRecordFailedError(index_or_id, s, new_legacy_value)
146
158
  if any(return_ids):
147
159
  return return_ids
148
160
  else:
@@ -1,21 +1,23 @@
1
1
  import json
2
2
  import logging
3
3
  import sys
4
- from datetime import datetime
5
- from datetime import timezone
4
+ from datetime import datetime, timezone
6
5
  from typing import Set
7
6
  from uuid import uuid4
8
7
 
9
8
  import i18n
10
9
  from folio_uuid.folio_uuid import FOLIONamespaces
11
- from folio_uuid.folio_uuid import FolioUUID
12
10
  from folioclient import FolioClient
13
11
 
14
- from folio_migration_tools.custom_exceptions import TransformationProcessError
15
- from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
12
+ from folio_migration_tools.custom_exceptions import (
13
+ TransformationProcessError,
14
+ TransformationRecordFailedError,
15
+ )
16
16
  from folio_migration_tools.helper import Helper
17
- from folio_migration_tools.library_configuration import FileDefinition
18
- from folio_migration_tools.library_configuration import LibraryConfiguration
17
+ from folio_migration_tools.library_configuration import (
18
+ FileDefinition,
19
+ LibraryConfiguration,
20
+ )
19
21
  from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
20
22
  MappingFileMapperBase,
21
23
  )
@@ -224,16 +226,16 @@ class ItemMapper(MappingFileMapperBase):
224
226
  return self.transform_status(mapped_value)
225
227
  elif folio_prop_name == "barcode":
226
228
  barcode = mapped_value
227
- if barcode.strip() and barcode in self.unique_barcodes:
229
+ normalized_barcode = barcode.strip().lower()
230
+ if normalized_barcode and normalized_barcode in self.unique_barcodes:
228
231
  Helper.log_data_issue(index_or_id, "Duplicate barcode", mapped_value)
229
232
  self.migration_report.add_general_statistics(i18n.t("Duplicate barcodes"))
230
233
  return f"{barcode}-{uuid4()}"
231
234
  else:
232
- if barcode.strip():
233
- self.unique_barcodes.add(barcode)
235
+ if normalized_barcode:
236
+ self.unique_barcodes.add(normalized_barcode)
234
237
  return barcode
235
238
  elif folio_prop_name == "holdingsRecordId":
236
- mapped_value = FolioUUID.clean_iii_identifiers(mapped_value)
237
239
  if mapped_value in self.holdings_id_map:
238
240
  return self.holdings_id_map[mapped_value][1]
239
241
  elif f"{self.bib_id_template}{mapped_value}" in self.holdings_id_map:
@@ -6,19 +6,18 @@ import re
6
6
  import uuid
7
7
  from functools import reduce
8
8
  from pathlib import Path
9
- from typing import Dict
10
- from typing import List
11
- from typing import Set
9
+ from typing import Dict, List, Set
12
10
  from uuid import UUID
13
11
 
14
12
  import i18n
15
- from folio_uuid.folio_uuid import FOLIONamespaces
16
- from folio_uuid.folio_uuid import FolioUUID
13
+ from folio_uuid.folio_uuid import FOLIONamespaces, FolioUUID
17
14
  from folioclient import FolioClient
18
15
 
19
- from folio_migration_tools.custom_exceptions import TransformationFieldMappingError
20
- from folio_migration_tools.custom_exceptions import TransformationProcessError
21
- from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
16
+ from folio_migration_tools.custom_exceptions import (
17
+ TransformationFieldMappingError,
18
+ TransformationProcessError,
19
+ TransformationRecordFailedError,
20
+ )
22
21
  from folio_migration_tools.library_configuration import LibraryConfiguration
23
22
  from folio_migration_tools.mapper_base import MapperBase
24
23
  from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
@@ -787,7 +786,7 @@ class MappingFileMapperBase(MapperBase):
787
786
  missing_keys_in_record,
788
787
  )
789
788
  else:
790
- logging.info("All maped legacy fields are in the legacy object")
789
+ logging.info("All mapped legacy fields are in the legacy object")
791
790
 
792
791
  def get_ref_data_tuple_by_code(self, ref_data, ref_name, code):
793
792
  return self.get_ref_data_tuple(ref_data, ref_name, code, "code")
@@ -872,7 +871,7 @@ class MappingFileMapperBase(MapperBase):
872
871
 
873
872
  def skip_property(property_name, property):
874
873
  return bool(
875
- property_name in ["metadata", "id", "type", "lastCheckIn"]
874
+ property_name in ["metadata", "id", "lastCheckIn"]
876
875
  or property_name.startswith("effective")
877
876
  or property.get("folio:isVirtual", False)
878
877
  or property.get("description", "") == "Deprecated"
@@ -5,9 +5,9 @@ import re
5
5
  import sys
6
6
  import urllib.parse
7
7
  import uuid
8
- import i18n
9
8
 
10
9
  import httpx
10
+ import i18n
11
11
  from folio_uuid.folio_uuid import FOLIONamespaces
12
12
  from folioclient import FolioClient
13
13
  from httpx import HTTPError
@@ -141,7 +141,7 @@ class CompositeOrderMapper(MappingFileMapperBase):
141
141
  }
142
142
 
143
143
  if os.environ.get("GITHUB_TOKEN"):
144
- logging.info("Using GITHB_TOKEN environment variable for Gihub API Access")
144
+ logging.info("Using GITHUB_TOKEN environment variable for GitHub API Access")
145
145
  github_headers["authorization"] = f"token {os.environ.get('GITHUB_TOKEN')}"
146
146
 
147
147
  # Start talkign to GitHub...
@@ -31,7 +31,7 @@ class OrganizationMapper(MappingFileMapperBase):
31
31
  ):
32
32
  # Build composite organization schema
33
33
  if os.environ.get("GITHUB_TOKEN"):
34
- logging.info("Using GITHB_TOKEN environment variable for Gihub API Access")
34
+ logging.info("Using GITHUB_TOKEN environment variable for GitHub API Access")
35
35
  organization_schema = OrganizationMapper.get_latest_acq_schemas_from_github(
36
36
  "folio-org", "mod-organizations-storage", "mod-orgs", "organization"
37
37
  )
@@ -2,14 +2,16 @@ import csv
2
2
  import json
3
3
  import logging
4
4
  import sys
5
- import i18n
6
5
 
6
+ import i18n
7
7
  from dateutil.parser import parse
8
8
  from folio_uuid.folio_namespaces import FOLIONamespaces
9
9
  from folioclient import FolioClient
10
10
 
11
- from folio_migration_tools.custom_exceptions import TransformationProcessError
12
- from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
11
+ from folio_migration_tools.custom_exceptions import (
12
+ TransformationProcessError,
13
+ TransformationRecordFailedError,
14
+ )
13
15
  from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
14
16
  MappingFileMapperBase,
15
17
  )
@@ -79,7 +81,7 @@ class UserMapper(MappingFileMapperBase):
79
81
  )
80
82
  if "personal" not in folio_user:
81
83
  folio_user["personal"] = {}
82
- folio_user["personal"]["preferredContactTypeId"] = "Email"
84
+ folio_user["personal"]["preferredContactTypeId"] = "email"
83
85
  folio_user["active"] = True
84
86
  if folio_user.get("requestPreference"):
85
87
  folio_user["requestPreference"].update(
@@ -6,9 +6,11 @@ import pymarc
6
6
  from folioclient import FolioClient
7
7
  from pymarc import field
8
8
 
9
- from folio_migration_tools.custom_exceptions import TransformationFieldMappingError
10
- from folio_migration_tools.custom_exceptions import TransformationProcessError
11
- from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
9
+ from folio_migration_tools.custom_exceptions import (
10
+ TransformationFieldMappingError,
11
+ TransformationProcessError,
12
+ TransformationRecordFailedError,
13
+ )
12
14
  from folio_migration_tools.helper import Helper
13
15
  from folio_migration_tools.library_configuration import FolioRelease
14
16
  from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
@@ -58,6 +60,7 @@ class Conditions:
58
60
  logging.info("%s\tcontributor_types", len(self.folio.contributor_types))
59
61
  logging.info("%s\talt_title_types", len(self.folio.alt_title_types))
60
62
  logging.info("%s\tidentifier_types", len(self.folio.identifier_types))
63
+ logging.info("%s\tsubject_types", len(self.folio.subject_types))
61
64
  # Raise for empty settings
62
65
  if not self.folio.contributor_types:
63
66
  raise TransformationProcessError("", "No contributor_types in FOLIO")
@@ -67,6 +70,8 @@ class Conditions:
67
70
  raise TransformationProcessError("", "No identifier_types in FOLIO")
68
71
  if not self.folio.alt_title_types:
69
72
  raise TransformationProcessError("", "No alt_title_types in FOLIO")
73
+ if not self.folio.subject_types:
74
+ raise TransformationProcessError("", "No subject_types in FOLIO")
70
75
 
71
76
  # Set defaults
72
77
  logging.info("Setting defaults")
@@ -113,9 +118,7 @@ class Conditions:
113
118
  logging.info("Default Callnumber type Name:\t%s", self.default_call_number_type["name"])
114
119
 
115
120
  def setup_and_validate_holdings_types(self):
116
- self.holdings_types = list(
117
- self.folio.folio_get_all("/holdings-types", "holdingsTypes", "", 1000)
118
- )
121
+ self.holdings_types = self.folio.holdings_types
119
122
  if not self.holdings_types:
120
123
  raise TransformationProcessError("", "No holdings_types in FOLIO")
121
124
  missing_holdings_types = [
@@ -846,6 +849,19 @@ class Conditions:
846
849
  + i18n.t("1 is public, all other values are Staff only")
847
850
  + ")",
848
851
  )
849
- if ind1 != "1":
852
+ if ind1 == "0":
850
853
  return "true"
851
854
  return "false"
855
+
856
+ def condition_set_subject_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
857
+ try:
858
+ t = self.get_ref_data_tuple_by_name(
859
+ self.folio.subject_types, "subject_types", parameter["name"]
860
+ )
861
+ self.mapper.migration_report.add("MappedSubjectTypes", t[1])
862
+ return t[0]
863
+ except Exception:
864
+ raise TransformationProcessError(
865
+ legacy_id,
866
+ f"Subject type not found for {parameter['name']} {marc_field}",
867
+ )
@@ -2,11 +2,10 @@ import calendar
2
2
  import contextlib
3
3
  import logging
4
4
  import re
5
- import i18n
6
5
  from typing import List
7
6
 
8
- from pymarc import Field
9
- from pymarc import Record
7
+ import i18n
8
+ from pymarc import Field, Record
10
9
 
11
10
  from folio_migration_tools.custom_exceptions import TransformationFieldMappingError
12
11
 
@@ -72,9 +71,20 @@ class HoldingsStatementsParser:
72
71
 
73
72
  else:
74
73
  for linked_value_field in linked_value_fields:
75
- parsed_dict = HoldingsStatementsParser.parse_linked_field(
76
- pattern_field, linked_value_field
77
- )
74
+ try:
75
+ parsed_dict = HoldingsStatementsParser.parse_linked_field(
76
+ pattern_field, linked_value_field
77
+ )
78
+ except KeyError:
79
+ raise TransformationFieldMappingError(
80
+ legacy_ids,
81
+ i18n.t(
82
+ "subfield present in %{linked_value_tag} but not in %{pattern_field}",
83
+ pattern_field=pattern_field,
84
+ linked_value_tag=linked_value_field,
85
+ ),
86
+ pattern_field,
87
+ )
78
88
  if parsed_dict["hlm_stmt"]:
79
89
  return_dict["hlm_stmts"].append(parsed_dict["hlm_stmt"])
80
90
  if parsed_dict["statement"]:
@@ -222,7 +232,7 @@ class HoldingsStatementsParser:
222
232
 
223
233
  @staticmethod
224
234
  def dedupe_list_of_dict(list_of_dict):
225
- return [dict(t) for t in {tuple(d.items()) for d in list_of_dict}]
235
+ return [dict(t) for t in {tuple(d.items()): None for d in list_of_dict}]
226
236
 
227
237
  @staticmethod
228
238
  def g_m(m: int):
@@ -254,8 +264,8 @@ class HoldingsStatementsParser:
254
264
  year = False
255
265
  is_span = False
256
266
  for chron_level in [cl for cl in "ijkl" if cl in linked_value_field]:
257
- desc = pattern_field[chron_level] or ""
258
- if linked_value_field[chron_level]:
267
+ desc = pattern_field.get(chron_level, "")
268
+ if linked_value_field.get(chron_level):
259
269
  if chron_level == "i" and desc == "(year)":
260
270
  hlm_stmt = linked_value_field[chron_level]
261
271
  if desc == "(year)":
@@ -295,9 +305,9 @@ class HoldingsStatementsParser:
295
305
  _to = ""
296
306
  is_span = False
297
307
  for enum_level in [el for el in "abcdef" if el in linked_value_field]:
298
- desc = pattern_field[enum_level] or ""
308
+ desc = pattern_field.get(enum_level, "")
299
309
  desc = desc.strip() if "(" not in desc else ""
300
- if linked_value_field[enum_level]:
310
+ if linked_value_field.get(enum_level):
301
311
  val, *val_rest = linked_value_field[enum_level].split("-")
302
312
  is_span = "-" in linked_value_field[enum_level] or is_span
303
313
  _from = f"{_from}{(':' if _from else '')}{desc}{val}"
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  import sys
3
4
  import time
4
5
  import traceback
@@ -6,17 +7,15 @@ from typing import List
6
7
 
7
8
  import i18n
8
9
  from folio_uuid.folio_namespaces import FOLIONamespaces
9
- from folio_uuid.folio_uuid import FolioUUID
10
- from pymarc import Field
11
- from pymarc import Record
12
- from pymarc import Subfield
10
+ from pymarc import Field, Record, Subfield
13
11
 
14
- from folio_migration_tools.custom_exceptions import TransformationProcessError
15
- from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
12
+ from folio_migration_tools.custom_exceptions import (
13
+ TransformationProcessError,
14
+ TransformationRecordFailedError,
15
+ )
16
16
  from folio_migration_tools.folder_structure import FolderStructure
17
17
  from folio_migration_tools.helper import Helper
18
- from folio_migration_tools.library_configuration import FileDefinition
19
- from folio_migration_tools.library_configuration import HridHandling
18
+ from folio_migration_tools.library_configuration import FileDefinition, HridHandling
20
19
  from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
21
20
  RulesMapperBase,
22
21
  )
@@ -33,6 +32,8 @@ class MarcFileProcessor:
33
32
  self.created_objects_file = created_objects_file
34
33
  if mapper.task_configuration.create_source_records:
35
34
  self.srs_records_file = open(self.folder_structure.srs_records_path, "w+")
35
+ if mapper.task_configuration.data_import_marc:
36
+ self.data_import_marc_file = open(self.folder_structure.data_import_marc_path, "wb+")
36
37
  self.unique_001s: set = set()
37
38
  self.failed_records_count: int = 0
38
39
  self.records_count: int = 0
@@ -86,6 +87,12 @@ class MarcFileProcessor:
86
87
  legacy_ids,
87
88
  self.object_type,
88
89
  )
90
+ if self.mapper.task_configuration.data_import_marc:
91
+ self.save_marc_record(
92
+ marc_record,
93
+ folio_rec,
94
+ self.object_type
95
+ )
89
96
  Helper.write_to_file(self.created_objects_file, folio_rec)
90
97
  self.mapper.migration_report.add_general_statistics(
91
98
  i18n.t("Inventory records written to disk")
@@ -122,6 +129,19 @@ class MarcFileProcessor:
122
129
  ):
123
130
  self.mapper.remove_from_id_map(folio_rec.get("formerIds", []))
124
131
 
132
+ def save_marc_record(
133
+ self,
134
+ marc_record: Record,
135
+ folio_rec: dict,
136
+ object_type: FOLIONamespaces
137
+ ):
138
+ self.mapper.save_data_import_marc_record(
139
+ self.data_import_marc_file,
140
+ object_type,
141
+ marc_record,
142
+ folio_rec,
143
+ )
144
+
125
145
  def save_srs_record(
126
146
  self,
127
147
  marc_record: Record,
@@ -247,7 +267,15 @@ class MarcFileProcessor:
247
267
  self.mapper.mapped_legacy_fields,
248
268
  )
249
269
  if self.mapper.task_configuration.create_source_records:
270
+ self.srs_records_file.seek(0)
271
+ if not self.srs_records_file.seek(0):
272
+ os.remove(self.srs_records_file.name)
250
273
  self.srs_records_file.close()
274
+ if self.mapper.task_configuration.data_import_marc:
275
+ self.data_import_marc_file.seek(0)
276
+ if not self.data_import_marc_file.read(1):
277
+ os.remove(self.data_import_marc_file.name)
278
+ self.data_import_marc_file.close()
251
279
  self.mapper.wrap_up()
252
280
 
253
281
  logging.info("Transformation report written to %s", report_file.name)
@@ -255,7 +283,6 @@ class MarcFileProcessor:
255
283
 
256
284
  def add_legacy_ids_to_map(self, folio_rec, filtered_legacy_ids):
257
285
  for legacy_id in filtered_legacy_ids:
258
- legacy_id = FolioUUID.clean_iii_identifiers(legacy_id)
259
286
  self.legacy_ids.add(legacy_id)
260
287
  if legacy_id not in self.mapper.id_map:
261
288
  self.mapper.id_map[legacy_id] = self.mapper.get_id_map_tuple(