folio-data-import 0.2.3__tar.gz → 0.2.5__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.

Potentially problematic release.


This version of folio-data-import might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: folio_data_import
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: A python module to interact with the data importing capabilities of the open-source FOLIO ILS
5
5
  License: MIT
6
6
  Author: Brooks Travis
@@ -19,7 +19,7 @@ Requires-Dist: flake8-bugbear (>=24.8.19,<25.0.0)
19
19
  Requires-Dist: flake8-docstrings (>=1.7.0,<2.0.0)
20
20
  Requires-Dist: flake8-isort (>=6.1.1,<7.0.0)
21
21
  Requires-Dist: folioclient (>=0.60.5,<0.61.0)
22
- Requires-Dist: httpx (>=0.23.0,<0.24.0)
22
+ Requires-Dist: httpx (>=0.27.2,<0.28.0)
23
23
  Requires-Dist: inquirer (>=3.4.0,<4.0.0)
24
24
  Requires-Dist: pyhumps (>=3.8.0,<4.0.0)
25
25
  Requires-Dist: pymarc (>=5.2.2,<6.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "folio_data_import"
3
- version = "0.2.3"
3
+ version = "0.2.5"
4
4
  description = "A python module to interact with the data importing capabilities of the open-source FOLIO ILS"
5
5
  authors = ["Brooks Travis <brooks.travis@gmail.com>"]
6
6
  license = "MIT"
@@ -15,7 +15,7 @@ folio-user-import = "folio_data_import.UserImport:sync_main"
15
15
  [tool.poetry.dependencies]
16
16
  python = "^3.9"
17
17
  folioclient = "^0.60.5"
18
- httpx = "^0.23.0"
18
+ httpx = "^0.27.2"
19
19
  pymarc = "^5.2.2"
20
20
  pyhumps = "^3.8.0"
21
21
  inquirer = "^3.4.0"
@@ -3,6 +3,7 @@ import asyncio
3
3
  import glob
4
4
  import io
5
5
  import os
6
+ import sys
6
7
  from typing import List
7
8
  import uuid
8
9
  from contextlib import ExitStack
@@ -30,6 +31,9 @@ except AttributeError:
30
31
  # The order in which the report summary should be displayed
31
32
  REPORT_SUMMARY_ORDERING = {"created": 0, "updated": 1, "discarded": 2, "error": 3}
32
33
 
34
+ # Set default timeout and backoff values for HTTP requests when retrying job status and final summary checks
35
+ RETRY_TIMEOUT_START = 1
36
+ RETRY_TIMEOUT_RETRY_FACTOR = 2
33
37
 
34
38
  class MARCImportJob:
35
39
  """
@@ -79,6 +83,7 @@ class MARCImportJob:
79
83
  self.import_profile_name = import_profile_name
80
84
  self.batch_size = batch_size
81
85
  self.batch_delay = batch_delay
86
+ self.current_retry_timeout = None
82
87
 
83
88
  async def do_work(self) -> None:
84
89
  """
@@ -148,10 +153,23 @@ class MARCImportJob:
148
153
  Raises:
149
154
  IndexError: If the job execution with the specified ID is not found.
150
155
  """
151
- job_status = self.folio_client.folio_get(
152
- "/metadata-provider/jobExecutions?statusNot=DISCARDED&uiStatusAny"
153
- "=PREPARING_FOR_PREVIEW&uiStatusAny=READY_FOR_PREVIEW&uiStatusAny=RUNNING&limit=50"
154
- )
156
+ try:
157
+ self.current_retry_timeout = (
158
+ self.current_retry_timeout * RETRY_TIMEOUT_RETRY_FACTOR
159
+ ) if self.current_retry_timeout else RETRY_TIMEOUT_START
160
+ job_status = self.folio_client.folio_get(
161
+ "/metadata-provider/jobExecutions?statusNot=DISCARDED&uiStatusAny"
162
+ "=PREPARING_FOR_PREVIEW&uiStatusAny=READY_FOR_PREVIEW&uiStatusAny=RUNNING&limit=50"
163
+ )
164
+ self.current_retry_timeout = None
165
+ except httpx.ConnectTimeout:
166
+ sleep(.25)
167
+ with httpx.Client(
168
+ timeout=self.current_retry_timeout,
169
+ verify=self.folio_client.ssl_verify
170
+ ) as temp_client:
171
+ self.folio_client.httpx_client = temp_client
172
+ return await self.get_job_status()
155
173
  try:
156
174
  status = [
157
175
  job for job in job_status["jobExecutions"] if job["id"] == self.job_id
@@ -392,9 +410,7 @@ class MARCImportJob:
392
410
  await self.get_job_status()
393
411
  sleep(1)
394
412
  if self.finished:
395
- job_summary = self.folio_client.folio_get(
396
- f"/metadata-provider/jobSummary/{self.job_id}"
397
- )
413
+ job_summary = await self.get_job_summary()
398
414
  job_summary.pop("jobExecutionId")
399
415
  job_summary.pop("totalErrors")
400
416
  columns = ["Summary"] + list(job_summary.keys())
@@ -425,6 +441,31 @@ class MARCImportJob:
425
441
  self.last_current = 0
426
442
  self.finished = False
427
443
 
444
+ async def get_job_summary(self) -> dict:
445
+ """
446
+ Retrieves the job summary for the current job execution.
447
+
448
+ Returns:
449
+ dict: The job summary for the current job execution.
450
+ """
451
+ try:
452
+ self.current_retry_timeout = (
453
+ self.current_retry_timeout * RETRY_TIMEOUT_RETRY_FACTOR
454
+ ) if self.current_retry_timeout else RETRY_TIMEOUT_START
455
+ job_summary = self.folio_client.folio_get(
456
+ f"/metadata-provider/jobSummary/{self.job_id}"
457
+ )
458
+ self.current_retry_timeout = None
459
+ except httpx.ReadTimeout: #
460
+ sleep(.25)
461
+ with httpx.Client(
462
+ timeout=self.current_retry_timeout,
463
+ verify=self.folio_client.ssl_verify
464
+ ) as temp_client:
465
+ self.folio_client.httpx_client = temp_client
466
+ return await self.get_job_summary()
467
+ return job_summary
468
+
428
469
 
429
470
  async def main() -> None:
430
471
  """
@@ -491,6 +532,17 @@ async def main() -> None:
491
532
  if args.member_tenant_id:
492
533
  folio_client.okapi_headers["x-okapi-tenant"] = args.member_tenant_id
493
534
 
535
+ if os.path.isabs(args.marc_file_path):
536
+ marc_files = [Path(x) for x in glob.glob(args.marc_file_path)]
537
+ else:
538
+ marc_files = list(Path("./").glob(args.marc_file_path))
539
+
540
+ if len(marc_files) == 0:
541
+ print(f"No files found matching {args.marc_file_path}. Exiting.")
542
+ sys.exit(1)
543
+ else:
544
+ print(marc_files)
545
+
494
546
  if not args.import_profile_name:
495
547
  import_profiles = folio_client.folio_get(
496
548
  "/data-import-profiles/jobProfiles",
@@ -511,8 +563,6 @@ async def main() -> None:
511
563
  ]
512
564
  answers = inquirer.prompt(questions)
513
565
  args.import_profile_name = answers["import_profile_name"]
514
- marc_files = [Path(x) for x in glob.glob(args.marc_file_path, root_dir="./")]
515
- print(marc_files)
516
566
  try:
517
567
  await MARCImportJob(
518
568
  folio_client,
@@ -181,12 +181,15 @@ class UserImporter: # noqa: R0902
181
181
  KeyError: If an address type name in the user object is not found in address_type_map.
182
182
 
183
183
  """
184
- if "personal" in user_obj and "addresses" in user_obj["personal"]:
185
- for address in user_obj["personal"]["addresses"]:
184
+ if "personal" in user_obj:
185
+ addresses = user_obj["personal"].pop("addresses", [])
186
+ mapped_addresses = []
187
+ for address in addresses:
186
188
  try:
187
189
  address["addressTypeId"] = self.address_type_map[
188
190
  address["addressTypeId"]
189
191
  ]
192
+ mapped_addresses.append(address)
190
193
  except KeyError:
191
194
  if address["addressTypeId"] not in self.address_type_map.values():
192
195
  print(
@@ -197,9 +200,8 @@ class UserImporter: # noqa: R0902
197
200
  f"Row {line_number}: Address type {address['addressTypeId']} not found"
198
201
  f", removing address\n"
199
202
  )
200
- del address
201
- if len(user_obj["personal"]["addresses"]) == 0:
202
- del user_obj["personal"]["addresses"]
203
+ if mapped_addresses:
204
+ user_obj["personal"]["addresses"] = mapped_addresses
203
205
 
204
206
  async def map_patron_groups(self, user_obj, line_number) -> None:
205
207
  """