caltechdata-api 1.6.0__tar.gz → 1.6.1__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 (21) hide show
  1. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/PKG-INFO +1 -1
  2. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/cli.py +103 -44
  3. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/md_to_json.py +62 -6
  4. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api.egg-info/PKG-INFO +1 -1
  5. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api.egg-info/requires.txt +2 -2
  6. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/setup.py +2 -2
  7. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/LICENSE +0 -0
  8. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/README.md +0 -0
  9. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/__init__.py +0 -0
  10. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/caltechdata_edit.py +0 -0
  11. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/caltechdata_write.py +0 -0
  12. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/customize_schema.py +0 -0
  13. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/download_file.py +0 -0
  14. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/get_files.py +0 -0
  15. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/get_metadata.py +0 -0
  16. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api/utils.py +0 -0
  17. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api.egg-info/SOURCES.txt +0 -0
  18. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api.egg-info/dependency_links.txt +0 -0
  19. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api.egg-info/entry_points.txt +0 -0
  20. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/caltechdata_api.egg-info/top_level.txt +0 -0
  21. {caltechdata_api-1.6.0 → caltechdata_api-1.6.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: caltechdata_api
3
- Version: 1.6.0
3
+ Version: 1.6.1
4
4
  Summary: Python wrapper for CaltechDATA API.
5
5
  Home-page: https://github.com/caltechlibrary/caltechdata_api
6
6
  Author: Thomas E Morrell
@@ -5,7 +5,7 @@ from caltechdata_api import caltechdata_write, caltechdata_edit
5
5
  from .md_to_json import parse_readme_to_json
6
6
  import json
7
7
  import os
8
- import configparser
8
+ from cryptography.fernet import Fernet
9
9
 
10
10
  CALTECHDATA_API = "https://data.caltech.edu/api/names?q=identifiers.identifier:{}"
11
11
  ORCID_API = "https://orcid.org/"
@@ -21,26 +21,60 @@ funderIdentifier = ""
21
21
  funderIdentifierType = ""
22
22
  funderName = ""
23
23
 
24
+ home_directory = os.path.expanduser("~")
25
+ caltechdata_directory = os.path.join(home_directory, ".caltechdata")
24
26
 
25
- CONFIG_FILE = "caltechdata_config.ini"
26
27
 
28
+ if not os.path.exists(caltechdata_directory):
29
+ os.makedirs(caltechdata_directory)
27
30
 
28
- def get_or_set_token():
29
- config = configparser.ConfigParser()
30
31
 
31
- if os.path.isfile(CONFIG_FILE):
32
- config.read(CONFIG_FILE)
33
- if "CaltechDATA" in config and "token" in config["CaltechDATA"]:
34
- return config["CaltechDATA"]["token"]
32
+ def generate_key():
33
+ return Fernet.generate_key()
34
+
35
+
36
+ # Load the key from a file or generate a new one if not present
37
+ def load_or_generate_key():
38
+ key_file = os.path.join(caltechdata_directory, "key.key")
39
+ if os.path.exists(key_file):
40
+ with open(key_file, "rb") as f:
41
+ return f.read()
35
42
  else:
43
+ key = generate_key()
44
+ with open(key_file, "wb") as f:
45
+ f.write(key)
46
+ return key
47
+
48
+
49
+ # Encrypt the token
50
+ def encrypt_token(token, key):
51
+ f = Fernet(key)
52
+ return f.encrypt(token.encode())
53
+
54
+
55
+ # Decrypt the token
56
+ def decrypt_token(encrypted_token, key):
57
+ f = Fernet(key)
58
+ return f.decrypt(encrypted_token).decode()
59
+
60
+
61
+ # Function to get or set token
62
+ def get_or_set_token():
63
+ key = load_or_generate_key()
64
+ token_file = os.path.join(caltechdata_directory, "token.txt")
65
+ try:
66
+ with open(token_file, "rb") as f:
67
+ encrypted_token = f.read()
68
+ token = decrypt_token(encrypted_token, key)
69
+ return token
70
+ except FileNotFoundError:
36
71
  while True:
37
- token = get_user_input("Enter your CaltechDATA token: ")
38
- confirm_token = get_user_input("Confirm your CaltechDATA token: ")
72
+ token = input("Enter your CaltechDATA token: ").strip()
73
+ confirm_token = input("Confirm your CaltechDATA token: ").strip()
39
74
  if token == confirm_token:
40
- config.add_section("CaltechDATA")
41
- config.set("CaltechDATA", "token", token)
42
- with open(CONFIG_FILE, "w") as configfile:
43
- config.write(configfile)
75
+ encrypted_token = encrypt_token(token, key)
76
+ with open(token_file, "wb") as f:
77
+ f.write(encrypted_token)
44
78
  return token
45
79
  else:
46
80
  print("Tokens do not match. Please try again.")
@@ -216,26 +250,42 @@ def get_names(orcid):
216
250
  return family_name, given_name
217
251
 
218
252
 
253
+ def write_s3cmd_config(access_key, secret_key, endpoint):
254
+ configf = os.path.join(home_directory, ".s3cfg")
255
+ if not os.path.exists(key_file):
256
+ with open(configf, "w") as file:
257
+ file.write(
258
+ f"""[default]
259
+ access_key = {access_key}
260
+ host_base = {endpoint}
261
+ host_bucket = %(bucket).{endpoint}
262
+ secret_key = {secret_key}
263
+ """
264
+ )
265
+
266
+
219
267
  def upload_supporting_file(record_id=None):
220
268
  filepath = ""
221
- file_link = ""
269
+ filepaths = []
270
+ file_links = []
222
271
  while True:
223
272
  choice = get_user_input(
224
273
  "Do you want to upload or link data files? (upload/link/n): "
225
274
  ).lower()
226
275
  if choice == "link":
227
- endpoint = "https://sdsc.osn.xsede.org/"
276
+ endpoint = "sdsc.osn.xsede.org"
228
277
  path = "ini230004-bucket01/"
229
-
230
278
  if not record_id:
231
- record_id = get_user_input("Folder where OSN files are uploaded")
232
-
279
+ access_key = get_user_input("Enter the access key: ")
280
+ secret_key = get_user_input("Enter the secret key: ")
281
+ write_s3cmd_config(access_key, secret_key, endpoint)
282
+ print("""S3 connection configured.""")
283
+ break
284
+ endpoint = f"https://{endpoint}/"
233
285
  s3 = s3fs.S3FileSystem(anon=True, client_kwargs={"endpoint_url": endpoint})
234
286
  # Find the files
235
287
  files = s3.glob(path + record_id + "/*")
236
288
 
237
- file_links = []
238
-
239
289
  for link in files:
240
290
  fname = link.split("/")[-1]
241
291
  if "." not in fname:
@@ -264,33 +314,38 @@ def upload_supporting_file(record_id=None):
264
314
  f for f in os.listdir() if not f.endswith(".json") and os.path.isfile(f)
265
315
  ]
