folio-data-import 0.2.6__py3-none-any.whl → 0.2.8rc1__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.
Potentially problematic release.
This version of folio-data-import might be problematic. Click here for more details.
- folio_data_import/UserImport.py +74 -13
- {folio_data_import-0.2.6.dist-info → folio_data_import-0.2.8rc1.dist-info}/METADATA +23 -4
- folio_data_import-0.2.8rc1.dist-info/RECORD +11 -0
- folio_data_import-0.2.6.dist-info/RECORD +0 -11
- {folio_data_import-0.2.6.dist-info → folio_data_import-0.2.8rc1.dist-info}/LICENSE +0 -0
- {folio_data_import-0.2.6.dist-info → folio_data_import-0.2.8rc1.dist-info}/WHEEL +0 -0
- {folio_data_import-0.2.6.dist-info → folio_data_import-0.2.8rc1.dist-info}/entry_points.txt +0 -0
folio_data_import/UserImport.py
CHANGED
|
@@ -5,6 +5,7 @@ import getpass
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import time
|
|
8
|
+
import uuid
|
|
8
9
|
from datetime import datetime as dt
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Tuple
|
|
@@ -41,12 +42,12 @@ class UserImporter: # noqa: R0902
|
|
|
41
42
|
self,
|
|
42
43
|
folio_client: folioclient.FolioClient,
|
|
43
44
|
library_name: str,
|
|
44
|
-
user_file_path: Path,
|
|
45
45
|
batch_size: int,
|
|
46
46
|
limit_simultaneous_requests: asyncio.Semaphore,
|
|
47
47
|
logfile: AsyncTextIOWrapper,
|
|
48
48
|
errorfile: AsyncTextIOWrapper,
|
|
49
49
|
http_client: httpx.AsyncClient,
|
|
50
|
+
user_file_path: Path = None,
|
|
50
51
|
user_match_key: str = "externalSystemId",
|
|
51
52
|
only_update_present_fields: bool = False,
|
|
52
53
|
default_preferred_contact_type: str = "002",
|
|
@@ -94,14 +95,34 @@ class UserImporter: # noqa: R0902
|
|
|
94
95
|
"""
|
|
95
96
|
return {x[name]: x["id"] for x in folio_client.folio_get_all(endpoint, key)}
|
|
96
97
|
|
|
98
|
+
@staticmethod
|
|
99
|
+
def validate_uuid(uuid_string: str) -> bool:
|
|
100
|
+
"""
|
|
101
|
+
Validate a UUID string.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
uuid_string (str): The UUID string to validate.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
bool: True if the UUID is valid, otherwise False.
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
uuid.UUID(uuid_string)
|
|
111
|
+
return True
|
|
112
|
+
except ValueError:
|
|
113
|
+
return False
|
|
114
|
+
|
|
97
115
|
async def do_import(self) -> None:
|
|
98
116
|
"""
|
|
99
117
|
Main method to import users.
|
|
100
118
|
|
|
101
119
|
This method triggers the process of importing users by calling the `process_file` method.
|
|
102
120
|
"""
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
if self.user_file_path:
|
|
122
|
+
with open(self.user_file_path, "r", encoding="utf-8") as openfile:
|
|
123
|
+
await self.process_file(openfile)
|
|
124
|
+
else:
|
|
125
|
+
raise FileNotFoundError("No user objects file provided")
|
|
105
126
|
|
|
106
127
|
async def get_existing_user(self, user_obj) -> dict:
|
|
107
128
|
"""
|
|
@@ -200,10 +221,20 @@ class UserImporter: # noqa: R0902
|
|
|
200
221
|
mapped_addresses = []
|
|
201
222
|
for address in addresses:
|
|
202
223
|
try:
|
|
203
|
-
|
|
204
|
-
address["addressTypeId"]
|
|
205
|
-
|
|
206
|
-
|
|
224
|
+
if (
|
|
225
|
+
self.validate_uuid(address["addressTypeId"])
|
|
226
|
+
and address["addressTypeId"] in self.address_type_map.values()
|
|
227
|
+
):
|
|
228
|
+
await self.logfile.write(
|
|
229
|
+
f"Row {line_number}: Address type {address['addressTypeId']} is a UUID, "
|
|
230
|
+
f"skipping mapping\n"
|
|
231
|
+
)
|
|
232
|
+
mapped_addresses.append(address)
|
|
233
|
+
else:
|
|
234
|
+
address["addressTypeId"] = self.address_type_map[
|
|
235
|
+
address["addressTypeId"]
|
|
236
|
+
]
|
|
237
|
+
mapped_addresses.append(address)
|
|
207
238
|
except KeyError:
|
|
208
239
|
if address["addressTypeId"] not in self.address_type_map.values():
|
|
209
240
|
print(
|
|
@@ -229,7 +260,16 @@ class UserImporter: # noqa: R0902
|
|
|
229
260
|
None
|
|
230
261
|
"""
|
|
231
262
|
try:
|
|
232
|
-
|
|
263
|
+
if (
|
|
264
|
+
self.validate_uuid(user_obj["patronGroup"])
|
|
265
|
+
and user_obj["patronGroup"] in self.patron_group_map.values()
|
|
266
|
+
):
|
|
267
|
+
await self.logfile.write(
|
|
268
|
+
f"Row {line_number}: Patron group {user_obj['patronGroup']} is a UUID, "
|
|
269
|
+
f"skipping mapping\n"
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
user_obj["patronGroup"] = self.patron_group_map[user_obj["patronGroup"]]
|
|
233
273
|
except KeyError:
|
|
234
274
|
if user_obj["patronGroup"] not in self.patron_group_map.values():
|
|
235
275
|
print(
|
|
@@ -256,7 +296,16 @@ class UserImporter: # noqa: R0902
|
|
|
256
296
|
mapped_departments = []
|
|
257
297
|
for department in user_obj.pop("departments", []):
|
|
258
298
|
try:
|
|
259
|
-
|
|
299
|
+
if (
|
|
300
|
+
self.validate_uuid(department)
|
|
301
|
+
and department in self.department_map.values()
|
|
302
|
+
):
|
|
303
|
+
await self.logfile.write(
|
|
304
|
+
f"Row {line_number}: Department {department} is a UUID, skipping mapping\n"
|
|
305
|
+
)
|
|
306
|
+
mapped_departments.append(department)
|
|
307
|
+
else:
|
|
308
|
+
mapped_departments.append(self.department_map[department])
|
|
260
309
|
except KeyError:
|
|
261
310
|
print(
|
|
262
311
|
f'Row {line_number}: Department "{department}" not found, ' # noqa: B907
|
|
@@ -674,7 +723,13 @@ class UserImporter: # noqa: R0902
|
|
|
674
723
|
mapped_service_points = []
|
|
675
724
|
for sp in spu_obj.pop("servicePointsIds", []):
|
|
676
725
|
try:
|
|
677
|
-
|
|
726
|
+
if self.validate_uuid(sp) and sp in self.service_point_map.values():
|
|
727
|
+
await self.logfile.write(
|
|
728
|
+
f"Service point {sp} is a UUID, skipping mapping\n"
|
|
729
|
+
)
|
|
730
|
+
mapped_service_points.append(sp)
|
|
731
|
+
else:
|
|
732
|
+
mapped_service_points.append(self.service_point_map[sp])
|
|
678
733
|
except KeyError:
|
|
679
734
|
print(
|
|
680
735
|
f'Service point "{sp}" not found, excluding service point from user: '
|
|
@@ -685,7 +740,13 @@ class UserImporter: # noqa: R0902
|
|
|
685
740
|
if "defaultServicePointId" in spu_obj:
|
|
686
741
|
sp_code = spu_obj.pop('defaultServicePointId', '')
|
|
687
742
|
try:
|
|
688
|
-
|
|
743
|
+
if self.validate_uuid(sp_code) and sp_code in self.service_point_map.values():
|
|
744
|
+
await self.logfile.write(
|
|
745
|
+
f"Default service point {sp_code} is a UUID, skipping mapping\n"
|
|
746
|
+
)
|
|
747
|
+
mapped_sp_id = sp_code
|
|
748
|
+
else:
|
|
749
|
+
mapped_sp_id = self.service_point_map[sp_code]
|
|
689
750
|
if mapped_sp_id not in spu_obj.get('servicePointsIds', []):
|
|
690
751
|
print(
|
|
691
752
|
f'Default service point "{sp_code}" not found in assigned service points, '
|
|
@@ -781,7 +842,7 @@ class UserImporter: # noqa: R0902
|
|
|
781
842
|
Process the user object file.
|
|
782
843
|
|
|
783
844
|
Args:
|
|
784
|
-
openfile: The file object to process.
|
|
845
|
+
openfile: The file or file-like object to process.
|
|
785
846
|
"""
|
|
786
847
|
tasks = []
|
|
787
848
|
for line_number, user in enumerate(openfile):
|
|
@@ -935,12 +996,12 @@ async def main() -> None:
|
|
|
935
996
|
importer = UserImporter(
|
|
936
997
|
folio_client,
|
|
937
998
|
library_name,
|
|
938
|
-
user_file_path,
|
|
939
999
|
batch_size,
|
|
940
1000
|
limit_async_requests,
|
|
941
1001
|
logfile,
|
|
942
1002
|
errorfile,
|
|
943
1003
|
http_client,
|
|
1004
|
+
user_file_path,
|
|
944
1005
|
args.user_match_key,
|
|
945
1006
|
args.update_only_present_fields,
|
|
946
1007
|
args.default_preferred_contact_type,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: folio_data_import
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8rc1
|
|
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
|
|
@@ -102,11 +102,30 @@ When this package is installed via PyPI or using `poetry install` from this repo
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
```
|
|
105
|
-
|
|
105
|
+
#### Matching Existing Users
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
Unlike mod-user-import, this importer does not require `externalSystemId` as the match point for your objects. If the user objects have `id` values, that will be used, falling back to `externalSystemId`. However, you can also specify `username` or `barcode` as the match point if desired, using the `--user_match_key` argument.
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
#### Preferred Contact Type Mapping
|
|
110
|
+
|
|
111
|
+
Another point of departure from the behavior of `mod-user-import` is the handling of `preferredContactTypeId`. This importer will accept either the `"001", "002", "003"...` values stored by the FOLIO, or the human-friendly strings used by `mod-user-import` (`"mail", "email", "text", "phone", "mobile"`). It will also __*set a customizable default for all users that do not otherwise have a valid value specified*__ (using `--default_preferred_contact_type`), unless a (valid) value is already present in the user record being updated.
|
|
112
|
+
|
|
113
|
+
#### Field Protection (*experimental*)
|
|
114
|
+
|
|
115
|
+
This script offers a rudimentary field protection implementation using custom fields. To enable this functionality, create a text custom field that has the field name `protectedFields`. In this field, you ca specify a comma-separated list of User schema field names, using dot-notation for nested fields. This protection should support all standard fields except addresses within `personal.addresses`. If you include `personal.addresses` in a user record, any existing addresses will be replaced by the new values.
|
|
116
|
+
|
|
117
|
+
##### Example
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
{
|
|
121
|
+
"protectedFields": "customFields.protectedFields,personal.preferredFirstName,barcode,personal.telephone,personal.addresses"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Would result in `preferredFirstName`, `barcode`, and `telephone` remaining unchanged, regardless of the contents of the incoming records.
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
#### How to use:
|
|
110
129
|
1. Generate a JSON lines (one JSON object per line) file of FOLIO user objects in the style of [mod-user-import](https://github.com/folio-org/mod-user-import)
|
|
111
130
|
2. Run the script and specify the required arguments (and any desired optional arguments), including the path to your file of user objects
|
|
112
131
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
folio_data_import/MARCDataImport.py,sha256=gFBq6DwghC3hXPkkM-c0XlPjtoZwITVAeEhH8joPIQo,23450
|
|
2
|
+
folio_data_import/UserImport.py,sha256=zs0kts3dCY2L0ZdMgtsbT0xrTXabvP3wxd9B75FcWF8,40984
|
|
3
|
+
folio_data_import/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
folio_data_import/__main__.py,sha256=kav_uUsnrIjGjVxQkk3exLKrc1mah9t2x3G6bGS-5I0,3710
|
|
5
|
+
folio_data_import/marc_preprocessors/__init__.py,sha256=Wt-TKkMhUyZWFS-WhAmbShKQLPjXmHKPb2vL6kvkqVA,72
|
|
6
|
+
folio_data_import/marc_preprocessors/_preprocessors.py,sha256=srx36pgY0cwl6_0z6CVOyM_Uzr_g2RObo1jJJjSEZJs,944
|
|
7
|
+
folio_data_import-0.2.8rc1.dist-info/LICENSE,sha256=qJX7wxMC7ky9Kq4v3zij8MjGEiC5wsB7pYeOhLj5TDk,1083
|
|
8
|
+
folio_data_import-0.2.8rc1.dist-info/METADATA,sha256=ofqGY2Z0W50cu5kZTkvLp6IRB2NyBsVyPSkmBA6yf3k,6115
|
|
9
|
+
folio_data_import-0.2.8rc1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
10
|
+
folio_data_import-0.2.8rc1.dist-info/entry_points.txt,sha256=498SxWVXeEMRNw3PUf-eoReZvKewmYwPBtZhIUPr_Jg,192
|
|
11
|
+
folio_data_import-0.2.8rc1.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
folio_data_import/MARCDataImport.py,sha256=gFBq6DwghC3hXPkkM-c0XlPjtoZwITVAeEhH8joPIQo,23450
|
|
2
|
-
folio_data_import/UserImport.py,sha256=2sTEJVZDVw9rS833LdanbMA_-EPkd8MqYlbznqUvlaQ,38445
|
|
3
|
-
folio_data_import/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
folio_data_import/__main__.py,sha256=kav_uUsnrIjGjVxQkk3exLKrc1mah9t2x3G6bGS-5I0,3710
|
|
5
|
-
folio_data_import/marc_preprocessors/__init__.py,sha256=Wt-TKkMhUyZWFS-WhAmbShKQLPjXmHKPb2vL6kvkqVA,72
|
|
6
|
-
folio_data_import/marc_preprocessors/_preprocessors.py,sha256=srx36pgY0cwl6_0z6CVOyM_Uzr_g2RObo1jJJjSEZJs,944
|
|
7
|
-
folio_data_import-0.2.6.dist-info/LICENSE,sha256=qJX7wxMC7ky9Kq4v3zij8MjGEiC5wsB7pYeOhLj5TDk,1083
|
|
8
|
-
folio_data_import-0.2.6.dist-info/METADATA,sha256=4WvmabtkdQEOPlw9z6ZuTh_KCTImcFPF7GgZYG_3_6g,5186
|
|
9
|
-
folio_data_import-0.2.6.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
10
|
-
folio_data_import-0.2.6.dist-info/entry_points.txt,sha256=498SxWVXeEMRNw3PUf-eoReZvKewmYwPBtZhIUPr_Jg,192
|
|
11
|
-
folio_data_import-0.2.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|