folio-migration-tools 1.2.1__py3-none-any.whl → 1.9.10__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 (73) hide show
  1. folio_migration_tools/__init__.py +11 -0
  2. folio_migration_tools/__main__.py +169 -85
  3. folio_migration_tools/circulation_helper.py +96 -59
  4. folio_migration_tools/config_file_load.py +66 -0
  5. folio_migration_tools/custom_dict.py +6 -4
  6. folio_migration_tools/custom_exceptions.py +21 -19
  7. folio_migration_tools/extradata_writer.py +46 -0
  8. folio_migration_tools/folder_structure.py +63 -66
  9. folio_migration_tools/helper.py +29 -21
  10. folio_migration_tools/holdings_helper.py +57 -34
  11. folio_migration_tools/i18n_config.py +9 -0
  12. folio_migration_tools/library_configuration.py +173 -13
  13. folio_migration_tools/mapper_base.py +317 -106
  14. folio_migration_tools/mapping_file_transformation/courses_mapper.py +203 -0
  15. folio_migration_tools/mapping_file_transformation/holdings_mapper.py +83 -69
  16. folio_migration_tools/mapping_file_transformation/item_mapper.py +98 -94
  17. folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +352 -0
  18. folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +702 -223
  19. folio_migration_tools/mapping_file_transformation/notes_mapper.py +90 -0
  20. folio_migration_tools/mapping_file_transformation/order_mapper.py +492 -0
  21. folio_migration_tools/mapping_file_transformation/organization_mapper.py +389 -0
  22. folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +38 -27
  23. folio_migration_tools/mapping_file_transformation/user_mapper.py +149 -361
  24. folio_migration_tools/marc_rules_transformation/conditions.py +650 -246
  25. folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +292 -130
  26. folio_migration_tools/marc_rules_transformation/hrid_handler.py +244 -0
  27. folio_migration_tools/marc_rules_transformation/loc_language_codes.xml +20846 -0
  28. folio_migration_tools/marc_rules_transformation/marc_file_processor.py +300 -0
  29. folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +136 -0
  30. folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +241 -0
  31. folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +681 -201
  32. folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +395 -429
  33. folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +531 -100
  34. folio_migration_tools/migration_report.py +85 -38
  35. folio_migration_tools/migration_tasks/__init__.py +1 -3
  36. folio_migration_tools/migration_tasks/authority_transformer.py +119 -0
  37. folio_migration_tools/migration_tasks/batch_poster.py +911 -198
  38. folio_migration_tools/migration_tasks/bibs_transformer.py +121 -116
  39. folio_migration_tools/migration_tasks/courses_migrator.py +192 -0
  40. folio_migration_tools/migration_tasks/holdings_csv_transformer.py +252 -247
  41. folio_migration_tools/migration_tasks/holdings_marc_transformer.py +321 -115
  42. folio_migration_tools/migration_tasks/items_transformer.py +264 -84
  43. folio_migration_tools/migration_tasks/loans_migrator.py +506 -195
  44. folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +187 -0
  45. folio_migration_tools/migration_tasks/migration_task_base.py +364 -74
  46. folio_migration_tools/migration_tasks/orders_transformer.py +373 -0
  47. folio_migration_tools/migration_tasks/organization_transformer.py +451 -0
  48. folio_migration_tools/migration_tasks/requests_migrator.py +130 -62
  49. folio_migration_tools/migration_tasks/reserves_migrator.py +253 -0
  50. folio_migration_tools/migration_tasks/user_transformer.py +180 -139
  51. folio_migration_tools/task_configuration.py +46 -0
  52. folio_migration_tools/test_infrastructure/__init__.py +0 -0
  53. folio_migration_tools/test_infrastructure/mocked_classes.py +406 -0
  54. folio_migration_tools/transaction_migration/legacy_loan.py +148 -34
  55. folio_migration_tools/transaction_migration/legacy_request.py +65 -25
  56. folio_migration_tools/transaction_migration/legacy_reserve.py +47 -0
  57. folio_migration_tools/transaction_migration/transaction_result.py +12 -1
  58. folio_migration_tools/translations/en.json +476 -0
  59. folio_migration_tools-1.9.10.dist-info/METADATA +169 -0
  60. folio_migration_tools-1.9.10.dist-info/RECORD +67 -0
  61. {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info}/WHEEL +1 -2
  62. folio_migration_tools-1.9.10.dist-info/entry_points.txt +3 -0
  63. folio_migration_tools/generate_schemas.py +0 -46
  64. folio_migration_tools/mapping_file_transformation/mapping_file_mapping_base_impl.py +0 -44
  65. folio_migration_tools/mapping_file_transformation/user_mapper_base.py +0 -212
  66. folio_migration_tools/marc_rules_transformation/bibs_processor.py +0 -163
  67. folio_migration_tools/marc_rules_transformation/holdings_processor.py +0 -284
  68. folio_migration_tools/report_blurbs.py +0 -219
  69. folio_migration_tools/transaction_migration/legacy_fee_fine.py +0 -36
  70. folio_migration_tools-1.2.1.dist-info/METADATA +0 -134
  71. folio_migration_tools-1.2.1.dist-info/RECORD +0 -50
  72. folio_migration_tools-1.2.1.dist-info/top_level.txt +0 -1
  73. {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,389 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import re
5
+ import sys
6
+
7
+ import httpx
8
+ from folio_uuid.folio_uuid import FOLIONamespaces
9
+ from folioclient import FolioClient
10
+
11
+ from folio_migration_tools.library_configuration import LibraryConfiguration
12
+ from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
13
+ MappingFileMapperBase,
14
+ )
15
+ from folio_migration_tools.mapping_file_transformation.notes_mapper import NotesMapper
16
+ from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
17
+ RefDataMapping,
18
+ )
19
+
20
+
21
+ class OrganizationMapper(MappingFileMapperBase):
22
+ def __init__(
23
+ self,
24
+ folio_client: FolioClient,
25
+ library_configuration: LibraryConfiguration,
26
+ task_config,
27
+ organization_map: dict,
28
+ organization_types_map,
29
+ address_categories_map,
30
+ email_categories_map,
31
+ phone_categories_map,
32
+ ):
33
+ # Build composite organization schema
34
+ if os.environ.get("GITHUB_TOKEN"):
35
+ logging.info("Using GITHUB_TOKEN environment variable for GitHub API Access")
36
+ organization_schema = OrganizationMapper.get_latest_acq_schemas_from_github(
37
+ "folio-org", "mod-organizations-storage", "mod-orgs", "organization"
38
+ )
39
+
40
+ super().__init__(
41
+ folio_client,
42
+ organization_schema,
43
+ organization_map,
44
+ None,
45
+ FOLIONamespaces.organizations,
46
+ library_configuration,
47
+ task_config,
48
+ )
49
+ self.organization_schema = organization_schema
50
+ # Set up reference data maps
51
+ self.set_up_reference_data_mapping(
52
+ organization_types_map,
53
+ address_categories_map,
54
+ email_categories_map,
55
+ phone_categories_map,
56
+ )
57
+
58
+ self.folio_client: FolioClient = folio_client
59
+ self.notes_mapper: NotesMapper = NotesMapper(
60
+ library_configuration,
61
+ None,
62
+ self.folio_client,
63
+ organization_map,
64
+ FOLIONamespaces.note,
65
+ True,
66
+ )
67
+ self.notes_mapper.migration_report = self.migration_report
68
+
69
+ # Commence the mapping work
70
+ def get_prop(self, legacy_organization, folio_prop_name, index_or_id, schema_default_value):
71
+ value_tuple = (
72
+ legacy_organization,
73
+ index_or_id,
74
+ folio_prop_name,
75
+ )
76
+
77
+ # Perfrom reference data mappings
78
+ if folio_prop_name == "organizationTypes":
79
+ return self.get_mapped_ref_data_value(
80
+ self.organization_types_map,
81
+ *value_tuple,
82
+ False,
83
+ )
84
+
85
+ elif re.compile(r"addresses\[(\d+)\]\.categories\[(\d+)\]").fullmatch(folio_prop_name):
86
+ return self.get_mapped_ref_data_value(
87
+ self.address_categories_map,
88
+ *value_tuple,
89
+ False,
90
+ )
91
+
92
+ elif re.compile(r"emails\[(\d+)\]\.categories\[(\d+)\]").fullmatch(folio_prop_name):
93
+ return self.get_mapped_ref_data_value(
94
+ self.email_categories_map,
95
+ *value_tuple,
96
+ False,
97
+ )
98
+
99
+ elif re.compile(r"phoneNumbers\[(\d+)\]\.categories\[(\d+)\]").fullmatch(folio_prop_name):
100
+ return self.get_mapped_ref_data_value(
101
+ self.phone_categories_map,
102
+ *value_tuple,
103
+ False,
104
+ )
105
+
106
+ elif re.compile(r"interfaces\[(\d+)\]\.interfaceCredential.interfaceId").fullmatch(
107
+ folio_prop_name
108
+ ):
109
+ return "replace_with_interface_id"
110
+
111
+ return super().get_prop(
112
+ legacy_organization, folio_prop_name, index_or_id, schema_default_value
113
+ )
114
+
115
+ def set_up_reference_data_mapping(
116
+ self,
117
+ organization_types_map,
118
+ address_categories_map,
119
+ email_categories_map,
120
+ phone_categories_map,
121
+ ):
122
+ """
123
+
124
+ Args:
125
+ organization_types_map (_type_): _description_
126
+ address_categories_map (_type_): _description_
127
+ email_categories_map (_type_): _description_
128
+ phone_categories_map (_type_): _description_
129
+ """
130
+
131
+ categories_shared_args = (
132
+ self.folio_client,
133
+ "/organizations-storage/categories",
134
+ "categories",
135
+ )
136
+
137
+ if address_categories_map:
138
+ self.address_categories_map = RefDataMapping(
139
+ *categories_shared_args, address_categories_map, "value", "CategoriesMapping"
140
+ )
141
+ else:
142
+ self.address_categories_map = None
143
+
144
+ if email_categories_map:
145
+ self.email_categories_map = RefDataMapping(
146
+ *categories_shared_args, email_categories_map, "value", "CategoriesMapping"
147
+ )
148
+ else:
149
+ self.email_categories_map = None
150
+
151
+ if phone_categories_map:
152
+ self.phone_categories_map = RefDataMapping(
153
+ *categories_shared_args, phone_categories_map, "value", "CategoriesMapping"
154
+ )
155
+ else:
156
+ self.phone_categories_map = None
157
+
158
+ if organization_types_map:
159
+ self.organization_types_map = RefDataMapping(
160
+ self.folio_client,
161
+ "/organizations-storage/organization-types",
162
+ "organizationTypes",
163
+ organization_types_map,
164
+ "name",
165
+ "OrganizationTypeMapping",
166
+ )
167
+ else:
168
+ self.organization_types_map = None
169
+
170
+ @staticmethod
171
+ def get_latest_acq_schemas_from_github(owner, repo, module, object):
172
+ """
173
+ Given a repository owner, a repository, a module name and the name
174
+ of a FOLIO acquisition object, returns a schema for that object that
175
+ also includes the schemas of any other referenced acq objects.
176
+
177
+ Args:
178
+ owner (_type_): _description_
179
+ repo (_type_): _description_
180
+ module (_type_): _description_
181
+ object (_type_): _description_
182
+
183
+ Returns:
184
+ _type_: _description_
185
+ """
186
+ try:
187
+ # Authenticate when calling GitHub, using an API key stored in .env
188
+ github_headers = {
189
+ "content-type": "application/json",
190
+ "User-Agent": "FOLIO Migration Tools (https://github.com/FOLIO-FSE/folio_migration_tools/)", # noqa:E501,B950
191
+ }
192
+
193
+ if os.environ.get("GITHUB_TOKEN"):
194
+ github_headers["authorization"] = f"token {os.environ.get('GITHUB_TOKEN')}"
195
+
196
+ # Start talkign to GitHub...
197
+ github_path = "https://raw.githubusercontent.com"
198
+ submodules = OrganizationMapper.get_submodules_of_latest_release(
199
+ owner, repo, github_headers
200
+ )
201
+
202
+ # Get the sha's of sunmodules acq-models and raml_utils
203
+ acq_models_sha = next(
204
+ (item["sha"] for item in submodules if item["path"] == "acq-models")
205
+ )
206
+
207
+ # # TODO Maybe - fetch raml_utils schemas if deemed necessary
208
+ # raml_utils_sha = next((item["sha"] for item in submodules
209
+ # if item["path"] == "raml-utils"))
210
+
211
+ acq_models_path = f"{github_path}/{owner}/acq-models/{acq_models_sha}/{module}/schemas"
212
+
213
+ req = httpx.get(f"{acq_models_path}/{object}.json", headers=github_headers)
214
+ req.raise_for_status()
215
+
216
+ object_schema = json.loads(req.text)
217
+
218
+ # Fetch referenced schemas
219
+ extended_object_schema = OrganizationMapper.build_extended_object(
220
+ object_schema, acq_models_path, github_headers
221
+ )
222
+
223
+ return extended_object_schema
224
+
225
+ except httpx.HTTPError as http_error:
226
+ logging.critical(f"Halting! \t{http_error}")
227
+ sys.exit(2)
228
+
229
+ except json.decoder.JSONDecodeError as json_error:
230
+ logging.critical(json_error)
231
+ sys.exit(2)
232
+
233
+ @staticmethod
234
+ def get_submodules_of_latest_release(owner, repo, github_headers):
235
+ """
236
+ Given a repository owner and a repository, identifies the latest
237
+ release of the repository and returns the submodules associated with
238
+ this release.
239
+
240
+ Args:
241
+ owner (_type_): _description_
242
+ repo (_type_): _description_
243
+ github_headers (_type_): _description_
244
+
245
+ Returns:
246
+ _type_: _description_
247
+ """
248
+
249
+ github_path = "https://api.github.com/repos"
250
+
251
+ # Get metadata for the latest release
252
+ latest_release_path = f"{github_path}/{owner}/{repo}/releases/latest"
253
+ req = httpx.get(f"{latest_release_path}", headers=github_headers, timeout=None)
254
+ req.raise_for_status()
255
+ latest_release = json.loads(req.text)
256
+
257
+ # Get the tag assigned to the latest release
258
+ release_tag = latest_release["tag_name"]
259
+ logging.info(f"Using schemas from latest {repo} release: {release_tag}")
260
+
261
+ # Get the tree for the latest release
262
+ tree_path = f"{github_path}/{owner}/{repo}/git/trees/{release_tag}"
263
+ req = httpx.get(tree_path, headers=github_headers, timeout=None)
264
+ req.raise_for_status()
265
+ release_tree = json.loads(req.text)
266
+
267
+ # Loop through the tree to get the sha of the folder with path "ramls"
268
+ ramls_sha = next((item["sha"] for item in release_tree["tree"] if item["path"] == "ramls"))
269
+
270
+ # Get the tree for the ramls folder
271
+ ramls_path = f"{github_path}/{owner}/{repo}/git/trees/{ramls_sha}"
272
+ req = httpx.get(ramls_path, headers=github_headers, timeout=None)
273
+ req.raise_for_status()
274
+ ramls_tree = json.loads(req.text)
275
+
276
+ # Loop through the tree to get the sha of submodules
277
+ submodules = [item for item in ramls_tree["tree"] if item["mode"] == "160000"]
278
+
279
+ return submodules
280
+
281
+ @staticmethod
282
+ def build_extended_object(object_schema, submodule_path, github_headers):
283
+ """
284
+ Takes an object schema (for example an organization) and the path to a
285
+ submodule repository and returns the same schema with the full schemas
286
+ of subordinate objects (for example aliases).
287
+
288
+ Args:
289
+ object_schema (_type_): _description_
290
+ submodule_path (_type_): _description_
291
+ github_headers (_type_): _description_
292
+
293
+ Returns:
294
+ _type_: _description_
295
+ """
296
+
297
+ supported_types = ["string", "boolean", "number", "integer", "text", "object", "array"]
298
+
299
+ try:
300
+ for property_name_level1, property_level1 in object_schema["properties"].items():
301
+ # For now, treat references to UUIDs like strings
302
+ # It's not great practice, but it's the way FOLIO mostly handles it
303
+ if "../../common/schemas/uuid.json" in property_level1.get("$ref", ""):
304
+ property_level1["type"] = "string"
305
+
306
+ # Preliminary implementation of tags
307
+ # https://github.com/folio-org/raml/blob/master/schemas/tags.schema
308
+ elif property_name_level1 == "tags":
309
+ property_level1["properties"] = {
310
+ "tagList": {
311
+ "description": "List of tags",
312
+ "type": "array",
313
+ "items": {"type": "string"},
314
+ }
315
+ }
316
+
317
+ elif property_name_level1 == "contacts":
318
+ contact_schema = OrganizationMapper.fetch_additional_schema("contact")
319
+ property_level1["items"] = contact_schema
320
+
321
+ elif property_name_level1 == "interfaces":
322
+ interface_schema = OrganizationMapper.fetch_additional_schema("interface")
323
+ interface_schema["required"] = ["name"]
324
+
325
+ # Temporarily add the credential object as a subproperty
326
+ interface_credential_schema = OrganizationMapper.fetch_additional_schema(
327
+ "interface_credential"
328
+ )
329
+ interface_credential_schema["required"] = (
330
+ ["username", "password", "interfaceId"],
331
+ )
332
+
333
+ interface_schema["properties"][
334
+ "interfaceCredential"
335
+ ] = interface_credential_schema
336
+
337
+ property_level1["items"] = interface_schema
338
+
339
+ elif (
340
+ property_level1.get("type") == "array"
341
+ and property_level1.get("items").get("$ref")
342
+ == "../../common/schemas/uuid.json"
343
+ ):
344
+ property_level1["items"]["type"] = "string"
345
+
346
+ # Report and discard unhandled properties
347
+ elif (
348
+ property_level1.get("type") not in supported_types
349
+ or "../" in property_level1.get("$ref", "")
350
+ or "../" in property_level1.get("items", {}).get("$ref", "")
351
+ ):
352
+ logging.info(f"Property not yet supported: {property_name_level1}")
353
+
354
+ # Handle object properties
355
+ elif property_level1.get("type") == "object" and property_level1.get("$ref"):
356
+ logging.info("Fecthing referenced schema for object %s", property_name_level1)
357
+
358
+ ref_object = property_level1["$ref"]
359
+ schema_url = f"{submodule_path}/{ref_object}"
360
+
361
+ req = httpx.get(schema_url, headers=github_headers, timeout=None)
362
+ req.raise_for_status()
363
+
364
+ property_level1 = dict(property_level1, **json.loads(req.text))
365
+
366
+ elif property_level1.get("type") == "array" and property_level1.get("items").get(
367
+ "$ref"
368
+ ):
369
+ ref_object = property_level1["items"]["$ref"]
370
+ schema_url = f"{submodule_path}/{ref_object}"
371
+
372
+ req = httpx.get(schema_url, headers=github_headers, timeout=None)
373
+ req.raise_for_status()
374
+
375
+ property_level1["items"] = dict(
376
+ property_level1["items"], **json.loads(req.text)
377
+ )
378
+
379
+ return object_schema
380
+
381
+ except httpx.HTTPError as he:
382
+ logging.error(he)
383
+
384
+ @staticmethod
385
+ def fetch_additional_schema(folio_object):
386
+ additional_schema = OrganizationMapper.get_latest_acq_schemas_from_github(
387
+ "folio-org", "mod-organizations-storage", "mod-orgs", folio_object
388
+ )
389
+ return additional_schema
@@ -3,8 +3,8 @@ import logging
3
3
  import sys
