folio-migration-tools 1.9.0rc3__py3-none-any.whl → 1.9.0rc5__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 (20) hide show
  1. folio_migration_tools/mapping_file_transformation/order_mapper.py +8 -4
  2. folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +3 -2
  3. folio_migration_tools/migration_tasks/batch_poster.py +90 -8
  4. folio_migration_tools/migration_tasks/courses_migrator.py +54 -8
  5. folio_migration_tools/migration_tasks/holdings_csv_transformer.py +102 -14
  6. folio_migration_tools/migration_tasks/items_transformer.py +133 -20
  7. folio_migration_tools/migration_tasks/loans_migrator.py +61 -9
  8. folio_migration_tools/migration_tasks/migration_task_base.py +104 -11
  9. folio_migration_tools/migration_tasks/orders_transformer.py +107 -14
  10. folio_migration_tools/migration_tasks/organization_transformer.py +79 -14
  11. folio_migration_tools/migration_tasks/requests_migrator.py +56 -7
  12. folio_migration_tools/migration_tasks/reserves_migrator.py +26 -4
  13. folio_migration_tools/migration_tasks/user_transformer.py +88 -18
  14. folio_migration_tools/transaction_migration/legacy_loan.py +13 -1
  15. folio_migration_tools/transaction_migration/legacy_reserve.py +3 -5
  16. {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/METADATA +1 -1
  17. {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/RECORD +20 -20
  18. {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/WHEEL +1 -1
  19. {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/LICENSE +0 -0
  20. {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/entry_points.txt +0 -0
@@ -6,9 +6,10 @@ import sys
6
6
  import time
7
7
  import traceback
8
8
  from datetime import datetime, timedelta
9
- from typing import Optional
9
+ from typing import Annotated, Optional
10
10
  from urllib.error import HTTPError
11
11
  from zoneinfo import ZoneInfo
12
+ from pydantic import Field
12
13
 
13
14
  import i18n
14
15
  from dateutil import parser as du_parser
@@ -35,13 +36,64 @@ from folio_migration_tools.transaction_migration.transaction_result import (
35
36
 
36
37
  class LoansMigrator(MigrationTaskBase):
37
38
  class TaskConfiguration(AbstractTaskConfiguration):
38
- name: str
39
- migration_task_type: str
40
- open_loans_files: list[FileDefinition]
41
- fallback_service_point_id: str
42
- starting_row: Optional[int] = 1
43
- item_files: Optional[list[FileDefinition]] = []
44
- patron_files: Optional[list[FileDefinition]] = []
39
+ name: Annotated[
40
+ str,
41
+ Field(
42
+ title="Task name",
43
+ description="The name of the task.",
44
+ ),
45
+ ]
46
+ migration_task_type: Annotated[
47
+ str,
48
+ Field(
49
+ title="Migration task type",
50
+ description="The type of the migration task.",
51
+ ),
52
+ ]
53
+ open_loans_files: Annotated[
54
+ Optional[list[FileDefinition]],
55
+ Field(
56
+ title="Open loans files",
57
+ description="List of files containing open loan data."
58
+ ),
59
+ ]
60
+ fallback_service_point_id: Annotated[
61
+ str,
62
+ Field(
63
+ title="Fallback service point ID",
64
+ description="Identifier of the fallback service point.",
65
+ ),
66
+ ]
67
+ starting_row: Annotated[
68
+ Optional[int],
69
+ Field(
70
+ title="Starting row",
71
+ description=(
72
+ "The starting row for data processing. "
73
+ "By default is 1."
74
+ ),
75
+ ),
76
+ ] = 1
77
+ item_files: Annotated[
78
+ Optional[list[FileDefinition]],
79
+ Field(
80
+ title="Item files",
81
+ description=(
82
+ "List of files containing item data. "
83
+ "By default is empty list."
84
+ ),
85
+ ),
86
+ ] = []
87
+ patron_files: Annotated[
88
+ Optional[list[FileDefinition]],
89
+ Field(
90
+ title="Patron files",
91
+ description=(
92
+ "List of files containing patron data. "
93
+ "By default is empty list."
94
+ ),
95
+ ),
96
+ ] = []
45
97
 
46
98
  @staticmethod
47
99
  def get_object_type() -> FOLIONamespaces:
@@ -729,7 +781,7 @@ def timings(t0, t0func, num_objects):
729
781
 
730
782
 
731
783
  def print_smtp_warning():
732
- s = """
784
+ s = r"""
733
785
  _____ __ __ _____ ______ ___
734
786
  / ____| | \/ | |_ _| | __ | |__ \\
735
787
  | (___ | \ / | | | | |__|_| ) |
@@ -1,4 +1,5 @@
1
1
  import csv
2
+ import io
2
3
  import json
3
4
  import logging
4
5
  import os
@@ -119,7 +120,11 @@ class MigrationTaskBase:
119
120
  TransformationProcessError: _description_
120
121
 
121
122
  """
122
- files = [source_path / f.file_name for f in file_defs if isfile(source_path / f.file_name)]
123
+ files = [
124
+ source_path / f.file_name
125
+ for f in file_defs
126
+ if isfile(source_path / f.file_name)
127
+ ]
123
128
  ret_str = ", ".join(f.file_name for f in file_defs)
124
129
 
125
130
  if files and len(files) < len(file_defs):
@@ -141,7 +146,9 @@ class MigrationTaskBase:
141
146
  @staticmethod
142
147
  def load_id_map(map_path, raise_if_empty=False):
143
148
  if not isfile(map_path):
144
- logging.warn("No legacy id map found at %s. Will build one from scratch", map_path)
149
+ logging.warn(
150
+ "No legacy id map found at %s. Will build one from scratch", map_path
151
+ )
145
152
  return {}
146
153
  id_map = {}
147
154
  loaded_rows = 0
@@ -194,7 +201,9 @@ class MigrationTaskBase:
194
201
  else:
195
202
  logger.setLevel(logging.INFO)
196
203
  stream_handler.setLevel(logging.INFO)
197
- stream_handler.addFilter(ExcludeLevelFilter(30)) # Exclude warnings from pymarc
204
+ stream_handler.addFilter(
205
+ ExcludeLevelFilter(30)
206
+ ) # Exclude warnings from pymarc
198
207
  stream_handler.setFormatter(formatter)
199
208
  logger.addHandler(stream_handler)
200
209
 
@@ -225,16 +234,22 @@ class MigrationTaskBase:
225
234
  def setup_records_map(self, mapping_file_path):
226
235
  with open(mapping_file_path) as mapping_file:
227
236
  field_map = json.load(mapping_file)
228
- logging.info("%s fields present in record mapping file", len(field_map["data"]))
237
+ logging.info(
238
+ "%s fields present in record mapping file", len(field_map["data"])
239
+ )
229
240
  mapped_fields = (
230
241
  f
231
242
  for f in field_map["data"]
232
243
  if f["legacy_field"] and f["legacy_field"] != "Not mapped"
233
244
  )
234
- logging.info("%s fields mapped in record mapping file", len(list(mapped_fields)))
245
+ logging.info(
246
+ "%s fields mapped in record mapping file", len(list(mapped_fields))
247
+ )
235
248
  return field_map
236
249
 
237
- def log_and_exit_if_too_many_errors(self, error: TransformationRecordFailedError, idx):
250
+ def log_and_exit_if_too_many_errors(
251
+ self, error: TransformationRecordFailedError, idx
252
+ ):
238
253
  self.num_exeptions += 1
239
254
  error.log_it()
240
255
  if self.num_exeptions / (1 + idx) > 0.2 and self.num_exeptions > 5000:
@@ -250,7 +265,9 @@ class MigrationTaskBase:
250
265
  if num_processed > 1 and num_processed % 10000 == 0:
251
266
  elapsed = num_processed / (time.time() - start_time)
252
267
  elapsed_formatted = "{0:.4g}".format(elapsed)
253
- logging.info(f"{num_processed:,} records processed. Recs/sec: {elapsed_formatted} ")
268
+ logging.info(
269
+ f"{num_processed:,} records processed. Recs/sec: {elapsed_formatted} "
270
+ )
254
271
 
255
272
  def do_work_marc_transformer(
256
273
  self,
@@ -259,7 +276,9 @@ class MigrationTaskBase:
259
276
  if self.folder_structure.failed_marc_recs_file.is_file():
260
277
  os.remove(self.folder_structure.failed_marc_recs_file)
261
278
  logging.info("Removed failed marc records file to prevent duplicating data")
262
- with open(self.folder_structure.created_objects_path, "w+") as created_records_file:
279
+ with open(
280
+ self.folder_structure.created_objects_path, "w+"
281
+ ) as created_records_file:
263
282
  self.processor = MarcFileProcessor(
264
283
  self.mapper, self.folder_structure, created_records_file
265
284
  )
@@ -271,13 +290,87 @@ class MigrationTaskBase:
271
290
  self.folder_structure,
272
291
  )
273
292
 
293
+ @staticmethod
294
+ def validate_ref_data_mapping_lines(lines, num_of_columns):
295
+ """
296
+ Helper method to validate the structure of individual lines in a mapping file.
297
+
298
+ Args:
299
+ lines (list): List of lines in the mapping file
300
+ num_of_columns (int): Number of columns expected in each line
301
+
302
+ Returns:
303
+ tuple: A tuple containing a list of invalid lines and a list of valid lines
304
+ """
305
+ invalid_lines = []
306
+ valid_lines = []
307
+ for idx, row in enumerate(lines, start=2):
308
+ if not row.strip():
309
+ if idx == len(lines) + 1:
310
+ continue
311
+ else:
312
+ invalid_lines.append(str(idx))
313
+ else:
314
+ line_length = len(row.split("\t"))
315
+ if line_length != num_of_columns:
316
+ invalid_lines.append(str(idx))
317
+ else:
318
+ valid_lines.append(str(idx))
319
+ return invalid_lines, valid_lines
320
+
321
+ @staticmethod
322
+ def verify_ref_data_mapping_file_structure(map_file: io.TextIOBase):
323
+ """
324
+ Helper method to validate the structure of a mapping file.
325
+
326
+ Args:
327
+ map_file (io.TextIOBase): The mapping file to validate
328
+
329
+ Raises:
330
+ TransformationProcessError: If the mapping file has rows with different number of columns
331
+
332
+ Returns:
333
+ None
334
+ """
335
+ current_pos = map_file.tell()
336
+ try:
337
+ map_file.seek(0)
338
+ num_of_columns = len(map_file.readline().split("\t"))
339
+ lines = map_file.readlines()
340
+ invalid_lines, valid_lines = MigrationTaskBase.validate_ref_data_mapping_lines(
341
+ lines, num_of_columns
342
+ )
343
+ if invalid_lines:
344
+ raise TransformationProcessError(
345
+ "",
346
+ (
347
+ f"Mapping file {map_file.name} has rows with different number "
348
+ f"of columns ({'Row' if len(invalid_lines) == 1 else 'Rows'} {', '.join(invalid_lines)})"
349
+ ),
350
+ )
351
+ if not valid_lines:
352
+ raise TransformationProcessError(
353
+ "", f"Map has no rows: {map_file.name}"
354
+ )
355
+ finally:
356
+ map_file.seek(current_pos)
357
+
358
+ @staticmethod
274
359
  def load_ref_data_mapping_file(
275
- self,
276
360
  folio_property_name: str,
277
361
  map_file_path: Path,
278
362
  folio_keys,
279
363
  required: bool = True,
280
364
  ):
365
+ """
366
+ Helper method to load a reference data mapping file.
367
+
368
+ Args:
369
+ folio_property_name (str): The name of the property in FOLIO
370
+ map_file_path (Path): The path to the mapping file
371
+ folio_keys (list): A list of FOLIO keys
372
+ required (bool): Whether the property is required or not
373
+ """
281
374
  if (
282
375
  folio_property_name in folio_keys
283
376
  or required
@@ -286,9 +379,9 @@ class MigrationTaskBase:
286
379
  ):
287
380
  try:
288
381
  with open(map_file_path) as map_file:
382
+ # Validate the structure of the mapping file
383
+ MigrationTaskBase.verify_ref_data_mapping_file_structure(map_file)
289
384
  ref_data_map = list(csv.DictReader(map_file, dialect="tsv"))
290
- if not ref_data_map:
291
- raise TransformationProcessError("", f"Map has no rows: {map_file_path}")
292
385
  logging.info(
293
386
  "Found %s rows in %s map",
294
387
  len(ref_data_map),
@@ -5,7 +5,8 @@ import logging
5
5
  import sys
6
6
  import time
7
7
  from os.path import isfile
8
- from typing import List, Optional
8
+ from typing import List, Optional, Annotated
9
+ from pydantic import Field
9
10
 
10
11
  import i18n
11
12
  from deepdiff import DeepDiff
@@ -26,7 +27,9 @@ from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base
26
27
  from folio_migration_tools.mapping_file_transformation.order_mapper import (
27
28
  CompositeOrderMapper,
28
29
  )
29
- from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
30
+ from folio_migration_tools.migration_tasks.migration_task_base import (
31
+ MigrationTaskBase,
32
+ )
30
33
  from folio_migration_tools.task_configuration import AbstractTaskConfiguration
31
34
 
32
35
  csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
@@ -35,18 +38,108 @@ csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
35
38
  # Read files and do some work
36
39
  class OrdersTransformer(MigrationTaskBase):
37
40
  class TaskConfiguration(AbstractTaskConfiguration):
38
- name: str
39
- migration_task_type: str
40
- files: List[FileDefinition]
41
- orders_mapping_file_name: str
42
- organizations_code_map_file_name: str
43
- acquisition_method_map_file_name: str
44
- payment_status_map_file_name: Optional[str] = ""
45
- receipt_status_map_file_name: Optional[str] = ""
46
- workflow_status_map_file_name: Optional[str] = ""
47
- location_map_file_name: Optional[str] = ""
48
- funds_map_file_name: Optional[str] = ""
49
- funds_expense_class_map_file_name: Optional[str] = ""
41
+ name: Annotated[
42
+ str,
43
+ Field(
44
+ title="Task name",
45
+ description="The name of the task.",
46
+ ),
47
+ ]
48
+ migration_task_type: Annotated[
49
+ str,
50
+ Field(
51
+ title="Migration task type",
52
+ description="Type of the migration task.",
53
+ ),
54
+ ]
55
+ files: Annotated[
56
+ List[FileDefinition],
57
+ Field(
58
+ title="Files",
59
+ description="List of the files.",
60
+ ),
61
+ ]
62
+ orders_mapping_file_name: Annotated[
63
+ str,
64
+ Field(
65
+ title="Orders Mapping File Name",
66
+ description="File name for orders mapping.",
67
+ ),
68
+ ]
69
+ organizations_code_map_file_name: Annotated[
70
+ str,
71
+ Field(
72
+ title="Organizations Code Map File Name",
73
+ description="File name for organizations code mapping.",
74
+ ),
75
+ ]
76
+ acquisition_method_map_file_name: Annotated[
77
+ str,
78
+ Field(
79
+ title="Acquisition Method Map File Name",
80
+ description="File name for acquisition method mapping.",
81
+ ),
82
+ ]
83
+ payment_status_map_file_name: Annotated[
84
+ Optional[str],
85
+ Field(
86
+ title="Payment Status Map File Name",
87
+ description=(
88
+ "File name for payment status mapping. "
89
+ "By default is empty string."
90
+ ),
91
+ ),
92
+ ] = ""
93
+ receipt_status_map_file_name: Annotated[
94
+ Optional[str],
95
+ Field(
96
+ title="Receipt Status Map File Name",
97
+ description=(
98
+ "File name for receipt status mapping. "
99
+ "By default is empty string."
100
+ ),
101
+ ),
102
+ ] = ""
103
+ workflow_status_map_file_name: Annotated[
104
+ Optional[str],
105
+ Field(
106
+ title="Workflow Status Map File Name",
107
+ description=(
108
+ "File name for workflow status mapping. "
109
+ "By default is empty string."
110
+ ),
111
+ ),
112
+ ] = ""
113
+ location_map_file_name: Annotated[
114
+ Optional[str],
115
+ Field(
116
+ title="Location Map File Name",
117
+ description=(
118
+ "File name for location mapping. "
119
+ "By default is empty string."
120
+ ),
121
+ ),
122
+ ] = ""
123
+ funds_map_file_name: Annotated[
124
+ Optional[str],
125
+ Field(
126
+ title="Funds Map File Name",
127
+ description=(
128
+ "File name for funds mapping. "
129
+ "By default is empty string."
130
+ ),
131
+ ),
132
+ ] = ""
133
+ funds_expense_class_map_file_name: Annotated[
134
+ Optional[str],
135
+ Field(
136
+ title="Funds Expense Class Map File Name",
137
+ description=(
138
+ "File name for funds expense class mapping. "
139
+ "By default is empty string."
140
+ ),
141
+ ),
142
+ ] = ""
50
143
 
51
144
  @staticmethod
52
145
  def get_object_type() -> FOLIONamespaces:
@@ -7,7 +7,8 @@ import time
7
7
  import uuid
8
8
  from hashlib import sha1
9
9
  from os.path import isfile
10
- from typing import List, Optional
10
+ from typing import List, Optional, Annotated
11
+ from pydantic import Field
11
12
 
12
13
  import i18n
13
14
  from folio_uuid.folio_namespaces import FOLIONamespaces
@@ -36,14 +37,78 @@ csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
36
37
  # Read files and do some work
37
38
  class OrganizationTransformer(MigrationTaskBase):
38
39
  class TaskConfiguration(AbstractTaskConfiguration):
39
- name: str
40
- migration_task_type: str
41
- files: List[FileDefinition]
42
- organization_map_path: str
43
- organization_types_map_path: Optional[str] = ""
44
- address_categories_map_path: Optional[str] = ""
45
- email_categories_map_path: Optional[str] = ""
46
- phone_categories_map_path: Optional[str] = ""
40
+ name: Annotated[
41
+ str,
42
+ Field(
43
+ description=(
44
+ "Name of this migration task. The name is being used to call the specific "
45
+ "task, and to distinguish tasks of similar types"
46
+ ),
47
+ ),
48
+ ]
49
+ migration_task_type: Annotated[
50
+ str,
51
+ Field(
52
+ title="Migration task type",
53
+ description=(
54
+ "The type of migration task you want to perform"
55
+ ),
56
+ ),
57
+ ]
58
+ files: Annotated[
59
+ List[FileDefinition],
60
+ Field(
61
+ title="Source files",
62
+ description=(
63
+ "List of MARC21 files with holdings records"
64
+ ),
65
+ ),
66
+ ]
67
+ organization_map_path: Annotated[
68
+ str,
69
+ Field(
70
+ title="Organization map path",
71
+ description=(
72
+ "Path to the organization map file"
73
+ ),
74
+ ),
75
+ ]
76
+ organization_types_map_path: Annotated[
77
+ Optional[str],
78
+ Field(
79
+ title="Organization types map path",
80
+ description=(
81
+ "Path to the organization types map file. By default is empty string"
82
+ ),
83
+ ),
84
+ ] = ""
85
+ address_categories_map_path: Annotated[
86
+ Optional[str],
87
+ Field(
88
+ title="Address categories map path",
89
+ description=(
90
+ "Path to the address categories map file. By default is empty string"
91
+ ),
92
+ ),
93
+ ] = ""
94
+ email_categories_map_path: Annotated[
95
+ Optional[str],
96
+ Field(
97
+ title="Email categories map path",
98
+ description=(
99
+ "Path to the email categories map file. By default is empty string"
100
+ ),
101
+ ),
102
+ ] = ""
103
+ phone_categories_map_path: Annotated[
104
+ Optional[str],
105
+ Field(
106
+ title="Phone categories map path",
107
+ description=(
108
+ "Path to the phone categories map file. By default is empty string"
109
+ ),
110
+ ),
111
+ ] = ""
47
112
 
48
113
  @staticmethod
49
114
  def get_object_type() -> FOLIONamespaces:
@@ -178,8 +243,8 @@ class OrganizationTransformer(MigrationTaskBase):
178
243
  self.mapper.handle_transformation_process_error(idx, process_error)
179
244
  except TransformationRecordFailedError as error:
180
245
  self.mapper.handle_transformation_record_failed_error(idx, error)
181
- except Exception as excepion:
182
- self.mapper.handle_generic_exception(idx, excepion)
246
+ except Exception as exception:
247
+ self.mapper.handle_generic_exception(idx, exception)
183
248
 
184
249
  self.mapper.migration_report.add_general_statistics(
185
250
  i18n.t("Number of objects in source data file")
@@ -227,7 +292,7 @@ class OrganizationTransformer(MigrationTaskBase):
227
292
  self.folder_structure.migration_reports_file,
228
293
  )
229
294
  self.mapper.migration_report.write_migration_report(
230
- i18n.t("Ogranization transformation report"),
295
+ i18n.t("Organization transformation report"),
231
296
  migration_report_file,
232
297
  self.start_datetime,
233
298
  )
@@ -269,7 +334,7 @@ class OrganizationTransformer(MigrationTaskBase):
269
334
  if address.get("isPrimary") is True:
270
335
  primary_address_exists = True
271
336
 
272
- # If none of the existing addresses is pimrary
337
+ # If none of the existing addresses is primary
273
338
  # Make the first one primary
274
339
  if not primary_address_exists:
275
340
  addresses[0]["isPrimary"] = True
@@ -364,7 +429,7 @@ class OrganizationTransformer(MigrationTaskBase):
364
429
 
365
430
  if len(identical_objects) > 0:
366
431
  self.mapper.migration_report.add_general_statistics(
367
- i18n.t("Number of reoccuring identical %{type}", type=extradata_object_type)
432
+ i18n.t("Number of reoccurring identical %{type}", type=extradata_object_type)
368
433
  )
369
434
  Helper.log_data_issue(
370
435
  f"{self.legacy_id}",
@@ -3,7 +3,8 @@ import json
3
3
  import logging
4
4
  import sys
5
5
  import time
6
- from typing import Optional
6
+ from typing import Optional, Annotated
7
+ from pydantic import Field
7
8
 
8
9
  import i18n
9
10
  from folio_uuid.folio_namespaces import FOLIONamespaces
@@ -24,12 +25,60 @@ from folio_migration_tools.transaction_migration.legacy_request import LegacyReq
24
25
 
25
26
  class RequestsMigrator(MigrationTaskBase):
26
27
  class TaskConfiguration(AbstractTaskConfiguration):
27
- name: str
28
- migration_task_type: str
29
- open_requests_file: FileDefinition
30
- starting_row: Optional[int] = 1
31
- item_files: Optional[list[FileDefinition]] = []
32
- patron_files: Optional[list[FileDefinition]] = []
28
+ name: Annotated[
29
+ str,
30
+ Field(
31
+ title="Task name",
32
+ description=(
33
+ "Name of this migration task. The name is being used to call "
34
+ "the specific task, and to distinguish tasks of similar types"
35
+ )
36
+ ),
37
+ ]
38
+ migration_task_type: Annotated[
39
+ str,
40
+ Field(
41
+ title="Migration task type",
42
+ description="The type of migration task you want to perform",
43
+ ),
44
+ ]
45
+ open_requests_file: Annotated[
46
+ FileDefinition,
47
+ Field(
48
+ title="Open requests file",
49
+ description="File with list of open requests",
50
+ ),
51
+ ]
52
+ starting_row: Annotated[
53
+ Optional[int],
54
+ Field(
55
+ title="Starting row",
56
+ description=(
57
+ "Row number to start processing data from. "
58
+ "Optional, by default is first row"
59
+ ),
60
+ ),
61
+ ] = 1
62
+ item_files: Annotated[
63
+ Optional[list[FileDefinition]],
64
+ Field(
65
+ title="Item files",
66
+ description=(
67
+ "List of files containing item data. "
68
+ "Optional, by default is empty list"
69
+ ),
70
+ ),
71
+ ] = []
72
+ patron_files: Annotated[
73
+ Optional[list[FileDefinition]],
74
+ Field(
75
+ title="Patron files",
76
+ description=(
77
+ "List of files containing patron data. "
78
+ "Optional, by default is empty list"
79
+ ),
80
+ ),
81
+ ] = []
33
82
 
34
83
  @staticmethod
35
84
  def get_object_type() -> FOLIONamespaces:
@@ -4,8 +4,9 @@ import logging
4
4
  import sys
5
5
  import time
6
6
  import traceback
7
- from typing import Dict
7
+ from typing import Dict, Annotated
8
8
  from urllib.error import HTTPError
9
+ from pydantic import Field
9
10
 
10
11
  import httpx
11
12
  import i18n
@@ -25,9 +26,30 @@ from folio_migration_tools.transaction_migration.legacy_reserve import LegacyRes
25
26
 
26
27
  class ReservesMigrator(MigrationTaskBase):
27
28
  class TaskConfiguration(AbstractTaskConfiguration):
28
- name: str
29
- migration_task_type: str
30
- course_reserve_file_path: FileDefinition
29
+ name: Annotated[
30
+ str,
31
+ Field(
32
+ title="Migration task name",
33
+ description=(
34
+ "Name of this migration task. The name is being used to call the specific "
35
+ "task, and to distinguish tasks of similar types"
36
+ ),
37
+ ),
38
+ ]
39
+ migration_task_type: Annotated[
40
+ str,
41
+ Field(
42
+ title="Migration task type",
43
+ description="The type of migration task you want to perform",
44
+ ),
45
+ ]
46
+ course_reserve_file_path: Annotated[
47
+ FileDefinition,
48
+ Field(
49
+ title="Course reserve file path",
50
+ description="Path to the file with course reserves",
51
+ ),
52
+ ]
31
53
 
32
54
  @staticmethod
33
55
  def get_object_type() -> FOLIONamespaces: