Skip to content

Commit 00d15ef

Browse files
committed
Refactor HTTP error handling in PlantDBClient.
Centralized HTTP error handling with `_handle_http_errors` method to improve code readability and consistency. Updated all relevant methods to use the new error handling approach and enhanced metadata validation for `create_file` method.
1 parent 7e5caca commit 00d15ef

File tree

1 file changed

+105
-48
lines changed

1 file changed

+105
-48
lines changed

src/client/plantdb/client/plantdb_client.py

+105-48
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def get_mime_type(extension):
4646
Parameters
4747
----------
4848
extension : str
49-
File extension (with or without leading dot)
49+
File extension (with or without a leading dot)
5050
5151
Returns
5252
-------
@@ -67,12 +67,74 @@ def get_mime_type(extension):
6767

6868

6969
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+
"""
71105

72106
def __init__(self, base_url):
107+
"""Initialize the PlantDBClient with a base URL."""
73108
self.base_url = base_url
74109
self.session = requests.Session()
75110

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+
76138
def list_scans(self, query=None, fuzzy=False):
77139
"""List all scans in the database.
78140
@@ -111,7 +173,9 @@ def list_scans(self, query=None, fuzzy=False):
111173
if fuzzy:
112174
params['fuzzy'] = fuzzy
113175
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)
115179
return response.json()
116180

117181
def create_scan(self, name, metadata=None):
@@ -151,7 +215,9 @@ def create_scan(self, name, metadata=None):
151215
if metadata:
152216
data['metadata'] = metadata
153217
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)
155221
return response.json()
156222

157223
def get_scan_metadata(self, scan_id, key=None):
@@ -193,7 +259,9 @@ def get_scan_metadata(self, scan_id, key=None):
193259
url = f"{self.base_url}/api/scan/{scan_id}/metadata"
194260
params = {'key': key} if key else None
195261
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)
197265
return response.json()
198266

199267
def update_scan_metadata(self, scan_id, metadata, replace=False):
@@ -237,7 +305,9 @@ def update_scan_metadata(self, scan_id, metadata, replace=False):
237305
'replace': replace
238306
}
239307
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)
241311
return response.json()
242312

243313
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):
280350
if fuzzy:
281351
params['fuzzy'] = fuzzy
282352
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)
284356
return response.json()
285357

286358
def create_fileset(self, fileset_id, scan_id, metadata=None):
@@ -327,36 +399,8 @@ def create_fileset(self, fileset_id, scan_id, metadata=None):
327399
response = self.session.post(url, json=data)
328400

329401
# 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()
360404

361405
def get_fileset_metadata(self, scan_id, fileset_id, key=None):
362406
"""Retrieve metadata for a specified fileset.
@@ -399,7 +443,9 @@ def get_fileset_metadata(self, scan_id, fileset_id, key=None):
399443
url = f"{self.base_url}/api/fileset/{scan_id}/{fileset_id}/metadata"
400444
params = {'key': key} if key else None
401445
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)
403449
return response.json()
404450

405451
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):
446492
'replace': replace
447493
}
448494
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)
450498
return response.json()
451499

452500
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):
491539
if fuzzy:
492540
params['fuzzy'] = fuzzy
493541
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)
495545
return response.json()
496546

497547
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
575625

576626
# Add metadata if provided
577627
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.")
579634

580635
# Prepare file data based on the type of file_data
581636
if isinstance(file_data, BytesIO):
@@ -596,11 +651,9 @@ def create_file(self, file_data, file_id, ext, scan_id, fileset_id, metadata=Non
596651
'file': (filename, file_handle, 'application/octet-stream')
597652
}
598653
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)
604657
return response.json()
605658

606659
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):
646699
url = f"{self.base_url}/api/file/{scan_id}/{fileset_id}/{file_id}/metadata"
647700
params = {'key': key} if key else None
648701
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)
650705
return response.json()
651706

652707
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
700755
'replace': replace
701756
}
702757
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)
704761
return response.json()

0 commit comments

Comments
 (0)