4
4
 
5
5
  from folioclient import FolioClient
6
+
6
7
  from folio_migration_tools.custom_exceptions import TransformationProcessError
7
- from folio_migration_tools.report_blurbs import Blurbs
8
8
 
9
9
 
10
10
  class RefDataMapping(object):
@@ -15,18 +15,16 @@ class RefDataMapping(object):
15
15
  array_name,
16
16
  the_map,
17
17
  key_type,
18
- blurb: Blurbs,
18
+ blurb_id,
19
19
  ):
20
20
  self.name = array_name
21
- self.cache = {}
22
- self.blurb = blurb
21
+ self.cache: dict = {}
22
+ self.blurb_id = blurb_id
23
23
  logging.info("%s reference data mapping. Initializing", self.name)
24
24
  logging.info("Fetching %s reference data from FOLIO", self.name)
25
- self.ref_data = list(
26
- folio_client.folio_get_all(ref_data_path, array_name, "", 1000)
27
- )
25
+ self.ref_data = list(folio_client.folio_get_all(ref_data_path, array_name, "", 1000))
28
26
  self.map = the_map
29
- self.regular_mappings = []
27
+ self.regular_mappings: list = []
30
28
  self.key_type = key_type
31
29
  self.hybrid_mappings = []
32
30
  self.mapped_legacy_keys = []