266
316
  print("\n".join(files))
267
- filename = get_user_input(
268
- "Enter the filename to upload as a supporting file: "
269
- )
270
- if filename in files:
271
- file_size = os.path.getsize(filename)
272
- if file_size > 1024 * 1024 * 1024:
273
- file_link = get_user_input(
274
- "Enter the S3 link to the file (File size is more than 1GB): "
275
- )
276
- if file_link:
277
- return filepath, file_link
317
+ while True:
318
+ filename = get_user_input(
319
+ "Enter the filename to upload as a supporting file (or 'n' to finish): "
320
+ )
321
+ if filename == "n":
322
+ break
323
+ if filename in files:
324
+ file_size = os.path.getsize(filename)
325
+ if file_size > 1024 * 1024 * 1024:
326
+ print(
327
+ """The file is greater than 1 GB. Please upload the
328
+ metadata to CaltechDATA, and you'll be provided
329
+ instructions to upload the files to S3 directly."""
330
+ )
278
331
  else:
279
- print("Link is required for files larger than 1GB.")
280
- continue
332
+ filepath = os.path.abspath(filename)
333
+ filepaths.append(filepath)
281
334
  else:
282
- filepath = os.path.abspath(filename)
283
- break
284
- else:
285
- print(
286
- f"Error: File '{filename}' not found. Please enter a valid filename."
287
- )
335
+ print(
336
+ f"Error: File '{filename}' not found. Please enter a valid filename."
337
+ )
338
+
339
+ add_more = get_user_input("Do you want to add more files? (y/n): ").lower()
340
+ if add_more != "y":
341
+ break
342
+
288
343
  elif choice == "n":
