@@ -46,7 +46,7 @@ def get_mime_type(extension):
46
46
Parameters
47
47
----------
48
48
extension : str
49
- File extension (with or without leading dot)
49
+ File extension (with or without a leading dot)
50
50
51
51
Returns
52
52
-------
@@ -67,12 +67,74 @@ def get_mime_type(extension):
67
67
68
68
69
69
class PlantDBClient :
70
- """Client for interacting with the PlantDB REST API."""
70
+ """Client for interacting with the PlantDB REST API.
71
+
72
+ This class provides methods to interact with a PlantDB REST API, allowing operations
73
+ on scans, filesets, and files. It handles authentication, error processing, and
74
+ provides a consistent interface for all API endpoints.
75
+
76
+ Parameters
77
+ ----------
78
+ base_url : str
79
+ The base URL of the PlantDB REST API.
80
+
81
+ Attributes
82
+ ----------
83
+ base_url : str
84
+ The base URL of the PlantDB REST API.
85
+ session : requests.Session
86
+ HTTP session that maintains cookies and connection pooling.
87
+
88
+ Notes
89
+ -----
90
+ This client automatically handles HTTP errors and extracts meaningful error messages
91
+ from the API responses. All methods will raise appropriate exceptions with
92
+ descriptive messages when API requests fail.
93
+
94
+ Examples
95
+ --------
96
+ >>> # Start a test REST API server first:
97
+ >>> # $ fsdb_rest_api --test
98
+ >>> from plantdb.client.plantdb_client import PlantDBClient
99
+ >>> from plantdb.client.rest_api import base_url
100
+ >>> client = PlantDBClient(base_url())
101
+ >>> scans = client.list_scans()
102
+ >>> print(scans)
103
+ ['virtual_plant', 'real_plant_analyzed', 'real_plant', 'virtual_plant_analyzed', 'arabidopsis000']
104
+ """
71
105
72
106
def __init__ (self , base_url ):
107
+ """Initialize the PlantDBClient with a base URL."""
73
108
self .base_url = base_url
74
109
self .session = requests .Session ()
75
110
111
+ def _handle_http_errors (self , response ):
112
+ """
113
+ Handles HTTP errors by raising a custom exception with an error message obtained
114
+ from the HTTP response. This function intercepts the original exception, extracts
115
+ the error message from the response JSON, and raises a new exception of the same
116
+ type with the extracted message.
117
+
118
+ Parameters
119
+ ----------
120
+ response : requests.Response
121
+ The HTTP response object from which the status and error message will be
122
+ assessed. The response object is expected to have a JSON body containing
123
+ a key "message" for error details.
124
+
125
+ Raises
126
+ ------
127
+ RequestException
128
+ If the HTTP response status code indicates an error. The raised exception is
129
+ of the same type as the original exception, with the message replaced by the
130
+ value of the "message" key from the response JSON body.
131
+ """
132
+ try :
133
+ response .raise_for_status ()
134
+ except RequestException as e :
135
+ response_data = response .json ()["message" ]
136
+ raise type (e )(response_data ) from e
137
+
76
138
def list_scans (self , query = None , fuzzy = False ):
77
139
"""List all scans in the database.
78
140
@@ -111,7 +173,9 @@ def list_scans(self, query=None, fuzzy=False):
111
173
if fuzzy :
112
174
params ['fuzzy' ] = fuzzy
113
175
response = self .session .get (url , params = params )
114
- response .raise_for_status ()
176
+
177
+ # Handle HTTP errors with explicit messages
178
+ self ._handle_http_errors (response )
115
179
return response .json ()
116
180
117
181
def create_scan (self , name , metadata = None ):
@@ -151,7 +215,9 @@ def create_scan(self, name, metadata=None):
151
215
if metadata :
152
216
data ['metadata' ] = metadata
153
217
response = self .session .post (url , json = data )
154
- response .raise_for_status ()
218
+
219
+ # Handle HTTP errors with explicit messages
220
+ self ._handle_http_errors (response )
155
221
return response .json ()
156
222
157
223
def get_scan_metadata (self , scan_id , key = None ):
@@ -193,7 +259,9 @@ def get_scan_metadata(self, scan_id, key=None):
193
259
url = f"{ self .base_url } /api/scan/{ scan_id } /metadata"
194
260
params = {'key' : key } if key else None
195
261
response = self .session .get (url , params = params )
196
- response .raise_for_status ()
262
+
263
+ # Handle HTTP errors with explicit messages
264
+ self ._handle_http_errors (response )
197
265
return response .json ()
198
266
199
267
def update_scan_metadata (self , scan_id , metadata , replace = False ):
@@ -237,7 +305,9 @@ def update_scan_metadata(self, scan_id, metadata, replace=False):
237
305
'replace' : replace
238
306
}
239
307
response = self .session .post (url , json = data )
240
- response .raise_for_status ()
308
+
309
+ # Handle HTTP errors with explicit messages
310
+ self ._handle_http_errors (response )
241
311
return response .json ()
242
312
243
313
def list_scan_filesets (self , scan_id , query = None , fuzzy = False ):
@@ -280,7 +350,9 @@ def list_scan_filesets(self, scan_id, query=None, fuzzy=False):
280
350
if fuzzy :
281
351
params ['fuzzy' ] = fuzzy
282
352
response = self .session .get (url , params = params )
283
- response .raise_for_status ()
353
+
354
+ # Handle HTTP errors with explicit messages
355
+ self ._handle_http_errors (response )
284
356
return response .json ()
285
357
286
358
def create_fileset (self , fileset_id , scan_id , metadata = None ):
@@ -327,36 +399,8 @@ def create_fileset(self, fileset_id, scan_id, metadata=None):
327
399
response = self .session .post (url , json = data )
328
400
329
401
# Handle HTTP errors with explicit messages
330
- try :
331
- response .raise_for_status ()
332
- except requests .exceptions .HTTPError as e :
333
- # Get the error message from the response if available
334
- error_detail = ""
335
- try :
336
- error_data = response .json ()
337
- if 'message' in error_data :
338
- error_detail = f": { error_data ['message' ]} "
339
- except :
340
- pass
341
-
342
- if response .status_code == 400 :
343
- raise ValueError (f"Invalid request data{ error_detail } " )
344
- elif response .status_code == 404 :
345
- raise ValueError (f"Resource not found{ error_detail } " )
346
- elif response .status_code == 500 :
347
- raise ValueError (f"Server error{ error_detail } " )
348
- else :
349
- # Re-raise the original exception if we can't provide a better message
350
- raise
351
-
352
- response_data = response .json ()
353
-
354
- # Check if the response contains an error message despite successful status code
355
- if response .status_code >= 200 and response .status_code < 300 :
356
- return response_data
357
- else :
358
- error_message = response_data .get ('message' , 'Unknown error' )
359
- raise ValueError (f"Failed to create fileset: { error_message } " )
402
+ self ._handle_http_errors (response )
403
+ return response .json ()
360
404
361
405
def get_fileset_metadata (self , scan_id , fileset_id , key = None ):
362
406
"""Retrieve metadata for a specified fileset.
@@ -399,7 +443,9 @@ def get_fileset_metadata(self, scan_id, fileset_id, key=None):
399
443
url = f"{ self .base_url } /api/fileset/{ scan_id } /{ fileset_id } /metadata"
400
444
params = {'key' : key } if key else None
401
445
response = self .session .get (url , params = params )
402
- response .raise_for_status ()
446
+
447
+ # Handle HTTP errors with explicit messages
448
+ self ._handle_http_errors (response )
403
449
return response .json ()
404
450
405
451
def update_fileset_metadata (self , scan_id , fileset_id , metadata , replace = False ):
@@ -446,7 +492,9 @@ def update_fileset_metadata(self, scan_id, fileset_id, metadata, replace=False):
446
492
'replace' : replace
447
493
}
448
494
response = self .session .post (url , json = data )
449
- response .raise_for_status ()
495
+
496
+ # Handle HTTP errors with explicit messages
497
+ self ._handle_http_errors (response )
450
498
return response .json ()
451
499
452
500
def list_fileset_files (self , scan_id , fileset_id , query = None , fuzzy = False ):
@@ -491,7 +539,9 @@ def list_fileset_files(self, scan_id, fileset_id, query=None, fuzzy=False):
491
539
if fuzzy :
492
540
params ['fuzzy' ] = fuzzy
493
541
response = self .session .get (url , params = params )
494
- response .raise_for_status ()
542
+
543
+ # Handle HTTP errors with explicit messages
544
+ self ._handle_http_errors (response )
495
545
return response .json ()
496
546
497
547
def create_file (self , file_data , file_id , ext , scan_id , fileset_id , metadata = None ):
@@ -575,7 +625,12 @@ def create_file(self, file_data, file_id, ext, scan_id, fileset_id, metadata=Non
575
625
576
626
# Add metadata if provided
577
627
if metadata :
578
- data ['metadata' ] = json .dumps (metadata )
628
+ if isinstance (metadata , dict ):
629
+ data ['metadata' ] = json .dumps (metadata )
630
+ elif isinstance (metadata , str ):
631
+ data ['metadata' ] = metadata
632
+ else :
633
+ raise TypeError ("Invalid metadata type. Must be a dictionary or string." )
579
634
580
635
# Prepare file data based on the type of file_data
581
636
if isinstance (file_data , BytesIO ):
@@ -596,11 +651,9 @@ def create_file(self, file_data, file_id, ext, scan_id, fileset_id, metadata=Non
596
651
'file' : (filename , file_handle , 'application/octet-stream' )
597
652
}
598
653
response = self .session .post (url , files = files , data = data )
599
- try :
600
- response .raise_for_status ()
601
- except RequestException as e :
602
- response_data = response .json ()["message" ]
603
- raise type (e )(response_data ) from e
654
+
655
+ # Handle HTTP errors with explicit messages
656
+ self ._handle_http_errors (response )
604
657
return response .json ()
605
658
606
659
def get_file_metadata (self , scan_id , fileset_id , file_id , key = None ):
@@ -646,7 +699,9 @@ def get_file_metadata(self, scan_id, fileset_id, file_id, key=None):
646
699
url = f"{ self .base_url } /api/file/{ scan_id } /{ fileset_id } /{ file_id } /metadata"
647
700
params = {'key' : key } if key else None
648
701
response = self .session .get (url , params = params )
649
- response .raise_for_status ()
702
+
703
+ # Handle HTTP errors with explicit messages
704
+ self ._handle_http_errors (response )
650
705
return response .json ()
651
706
652
707
def update_file_metadata (self , scan_id , fileset_id , file_id , metadata , replace = False ):
@@ -700,5 +755,7 @@ def update_file_metadata(self, scan_id, fileset_id, file_id, metadata, replace=F
700
755
'replace' : replace
701
756
}
702
757
response = self .session .post (url , json = data )
703
- response .raise_for_status ()
758
+
759
+ # Handle HTTP errors with explicit messages
760
+ self ._handle_http_errors (response )
704
761
return response .json ()
0 commit comments