@@ -45,6 +43,9 @@ class RefDataMapping(object):
45
43
  return self.cached_dict.get(key_value.lower().strip(), ())
46
44
 
47
45
  def setup_mappings(self):
46
+ if not self.map:
47
+ logging.info("%s legacy map file is empty or not provided", self.name)
48
+ return
48
49
  self.pre_validate_map()
49
50
  for idx, mapping in enumerate(self.map):
50
51
  try:
@@ -64,7 +65,8 @@ class RefDataMapping(object):
64
65
  x = mapping.get(f"folio_{self.key_type}", "")
65
66
  raise TransformationProcessError(
66
67
  "",
67
- f"No {self.name} - {x} - set up in map or tenant. Check for inconstencies in {self.name} naming."
68
+ f"No {self.name} - {x} - set up in map or tenant. Check for "
69
+ f"inconstencies in {self.name} naming. "
68
70
  f"Add a row to mapping file with *:s and a valid {self.name}",
69
71
  )
70
72
  else:
@@ -74,9 +76,7 @@ class RefDataMapping(object):
74
76
  self.regular_mappings.append(mapping)
75
77
  t = self.get_ref_data_tuple(mapping[f"folio_{self.key_type}"])
76
78
  if not t:
77
- raise TransformationProcessError(
78
- "", f"Mapping not found for {mapping}"
79
- )
79
+ raise TransformationProcessError("", f"Mapping not found for {mapping}")
80
80
  mapping["folio_id"] = t[0]