289
344
  break
290
345
  else:
291
346
  print("Invalid input. Please enter 'link' or 'upload' or 'n'.")
292
347
 
293
- return filepath, file_link
348
+ return filepaths, file_links
294
349
 
295
350
 
296
351
  def upload_data_from_file():
@@ -361,7 +416,9 @@ def create_record():
361
416
  )
362
417
  rec_id = response
363
418
  print(
364
- f"You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}"
419
+ f"""You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}
420
+ If you need to upload large files to S3, you can type
421
+ `s3cmd put DATA_FILE s3://ini230004-bucket01/{rec_id}/"""
365
422
  )
366
423
  break
367
424
  else:
@@ -424,7 +481,9 @@ def create_record():
424
481
  )
425
482
  rec_id = response
426
483
  print(
427
- f"You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}"
484
+ f"""You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}
485
+ If you need to upload large files to S3, you can type
486
+ `s3cmd put DATA_FILE s3://ini230004-bucket01/{rec_id}/"""
428
487
  )
429
488
  with open(response + ".json", "w") as file:
430
489
  json.dump(metadata, file, indent=2)
@@ -474,7 +533,7 @@ def edit_record():
474
533
  )
475
534
  rec_id = response
476
535
  print(
477
- f"You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}"
536
+ f"You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}\n"
478
537
  )
479
538
 
480
539
 
@@ -33,6 +33,7 @@ def expand_special_keys(key, value):
33
33
  {
34
34
  "nameIdentifier": orcid,
35
35
  "nameIdentifierScheme": "ORCID",
36
+ "schemeUri": f"https://orcid.org/{value}",
36
37
  }
37
38
  ]
38
39
  return value
@@ -55,13 +56,20 @@ def parse_readme_to_json(readme_path):
55
56
  else:
56
57
  json_data["titles"] = [{"title": title_line.replace("# ", "")}]
57
58
 
59
+ contributors = []
60
+ identifiers = []
61
+ item_list = []
62
+
58
63
  section_pattern = re.compile(r"^##\s+(.*)$")
59
64
  key_value_pattern = re.compile(r"^-\s+(.*?):\s+(.*)$")
60
65
  link_pattern = re.compile(r"\[.*?\]\((.*?)\)")
61
66
 
62
67
  for line_number, line in enumerate(lines, 1):
63
68
  if not line.strip():
64
- if current_object and current_section:
69
+ if item_list and current_section:
70
+ json_data[current_section] = item_list
71
+ item_list = []
72
+ elif current_object and current_section:
65
73
  if current_section == "types":
66
74
  json_data[current_section] = current_object
67
75
  elif len(current_object) == 1:
@@ -70,6 +78,12 @@ def parse_readme_to_json(readme_path):
70
78
  json_data[current_section] = value
71
79
  else:
72
80
  json_data[current_section].append(current_object)
81
+ elif current_section in ["creators", "contributors"]:
82
+ contributors.append(current_object)
83
+ current_object = {}
84
+ elif current_section == "identifiers":
85
+ identifiers.append(current_object)
86
+ current_object = {}
73
87
  else:
74
88
  json_data[current_section].append(current_object)
75
89
  current_object = {}
@@ -77,7 +91,30 @@ def parse_readme_to_json(readme_path):
77
91
 
