caltechdata-api 1.5.0__tar.gz → 1.6.0__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 (22) hide show
  1. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/PKG-INFO +36 -20
  2. caltechdata_api-1.6.0/README.md +78 -0
  3. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api/caltechdata_edit.py +0 -1
  4. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api/caltechdata_write.py +2 -3
  5. caltechdata_api-1.6.0/caltechdata_api/cli.py +608 -0
  6. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api/customize_schema.py +0 -2
  7. caltechdata_api-1.6.0/caltechdata_api/md_to_json.py +139 -0
  8. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api/utils.py +0 -1
  9. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api.egg-info/PKG-INFO +36 -20
  10. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api.egg-info/SOURCES.txt +3 -0
  11. caltechdata_api-1.6.0/caltechdata_api.egg-info/entry_points.txt +3 -0
  12. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api.egg-info/requires.txt +2 -0
  13. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/setup.py +14 -1
  14. caltechdata_api-1.5.0/README.md +0 -62
  15. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/LICENSE +0 -0
  16. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api/__init__.py +0 -0
  17. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api/download_file.py +0 -0
  18. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api/get_files.py +0 -0
  19. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api/get_metadata.py +0 -0
  20. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api.egg-info/dependency_links.txt +0 -0
  21. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/caltechdata_api.egg-info/top_level.txt +0 -0
  22. {caltechdata_api-1.5.0 → caltechdata_api-1.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: caltechdata_api
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: Python wrapper for CaltechDATA API.
5
5
  Home-page: https://github.com/caltechlibrary/caltechdata_api
6
6
  Author: Thomas E Morrell
@@ -18,38 +18,56 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
 
20
20
 
21
- # caltechdata_api
21
+ # CaltechDATA API Python Library
22
22
 
23
- Python library for using the CaltechDATA API
23
+ The `caltechdata_api` Python library provides a convenient interface for interacting with the CaltechDATA API. It allows users to write files, create DataCite 4 standard JSON records, edit existing records, and retrieve metadata from the CaltechDATA repository.
24
24
 
25
- - caltechdata_write write files and a DataCite 4 standard json record to CaltechDATA repository
26
- - caltechdata_edit edits records in CaltechDATA
27
- - get_metadata gets metadata from CaltechDATA records
25
+ ## Features
28
26
 
29
- Requires Python 3 (Recommended via Anaconda https://www.anaconda.com/download) with reqests library.
27
+ ### Writing and Editing Records
28
+ - `caltechdata_write`: Writes files and a DataCite 4 standard JSON record to the CaltechDATA repository.
29
+ - `caltechdata_edit`: Edits existing records in CaltechDATA.
30
+
31
+ ### Metadata Operations
32
+ - `get_metadata`: Retrieves metadata from CaltechDATA records.
33
+
34
+ ## Requirements
35
+
36
+ - Python 3 (Anaconda is recommended)
37
+ - `requests` library
38
+
39
+ ## Installation
40
+
41
+ Install the library via pip:
42
+
43
+ ```shell
44
+ pip install caltechdata_api
45
+ ```
30
46
 
31
47
  ## Examples
32
48
 
33
49
  There are some example python scripts in the GitHub repository.
34
50
 
35
- Create a record:
51
+ ###Create a record:
36
52
 
37
53
  ```shell
38
54
  python write.py example.json -fnames logo.gif
39
- pbkn6-m9y63
55
+ # Output: pbkn6-m9y63 (unique identifier)
40
56
  ```
41
- The response will be the unique identifier for the record. You can put this at
57
+ > The response will be the unique identifier for the record. You can put this at
42
58
  the end of a url to visit the record (e.g.
43
59
  https://data.caltechlibrary.dev/records/pbkn6-m9y63)
44
60
 
45
- Edit a record (make changes to the example.json file to see a change)
61
+ ###Edit a record
62
+ Make changes to the example.json file to see a change)
46
63
  ```
47
64
  python edit.py example.json -id pbkn6-m9y63
48
65
  10.33569/pbkn6-m9y63
49
66
  ```
50
- The response is the DOI for the record, which includes the unique identifier
67
+ > The response is the DOI for the record, which includes the unique identifier
51
68
  for the record in the default configuration.
52
69
 
70
+ ## Using Custom DOIs
53
71
  Some groups have worked with the library to create custom DOIs. These can be
54
72
  passed in the metadata like:
55
73
 
@@ -66,18 +84,16 @@ python edit.py example_custom.json -id m6zxz-p4j22
66
84
 
67
85
  This returns the custom DOI of the record if it is successful.
68
86
 
69
- ## Setup
70
87
 
71
- Install by typing 'pip install caltechdata_api'
88
+ ## Setup and Authentication
72
89
 
73
- ## Usage
90
+ 1. Acquire a personal access token from your CaltechDATA account (found under "Applications" at the top right of your screen).
91
+ 2. Copy the token to a file named token.bash.
92
+ 3. Load the token in the command line using source token.bash.
74
93
 
75
- You need to acquire a personal access token from your CaltechDATA account
76
- (find it at the top right of your screen under "Applications").
77
- Then copy the token to token.bash. Type `source token.bash` in
78
- the command line to load the token.
94
+ ## Note on Testing
79
95
 
80
- Only test your application on the test repository (data.caltechlibrary.dev). Testing the API on the public
96
+ Only test your application on the test repository (`data.caltechlibrary.dev`). Testing the API on the public
81
97
  repository will generate junk records that are annoying to delete.
82
98
 
83
99
 
@@ -0,0 +1,78 @@
1
+ # CaltechDATA API Python Library
2
+
3
+ The `caltechdata_api` Python library provides a convenient interface for interacting with the CaltechDATA API. It allows users to write files, create DataCite 4 standard JSON records, edit existing records, and retrieve metadata from the CaltechDATA repository.
4
+
5
+ ## Features
6
+
7
+ ### Writing and Editing Records
8
+ - `caltechdata_write`: Writes files and a DataCite 4 standard JSON record to the CaltechDATA repository.
9
+ - `caltechdata_edit`: Edits existing records in CaltechDATA.
10
+
11
+ ### Metadata Operations
12
+ - `get_metadata`: Retrieves metadata from CaltechDATA records.
13
+
14
+ ## Requirements
15
+
16
+ - Python 3 (Anaconda is recommended)
17
+ - `requests` library
18
+
19
+ ## Installation
20
+
21
+ Install the library via pip:
22
+
23
+ ```shell
24
+ pip install caltechdata_api
25
+ ```
26
+
27
+ ## Examples
28
+
29
+ There are some example python scripts in the GitHub repository.
30
+
31
+ ###Create a record:
32
+
33
+ ```shell
34
+ python write.py example.json -fnames logo.gif
35
+ # Output: pbkn6-m9y63 (unique identifier)
36
+ ```
37
+ > The response will be the unique identifier for the record. You can put this at
38
+ the end of a url to visit the record (e.g.
39
+ https://data.caltechlibrary.dev/records/pbkn6-m9y63)
40
+
41
+ ###Edit a record
42
+ Make changes to the example.json file to see a change)
43
+ ```
44
+ python edit.py example.json -id pbkn6-m9y63
45
+ 10.33569/pbkn6-m9y63
46
+ ```
47
+ > The response is the DOI for the record, which includes the unique identifier
48
+ for the record in the default configuration.
49
+
50
+ ## Using Custom DOIs
51
+ Some groups have worked with the library to create custom DOIs. These can be
52
+ passed in the metadata like:
53
+
54
+ ```shell
55
+ python write.py example_custom.json -fnames logo.gif
56
+ m6zxz-p4j22
57
+ ```
58
+
59
+ And then you can edit with
60
+ ```
61
+ python edit.py example_custom.json -id m6zxz-p4j22
62
+ 10.5281/inveniordm.1234
63
+ ```
64
+
65
+ This returns the custom DOI of the record if it is successful.
66
+
67
+
68
+ ## Setup and Authentication
69
+
70
+ 1. Acquire a personal access token from your CaltechDATA account (found under "Applications" at the top right of your screen).
71
+ 2. Copy the token to a file named token.bash.
72
+ 3. Load the token in the command line using source token.bash.
73
+
74
+ ## Note on Testing
75
+
76
+ Only test your application on the test repository (`data.caltechlibrary.dev`). Testing the API on the public
77
+ repository will generate junk records that are annoying to delete.
78
+
@@ -33,7 +33,6 @@ def caltechdata_accept(ids, token=None, production=False):
33
33
  }
34
34
 
35
35
  for idv in ids:
36
-
37
36
  result = requests.get(
38
37
  url + "/api/records/" + idv + "/draft/review", headers=headers
39
38
  )
@@ -67,9 +67,9 @@ def write_files_rdm(files, file_link, headers, f_headers, s3=None):
67
67
  def add_file_links(
68
68
  metadata, file_links, file_descriptions=[], additional_descriptions="", s3_link=None
69
69
  ):
70
- # Currently configured for OSN S3 links
70
+ # Currently configured for S3 links, assuming all are at same endpoint
71
71
  link_string = ""
72
- endpoint = "https://renc.osn.xsede.org/"
72
+ endpoint = "https://" + file_links[0].split("/")[2]
73
73
  s3 = s3fs.S3FileSystem(anon=True, client_kwargs={"endpoint_url": endpoint})
74
74
  index = 0
75
75
  for link in file_links:
@@ -102,7 +102,6 @@ def add_file_links(
102
102
 
103
103
 
104
104
  def send_to_community(review_link, data, headers, publish, community, message=None):
105
-
106
105
  if not message:
107
106
  message = "This record is submitted automatically with the CaltechDATA API"
108
107
 
@@ -0,0 +1,608 @@
1
+ import argparse
2
+ import requests
3
+ import s3fs
4
+ from caltechdata_api import caltechdata_write, caltechdata_edit
5
+ from .md_to_json import parse_readme_to_json
6
+ import json
7
+ import os
8
+ import configparser
9
+
10
+ CALTECHDATA_API = "https://data.caltech.edu/api/names?q=identifiers.identifier:{}"
11
+ ORCID_API = "https://orcid.org/"
12
+ HEADERS = {"Accept": "application/json"}
13
+
14
+ name = ""
15
+ affiliationIdentifierScheme = ""
16
+ affiliation_identifier = ""
17
+
18
+ awardNumber = ""
19
+ awardTitle = ""
20
+ funderIdentifier = ""
21
+ funderIdentifierType = ""
22
+ funderName = ""
23
+
24
+
25
+ CONFIG_FILE = "caltechdata_config.ini"
26
+
27
+
28
+ def get_or_set_token():
29
+ config = configparser.ConfigParser()
30
+
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"]
35
+ else:
36
+ while True:
37
+ token = get_user_input("Enter your CaltechDATA token: ")
38
+ confirm_token = get_user_input("Confirm your CaltechDATA token: ")
39
+ 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)
44
+ return token
45
+ else:
46
+ print("Tokens do not match. Please try again.")
47
+
48
+
49
+ def welcome_message():
50
+ print("Welcome to CaltechDATA CLI")
51
+
52
+
53
+ def get_user_input(prompt, required=True):
54
+ while True:
55
+ user_input = input(prompt)
56
+ if required and not user_input:
57
+ print("This field is required. Please provide a value.")
58
+ else:
59
+ return user_input
60
+
61
+
62
+ def confirm_upload():
63
+ while True:
64
+ user_input = input("Do you want to send this record to CaltechDATA? (y/n): ")
65
+ if user_input.lower() == "y":
66
+ return True
67
+ elif user_input.lower() == "n":
68
+ print("Upload canceled.")
69
+ return False
70
+ else:
71
+ print("Invalid input. Please enter 'y' or 'n'.")
72
+
73
+
74
+ def check_award_number(award_number):
75
+ response = requests.get(
76
+ f"https://data.caltech.edu/api/awards?q=number:{award_number}"
77
+ )
78
+ data = response.json()
79
+ total_hits = data.get("hits", {}).get("total", 0)
80
+ return total_hits > 0
81
+
82
+
83
+ def get_funding_entries():
84
+ while True:
85
+ try:
86
+ num_entries = int(
87
+ input("How many funding entries do you want to provide? ")
88
+ )
89
+ if num_entries >= 0:
90
+ return num_entries
91
+ else:
92
+ print("Please enter a non-negative integer.")
93
+ except ValueError:
94
+ print("Please enter a valid integer.")
95
+
96
+
97
+ def validate_funder_identifier(funder_identifier):
98
+ response = requests.get(f"https://api.ror.org/organizations/{funder_identifier}")
99
+ if response.status_code == 200:
100
+ return True
101
+ else:
102
+ return False
103
+
104
+
105
+ def get_funding_details():
106
+ award_number = get_user_input("Enter the award number for funding: ")
107
+ award_exists = check_award_number(award_number)
108
+ if not award_exists:
109
+ print(
110
+ f"""Error: No award with number '{award_number}' found in
111
+ CaltechDATA. You will need to provide more details about the
112
+ funding."""
113
+ )
114
+ award_title = get_user_input("Enter the award title for funding: ")
115
+ while True:
116
+ funder_identifier = get_user_input("Enter the funder ROR (https://ror.org): ")
117
+ if validate_funder_identifier(funder_identifier):
118
+ break
119
+ else:
120
+ print(
121
+ """This funder identifier is not a ROR. Please enter a valid
122
+ ROR identifier (without the url). For example the ROR for the
123
+ NSF is 021nxhr62."""
124
+ )
125
+ print("-" * 10)
126
+ return {
127
+ "awardNumber": award_number,
128
+ "awardTitle": award_title,
129
+ "funderIdentifier": funder_identifier,
130
+ "funderIdentifierType": "ROR",
131
+ }
132
+
133
+
134
+ def parse_arguments():
135
+ welcome_message()
136
+ args = {}
137
+ args["title"] = get_user_input("Enter the title of the dataset: ")
138
+ args["description"] = get_user_input(
139
+ "Enter the abstract or description of the dataset: "
140
+ )
141
+ print("License options:")
142
+ print("1. Creative Commons Zero Waiver (cc-zero)")
143
+ print("2. Creative Commons Attribution (cc-by)")
144
+ print("3. Creative Commons Attribution Non Commercial (cc-by-nc)")
145
+
146
+ # Prompt user to select a license
147
+ while True:
148
+ license_number = input(
149
+ "Enter the number corresponding to the desired license: "
150
+ )
151
+ if license_number.isdigit() and 1 <= int(license_number) <= 8:
152
+ # Valid license number selected
153
+ args["license"] = {
154
+ "1": "cc0-1.0",
155
+ "2": "cc-by-4.0",
156
+ "3": "cc-by-nc-4.0",
157
+ }[license_number]
158
+ break
159
+ else:
160
+ print("Invalid input. Please enter a number between 1 and 8.")
161
+
162
+ while True:
163
+ orcid = get_user_input("Enter your ORCID identifier: ")
164
+ family_name, given_name = get_names(orcid)
165
+ if family_name is not None and given_name is not None:
166
+ args["orcid"] = orcid
167
+ break # Break out of the loop if names are successfully retrieved
168
+ retry = input("Do you want to try again? (y/n): ")
169
+ if retry.lower() != "y":
170
+ print("Exiting program.")
171
+ return
172
+ # Optional arguments
173
+ num_funding_entries = get_funding_entries()
174
+ funding_references = []
175
+ for _ in range(num_funding_entries):
176
+ funding_references.append(get_funding_details())
177
+ args["fundingReferences"] = funding_references
178
+ return args
179
+
180
+
181
+ def query_caltechdata_api(orcid):
182
+ response = requests.get(CALTECHDATA_API.format(orcid), headers=HEADERS)
183
+ return response.json()
184
+
185
+
186
+ def query_orcid_api(orcid):
187
+ response = requests.get(ORCID_API + orcid, headers=HEADERS)
188
+ return response.json()
189
+
190
+
191
+ def get_names(orcid):
192
+ caltechdata_response = query_caltechdata_api(orcid)
193
+ global affiliationIdentifierScheme, affiliation_identifier, name
194
+ if caltechdata_response.get("hits", {}).get("hits"):
195
+ hit = caltechdata_response["hits"]["hits"][0]
196
+ family_name = hit.get("family_name", "")
197
+ given_name = hit.get("given_name", "")
198
+ affiliation_identifier = "05dxps055"
199
+ affiliationIdentifierScheme = "ROR"
200
+ name = "California Institute of Technology"
201
+
202
+ else:
203
+ orcid_link = "https://orcid.org/"
204
+ headers = {"Accept": "application/json"}
205
+ orcid_response = requests.get(orcid_link + orcid, headers=headers)
206
+ try:
207
+ orcid_data = orcid_response.json()
208
+ name_info = orcid_data.get("person", {}).get("name", {})
209
+ family_name = name_info.get("family-name", {}).get("value", "")
210
+ given_name = name_info.get("given-names", {}).get("value", "")
211
+ except json.decoder.JSONDecodeError:
212
+ print(
213
+ f"Error: ORCID identifier not found or invalid. Please check the ORCID identifier and try again."
214
+ )
215
+ return None, None
216
+ return family_name, given_name
217
+
218
+
219
+ def upload_supporting_file(record_id=None):
220
+ filepath = ""
221
+ file_link = ""
222
+ while True:
223
+ choice = get_user_input(
224
+ "Do you want to upload or link data files? (upload/link/n): "
225
+ ).lower()
226
+ if choice == "link":
227
+ endpoint = "https://sdsc.osn.xsede.org/"
228
+ path = "ini230004-bucket01/"
229
+
230
+ if not record_id:
231
+ record_id = get_user_input("Folder where OSN files are uploaded")
232
+
233
+ s3 = s3fs.S3FileSystem(anon=True, client_kwargs={"endpoint_url": endpoint})
234
+ # Find the files
235
+ files = s3.glob(path + record_id + "/*")
236
+
237
+ file_links = []
238
+
239
+ for link in files:
240
+ fname = link.split("/")[-1]
241
+ if "." not in fname:
242
+ # If there is a directory, get files
243
+ folder_files = s3.glob(link + "/*")
244
+ for file in folder_files:
245
+ name = file.split("/")[-1]
246
+ if "." not in name:
247
+ level_2_files = s3.glob(file + "/*")
248
+ for f in level_2_files:
249
+ name = f.split("/")[-1]
250
+ if "." not in name:
251
+ level_3_files = s3.glob(f + "/*")
252
+ for l3 in level_3_files:
253
+ file_links.append(endpoint + l3)
254
+ else:
255
+ file_links.append(endpoint + f)
256
+ else:
257
+ file_links.append(endpoint + file)
258
+ else:
259
+ file_links.append(endpoint + link)
260
+ return filepath, file_links
261
+ elif choice == "upload":
262
+ print("Current files in the directory:")
263
+ files = [
264
+ f for f in os.listdir() if not f.endswith(".json") and os.path.isfile(f)
265
+ ]
266
+ 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
278
+ else:
279
+ print("Link is required for files larger than 1GB.")
280
+ continue
281
+ 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
+ )
288
+ elif choice == "n":
289
+ break
290
+ else:
291
+ print("Invalid input. Please enter 'link' or 'upload' or 'n'.")
292
+
293
+ return filepath, file_link
294
+
295
+
296
+ def upload_data_from_file():
297
+ while True:
298
+ print("Current JSON files in the directory:")
299
+ files = [f for f in os.listdir() if f.endswith(".json") and os.path.isfile(f)]
300
+ print("\n".join(files))
301
+
302
+ filename = get_user_input(
303
+ "Enter a README.md or JSON filename to upload to CaltechDATA (or type 'exit' to go back): "
304
+ )
305
+
306
+ if filename.lower() == "exit":
307
+ return None
308
+
309
+ if filename == "README.md":
310
+ data = parse_readme_to_json(filename)
311
+ return data
312
+ else:
313
+ try:
314
+ with open(filename, "r") as file:
315
+ data = json.load(file)
316
+ return data
317
+
318
+ except json.JSONDecodeError as e:
319
+ print(f"Error: Invalid JSON format in the file '{filename}'. {str(e)}")
320
+
321
+
322
+ def main():
323
+ choice = get_user_input(
324
+ "Do you want to create or edit a CaltechDATA record? (create/edit): "
325
+ ).lower()
326
+ if choice == "create":
327
+ create_record()
328
+ elif choice == "edit":
329
+ edit_record()
330
+ else:
331
+ print("Invalid choice. Please enter 'create' or 'edit'.")
332
+
333
+
334
+ def create_record():
335
+ token = get_or_set_token()
336
+ print("Using CaltechDATA token:", token)
337
+ while True:
338
+ choice = get_user_input(
339
+ "Do you want to use metadata from an existing file or create new metadata? (existing/create): "
340
+ ).lower()
341
+ if choice == "existing":
342
+ existing_data = upload_data_from_file()
343
+ filepath, file_link = upload_supporting_file()
344
+ if existing_data:
345
+ if filepath != "":
346
+ response = caltechdata_write(
347
+ existing_data, token, filepath, production=False, publish=False
348
+ )
349
+ elif file_link != "":
350
+ response = caltechdata_write(
351
+ existing_data,
352
+ token,
353
+ file_links=[file_link],
354
+ s3_link=file_link,
355
+ production=False,
356
+ publish=False,
357
+ )
358
+ else:
359
+ response = caltechdata_write(
360
+ existing_data, token, production=False, publish=False
361
+ )
362
+ rec_id = response
363
+ print(
364
+ f"You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}"
365
+ )
366
+ break
367
+ else:
368
+ print("Going back to the main menu.")
369
+ elif choice == "create":
370
+ args = parse_arguments()
371
+ family_name, given_name = get_names(args["orcid"])
372
+ metadata = {
373
+ "titles": [{"title": args["title"]}],
374
+ "descriptions": [
375
+ {"description": args["description"], "descriptionType": "Abstract"}
376
+ ],
377
+ "creators": [
378
+ {
379
+ "affiliation": [
380
+ {
381
+ "affiliationIdentifier": affiliation_identifier,
382
+ "affiliationIdentifierScheme": affiliationIdentifierScheme,
383
+ "name": name,
384
+ }
385
+ ],
386
+ "familyName": family_name,
387
+ "givenName": given_name,
388
+ "name": f"{family_name}, {given_name}",
389
+ "nameIdentifiers": [
390
+ {
391
+ "nameIdentifier": args["orcid"],
392
+ "nameIdentifierScheme": "ORCID",
393
+ }
394
+ ],
395
+ "nameType": "Personal",
396
+ }
397
+ ],
398
+ "types": {"resourceType": "", "resourceTypeGeneral": "Dataset"},
399
+ "rightsList": [
400
+ {
401
+ "rightsIdentifier": args["license"],
402
+ }
403
+ ],
404
+ "fundingReferences": args["fundingReferences"],
405
+ "schemaVersion": "http://datacite.org/schema/kernel-4",
406
+ }
407
+ filepath, file_link = upload_supporting_file()
408
+ if confirm_upload():
409
+ if filepath != "":
410
+ response = caltechdata_write(
411
+ metadata, token, filepath, production=False, publish=False
412
+ )
413
+ elif file_link != "":
414
+ response = caltechdata_write(
415
+ metadata,
416
+ token,
417
+ file_links=[file_link],
418
+ production=False,
419
+ publish=False,
420
+ )
421
+ else:
422
+ response = caltechdata_write(
423
+ metadata, token, production=False, publish=False
424
+ )
425
+ rec_id = response
426
+ print(
427
+ f"You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}"
428
+ )
429
+ with open(response + ".json", "w") as file:
430
+ json.dump(metadata, file, indent=2)
431
+ break
432
+ else:
433
+ break
434
+ else:
435
+ print("Invalid choice. Please enter 'existing' or 'create'.")
436
+
437
+
438
+ def edit_record():
439
+ record_id = input("Enter the CaltechDATA record ID: ")
440
+ token = get_or_set_token()
441
+ file_name = download_file_by_id(record_id, token)
442
+ if file_name:
443
+ try:
444
+ # Read the edited metadata file
445
+ with open(file_name, "r") as file:
446
+ metadata = json.load(file)
447
+ response = caltechdata_edit(
448
+ record_id, metadata, token, production=False, publish=False
449
+ )
450
+ if response:
451
+ print("Metadata edited successfully.")
452
+ else:
453
+ print("Failed to edit metadata.")
454
+ except Exception as e:
455
+ print(f"An error occurred during metadata editing: {e}")
456
+ else:
457
+ print("No metadata file found.")
458
+ choice = get_user_input("Do you want to add files? (y/n): ").lower()
459
+ if choice == "y":
460
+ filepath, file_link = upload_supporting_file(record_id)
461
+ print(file_link)
462
+ if filepath != "":
463
+ response = caltechdata_edit(
464
+ record_id, token=token, files=filepath, production=False, publish=False
465
+ )
466
+ elif file_link != "":
467
+ response = caltechdata_edit(
468
+ record_id,
469
+ metadata,
470
+ token=token,
471
+ file_links=file_link,
472
+ production=False,
473
+ publish=False,
474
+ )
475
+ rec_id = response
476
+ print(
477
+ f"You can view and publish this record at https://data.caltechlibrary.dev/uploads/{rec_id}"
478
+ )
479
+
480
+
481
+ def download_file_by_id(record_id, token=None):
482
+ url = f"https://data.caltechlibrary.dev/api/records/{record_id}"
483
+
484
+ headers = {
485
+ "accept": "application/vnd.datacite.datacite+json",
486
+ }
487
+
488
+ if token:
489
+ headers["Authorization"] = "Bearer %s" % token
490
+
491
+ try:
492
+ response = requests.get(url, headers=headers)
493
+ if response.status_code != 200:
494
+ # Might have a draft
495
+ response = requests.get(
496
+ url + "/draft",
497
+ headers=headers,
498
+ )
499
+ if response.status_code != 200:
500
+ raise Exception(f"Record {record_id} does not exist, cannot edit")
501
+ file_content = response.content
502
+ file_name = f"downloaded_data_{record_id}.json"
503
+ with open(file_name, "wb") as file:
504
+ file.write(file_content)
505
+ print(f"Metadata downloaded successfully: {file_name}")
506
+ with open(file_name, "r") as file:
507
+ metadata = json.load(file)
508
+ while True:
509
+ print("Fields:")
510
+ for i, field in enumerate(metadata.keys()):
511
+ print(f"{i + 1}. {field}")
512
+
513
+ field_choice = int(
514
+ input(
515
+ "Enter the number of the field you want to edit (or 0 to skip, 'exit' to exit): "
516
+ )
517
+ )
518
+
519
+ if field_choice == 0:
520
+ break
521
+
522
+ selected_field = list(metadata.keys())[field_choice - 1]
523
+
524
+ if isinstance(metadata[selected_field], list):
525
+ while True:
526
+ print(f"Items in {selected_field}:")
527
+ for i, item in enumerate(metadata[selected_field]):
528
+ print(f"{i + 1}. {item}")
529
+
530
+ item_choice = int(
531
+ input(
532
+ "Enter the number of the item you want to edit (or 0 to go back): "
533
+ )
534
+ )
535
+
536
+ if item_choice == 0:
537
+ break
538
+
539
+ selected_item = metadata[selected_field][item_choice - 1]
540
+
541
+ while True:
542
+ print(f"Subfields for {selected_field}:")
543
+ for i, subfield in enumerate(selected_item.keys()):
544
+ print(f"{i + 1}. {subfield}")
545
+
546
+ subfield_choice = int(
547
+ input(
548
+ "Enter the number of the subfield you want to edit (or 0 to go back): "
549
+ )
550
+ )
551
+
552
+ if subfield_choice == 0:
553
+ break
554
+
555
+ selected_subfield = list(selected_item.keys())[
556
+ subfield_choice - 1
557
+ ]
558
+
559
+ new_value = input(
560
+ f"Enter the new value for {selected_subfield}: "
561
+ )
562
+
563
+ metadata[selected_field][item_choice - 1][
564
+ selected_subfield
565
+ ] = new_value
566
+
567
+ with open(file_name, "w") as file:
568
+ json.dump(metadata, file, indent=2)
569
+
570
+ print(f"File updated successfully.")
571
+
572
+ else:
573
+ while True:
574
+ print(f"Subfields for {selected_field}:")
575
+ for i, subfield in enumerate(metadata[selected_field].keys()):
576
+ print(f"{i + 1}. {subfield}")
577
+
578
+ subfield_choice = int(
579
+ input(
580
+ "Enter the number of the subfield you want to edit (or 0 to go back): "
581
+ )
582
+ )
583
+
584
+ if subfield_choice == 0:
585
+ break
586
+
587
+ selected_subfield = list(metadata[selected_field].keys())[
588
+ subfield_choice - 1
589
+ ]
590
+
591
+ new_value = input(
592
+ f"Enter the new value for {selected_subfield}: "
593
+ )
594
+
595
+ metadata[selected_field][selected_subfield] = new_value
596
+
597
+ with open(file_name, "w") as file:
598
+ json.dump(metadata, file, indent=2)
599
+
600
+ print(f"File updated successfully.")
601
+
602
+ except Exception as e:
603
+ print(f"An error occurred: {e}")
604
+ return file_name
605
+
606
+
607
+ if __name__ == "__main__":
608
+ main()
@@ -57,7 +57,6 @@ def get_vocabularies():
57
57
 
