|
3 | 3 | import hmac |
4 | 4 | import importlib |
5 | 5 | import json |
| 6 | +import logging |
6 | 7 | import re |
7 | 8 | import requests |
8 | 9 | import requests.adapters |
|
48 | 49 | class CustomHTTPSAdapter(requests.adapters.HTTPAdapter): |
49 | 50 | """A custom HTTPS adapter that supports SSLContext (including certificate pinning).""" |
50 | 51 |
|
51 | | - def __init__(self, ssl_context: ssl.SSLContext = None, **kwargs): |
| 52 | + def __init__(self, ssl_context: Optional[ssl.SSLContext] = None, **kwargs): |
52 | 53 | self.ssl_context = ssl_context or create_urllib3_context() |
53 | 54 | super().__init__(**kwargs) |
54 | 55 |
|
@@ -262,7 +263,9 @@ def get_signature( |
262 | 263 | ) |
263 | 264 |
|
264 | 265 |
|
265 | | -def should_retry_request(error, method: str = None, retries_left: int = None) -> bool: |
| 266 | +def should_retry_request( |
| 267 | + error, method: Optional[str] = None, retries_left: Optional[int] = None |
| 268 | +) -> bool: |
266 | 269 | """Determines whether a request should be retried based on the error. |
267 | 270 |
|
268 | 271 | :param error: The error object to check. |
@@ -295,14 +298,15 @@ def send_request( |
295 | 298 | payload: Optional[dict] = None, |
296 | 299 | body: Optional[dict] = None, |
297 | 300 | time_unit: Optional[str] = None, |
298 | | - response_model: Type[T] = None, |
| 301 | + response_model: Optional[Type[T]] = None, |
299 | 302 | is_signed: bool = False, |
300 | 303 | signer: Optional[Signers] = None, |
301 | 304 | ) -> ApiResponse[T]: |
302 | 305 | """Sends an HTTP request with the specified configuration, method, path, and |
303 | 306 | optional payload and time unit. |
304 | 307 |
|
305 | | - The `send_request` function is responsible for sending an HTTP request with the provided parameters. It handles retries, error handling, and response processing. The function takes the following parameters: |
| 308 | + The `send_request` function is responsible for sending an HTTP request with the provided parameters. |
| 309 | + It handles retries, error handling, and response processing. The function takes the following parameters: |
306 | 310 |
|
307 | 311 | - `configuration`: The configuration object containing the necessary information for sending the request. |
308 | 312 | - `method`: The HTTP method to use (e.g. "GET", "POST", etc.). |
@@ -424,17 +428,27 @@ def send_request( |
424 | 428 | or (not isinstance(parsed[0], list) if is_list else False) |
425 | 429 | ) |
426 | 430 | if (is_list and not is_flat_list) or not response_model: |
427 | | - data_function = lambda: parsed |
| 431 | + |
| 432 | + def data_function(): |
| 433 | + return parsed |
| 434 | + |
428 | 435 | elif is_oneof or is_list or hasattr(response_model, "from_dict"): |
429 | | - data_function = lambda: response_model.from_dict(parsed) |
| 436 | + |
| 437 | + def data_function(): |
| 438 | + return response_model.from_dict(parsed) |
| 439 | + |
430 | 440 | else: |
431 | | - data_function = lambda: response_model.model_validate(parsed) |
| 441 | + |
| 442 | + def data_function(): |
| 443 | + return response_model.model_validate(parsed) |
432 | 444 |
|
433 | 445 | try: |
434 | 446 | data_function() |
435 | 447 | final_data_function = data_function |
436 | 448 | except Exception: |
437 | | - final_data_function = lambda: parsed |
| 449 | + |
| 450 | + def final_data_function(): |
| 451 | + return parsed |
438 | 452 |
|
439 | 453 | return ApiResponse[T]( |
440 | 454 | data_function=final_data_function, |
@@ -558,16 +572,19 @@ def parse_proxies( |
558 | 572 | return {"https": proxy_url, "http": proxy_url} |
559 | 573 |
|
560 | 574 |
|
561 | | -def ws_streams_placeholder(stream: str, params: dict = {}) -> str: |
| 575 | +def ws_streams_placeholder(stream: str, params: Optional[dict] = None) -> str: |
562 | 576 | """Replaces placeholders in the stream string with values from the params dictionary. |
563 | 577 |
|
564 | 578 | Args: |
565 | 579 | stream (str): The stream string with placeholders. |
566 | | - params (dict): A dictionary containing values to replace the placeholders. |
| 580 | + params (Optional[dict]): A dictionary containing values to replace the placeholders. Defaults to None. |
567 | 581 |
|
568 | 582 | Returns: |
569 | 583 | str: The stream string with placeholders replaced by actual values. |
570 | 584 | """ |
| 585 | + if params is None: |
| 586 | + params = {} |
| 587 | + |
571 | 588 | params = {k: v for k, v in params.items() if v is not None} |
572 | 589 |
|
573 | 590 | normalized_variables = { |
@@ -698,17 +715,19 @@ def ws_api_payload(config, payload: Dict, websocket_options: WebsocketApiOptions |
698 | 715 |
|
699 | 716 |
|
700 | 717 | def websocket_api_signature( |
701 | | - config, payload: Optional[Dict] = {}, signer: Signers = None |
| 718 | + config, payload: Optional[Dict] = None, signer: Optional[Signers] = None |
702 | 719 | ) -> dict: |
703 | 720 | """Generate signature for websocket API |
704 | 721 |
|
705 | 722 | Args: |
706 | 723 | payload (Optional[Dict]): Payload. |
707 | | - signer (Signers): Signer for the payload. |
| 724 | + signer (Optional[Signers]): Signer for the payload. |
708 | 725 | Returns: |
709 | 726 | dict: The generated signature for the WebSocket API. |
710 | 727 | """ |
711 | 728 |
|
| 729 | + if payload is None: |
| 730 | + payload = {} |
712 | 731 | payload["apiKey"] = config.api_key |
713 | 732 | payload["timestamp"] = get_timestamp() |
714 | 733 | parameters = OrderedDict(sorted(payload.items())) |
@@ -803,5 +822,31 @@ def parse_user_event(payload: dict, response_model_cls: Type[BaseModel]) -> Base |
803 | 822 | return response_model_cls(**kwargs) |
804 | 823 |
|
805 | 824 | except Exception as e: |
806 | | - print(f"Failed to parse {event_name}: {e}") |
| 825 | + logging.warning(f"Failed to parse {event_name}: {e}") |
807 | 826 | return response_model_cls(actual_instance=payload) |
| 827 | + |
| 828 | + |
| 829 | +def redact_sensitive_info(config: dict) -> dict: |
| 830 | + """Redacts sensitive information from the configuration for logging purposes. |
| 831 | +
|
| 832 | + Args: |
| 833 | + config (Union[ConfigurationWebSocketAPI, ConfigurationRestAPI]): The configuration object to redact. |
| 834 | + Returns: |
| 835 | + dict: A dictionary representation of the configuration with sensitive information redacted. |
| 836 | + """ |
| 837 | + redacted_config = copy.deepcopy(config) |
| 838 | + SENSITIVE_KEYS = ["apiKey", "signature"] |
| 839 | + |
| 840 | + if isinstance(redacted_config, dict) or isinstance(redacted_config, OrderedDict): |
| 841 | + for key, value in redacted_config.items(): |
| 842 | + if key in SENSITIVE_KEYS: |
| 843 | + redacted_config[key] = "[REDACTED]" |
| 844 | + else: |
| 845 | + if isinstance(value, (dict, OrderedDict, list)): |
| 846 | + redacted_config[key] = redact_sensitive_info(value) |
| 847 | + elif isinstance(redacted_config, list): |
| 848 | + for i, item in enumerate(redacted_config): |
| 849 | + if isinstance(item, (dict, OrderedDict, list)): |
| 850 | + redacted_config[i] = redact_sensitive_info(item) |
| 851 | + |
| 852 | + return redacted_config |
0 commit comments