81
81
  except TransformationProcessError as transformation_process_error:
82
82
  raise transformation_process_error from transformation_process_error
@@ -90,10 +90,12 @@ class RefDataMapping(object):
90
90
 
91
91
  self.post_validate_map()
92
92
  logging.info(
93
- f"Loaded {len(self.regular_mappings)} mappings for {len(self.ref_data)} {self.name} in FOLIO"
93
+ f"Loaded {len(self.regular_mappings)} mappings for {len(self.ref_data)} {self.name} "
94
+ "in FOLIO"
94
95
  )
95
96
  logging.info(
96
- f"loaded {len(self.hybrid_mappings)} hybrid mappings for {len(self.ref_data)} {self.name} in FOLIO"
97
+ f"loaded {len(self.hybrid_mappings)} hybrid mappings for {len(self.ref_data)} "
98
+ f"{self.name} in FOLIO"
97
99
  )
98
100
 
99
101
  def get_hybrid_mapping(self, legacy_object):
@@ -128,24 +130,18 @@ class RefDataMapping(object):
128
130
  return self.cache[obj_key]
129
131
  prepped_props = {k: legacy_object[k].strip() for k in self.mapped_legacy_keys}
130
132
  for mapping in self.regular_mappings:
131
- match_number = sum(
132
- prepped_props[k] == mapping[k] for k in self.mapped_legacy_keys
133
- )
133
+ match_number = sum(prepped_props[k] == mapping[k] for k in self.mapped_legacy_keys)
134
134
  if match_number == len(self.mapped_legacy_keys):