58
58
 
59
59
  def customize_schema(json_record, schema="43"):
60
-
61
60
  if schema == "43":
62
61
  return customize_schema_rdm(json_record)
63
62
  else:
@@ -134,7 +133,6 @@ def rdm_creators_contributors(person_list, peopleroles):
134
133
 
135
134
 
136
135
  def customize_schema_rdm(json_record):
137
-
138
136
  # Get vocabularies used in InvenioRDM
139
137
  vocabularies = get_vocabularies()
140
138
 
@@ -0,0 +1,139 @@
1
+ import re
2
+ import json
3
+ import requests
4
+
5
+
6
+ class ReadmeFormatException(Exception):
7
+ """Custom exception for errors in the README format."""
8
+
9
+
10
+ def camel_case(s):
11
+ """Converts a string to camelCase."""
12
+ s = re.sub(r"(\s|_|-)+", " ", s).title().replace(" ", "")
13
+ return s[0].lower() + s[1:] if s else ""
14
+
15
+
16
+ def expand_special_keys(key, value):
17
+ """Expand special keys into their structured format (affiliation, nameIdentifiers)."""
18
+ if key == "affiliation":
19
+ if "ror.org" not in value:
20
+ raise ValueError("Affiliation Identifier is not a ROR")
21
+ ror = value.split("ror.org/")[1].split("]")[0]
22
+ response = requests.get(f"https://api.ror.org/organizations/{ror}").json()
23
+ return [
24
+ {
25
+ "affiliationIdentifier": ror,
26
+ "affiliationIdentifierScheme": "ROR",
27
+ "name": response["name"],
28
+ }
29
+ ]
30
+ elif key == "nameIdentifiers":
31
+ orcid = value.split("orcid.org/")[1].split("]")[0]
32
+ return [
33
+ {
34
+ "nameIdentifier": orcid,
35
+ "nameIdentifierScheme": "ORCID",
36
+ }
37
+ ]
38
+ return value
39
+
40
+
41
+ def parse_readme_to_json(readme_path):
42
+ try:
43
+ with open(readme_path, "r") as file:
44
+ lines = file.read().split("\n")
45
+ except IOError as e:
46
+ raise ReadmeFormatException(f"Failed to open or read the file: {e}")
47
+
48
+ json_data = {}
49
+ current_section = None
50
+ current_object = {}
51
+
52
+ title_line = lines.pop(0)
53
+ if title_line.startswith("#") == False:
54
+ raise ValueError('README.md needs to start with "# Title"')
55
+ else:
56
+ json_data["titles"] = [{"title": title_line.replace("# ", "")}]
57
+
58
+ section_pattern = re.compile(r"^##\s+(.*)$")
59
+ key_value_pattern = re.compile(r"^-\s+(.*?):\s+(.*)$")
60
+ link_pattern = re.compile(r"\[.*?\]\((.*?)\)")
61
+
62
+ for line_number, line in enumerate(lines, 1):
63
+ if not line.strip():
64
+ if current_object and current_section:
65
+ if current_section == "types":
66
+ json_data[current_section] = current_object
67
+ elif len(current_object) == 1:
68
+ key, value = next(iter(current_object.items()))
69
+ if key in ["language", "publicationYear", "publisher", "version"]:
70
+ json_data[current_section] = value
71
+ else:
72
+ json_data[current_section].append(current_object)
73
+ else:
74
+ json_data[current_section].append(current_object)
75
+ current_object = {}
76
+ continue
77
+
78
+ section_match = section_pattern.match(line)
79
+ if section_match:
80
+ if current_section and current_object:
81
+ if current_section == "types":
82
+ json_data[current_section] = current_object
83
+ elif len(current_object) == 1:
84
+ key, value = next(iter(current_object.items()))
85
+ if key in ["language", "publicationYear", "publisher", "version"]:
86
+ json_data[current_section].append(value)
87
+ else:
88
+ json_data[current_section].append(current_object)
89
+ else:
90
+ json_data[current_section].append(current_object)
91
+ current_object = {}
92
+ current_section = camel_case(section_match.group(1))
93
+ json_data[current_section] = [] if current_section != "types" else {}
94
+ continue
95
+
96
+ key_value_match = key_value_pattern.match(line)
97
+ if key_value_match and current_section:
98
+ key, value = key_value_match.groups()
99
+ key = camel_case(key)
100
+
101
+ if key in ["affiliation", "nameIdentifiers"]:
102
+ value = expand_special_keys(key, value)
103
+ else:
104
+ link_match = link_pattern.search(value)
105
+ if link_match:
106
+ value = link_match.group(1)
107
+
108
+ current_object[key] = value
109
+
110
+ elif line.strip() and not section_match:
111
+ raise ReadmeFormatException(
112
+ f"Incorrect format detected at line {line_number}: {line}"
113
+ )
114
+
115
+ if current_section and current_object:
116
+ if current_section == "types":
117
+ json_data[current_section] = current_object
118
+ elif len(current_object) == 1:
119
+ key, value = next(iter(current_object.items()))
120
+ if key in ["language", "publicationYear", "publisher", "version"]:
121
+ json_data[current_section].append(value)
122
+ else:
123
+ json_data[current_section].append(current_object)
124
+ else:
125
+ json_data[current_section].append(current_object)
126
+
127
+ return json_data
128
+
129
+
130
+ if __name__ == "__main__":
131
+ readme_path = "exampleREADME.md"
132
+ try:
133
+ json_data = parse_readme_to_json(readme_path)
134
+ output_json_path = "output1.json"
135
+ with open(output_json_path, "w") as json_file:
136
+ json.dump(json_data, json_file, indent=4)
137
+ print(f"Converted JSON saved to {output_json_path}")
138
+ except ReadmeFormatException as e:
139
+ print(f"Error parsing README file: {e}")
@@ -54,7 +54,6 @@ def humanbytes(num: Union[int, float], metric: bool = True, precision: int = 1)
54
54
 
