-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdataverse_api.py
More file actions
271 lines (219 loc) · 8.61 KB
/
Copy pathdataverse_api.py
File metadata and controls
271 lines (219 loc) · 8.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
"""
Dataverse API interface for bulk updating file metadata
Handles authentication and communication with Dataverse instances
"""
import os
import sys
import json
import argparse
import requests
from typing import Dict, Any, List, Tuple
from utils import load_json_file, load_csv_file
class DataverseAPI:
"""Interface for Dataverse API operations"""
def __init__(self, server_url: str, api_token: str):
"""
Initialize Dataverse API client
Args:
server_url: URL of Dataverse instance (e.g., https://dataverse.example.org)
api_token: API token for authentication
"""
self.server_url = server_url.rstrip('/')
self.api_token = api_token
self.headers = {
"X-Dataverse-key": api_token,
"Accept": "application/json"
}
def _make_request(self, method: str, endpoint: str, data: Dict = None, json_data: Dict = None) -> Tuple[bool, Any]:
"""
Make HTTP request to Dataverse API
Args:
method: HTTP method (GET, POST, PUT, DELETE)
endpoint: API endpoint path
data: Form data for request
json_data: JSON data for request
Returns:
Tuple of (success, response_data)
"""
url = f"{self.server_url}/api/v1{endpoint}"
try:
if method == "GET":
response = requests.get(url, headers=self.headers)
elif method == "POST":
response = requests.post(url, headers=self.headers, data=data, json=json_data)
elif method == "PUT":
response = requests.put(url, headers=self.headers, data=data, json=json_data)
elif method == "DELETE":
response = requests.delete(url, headers=self.headers)
else:
return False, f"Unsupported method: {method}"
if response.status_code in [200, 201, 204]:
return True, response.json() if response.text else {}
else:
return False, f"Status {response.status_code}: {response.text}"
except Exception as e:
return False, str(e)
def get_dataset_metadata(self, dataset_id: str) -> Tuple[bool, Dict]:
"""
Get metadata for a dataset
Args:
dataset_id: Dataset PID or ID
Returns:
Tuple of (success, metadata)
"""
return self._make_request("GET", f"/datasets/{dataset_id}")
def get_file_metadata(self, file_id: str) -> Tuple[bool, Dict]:
"""
Get metadata for a specific file
Args:
file_id: File ID
Returns:
Tuple of (success, file_metadata)
"""
return self._make_request("GET", f"/files/{file_id}")
def update_file_metadata(self, file_id: str, metadata: Dict) -> Tuple[bool, Any]:
"""
Update metadata for a specific file
Args:
file_id: File ID
metadata: Dictionary with metadata to update (e.g., {"description": "new description", "directoryLabel": "path"})
Returns:
Tuple of (success, response)
"""
return self._make_request("POST", f"/files/{file_id}/metadata", data=metadata)
def get_dataset_files(self, dataset_id: str) -> Tuple[bool, List[Dict]]:
"""
Get list of files in a dataset
Args:
dataset_id: Dataset PID or ID
Returns:
Tuple of (success, list of files)
"""
success, data = self._make_request("GET", f"/datasets/{dataset_id}/versions/latest/files")
if success and "data" in data:
return True, data["data"]
return success, data
def test_connection(self) -> bool:
"""
Test connection to Dataverse server
Returns:
True if connection successful
"""
success, _ = self._make_request("GET", "/info/version")
return success
def push_updates_from_csv(csv_file: str, server_url: str, api_token: str, dry_run: bool = True) -> None:
"""
Push metadata updates from CSV file to Dataverse
Args:
csv_file: Path to CSV file with updates
server_url: Dataverse server URL
api_token: API token for authentication
dry_run: If True, show what would be updated without making changes
"""
# Initialize API
api = DataverseAPI(server_url, api_token)
# Test connection
print("Testing connection to Dataverse...")
if not api.test_connection():
print("✗ Failed to connect to Dataverse server")
sys.exit(1)
print("✓ Connected to Dataverse")
# Load CSV
try:
rows = load_csv_file(csv_file)
except Exception as e:
print(f"Error reading CSV: {str(e)}")
sys.exit(1)
# Group by DOI and file ID
updates: Dict[str, List[Dict]] = {}
for row in rows:
doi = row.get("DOI", "").strip()
file_id = row.get("file_id", "").strip()
if not doi or not file_id:
continue
if doi not in updates:
updates[doi] = []
# Determine what changed
new_desc = row.get("new_description", "").strip()
new_path = row.get("new_file_path", "").strip()
if new_desc or new_path:
updates[doi].append({
"file_id": file_id,
"file_label": row.get("file_label", ""),
"original_description": row.get("original_description", ""),
"new_description": new_desc,
"new_file_path": new_path
})
# Process updates
total_updates = 0
successful_updates = 0
for doi, file_updates in updates.items():
print(f"\n{'='*60}")
print(f"Dataset: {doi}")
print(f"Files to update: {len(file_updates)}")
for update in file_updates:
print(f"\n File ID: {update['file_id']}")
print(f" Label: {update['file_label']}")
metadata = {}
if update['new_description']:
print(f" Description: {update['original_description'][:50]}... → {update['new_description'][:50]}...")
metadata['description'] = update['new_description']
if update['new_file_path']:
print(f" Path: → {update['new_file_path']}")
metadata['directoryLabel'] = update['new_file_path']
if not dry_run and metadata:
success, response = api.update_file_metadata(update['file_id'], metadata)
if success:
print(" ✓ Updated")
successful_updates += 1
else:
print(f" ✗ Error: {response}")
total_updates += 1
# Summary
print(f"\n{'='*60}")
if dry_run:
print(f"DRY RUN: Would update {total_updates} files")
print("Use --no-dry-run to apply changes")
else:
print(f"Updated {successful_updates}/{total_updates} files successfully")
def main():
parser = argparse.ArgumentParser(
description="Push metadata updates to Dataverse"
)
parser.add_argument(
"--csv-file",
type=str,
required=True,
help="CSV file with metadata updates"
)
parser.add_argument(
"--server-url",
type=str,
default=os.getenv("DATAVERSE_SERVER_URL", ""),
help="Dataverse server URL (or set DATAVERSE_SERVER_URL env var)"
)
parser.add_argument(
"--api-token",
type=str,
default=os.getenv("DATAVERSE_API_TOKEN", ""),
help="Dataverse API token (or set DATAVERSE_API_TOKEN env var)"
)
parser.add_argument(
"--no-dry-run",
action="store_true",
help="Apply changes (by default, only shows what would be updated)"
)
args = parser.parse_args()
# Validate inputs
if not args.server_url:
print("Error: Dataverse server URL required (--server-url or DATAVERSE_SERVER_URL env var)")
sys.exit(1)
if not args.api_token:
print("Error: Dataverse API token required (--api-token or DATAVERSE_API_TOKEN env var)")
sys.exit(1)
if not os.path.exists(args.csv_file):
print(f"Error: CSV file not found: {args.csv_file}")
sys.exit(1)
push_updates_from_csv(args.csv_file, args.server_url, args.api_token, dry_run=not args.no_dry_run)
if __name__ == "__main__":
main()