folio-migration-tools 1.9.0rc2__tar.gz → 1.9.0rc4__tar.gz

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 (66) hide show
  1. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/PKG-INFO +1 -1
  2. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/pyproject.toml +5 -1
  3. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/holdings_mapper.py +1 -1
  4. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/order_mapper.py +8 -4
  5. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +22 -2
  6. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/batch_poster.py +143 -26
  7. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/courses_migrator.py +54 -8
  8. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/holdings_csv_transformer.py +102 -14
  9. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/holdings_marc_transformer.py +46 -4
  10. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/items_transformer.py +133 -20
  11. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/loans_migrator.py +61 -9
  12. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/migration_task_base.py +104 -11
  13. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/orders_transformer.py +107 -14
  14. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/organization_transformer.py +79 -14
  15. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/requests_migrator.py +56 -7
  16. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/reserves_migrator.py +26 -4
  17. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/user_transformer.py +88 -18
  18. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/task_configuration.py +2 -2
  19. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/transaction_migration/legacy_loan.py +13 -1
  20. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/transaction_migration/legacy_reserve.py +3 -5
  21. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/LICENSE +0 -0
  22. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/README.md +0 -0
  23. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/__init__.py +0 -0
  24. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/__main__.py +0 -0
  25. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/circulation_helper.py +0 -0
  26. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/colors.py +0 -0
  27. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/config_file_load.py +0 -0
  28. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/custom_dict.py +0 -0
  29. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/custom_exceptions.py +0 -0
  30. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/extradata_writer.py +0 -0
  31. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/folder_structure.py +0 -0
  32. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/helper.py +0 -0
  33. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/holdings_helper.py +0 -0
  34. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/i18n_config.py +0 -0
  35. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/library_configuration.py +0 -0
  36. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapper_base.py +0 -0
  37. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/__init__.py +0 -0
  38. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/courses_mapper.py +0 -0
  39. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/item_mapper.py +0 -0
  40. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +0 -0
  41. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +0 -0
  42. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/notes_mapper.py +0 -0
  43. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/organization_mapper.py +0 -0
  44. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +0 -0
  45. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/mapping_file_transformation/user_mapper.py +0 -0
  46. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/__init__.py +0 -0
  47. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/conditions.py +0 -0
  48. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +0 -0
  49. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/hrid_handler.py +0 -0
  50. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/loc_language_codes.xml +0 -0
  51. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/marc_file_processor.py +0 -0
  52. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +0 -0
  53. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +0 -0
  54. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +0 -0
  55. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +0 -0
  56. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_report.py +0 -0
  57. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/__init__.py +0 -0
  58. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/authority_transformer.py +0 -0
  59. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/bibs_transformer.py +0 -0
  60. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +0 -0
  61. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/test_infrastructure/__init__.py +0 -0
  62. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/test_infrastructure/mocked_classes.py +0 -0
  63. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/transaction_migration/__init__.py +0 -0
  64. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/transaction_migration/legacy_request.py +0 -0
  65. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/transaction_migration/transaction_result.py +0 -0
  66. {folio_migration_tools-1.9.0rc2 → folio_migration_tools-1.9.0rc4}/src/folio_migration_tools/translations/en.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: folio_migration_tools
3
- Version: 1.9.0rc2
3
+ Version: 1.9.0rc4
4
4
  Summary: A tool allowing you to migrate data from legacy ILS:s (Library systems) into FOLIO LSP
5
5
  License: MIT
6
6
  Keywords: FOLIO,ILS,LSP,Library Systems,MARC21,Library data
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "folio_migration_tools"
3
- version = "1.9.0rc2"
3
+ version = "1.9.0rc4"
4
4
  description = "A tool allowing you to migrate data from legacy ILS:s (Library systems) into FOLIO LSP"
