1+ import json
2+ import logging
13from collections .abc import Callable , Mapping
24from typing import Any , Literal , TypeVar
35
1012)
1113from pydantic import BaseModel , TypeAdapter , ValidationError
1214
15+ from blueapi import __version__
1316from blueapi .config import RestConfig
1417from blueapi .service .authentication import JWTAuth , SessionManager
1518from blueapi .service .model import (
3235
3336TRACER = get_tracer ("rest" )
3437
38+ LOGGER = logging .getLogger (__name__ )
39+
3540
3641class UnauthorisedAccessError (Exception ):
3742 pass
@@ -41,6 +46,10 @@ class BlueskyRemoteControlError(Exception):
4146 pass
4247
4348
49+ class NonJsonResponseError (Exception ):
50+ pass
51+
52+
4453class BlueskyRequestError (Exception ):
4554 def __init__ (self , code : int , message : str ) -> None :
4655 super ().__init__ (message , code )
@@ -105,7 +114,7 @@ def _exception(response: requests.Response) -> Exception | None:
105114 if code < 400 :
106115 return None
107116 elif code == 404 :
108- return KeyError (str (response . json ( )))
117+ return KeyError (str (_response_json ( response )))
109118 else :
110119 return BlueskyRemoteControlError (code , str (response ))
111120
@@ -120,7 +129,7 @@ def _create_task_exceptions(response: requests.Response) -> Exception | None:
120129 return UnknownPlanError ()
121130 elif code == 422 :
122131 try :
123- content = response . json ( )
132+ content = _response_json ( response )
124133 return InvalidParametersError (
125134 TypeAdapter (list [ParameterError ]).validate_python (
126135 content .get ("detail" , [])
@@ -134,6 +143,18 @@ def _create_task_exceptions(response: requests.Response) -> Exception | None:
134143 return BlueskyRequestError (code , response .text )
135144
136145
146+ def _response_json (response : requests .Response ) -> Any :
147+ try :
148+ return response .json ()
149+ except json .decoder .JSONDecodeError as exc :
150+ LOGGER .debug (
151+ f"Invalid json response from <{ response .request .url } >: <{ response .content } >"
152+ )
153+ raise NonJsonResponseError (
154+ "Response does not contain a valid JSON object"
155+ ) from exc
156+
157+
137158class BlueapiRestClient :
138159 _config : RestConfig
139160 _session_manager : SessionManager | None
@@ -271,7 +292,20 @@ def _request_and_deserialize(
271292 raise exception
272293 if response .status_code == status .HTTP_204_NO_CONTENT :
273294 raise NoContentError (target_type )
274- deserialized = TypeAdapter (target_type ).validate_python (response .json ())
295+ if (server_version := response .headers .get ("x-blueapi-version" )) is not None :
296+ from packaging .version import Version
297+
298+ if (server_version := Version (server_version ).base_version ) != (
299+ client_version := Version (__version__ ).base_version
300+ ):
301+ LOGGER .warning (
302+ f"Version mismatch: Blueapi server version is { server_version } "
303+ f"but client version is { client_version } . "
304+ f"Some features may not work as expected."
305+ )
306+ deserialized = TypeAdapter (target_type ).validate_python (
307+ _response_json (response )
308+ )
275309 return deserialized
276310
277311
0 commit comments