78
92
  section_match = section_pattern.match(line)
79
93
  if section_match:
80
- if current_section and current_object:
94
+ if item_list:
95
+ json_data[current_section] = item_list
96
+ elif current_object:
97
+ if current_section in json_data:
98
+ if isinstance(json_data[current_section], list):
99
+ json_data[current_section].append(current_object)
100
+ elif isinstance(json_data[current_section], dict):
101
+ json_data[current_section].update(current_object)
102
+ else:
103
+ json_data[current_section] = (
104
+ [current_object]
105
+ if current_section != "types"
106
+ else current_object
107
+ )
108
+ current_object = {}
109
+
110
+ elif contributors and current_section in ["creators", "contributors"]:
111
+ json_data[current_section] = contributors
112
+ contributors = []
113
+ elif identifiers and current_section == "identifiers":
114
+ json_data[current_section] = identifiers
115
+ identifiers = []
116
+
117
+ elif current_section and current_object:
81
118
  if current_section == "types":
82
119
  json_data[current_section] = current_object
83
120
  elif len(current_object) == 1:
@@ -100,19 +137,38 @@ def parse_readme_to_json(readme_path):
100
137
 
101
138
  if key in ["affiliation", "nameIdentifiers"]:
102
139
  value = expand_special_keys(key, value)
140
+ elif (
141
+ key == "nameType"
142
+ and current_object
143
+ and current_section in ["creators", "contributors"]
144
+ ):
145
+ contributors.append(current_object)
146
+ current_object = {}
147
+ elif current_section in ["subjects"]:
148
+ item_list.append({key: value})
149
+ elif current_section == "dates":
150
+ if key == "date":
151
+ current_object["date"] = value
152
+ elif key == "dateType":
153
+ current_object["dateType"] = value
154
+ item_list.append(current_object)
155
+ current_object = {}
103
156
  else:
104
157
  link_match = link_pattern.search(value)
105
158
  if link_match:
106
159
  value = link_match.group(1)
107
-
108
- current_object[key] = value
160
+ current_object[key] = value
109
161
 
110
162
  elif line.strip() and not section_match:
111
163
  raise ReadmeFormatException(
112
164
  f"Incorrect format detected at line {line_number}: {line}"
113
165
  )
114
166
 
115
- if current_section and current_object:
167
+ if contributors and current_section in ["creators", "contributors"]:
168
+ json_data[current_section] = contributors
169
+ elif identifiers and current_section == "identifiers":
170
+ json_data[current_section] = identifiers
171
+ elif current_section and current_object:
116
172
  if current_section == "types":
117
173
  json_data[current_section] = current_object
118
174
  elif len(current_object) == 1:
@@ -128,7 +184,7 @@ def parse_readme_to_json(readme_path):
128
184
 
129
185
 
130
186
  if __name__ == "__main__":
131
- readme_path = "exampleREADME.md"
187
+ readme_path = "/Users/elizabethwon/downloads/exampleREADME.md"
132
188
  try:
133
189
  json_data = parse_readme_to_json(readme_path)
134
190
  output_json_path = "output1.json"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: caltechdata-api
3
- Version: 1.6.0
3
+ Version: 1.6.1
4
4
  Summary: Python wrapper for CaltechDATA API.
5
5
  Home-page: https://github.com/caltechlibrary/caltechdata_api
6
6
  Author: Thomas E Morrell
@@ -3,5 +3,5 @@ datacite>1.1.0
3
3
  tqdm>=4.62.3
4
4
  pyyaml
5
5
  s3fs
6
- configparser
7
- awscli
6
+ cryptography
7
+ s3cmd
@@ -66,8 +66,8 @@ REQUIRED = [
66
66
  "tqdm>=4.62.3",
67
67
  "pyyaml",
68
68
  "s3fs",
69
- "configparser",
70
- "awscli",
69
+ "cryptography",
70
+ "s3cmd",
71
71
  ]
72
72
 
73
73
  # What packages are optional?
File without changes