1111from typing import Optional
1212from typing import Set
1313from typing import cast
14+ from typing import overload
1415from urllib .parse import urlencode
1516
1617# Third party imports
2122from fitbit_client .exceptions import ERROR_TYPE_EXCEPTIONS
2223from fitbit_client .exceptions import FitbitAPIException
2324from fitbit_client .exceptions import STATUS_CODE_EXCEPTIONS
25+ from fitbit_client .utils .curl_debug_mixin import CurlDebugMixin
26+ from fitbit_client .utils .types import FormDataDict
27+ from fitbit_client .utils .types import JSONDict
28+ from fitbit_client .utils .types import JSONList
2429from fitbit_client .utils .types import JSONType
30+ from fitbit_client .utils .types import ParamDict
2531
2632# Constants for important fields to track in logging
2733IMPORTANT_RESPONSE_FIELDS : Set [str ] = {
4147}
4248
4349
44- class BaseResource :
50+ class BaseResource ( CurlDebugMixin ) :
4551 """Provides foundational functionality for all Fitbit API resource classes.
4652
4753 The BaseResource class implements core functionality that all specific resource
@@ -61,7 +67,7 @@ class BaseResource:
6167 - Request handling with comprehensive error management
6268 - Response parsing with type safety
6369 - Detailed logging of requests, responses, and errors
64- - Debug capabilities for API troubleshooting
70+ - Debug capabilities for API troubleshooting (via CurlDebugMixin)
6571 - OAuth2 authentication management
6672
6773 Note:
@@ -134,7 +140,7 @@ def _build_url(
134140 return f"{ self .API_BASE } /{ api_version } /user/{ user_id } /{ endpoint } "
135141 return f"{ self .API_BASE } /{ api_version } /{ endpoint } "
136142
137- def _extract_important_fields (self , data : Dict [ str , JSONType ] ) -> Dict [str , int | str ]:
143+ def _extract_important_fields (self , data : JSONDict ) -> Dict [str , JSONType ]:
138144 """
139145 Extract important fields from response data for logging.
140146
@@ -150,7 +156,7 @@ def _extract_important_fields(self, data: Dict[str, JSONType]) -> Dict[str, int
150156 """
151157 extracted = {}
152158
153- def extract_recursive (d : Dict [ str , Any ] , prefix : str = "" ) -> None :
159+ def extract_recursive (d : JSONDict , prefix : str = "" ) -> None :
154160 for key , value in d .items ():
155161 full_key = f"{ prefix } .{ key } " if prefix else key
156162
@@ -189,69 +195,6 @@ def _get_calling_method(self) -> str:
189195 frame = frame .f_back
190196 return "unknown"
191197
192- def _build_curl_command (
193- self ,
194- url : str ,
195- http_method : str ,
196- data : Optional [Dict [str , Any ]] = None ,
197- json : Optional [Dict [str , Any ]] = None ,
198- params : Optional [Dict [str , Any ]] = None ,
199- ) -> str :
200- """
201- Build a curl command string for debugging API requests.
202-
203- Args:
204- url: Full API URL
205- http_method: HTTP method (GET, POST, DELETE)
206- data: Optional form data for POST requests
207- json: Optional JSON data for POST requests
208- params: Optional query parameters for GET requests
209-
210- Returns:
211- Complete curl command as a multi-line string
212-
213- The generated command includes:
214- - The HTTP method (for non-GET requests)
215- - Authorization header with OAuth token
216- - Request body (if data or json is provided)
217- - Query parameters (if provided)
218-
219- The command is formatted with line continuations for readability and
220- can be copied directly into a terminal for testing.
221-
222- Example output:
223- curl \\
224- -X POST \\
225- -H "Authorization: Bearer <token>" \\
226- -H "Content-Type: application/json" \\
227- -d '{"name": "value"}' \\
228- 'https://api.fitbit.com/1/user/-/foods/log.json'
229- """
230- # Start with base command
231- cmd_parts = ["curl -v" ]
232-
233- # Add method
234- if http_method != "GET" :
235- cmd_parts .append (f"-X { http_method } " )
236-
237- # Add auth header
238- cmd_parts .append (f'-H "Authorization: Bearer { self .oauth .token ["access_token" ]} "' )
239-
240- # Add data if present
241- if json :
242- cmd_parts .append (f"-d '{ dumps (json )} '" )
243- cmd_parts .append ('-H "Content-Type: application/json"' )
244- elif data :
245- cmd_parts .append (f"-d '{ urlencode (data )} '" )
246- cmd_parts .append ('-H "Content-Type: application/x-www-form-urlencoded"' )
247-
248- # Add URL with parameters if present
249- if params :
250- url = f"{ url } ?{ urlencode (params )} "
251- cmd_parts .append (f"'{ url } '" )
252-
253- return " \\ \n " .join (cmd_parts )
254-
255198 def _log_response (
256199 self , calling_method : str , endpoint : str , response : Response , content : Optional [Dict ] = None
257200 ) -> None :
@@ -391,10 +334,10 @@ def _handle_error_response(self, response: Response) -> None:
391334 def _make_request (
392335 self ,
393336 endpoint : str ,
394- data : Optional [Dict [ str , Any ] ] = None ,
395- json : Optional [Dict [ str , Any ] ] = None ,
396- params : Optional [Dict [ str , Any ] ] = None ,
397- headers : Dict [str , Any ] = {},
337+ data : Optional [FormDataDict ] = None ,
338+ json : Optional [JSONDict ] = None ,
339+ params : Optional [ParamDict ] = None ,
340+ headers : Dict [str , str ] = {},
398341 user_id : str = "-" ,
399342 requires_user_id : bool = True ,
400343 http_method : str = "GET" ,
@@ -421,7 +364,7 @@ def _make_request(
421364
422365 Returns:
423366 JSONType: The API response in one of these formats:
424- - Dict[str, Any] : For most JSON object responses
367+ - JSONDict : For most JSON object responses
425368 - List[Any]: For endpoints that return JSON arrays
426369 - str: For XML/TCX responses
427370 - None: For successful DELETE operations or debug mode
0 commit comments