5
5
  authors = [
6
6
  {name = "Theodor Tolstoy", email = "github.teddes@tolstoy.se"},
@@ -77,6 +77,10 @@ pandas = "^1.5.3"
77
77
  types-requests = "^2.28.11.17"
78
78
  types-python-dateutil = "^2.8.19.11"
79
79
  ipykernel = "^6.29.5"
80
+ pytest-asyncio = "^0.23.0"
80
81
 
81
82
  [tool.poetry.extras]
82
83
  docs = ["m2r", "sphinx", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "toml"]
84
+
85
+ [tool.poetry.requires-plugins]
86
+ poetry-plugin-export = ">=1.8"
@@ -101,7 +101,7 @@ class HoldingsMapper(MappingFileMapperBase):
101
101
  if legacy_value.startswith("[") and len(legacy_value.split(",")) == 1:
102
102
  try:
103
103
  legacy_value = ast.literal_eval(str(legacy_value))[0]
104
- except SyntaxError:
104
+ except (SyntaxError, ValueError):
105
105
  return legacy_value
106
106
  return legacy_value
107
107
 
@@ -25,7 +25,6 @@ from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
25
25
 
26
26
 
27
27
  class CompositeOrderMapper(MappingFileMapperBase):
28
- VALID_PO_NUMBER_CHARACTERS = r"[A-Za-z0-9]"
29
28
 
30
29
  def __init__(
31
30
  self,
@@ -270,9 +269,9 @@ class CompositeOrderMapper(MappingFileMapperBase):
270
269
  and "$ref" in object_schema
271
270
  and object_schema["type"] == "object"
272
271
  ):
273
- submodule_path["properties"] = CompositeOrderMapper.inject_schema_by_ref(
272
+ object_schema["properties"] = CompositeOrderMapper.inject_schema_by_ref(
274
273
  submodule_path, github_headers, object_schema
275
- ).get("properties")
274
+ ).get("properties", {})#TODO: Investigate new CustomFields schema and figure out how to actually handle it
276
275
 
277
276
  for property_name_level1, property_level1 in object_schema.get(
278
277
  "properties", {}
@@ -385,7 +384,7 @@ class CompositeOrderMapper(MappingFileMapperBase):
385
384
  index_or_id: str,
386
385
  po_number: str,
387
386
  ):
388
- if re.sub(self.VALID_PO_NUMBER_CHARACTERS, "", po_number):
387
+ if not self.is_valid_po_number(po_number):
389
388
  self.migration_report.add(
390
389
  "PurchaseOrderVendorLinking",
391
390
  i18n.t("RECORD FAILED: PO number has invalid character(s)"),
@@ -396,6 +395,11 @@ class CompositeOrderMapper(MappingFileMapperBase):
396
395
  po_number,
397
396
  )
398
397
 
398
+ @staticmethod
399
+ def is_valid_po_number(po_number: str) -> bool:
400
+ valid_po_number_characters = r"[A-Za-z0-9]"
401
+ return re.sub(valid_po_number_characters, "", po_number) == ""
402
+
399
403
  def get_matching_record_from_folio(
400
404
  self,
401
405
  index_or_id,
@@ -69,7 +69,6 @@ class RulesMapperHoldings(RulesMapperBase):
69
69
  logging.info("Fetching mapping rules from the tenant")
70
70
  rules_endpoint = "/mapping-rules/marc-holdings"
71
71
  self.mappings = self.folio_client.folio_get_single_object(rules_endpoint)
72
- self.fix_853_bug_in_rules()
73
72
 
74
73
  def fix_853_bug_in_rules(self):
75
74
  f852_mappings = self.mappings["852"]
@@ -90,6 +89,27 @@ class RulesMapperHoldings(RulesMapperBase):
90
89
  )
91
90
  self.mappings["852"] = new_852_mapping
92
91
 
92
+ def integrate_supplemental_mfhd_mappings(self, new_rules={}):
93
+ try:
94
+ self.mappings.update(new_rules)
95
+ self.fix_853_bug_in_rules()
96
+ except Exception as e:
97
+ raise TransformationProcessError(
98
+ "",
99
+ "Failed to integrate supplemental mfhd mappings",
100
+ str(e),
101
+ )
102
+
103
+ def prep_852_notes(self, marc_record: Record):
104
+ for field in marc_record.get_fields("852"):
105
+ field.subfields.sort(key=lambda x: x[0])
106
+ new_952 = Field(
107
+ tag="952",
108
+ indicators=["f", "f"],
109
+ subfields=field.subfields
110
+ )
111
+ marc_record.add_ordered_field(new_952)
112
+
93
113
  def parse_record(
94
114
  self, marc_record: Record, file_def: FileDefinition, legacy_ids: List[str]
95
115
  ) -> list[dict]:
@@ -111,7 +131,7 @@ class RulesMapperHoldings(RulesMapperBase):
111
131
 
112
132
  self.print_progress()
113
133
  folio_holding = self.perform_initial_preparation(marc_record, legacy_ids)
114
-
134
+ self.prep_852_notes(marc_record)
115
135
  self.migration_report.add("RecordStatus", marc_record.leader[5])
116
136
  ignored_subsequent_fields: set = set()
117
137
  num_852s = 0
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import copy
2
3
  import json
3
4
  import logging
@@ -50,46 +51,84 @@ class BatchPoster(MigrationTaskBase):
50
51
  """
51
52
 
52
53
  class TaskConfiguration(AbstractTaskConfiguration):
53
- name: str
54
- migration_task_type: str
55
- object_type: str
56
- files: List[FileDefinition]
57
- batch_size: int
54
+ name: Annotated[
55
+ str,
56
+ Field(
57
+ title="Task name",
58
+ description="The name of the task",
59
+ ),
60
+ ]
61
+ migration_task_type: Annotated[
62
+ str,
63
+ Field(
64
+ title="Migration task type",
65
+ description="The type of migration task",
66
+ ),
67
+ ]
68
+ object_type: Annotated[
69
+ str,
70
+ Field(
71
+ title="Object type",
72
+ description=(
73
+ "The type of object being migrated"
74
+ "Examples of possible values: "
75
+ "'Extradata', 'SRS', Instances', 'Holdings', 'Items'"
76
+ ),
77
+ ),
78
+ ]
79
+ files: Annotated[
80
+ List[FileDefinition],
81
+ Field(
82
+ title="List of files",
83
+ description="List of files to be processed",
84
+ ),
85
+ ]
86
+ batch_size: Annotated[
87
+ int,
88
+ Field(
89
+ title="Batch size",
90
+ description="The batch size for processing files",
91
+ ),
92
+ ]
58
93
  rerun_failed_records: Annotated[
59
94
  bool,
60
95
  Field(
96
+ title="Rerun failed records",
61
97
  description=(
62
- "Toggles whether or not BatchPoster should try to rerun failed batches or "
63
- "just leave the failing records on disk."
64
- )
98
+ "Toggles whether or not BatchPoster should try to rerun "
99
+ "failed batches or just leave the failing records on disk."
100
+ ),
65
101
  ),
66
102
  ] = True
67
103
  use_safe_inventory_endpoints: Annotated[
68
104
  bool,
69
105
  Field(
106
+ title="Use safe inventory endpoints",
70
107
  description=(
71
- "Toggles the use of the safe/unsafe Inventory storage endpoints. "
72
- "Unsafe circumvents the Optimistic locking in FOLIO. Defaults to "
73
- "True (using the 'safe' options)"
74
- )
108
+ "Toggles the use of the safe/unsafe Inventory storage "
109
+ "endpoints. Unsafe circumvents the Optimistic locking "
110
+ "in FOLIO. Defaults to True (using the 'safe' options)"
111
+ ),
75
112
  ),
76
113
  ] = True
77
114
  extradata_endpoints: Annotated[
78
115
  dict,
79
116
  Field(
117
+ title="Extradata endpoints",
80
118
  description=(
81
119
  "A dictionary of extradata endpoints. "
82
120
  "The key is the object type and the value is the endpoint"
83
- )
121
+ ),
84
122
  ),
85
123
  ] = {}
86
124
  upsert: Annotated[
87
125
  bool,
88
126
  Field(
127
+ title="Upsert",
89
128
  description=(
90
- "Toggles whether or not to use the upsert feature of the Inventory storage "
91
- "endpoints. Defaults to False"
92
- )
129
+ "Toggles whether or not to use the upsert feature "
130
+ "of the Inventory storage endpoints. Defaults to False"
131
+ ),
93
132
  ),
94
133
  ] = False
95
134
 
@@ -188,9 +227,10 @@ class BatchPoster(MigrationTaskBase):
188
227
 
189
228
  except Exception as ee:
190
229
  if "idx" in locals() and self.task_configuration.files[idx:]:
191
- for file in self.task_configuration.files[idx:]:
230
+ for file_def in self.task_configuration.files[idx:]:
231
+ path = self.folder_structure.results_folder / file_def.file_name
192
232
  try:
193
- with open(file, "r") as failed_file:
233
+ with open(path, "r") as failed_file:
194
234
  failed_file.seek(self.processed)
195
235
  failed_recs_file.write(failed_file.read())
196
236
  self.processed = 0
@@ -209,22 +249,84 @@ class BatchPoster(MigrationTaskBase):
209
249
  if self.task_configuration.object_type == "SRS":
210
250
  self.commit_snapshot()
211
251
 
252
+ @staticmethod
253
+ def set_consortium_source(json_rec):
254
+ if json_rec['source'] == 'MARC':
255
+ json_rec['source'] = 'CONSORTIUM-MARC'
256
+ elif json_rec['source'] == 'FOLIO':
257
+ json_rec['source'] = 'CONSORTIUM-FOLIO'
258
+
259
+ def set_version(self, batch, query_api, object_type) -> None:
260
+ """
261
+ Synchronous wrapper for set_version_async
262
+ """
263
+ loop = asyncio.get_event_loop()
264
+ if loop.is_running():
265
+ loop.run_until_complete(self.set_version_async(batch, query_api, object_type))
266
+ else:
267
+ asyncio.run(self.set_version_async(batch, query_api, object_type))
268
+
269
+ async def set_version_async(self, batch, query_api, object_type) -> None:
270
+ """
271
+ Fetches the current version of the records in the batch, if the record exists in FOLIO
272
+
273
+ Args:
274
+ batch (list): List of records to fetch versions for
275
+ query_api (str): The query API endpoint to use
276
+ object_type (str): The key in the API response that contains the records
277
+
278
+ Returns:
279
+ None
280
+ """
281
+ fetch_batch_size = 90
282
+ fetch_tasks = []
283
+ updates = {}
284
+ async with httpx.AsyncClient(base_url=self.folio_client.okapi_url) as client:
285
+ for i in range(0, len(batch), fetch_batch_size):
286
+ batch_slice = batch[i:i + fetch_batch_size]
287
+ fetch_tasks.append(
288
+ client.get(
289
+ query_api,
290
+ params={
291
+ "query": f"id==({' OR '.join([record['id'] for record in batch_slice if 'id' in record])})",
292
+ "limit": fetch_batch_size
293
+ },
294
+ headers=self.folio_client.okapi_headers
295
+ )
296
+ )
297
+ responses = await asyncio.gather(*fetch_tasks)
298
+
299
+ for response in responses:
300
+ if response.status_code == 200:
301
+ response_json = await response.json()
302
+ for record in response_json[object_type]:
303
+ updates[record["id"]] = {
304
+ "_version": record["_version"],
305
+ }
306
+ if "status" in record:
307
+ updates[record["id"]]["status"] = record["status"]
308
+ else:
309
+ logging.error(
310
+ "Failed to fetch current records. HTTP %s\t%s",
311
+ response.status_code,
312
+ response.text,
313
+ )
314
+ for record in batch:
315
+ if record["id"] in updates:
316
+ record.update(updates[record["id"]])
317
+
212
318
  def post_record_batch(self, batch, failed_recs_file, row):
213
319
  json_rec = json.loads(row.split("\t")[-1])
214
- if (
215
- self.task_configuration.object_type in ["Instances", "Holdings", "Items"]
216
- and not self.task_configuration.use_safe_inventory_endpoints
217
- ):
218
- self.migration_report.add_general_statistics(
219
- i18n.t("Set _version to -1 to enable upsert")
220
- )
221
- json_rec["_version"] = -1
320
+ if self.task_configuration.object_type == "ShadowInstances":
321
+ self.set_consortium_source(json_rec)
222
322
  if self.task_configuration.object_type == "SRS":
223
323
  json_rec["snapshotId"] = self.snapshot_id
224
324
  if self.processed == 1:
225
325
  logging.info(json.dumps(json_rec, indent=True))
226
326
  batch.append(json_rec)
227
327
  if len(batch) == int(self.batch_size):
328
+ if self.query_params.get("upsert", False) and self.api_info.get("query_endpoint", ""):
329
+ self.set_version(batch, self.api_info['query_endpoint'], self.api_info['object_name'])
228
330
  self.post_batch(batch, failed_recs_file, self.processed)
229
331
  batch = []
230
332
  return batch
@@ -609,6 +711,7 @@ def get_api_info(object_type: str, use_safe: bool = True):
609
711
  if use_safe
610
712
  else "/item-storage/batch/synchronous-unsafe"
611
713
  ),
714
+ "query_endpoint": "/item-storage/items",
612
715
  "is_batch": True,
613
716
  "total_records": False,
614
717
  "addSnapshotId": False,
@@ -621,12 +724,26 @@ def get_api_info(object_type: str, use_safe: bool = True):
621
724
  if use_safe
622
725
  else "/holdings-storage/batch/synchronous-unsafe"
623
726
  ),
727
+ "query_endpoint": "/holdings-storage/holdings",
624
728
  "is_batch": True,
625
729
  "total_records": False,
626
730
  "addSnapshotId": False,
627
731
  "supports_upsert": True,
628
732
  },
629
733
  "Instances": {
734
+ "object_name": "instances",
735
+ "api_endpoint": (
736
+ "/instance-storage/batch/synchronous"
737
+ if use_safe
738
+ else "/instance-storage/batch/synchronous-unsafe"
739
+ ),
740
+ "query_endpoint": "/instance-storage/instances",
741
+ "is_batch": True,
742
+ "total_records": False,
743
+ "addSnapshotId": False,
744
+ "supports_upsert": True,
745
+ },
746
+ "ShadowInstances": {
630
747
  "object_name": "instances",
631
748
  "api_endpoint": (
632
749
  "/instance-storage/batch/synchronous"
@@ -4,7 +4,8 @@ import logging
4
4
  import sys
5
5
  import time
6
6
  import traceback
7
- from typing import Optional
7
+ from typing import Optional, Annotated
8
+ from pydantic import Field
8
9
 
9
10
  import i18n
10
11
  from folio_uuid.folio_namespaces import FOLIONamespaces
@@ -29,13 +30,58 @@ from folio_migration_tools.task_configuration import AbstractTaskConfiguration
29
30
 
30
31
  class CoursesMigrator(MigrationTaskBase):
31
32
  class TaskConfiguration(AbstractTaskConfiguration):
32
- name: str
33
- composite_course_map_path: str
34
- migration_task_type: str
35
- courses_file: FileDefinition
36
- terms_map_path: str
37
- departments_map_path: str
38
- look_up_instructor: Optional[bool] = False
33
+ name: Annotated[
34
+ str,
35
+ Field(
36
+ title="Task name",
37
+ description="The name of the task",
38
+ ),
39
+ ]
40
+ composite_course_map_path: Annotated[
41
+ str,
42
+ Field(
43
+ title="Composite course map path",
44
+ description="Path to the composite course map file",
45
+ ),
46
+ ]
47
+ migration_task_type: Annotated[
48
+ str,
49
+ Field(
50
+ title="Migration task type",
51
+ description="Type of migration task",
52
+ ),
53
+ ]
54
+ courses_file: Annotated[
55
+ FileDefinition,
56
+ Field(
57
+ title="Courses file",
58
+ description="File containing course data",
59
+ ),
60
+ ]
61
+ terms_map_path: Annotated[
62
+ str,
63
+ Field(
64
+ title="Terms map path",
65
+ description="Path to the terms map file",
66
+ ),
67
+ ]
68
+ departments_map_path: Annotated[
69
+ str,
70
+ Field(
71
+ title="Departments map path",
72
+ description="Path to the departments map file",
73
+ ),
74
+ ]
75
+ look_up_instructor: Annotated[
76
+ Optional[bool],
77
+ Field(
78
+ title="Look up instructor",
79
+ description=(
80
+ "Flag to indicate whether to look up instructors. "
81
+ "By default is False."
82
+ ),
83
+ ),
84
+ ] = False
39
85
 
40
86
  @staticmethod
41
87
  def get_object_type() -> FOLIONamespaces:
@@ -39,37 +39,125 @@ csv.register_dialect("tsv", delimiter="\t")
39
39
 
40
40
  class HoldingsCsvTransformer(MigrationTaskBase):
41
41
  class TaskConfiguration(AbstractTaskConfiguration):
42
- name: str
43
- migration_task_type: str
44
- hrid_handling: HridHandling
45
- files: List[FileDefinition]
46
- holdings_map_file_name: str
47
- location_map_file_name: str
48
- default_call_number_type_name: str
49
- previously_generated_holdings_files: Optional[list[str]] = []
50
- fallback_holdings_type_id: str
42
+ name: Annotated[
43
+ str,
44
+ Field(
45
+ title="Task name",
46
+ description="Name of the task",
47
+ ),
48
+ ]
49
+ migration_task_type: Annotated[
50
+ str,
51
+ Field(
52
+ title="Migration task type",
53
+ description="Type of migration task",
54
+ ),
55
+ ]
56
+ hrid_handling: Annotated[
57
+ HridHandling,
58
+ Field(
59
+ title="HRID handling",
60
+ description=(
61
+ "Determining how the HRID generation "
62
+ "should be handled."
63
+ ),
64
+ ),
65
+ ]
66
+ files: Annotated[
67
+ List[FileDefinition],
68
+ Field(
69
+ title="Files",
70
+ description="List of files",
71
+ ),
72
+ ]
73
+ holdings_map_file_name: Annotated[
74
+ str,
75
+ Field(
76
+ title="Holdings map file name",
77
+ description="File name for holdings map",
78
+ ),
79
+ ]
80
+ location_map_file_name: Annotated[
81
+ str,
82
+ Field(
83
+ title="Location map file name",
84
+ description="File name for location map",
85
+ ),
86
+ ]
87
+ default_call_number_type_name: Annotated[
88
+ str,
89
+ Field(
90
+ title="Default call number type name",
91
+ description="Default name for call number type",
92
+ ),
93
+ ]
94
+ previously_generated_holdings_files: Annotated[
95
+ Optional[list[str]],
96
+ Field(
97
+ title="Previously generated holdings files",
98
+ description=(
99
+ "List of previously generated holdings files. "
100
+ "By default is empty list."
101
+ ),
102
+ ),
103
+ ] = []
104
+ fallback_holdings_type_id: Annotated[
105
+ str,
106
+ Field(
107
+ title="Fallback holdings type ID",
108
+ description="ID for fallback holdings type",
109
+ ),
110
+ ]
51
111
  holdings_type_uuid_for_boundwiths: Annotated[
52
112
  str,
53
113
  Field(
54
114
  title="Holdings Type for Boundwith Holdings",
55
115
  description=(
56
116
  "UUID for a Holdings type (set in Settings->Inventory) "
57
- "for Bound-with Holdings)"
117
+ "for Bound-with Holdings. Default is empty string."
58
118
  ),
59
119
  ),
60
120
  ] = ""
61
- call_number_type_map_file_name: Optional[str]
62
- holdings_merge_criteria: Optional[list[str]] = [
121
+ call_number_type_map_file_name: Annotated[
122
+ Optional[str],
123
+ Field(
124
+ title="Call number type map file name",
125
+ description="File name for call number type map",
126
+ ),
127
+ ]
128
+ holdings_merge_criteria: Annotated[
129
+ Optional[list[str]],
130
+ Field(
131
+ title="Holdings merge criteria",
132
+ description=(
133
+ "List of holdings merge criteria. "
134
+ "Default value is "
135
+ "['instanceId', 'permanentLocationId', 'callNumber']."
136
+ ),
137
+ ),
138
+ ] = [
63
139
  "instanceId",
64
140
  "permanentLocationId",
65
141
  "callNumber",
66
142
  ]
67
- reset_hrid_settings: Optional[bool] = False
143
+ reset_hrid_settings: Annotated[
144
+ Optional[bool],
145
+ Field(
146
+ title="Reset HRID settings",
147
+ description=(
148
+ "At the end of the run reset "
149
+ "FOLIO with the HRID settings. Default is FALSE."
150
+ ),
151
+ ),
152
+ ] = False
68
153
  update_hrid_settings: Annotated[
69
154
  bool,
70
155
  Field(
71
156
  title="Update HRID settings",
72
- description="At the end of the run, update FOLIO with the HRID settings",
157
+ description=(
158
+ "At the end of the run update "
159
+ "FOLIO with the HRID settings. Default is TRUE."
160
+ ),
73
161
  ),
74
162
  ] = True
75
163