55
55
 
56
56
  if __name__ == "__main__":
57
-
58
57
  print(humanbytes(2251799813685247)) # 2 pebibytes
59
58
  print(humanbytes(2000000000000000, True)) # 2 petabytes
60
59
  print(humanbytes(1099511627776)) # 1 tebibyte
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: caltechdata-api
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: Python wrapper for CaltechDATA API.
5
5
  Home-page: https://github.com/caltechlibrary/caltechdata_api
6
6
  Author: Thomas E Morrell
@@ -18,38 +18,56 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
 
20
20
 
21
- # caltechdata_api
21
+ # CaltechDATA API Python Library
22
22
 
23
- Python library for using the CaltechDATA API
23
+ The `caltechdata_api` Python library provides a convenient interface for interacting with the CaltechDATA API. It allows users to write files, create DataCite 4 standard JSON records, edit existing records, and retrieve metadata from the CaltechDATA repository.
24
24
 
25
- - caltechdata_write write files and a DataCite 4 standard json record to CaltechDATA repository
26
- - caltechdata_edit edits records in CaltechDATA
27
- - get_metadata gets metadata from CaltechDATA records
25
+ ## Features
28
26
 
29
- Requires Python 3 (Recommended via Anaconda https://www.anaconda.com/download) with reqests library.
27
+ ### Writing and Editing Records
28
+ - `caltechdata_write`: Writes files and a DataCite 4 standard JSON record to the CaltechDATA repository.
29
+ - `caltechdata_edit`: Edits existing records in CaltechDATA.
30
+
31
+ ### Metadata Operations
32
+ - `get_metadata`: Retrieves metadata from CaltechDATA records.
33
+
34
+ ## Requirements
35
+
36
+ - Python 3 (Anaconda is recommended)
37
+ - `requests` library
38
+
39
+ ## Installation
40
+
41
+ Install the library via pip:
42
+
43
+ ```shell
44
+ pip install caltechdata_api
45
+ ```
30
46
 
31
47
  ## Examples
32
48
 
33
49
  There are some example python scripts in the GitHub repository.
34
50
 
35
- Create a record:
51
+ ###Create a record:
36
52
 
37
53
  ```shell
38
54
  python write.py example.json -fnames logo.gif
39
- pbkn6-m9y63
55
+ # Output: pbkn6-m9y63 (unique identifier)
40
56
  ```
41
- The response will be the unique identifier for the record. You can put this at
57
+ > The response will be the unique identifier for the record. You can put this at
42
58
  the end of a url to visit the record (e.g.
43
59
  https://data.caltechlibrary.dev/records/pbkn6-m9y63)
44
60
 
45
- Edit a record (make changes to the example.json file to see a change)
61
+ ###Edit a record
62
+ Make changes to the example.json file to see a change)
46
63
  ```
47
64
  python edit.py example.json -id pbkn6-m9y63
48
65
  10.33569/pbkn6-m9y63
49
66
  ```
50
- The response is the DOI for the record, which includes the unique identifier
67
+ > The response is the DOI for the record, which includes the unique identifier
51
68
  for the record in the default configuration.
52
69
 
70
+ ## Using Custom DOIs
53
71
  Some groups have worked with the library to create custom DOIs. These can be
54
72
  passed in the metadata like:
55
73
 
@@ -66,18 +84,16 @@ python edit.py example_custom.json -id m6zxz-p4j22
66
84
 
67
85
  This returns the custom DOI of the record if it is successful.
68
86
 
69
- ## Setup
70
87
 
71
- Install by typing 'pip install caltechdata_api'
88
+ ## Setup and Authentication
72
89
 
73
- ## Usage
90
+ 1. Acquire a personal access token from your CaltechDATA account (found under "Applications" at the top right of your screen).
91
+ 2. Copy the token to a file named token.bash.
92
+ 3. Load the token in the command line using source token.bash.
74
93
 
75
- You need to acquire a personal access token from your CaltechDATA account
76
- (find it at the top right of your screen under "Applications").
77
- Then copy the token to token.bash. Type `source token.bash` in
78
- the command line to load the token.
94
+ ## Note on Testing
79
95
 
80
- Only test your application on the test repository (data.caltechlibrary.dev). Testing the API on the public
96
+ Only test your application on the test repository (`data.caltechlibrary.dev`). Testing the API on the public
81
97
  repository will generate junk records that are annoying to delete.
82
98
 
83
99
 
@@ -4,13 +4,16 @@ setup.py
4
4
  caltechdata_api/__init__.py
5
5
  caltechdata_api/caltechdata_edit.py
6
6
  caltechdata_api/caltechdata_write.py
7
+ caltechdata_api/cli.py
7
8
  caltechdata_api/customize_schema.py
8
9
  caltechdata_api/download_file.py
9
10
  caltechdata_api/get_files.py
10
11
  caltechdata_api/get_metadata.py
12
+ caltechdata_api/md_to_json.py
11
13
  caltechdata_api/utils.py
12
14
  caltechdata_api.egg-info/PKG-INFO
13
15
  caltechdata_api.egg-info/SOURCES.txt
14
16
  caltechdata_api.egg-info/dependency_links.txt
17
+ caltechdata_api.egg-info/entry_points.txt
15
18
  caltechdata_api.egg-info/requires.txt
16
19
  caltechdata_api.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ caltechdata_api = caltechdata_api.cli:main
3
+
@@ -3,3 +3,5 @@ datacite>1.1.0
3
3
  tqdm>=4.62.3
4
4
  pyyaml
5
5
  s3fs
6
+ configparser
7
+ awscli
@@ -60,7 +60,15 @@ name = meta["name"]
60
60
  REQUIRES_PYTHON = ">=3.6.0"
61
61
 
62
62
  # What packages are required for this module to be executed?
63
- REQUIRED = ["requests", "datacite>1.1.0", "tqdm>=4.62.3", "pyyaml", "s3fs"]
63
+ REQUIRED = [
64
+ "requests",
65
+ "datacite>1.1.0",
66
+ "tqdm>=4.62.3",
67
+ "pyyaml",
68
+ "s3fs",
69
+ "configparser",
70
+ "awscli",
71
+ ]
64
72
 
65
73
  # What packages are optional?
66
74
  EXTRAS = {
@@ -163,4 +171,9 @@ setup(
163
171
  cmdclass={
164
172
  "upload": UploadCommand,
165
173
  },
174
+ entry_points={
175
+ "console_scripts": [
176
+ "caltechdata_api=caltechdata_api.cli:main",
177
+ ],
178
+ },
166
179
  )
@@ -1,62 +0,0 @@
1
- # caltechdata_api
2
-
3
- Python library for using the CaltechDATA API
4
-
5
- - caltechdata_write write files and a DataCite 4 standard json record to CaltechDATA repository
6
- - caltechdata_edit edits records in CaltechDATA
7
- - get_metadata gets metadata from CaltechDATA records
8
-
9
- Requires Python 3 (Recommended via Anaconda https://www.anaconda.com/download) with reqests library.
10
-
11
- ## Examples
12
-
13
- There are some example python scripts in the GitHub repository.
14
-
15
- Create a record:
16
-
17
- ```shell
18
- python write.py example.json -fnames logo.gif
19
- pbkn6-m9y63
20
- ```
21
- The response will be the unique identifier for the record. You can put this at
22
- the end of a url to visit the record (e.g.
23
- https://data.caltechlibrary.dev/records/pbkn6-m9y63)
24
-
25
- Edit a record (make changes to the example.json file to see a change)
26
- ```
27
- python edit.py example.json -id pbkn6-m9y63
28
- 10.33569/pbkn6-m9y63
29
- ```
30
- The response is the DOI for the record, which includes the unique identifier
31
- for the record in the default configuration.
32
-
33
- Some groups have worked with the library to create custom DOIs. These can be
34
- passed in the metadata like:
35
-
36
- ```shell
37
- python write.py example_custom.json -fnames logo.gif
38
- m6zxz-p4j22
39
- ```
40
-
41
- And then you can edit with
42
- ```
43
- python edit.py example_custom.json -id m6zxz-p4j22
44
- 10.5281/inveniordm.1234
45
- ```
46
-
47
- This returns the custom DOI of the record if it is successful.
48
-
49
- ## Setup
50
-
51
- Install by typing 'pip install caltechdata_api'
52
-
53
- ## Usage
54
-
55
- You need to acquire a personal access token from your CaltechDATA account
56
- (find it at the top right of your screen under "Applications").
57
- Then copy the token to token.bash. Type `source token.bash` in
58
- the command line to load the token.
59
-
60
- Only test your application on the test repository (data.caltechlibrary.dev). Testing the API on the public
61
- repository will generate junk records that are annoying to delete.
62
-
File without changes