135
135
  self.cache[obj_key] = mapping
136
136
  return mapping
137
137
  return None
138
138
 
139
139
  def is_hybrid_default_mapping(self, mapping):
140
- legacy_values = [
141
- value for key, value in mapping.items() if key in self.mapped_legacy_keys
142
- ]
140
+ legacy_values = [value for key, value in mapping.items() if key in self.mapped_legacy_keys]
143
141
  return "*" in legacy_values and not self.is_default_mapping(mapping)
144
142
 
145
143
  def is_default_mapping(self, mapping):
146
- legacy_values = [
147
- value for key, value in mapping.items() if key in self.mapped_legacy_keys
148
- ]
144
+ legacy_values = [value for key, value in mapping.items() if key in self.mapped_legacy_keys]
149
145
  return all(f == "*" for f in legacy_values)
150
146
 
151
147
  def pre_validate_map(self):
@@ -163,12 +159,17 @@ class RefDataMapping(object):
163
159
  )
164
160
  if any(folio_values_not_in_map):
165
161
  logging.info(
166
- f"Values from {self.name} ref data in FOLIO that are not in the map: {folio_values_not_in_map}"
162
+ "Values from %s ref data in FOLIO that are not in the map: %s",
163
+ self.name,
164
+ folio_values_not_in_map,
167
165
  )
