caltechdata-api 1.7.1__tar.gz → 1.8.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.7.1 → caltechdata_api-1.8.1}/PKG-INFO +1 -1
  2. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/caltechdata_write.py +3 -5
  3. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/cli.py +141 -65
  4. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/customize_schema.py +148 -1
  5. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api.egg-info/PKG-INFO +1 -1
  6. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/LICENSE +0 -0
  7. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/README.md +0 -0
  8. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/__init__.py +0 -0
  9. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/caltechdata_edit.py +0 -0
  10. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/download_file.py +0 -0
  11. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/get_files.py +0 -0
  12. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/get_metadata.py +0 -0
  13. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/md_to_json.py +0 -0
  14. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api/utils.py +0 -0
  15. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api.egg-info/SOURCES.txt +0 -0
  16. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api.egg-info/dependency_links.txt +0 -0
  17. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api.egg-info/entry_points.txt +0 -0
  18. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api.egg-info/requires.txt +0 -0
  19. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/caltechdata_api.egg-info/top_level.txt +0 -0
  20. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/setup.cfg +0 -0
  21. {caltechdata_api-1.7.1 → caltechdata_api-1.8.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: caltechdata_api
3
- Version: 1.7.1
3
+ Version: 1.8.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, Bhattarai Rohan , Won Elizabeth
@@ -1,7 +1,7 @@
1
1
  import copy
2
2
  import json
3
- import os, requests
4
-
3
+ import os
4
+ import requests
5
5
  import s3fs
6
6
  from requests import session
7
7
  from json.decoder import JSONDecodeError
@@ -49,8 +49,6 @@ def write_files_rdm(files, file_link, headers, f_headers, s3=None, keepfiles=Fal
49
49
  infile = open(name, "rb")
50
50
  else:
51
51
  infile = open(f_list[name], "rb")
52
- # size = infile.seek(0, 2)
53
- # infile.seek(0, 0) # reset at beginning
54
52
  result = requests.put(link, headers=f_headers, data=infile)
55
53
  if result.status_code != 200:
56
54
  raise Exception(result.text)
@@ -68,7 +66,7 @@ def write_files_rdm(files, file_link, headers, f_headers, s3=None, keepfiles=Fal
68
66
  def add_file_links(
69
67
  metadata, file_links, file_descriptions=[], additional_descriptions="", s3_link=None
70
68
  ):
71
- # Currently configured for S3 links, assuming all are at same endpoint
69
+ # Currently configured for S3 links, assuming all are at the same endpoint
72
70
  link_string = ""
73
71
  endpoint = "https://" + file_links[0].split("/")[2]
74
72
  s3 = s3fs.S3FileSystem(anon=True, client_kwargs={"endpoint_url": endpoint})
@@ -59,11 +59,14 @@ def decrypt_token(encrypted_token, key):
59
59
  return f.decrypt(encrypted_token).decode()
60
60
 
61
61
 
62
- # Function to get or set token
63
- def get_or_set_token():
64
-
62
+ # Function to get or set token with support for test system
63
+ def get_or_set_token(production=True):
65
64
  key = load_or_generate_key()
66
- token_file = os.path.join(caltechdata_directory, "token.txt")
65
+
66
+ # Use different token files for production and test environments
67
+ token_filename = "token.txt" if production else "token_test.txt"
68
+ token_file = os.path.join(caltechdata_directory, token_filename)
69
+
67
70
  try:
68
71
  with open(token_file, "rb") as f:
69
72
  encrypted_token = f.read()
@@ -71,8 +74,12 @@ def get_or_set_token():
71
74
  return token
72
75
  except FileNotFoundError:
73
76
  while True:
74
- token = input("Enter your CaltechDATA token: ").strip()
75
- confirm_token = input("Confirm your CaltechDATA token: ").strip()
77
+ token = input(
78
+ f"Enter your {'Production' if production else 'Test'} CaltechDATA token: "
79
+ ).strip()
80
+ confirm_token = input(
81
+ f"Confirm your {'Production' if production else 'Test'} CaltechDATA token: "
82
+ ).strip()
76
83
  if token == confirm_token:
77
84
  encrypted_token = encrypt_token(token, key)
78
85
  with open(token_file, "wb") as f:
@@ -133,7 +140,7 @@ def get_funding_entries():
133
140
  def validate_funder_identifier(funder_identifier):
134
141
  response = requests.get(f"https://api.ror.org/organizations/{funder_identifier}")
135
142
  if response.status_code == 200:
136
- return True
143
+ return response.json().get("name")
137
144
  else:
138
145
  return False
139
146
 
@@ -150,7 +157,8 @@ def get_funding_details():
150
157
  award_title = get_user_input("Enter the award title for funding: ")
151
158
  while True:
152
159
  funder_identifier = get_user_input("Enter the funder ROR (https://ror.org): ")
153
- if validate_funder_identifier(funder_identifier):
160
+ name = validate_funder_identifier(funder_identifier)
161
+ if name:
154
162
  break
155
163
  else:
156
164
  print(
@@ -162,6 +170,7 @@ def get_funding_details():
162
170
  return {
163
171
  "awardNumber": award_number,
164
172
  "awardTitle": award_title,
173
+ "funderName": name,
165
174
  "funderIdentifier": funder_identifier,
166
175
  "funderIdentifierType": "ROR",
167
176
  }
@@ -187,9 +196,18 @@ def parse_arguments():
187
196
  if license_number.isdigit() and 1 <= int(license_number) <= 8:
188
197
  # Valid license number selected
189
198
  args["license"] = {
190
- "1": "cc0-1.0",
191
- "2": "cc-by-4.0",
192
- "3": "cc-by-nc-4.0",
199
+ "1": {
200
+ "rights": "Creative Commons Zero v1.0 Universal",
201
+ "rightsIdentifier": "cc0-1.0",
202
+ },
203
+ "2": {
204
+ "rights": "Creative Commons Attribution v4.0 Universal",
205
+ "rightsIdentifier": "cc-by-4.0",
206
+ },
207
+ "3": {
208
+ "rights": "Creative Commons Attribution Non-Commercial v4.0 Universal",
209
+ "rightsIdentifier": "cc-by-nc-4.0",
210
+ },
193
211
  }[license_number]
194
212
  break
195
213
  else:
@@ -252,9 +270,11 @@ def get_names(orcid):
252
270
  return family_name, given_name
253
271
 
254
272
 
255
- def write_s3cmd_config(access_key, secret_key, endpoint):
273
+ def write_s3cmd_config(endpoint):
256
274
  configf = os.path.join(home_directory, ".s3cfg")
257
275
  if not os.path.exists(configf):
276
+ access_key = get_user_input("Enter the access key: ")
277
+ secret_key = get_user_input("Enter the secret key: ")
258
278
  with open(configf, "w") as file:
259
279
  file.write(
260
280
  f"""[default]
@@ -279,9 +299,7 @@ def upload_supporting_file(record_id=None):
279
299
  endpoint = "sdsc.osn.xsede.org"
280
300
  path = "ini230004-bucket01/"
281
301
  if not record_id:
282
- access_key = get_user_input("Enter the access key: ")
283
- secret_key = get_user_input("Enter the secret key: ")
284
- write_s3cmd_config(access_key, secret_key, endpoint)
302
+ write_s3cmd_config(endpoint)
285
303
  print("""S3 connection configured.""")
286
304
  break
287
305
  endpoint = f"https://{endpoint}/"
@@ -376,20 +394,35 @@ def upload_data_from_file():
376
394
  print(f"Error: Invalid JSON format in the file '{filename}'. {str(e)}")
377
395
 
378
396
 
397
+ def parse_args():
398
+ """Parse command-line arguments."""
399
+ parser = argparse.ArgumentParser(description="CaltechDATA CLI tool.")
400
+ parser.add_argument(
401
+ "-test", action="store_true", help="Use test mode, sets production to False"
402
+ )
403
+ args = parser.parse_args()
404
+ return args
405
+
406
+
379
407
  def main():
408
+ args = parse_args()
409
+
410
+ production = not args.test # Set production to False if -test flag is provided
411
+
380
412
  choice = get_user_input(
381
413
  "Do you want to create or edit a CaltechDATA record? (create/edit): "
382
414
  ).lower()
383
415
  if choice == "create":
384
- create_record()
416
+ create_record(production)
385
417
  elif choice == "edit":
386
- edit_record()
418
+ edit_record(production)
387
419
  else:
388
420
  print("Invalid choice. Please enter 'create' or 'edit'.")
389
421
 
390
422
 
391
- def create_record():
392
- token = get_or_set_token()
423
+ def create_record(production):
424
+ token = get_or_set_token(production)
425
+ # keep_file = input("Do you want to keep your existing files? (yes/no): ").lower() == "yes"
393
426
  print("Using CaltechDATA token:", token)
394
427
  while True:
395
428
  choice = get_user_input(
@@ -401,7 +434,11 @@ def create_record():
401
434
  if existing_data:
402
435
  if filepath != "":
403
436
  response = caltechdata_write(
404
- existing_data, token, filepath, production=True, publish=False
437
+ existing_data,
438
+ token,
439
+ filepath,
440
+ production=production,
441
+ publish=False,
405
442
  )
406
443
  elif file_link != "":
407
444
  response = caltechdata_write(
@@ -414,15 +451,10 @@ def create_record():
414
451
  )
415
452
  else:
416
453
  response = caltechdata_write(
417
- existing_data, token, production=True, publish=False
454
+ existing_data, token, production=production, publish=False
418
455
  )
419
456
  rec_id = response
420
- print(
421
- f"""You can view and publish this record at
422
- https://data.caltech.edu/uploads/{rec_id}
423
- If you need to upload large files to S3, you can type
424
- `s3cmd put DATA_FILE s3://ini230004-bucket01/{rec_id}/"""
425
- )
457
+ print_upload_message(rec_id, production)
426
458
  break
427
459
  else:
428
460
  print("Going back to the main menu.")
@@ -457,9 +489,7 @@ def create_record():
457
489
  ],
458
490
  "types": {"resourceType": "", "resourceTypeGeneral": "Dataset"},
459
491
  "rightsList": [
460
- {
461
- "rightsIdentifier": args["license"],
462
- }
492
+ args["license"],
463
493
  ],
464
494
  "fundingReferences": args["fundingReferences"],
465
495
  "schemaVersion": "http://datacite.org/schema/kernel-4",
@@ -468,27 +498,23 @@ def create_record():
468
498
  if confirm_upload():
469
499
  if filepath != "":
470
500
  response = caltechdata_write(
471
- metadata, token, filepath, production=True, publish=False
501
+ metadata, token, filepath, production=production, publish=False
472
502
  )
473
503
  elif file_link != "":
474
504
  response = caltechdata_write(
475
505
  metadata,
476
506
  token,
477
507
  file_links=[file_link],
478
- production=True,
508
+ production=production,
479
509
  publish=False,
480
510
  )
481
511
  else:
482
512
  response = caltechdata_write(
483
- metadata, token, production=True, publish=False
513
+ metadata, token, production=production, publish=False
484
514
  )
485
515
  rec_id = response
486
- print(
487
- f"""You can view and publish this record at
488
- https://data.caltech.edu/uploads/{rec_id}
489
- If you need to upload large files to S3, you can type
490
- `s3cmd put DATA_FILE s3://ini230004-bucket01/{rec_id}/"""
491
- )
516
+
517
+ print_upload_message(rec_id, production)
492
518
  with open(response + ".json", "w") as file:
493
519
  json.dump(metadata, file, indent=2)
494
520
  break
@@ -498,17 +524,32 @@ def create_record():
498
524
  print("Invalid choice. Please enter 'existing' or 'create'.")
499
525
 
500
526
 
501
- def edit_record():
527
+ def print_upload_message(rec_id, production):
528
+ base_url = (
529
+ "https://data.caltech.edu/uploads/"
530
+ if production
531
+ else "https://data.caltechlibrary.dev/uploads/"
532
+ )
533
+ print(
534
+ f"""You can view and publish this record at
535
+ {base_url}{rec_id}
536
+ If you need to upload large files to S3, you can type
537
+ `s3cmd put DATA_FILE s3://ini230004-bucket01/{rec_id}/`"""
538
+ )
539
+
540
+
541
+ def edit_record(production):
502
542
  record_id = input("Enter the CaltechDATA record ID: ")
503
- token = get_or_set_token()
543
+ token = get_or_set_token(production)
504
544
  file_name = download_file_by_id(record_id, token)
545
+
505
546
  if file_name:
506
547
  try:
507
548
  # Read the edited metadata file
508
549
  with open(file_name, "r") as file:
509
550
  metadata = json.load(file)
510
551
  response = caltechdata_edit(
511
- record_id, metadata, token, production=True, publish=False
552
+ record_id, metadata, token, production=production, publish=False
512
553
  )
513
554
  if response:
514
555
  print("Metadata edited successfully.")
@@ -520,31 +561,54 @@ def edit_record():
520
561
  print("No metadata file found.")
521
562
  choice = get_user_input("Do you want to add files? (y/n): ").lower()
522
563
  if choice == "y":
523
- API_URL_TEMPLATE = "https://data.caltech.edu/api/records/{record_id}/files"
564
+ if production:
565
+ API_URL_TEMPLATE = "https://data.caltech.edu/api/records/{record_id}/files"
566
+ API_URL_TEMPLATE_DRAFT = (
567
+ "https://data.caltech.edu/api/records/{record_id}/draft/files"
568
+ )
569
+ else:
570
+ API_URL_TEMPLATE = (
571
+ "https://data.caltechlibrary.dev/api/records/{record_id}/files"
572
+ )
573
+ API_URL_TEMPLATE_DRAFT = (
574
+ "https://data.caltechlibrary.dev/api/records/{record_id}/draft/files"
575
+ )
576
+
524
577
  url = API_URL_TEMPLATE.format(record_id=record_id)
578
+ url_draft = API_URL_TEMPLATE_DRAFT.format(record_id=record_id)
525
579
 
526
- API_URL_TEMPLATE2 = (
527
- "https://data.caltech.edu/api/records/{record_id}/draft/files"
528
- )
529
- url2 = API_URL_TEMPLATE2.format(record_id=record_id)
530
- response = requests.get(url)
531
- response2 = requests.get(url2)
532
- filepath, file_link = upload_supporting_file(record_id)
533
- print(file_link)
534
- if response.status_code == 404 and response2.status_code == 404:
580
+ headers = {
581
+ "accept": "application/json",
582
+ }
583
+
584
+ if token:
585
+ headers["Authorization"] = "Bearer %s" % token
586
+
587
+ response = requests.get(url, headers=headers)
588
+ response_draft = requests.get(url_draft, headers=headers)
589
+ data = response.json()
590
+ data_draft = response_draft.json()
591
+ # Check if 'entries' exists and its length
592
+ if (
593
+ len(data.get("entries", [])) == 0
594
+ and len(data_draft.get("entries", [])) == 0
595
+ ):
535
596
  keepfile = False
536
597
  else:
537
- keepfile = input("Do you want to keep existing files? y/n: ")
538
- if keepfile == "y":
539
- keepfile = True
540
- else:
541
- keepfile = False
598
+ keepfile = (
599
+ input("Do you want to keep existing files? (y/n): ").lower() == "y"
600
+ )
601
+
602
+ filepath, file_link = upload_supporting_file(record_id)
603
+ if file_link:
604
+ print(file_link)
605
+
542
606
  if filepath != "":
543
607
  response = caltechdata_edit(
544
608
  record_id,
545
609
  token=token,
546
610
  files=filepath,
547
- production=True,
611
+ production=production,
548
612
  publish=False,
549
613
  keepfiles=keepfile,
550
614
  )
@@ -554,14 +618,13 @@ def edit_record():
554
618
  metadata,
555
619
  token=token,
556
620
  file_links=file_link,
557
- production=True,
621
+ production=production,
558
622
  publish=False,
559
- keepfile=keepfile,
623
+ keepfiles=keepfile,
560
624
  )
625
+
561
626
  rec_id = response
562
- print(
563
- f"You can view and publish this record at https://data.caltech.edu/uploads/{rec_id}\n"
564
- )
627
+ print_upload_message(rec_id, production)
565
628
 
566
629
 
567
630
  def download_file_by_id(record_id, token=None):
@@ -576,7 +639,6 @@ def download_file_by_id(record_id, token=None):
576
639
 
577
640
  try:
578
641
  response = requests.get(url, headers=headers)
579
-
580
642
  if response.status_code != 200:
581
643
  # Might have a draft
582
644
  response = requests.get(
@@ -584,7 +646,21 @@ def download_file_by_id(record_id, token=None):
584
646
  headers=headers,
585
647
  )
586
648
  if response.status_code != 200:
587
- raise Exception(f"Record {record_id} does not exist, cannot edit")
649
+ url = f"https://data.caltechlibrary.dev/api/records/{record_id}"
650
+ response = requests.get(
651
+ url,
652
+ headers=headers,
653
+ )
654
+ if response.status_code != 200:
655
+ # Might have a draft
656
+ response = requests.get(
657
+ url + "/draft",
658
+ headers=headers,
659
+ )
660
+ if response.status_code != 200:
661
+ raise Exception(
662
+ f"Record {record_id} does not exist, cannot edit"
663
+ )
588
664
  file_content = response.content
589
665
  file_name = f"downloaded_data_{record_id}.json"
590
666
  with open(file_name, "wb") as file:
@@ -134,8 +134,9 @@ def rdm_creators_contributors(person_list, peopleroles):
134
134
 
135
135
  def customize_schema_rdm(json_record):
136
136
  # Get vocabularies used in InvenioRDM
137
- vocabularies = get_vocabularies()
138
137
 
138
+ vocabularies = get_vocabularies()
139
+ validate_metadata(json_record)
139
140
  peopleroles = vocabularies["crr"]
140
141
  resourcetypes = vocabularies["rsrct"]
141
142
  descriptiontypes = vocabularies["dty"]
@@ -386,6 +387,152 @@ def customize_schema_rdm(json_record):
386
387
  return final
387
388
 
388
389
 
390
+ def validate_metadata(json_record):
391
+ """
392
+ Validates the presence and structure of required fields in a CaltechDATA JSON record.
393
+ Raises an exception if any required field is missing or structured incorrectly.
394
+ """
395
+ errors = []
396
+
397
+ # Check for 'types' and 'resourceTypeGeneral'
398
+ if "types" not in json_record:
399
+ errors.append("'types' field is missing.")
400
+ elif not isinstance(json_record["types"], dict):
401
+ errors.append("'types' field should be a dictionary.")
402
+ elif "resourceTypeGeneral" not in json_record["types"]:
403
+ errors.append("'resourceTypeGeneral' field is missing in 'types'.")
404
+
405
+ # Check for 'title'
406
+ if "titles" not in json_record:
407
+ errors.append("'titles' field is missing.")
408
+ elif not isinstance(json_record["titles"], list) or len(json_record["titles"]) == 0:
409
+ errors.append("'titles' should be a non-empty list.")
410
+ else:
411
+ # Ensure each title is a dictionary with 'title' field
412
+ for title in json_record["titles"]:
413
+ if not isinstance(title, dict) or "title" not in title:
414
+ errors.append(
415
+ "Each entry in 'titles' must be a dictionary with a 'title' key."
416
+ )
417
+
418
+ # Publication date is handled by customize function
419
+
420
+ # Check for 'creators'
421
+ if "creators" not in json_record:
422
+ errors.append("'creators' field is missing.")
423
+ elif (
424
+ not isinstance(json_record["creators"], list)
425
+ or len(json_record["creators"]) == 0
426
+ ):
427
+ errors.append("'creators' should be a non-empty list.")
428
+ else:
429
+ for creator in json_record["creators"]:
430
+ if not isinstance(creator, dict) or "name" not in creator:
431
+ errors.append(
432
+ "Each creator in 'creators' must be a dictionary with a 'name' key."
433
+ )
434
+
435
+ # Check for 'contributors'
436
+ if "contributors" in json_record:
437
+ if not isinstance(json_record["contributors"], list):
438
+ errors.append("'contributors' should be a list.")
439
+ else:
440
+ for contributor in json_record["contributors"]:
441
+ if not isinstance(contributor, dict) or "name" not in contributor:
442
+ errors.append(
443
+ "Each contributor must be a dictionary with a 'name' key."
444
+ )
445
+
446
+ # Check for 'resourceType'
447
+ if "resourceType" not in json_record["types"]:
448
+ errors.append("'resourceType' field is missing in 'types'.")
449
+ elif not isinstance(json_record["types"]["resourceType"], str):
450
+ errors.append("'resourceType' should be a string.")
451
+
452
+ # Check for 'identifiers'
453
+ if "identifiers" in json_record:
454
+ if not isinstance(json_record["identifiers"], list):
455
+ errors.append("'identifiers' should be a list.")
456
+ else:
457
+ for identifier in json_record["identifiers"]:
458
+ if (
459
+ not isinstance(identifier, dict)
460
+ or "identifier" not in identifier
461
+ or "identifierType" not in identifier
462
+ ):
463
+ errors.append(
464
+ "Each identifier must be a dictionary with 'identifier' and 'identifierType' keys."
465
+ )
466
+
467
+ # Check for 'subjects'
468
+ if "subjects" in json_record:
469
+ if not isinstance(json_record["subjects"], list):
470
+ errors.append("'subjects' should be a list.")
471
+ else:
472
+ for subject in json_record["subjects"]:
473
+ if not isinstance(subject, dict) or "subject" not in subject:
474
+ errors.append(
475
+ "Each subject must be a dictionary with a 'subject' key."
476
+ )
477
+
478
+ # Check for 'relatedIdentifiers'
479
+ if "relatedIdentifiers" in json_record:
480
+ if not isinstance(json_record["relatedIdentifiers"], list):
481
+ errors.append("'relatedIdentifiers' should be a list.")
482
+ else:
483
+ for related_id in json_record["relatedIdentifiers"]:
484
+ if (
485
+ not isinstance(related_id, dict)
486
+ or "relatedIdentifier" not in related_id
487
+ ):
488
+ errors.append(
489
+ "Each relatedIdentifier must be a dictionary with a 'relatedIdentifier' key."
490
+ )
491
+
492
+ # Check for 'rightsList'
493
+ if "rightsList" in json_record:
494
+ if not isinstance(json_record["rightsList"], list):
495
+ errors.append("'rightsList' should be a list.")
496
+ else:
497
+ for rights in json_record["rightsList"]:
498
+ if not isinstance(rights, dict) or "rights" not in rights:
499
+ errors.append(
500
+ "Each entry in 'rightsList' must be a dictionary with a 'rights' key."
501
+ )
502
+
503
+ # Check for 'geoLocations'
504
+ if "geoLocations" in json_record:
505
+ if not isinstance(json_record["geoLocations"], list):
506
+ errors.append("'geoLocations' should be a list.")
507
+ else:
508
+ for location in json_record["geoLocations"]:
509
+ if not isinstance(location, dict):
510
+ errors.append("Each entry in 'geoLocations' must be a dictionary.")
511
+ elif (
512
+ "geoLocationPoint" not in location
513
+ and "geoLocationBox" not in location
514
+ and "geoLocationPlace" not in location
515
+ ):
516
+ errors.append(
517
+ "Each geoLocation entry must contain at least one of 'geoLocationPoint', 'geoLocationBox', or 'geoLocationPlace'."
518
+ )
519
+
520
+ # Check for 'fundingReferences'
521
+ if "fundingReferences" in json_record:
522
+ if not isinstance(json_record["fundingReferences"], list):
523
+ errors.append("'fundingReferences' should be a list.")
524
+ else:
525
+ for funding in json_record["fundingReferences"]:
526
+ if not isinstance(funding, dict):
527
+ errors.append("Each funding reference must be a dictionary.")
528
+ if "funderName" not in funding:
529
+ errors.append("Each funding reference must contain 'funderName'.")
530
+
531
+ # Return errors if any are found
532
+ if errors:
533
+ raise ValueError(f"Validation errors in metadata: {', '.join(errors)}")
534
+
535
+
389
536
  if __name__ == "__main__":
390
537
  # Read in from file for demo purposes
391
538
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: caltechdata-api
3
- Version: 1.7.1
3
+ Version: 1.8.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, Bhattarai Rohan , Won Elizabeth
File without changes