168
166
  if any(map_values_not_in_folio):
169
167
  raise TransformationProcessError(
170
168
  "",
171
- f"Values ({self.key_type}) from {self.name} map are not in FOLIO: {map_values_not_in_folio}",
169
+ (
170
+ f"Values ({self.key_type}) from {self.name} map are not in "
171
+ f"FOLIO: {map_values_not_in_folio}"
172
+ ),
172
173
  )
173
174
 
174
175
  def post_validate_map(self):
@@ -176,7 +177,8 @@ class RefDataMapping(object):
176
177
  raise TransformationProcessError(
177
178
  "",
178
179
  f"No fallback {self.name} set up in map."
179
- f"Add a row to mapping file with *:s in all legacy columns and a valid {self.name} value",
180
+ f"Add a row to mapping file with *:s in all legacy "
181
+ f"columns and a valid {self.name} value",
180
182
  )
181
183
  for mapping in self.map:
182
184
  if f"folio_{self.key_type}" not in mapping:
@@ -209,5 +211,14 @@ def get_mapped_legacy_keys(mapping):
209
211
  k.strip()
210
212
  for k in mapping.keys()
211
213
  if k
212
- not in ["folio_group", "folio_code", "folio_id", "folio_name", "legacy_code"]
214
+ not in [
215
+ "folio_group",
216
+ "folio_code",
217
+ "folio_id",
218
+ "folio_name",
219
+ "legacy_code",
220
+ "folio_value",
221
+ "folio_owner",
222
+ "folio_feeFineType",
223
+ ]
213
224
  ]