github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/python/aistore/sdk/request_client.py (about) 1 # 2 # Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved. 3 # 4 import os 5 from urllib.parse import urljoin, urlencode 6 from typing import TypeVar, Type, Any, Dict 7 8 import requests 9 10 from aistore.sdk.const import ( 11 JSON_CONTENT_TYPE, 12 HEADER_USER_AGENT, 13 USER_AGENT_BASE, 14 HEADER_CONTENT_TYPE, 15 AIS_SERVER_CRT, 16 ) 17 from aistore.sdk.utils import handle_errors, decode_response 18 from aistore.version import __version__ as sdk_version 19 20 T = TypeVar("T") 21 22 23 # pylint: disable=unused-variable, duplicate-code 24 class RequestClient: 25 """ 26 Internal client for buckets, objects, jobs, etc. to use for making requests to an AIS cluster 27 28 Args: 29 endpoint (str): AIStore endpoint 30 """ 31 32 def __init__( 33 self, 34 endpoint: str, 35 skip_verify: bool = False, 36 ca_cert: str = None, 37 timeout=None, 38 ): 39 self._endpoint = endpoint 40 self._base_url = urljoin(endpoint, "v1") 41 self._session = requests.sessions.session() 42 self._timeout = timeout 43 if "https" in self._endpoint: 44 self._set_session_verification(skip_verify, ca_cert) 45 46 def _set_session_verification(self, skip_verify: bool, ca_cert: str): 47 """ 48 Set session verify value for validating the server's SSL certificate 49 The requests library allows this to be a boolean or a string path to the cert 50 If we do not skip verification, the order is: 51 1. Provided cert path 52 2. Cert path from env var. 53 3. True (verify with system's approved CA list) 54 """ 55 if skip_verify: 56 self._session.verify = False 57 return 58 if ca_cert: 59 self._session.verify = ca_cert 60 return 61 env_crt = os.getenv(AIS_SERVER_CRT) 62 self._session.verify = env_crt if env_crt else True 63 64 @property 65 def base_url(self): 66 """ 67 Returns: AIS cluster base url 68 """ 69 return self._base_url 70 71 @property 72 def endpoint(self): 73 """ 74 Returns: AIS cluster endpoint 75 """ 76 return self._endpoint 77 78 @property 79 def session(self): 80 """ 81 Returns: Active request session 82 """ 83 return self._session 84 85 def request_deserialize( 86 self, method: str, path: str, res_model: Type[T], **kwargs 87 ) -> T: 88 """ 89 Make a request to the AIS cluster and deserialize the response to a defined type 90 Args: 91 method (str): HTTP method, e.g. POST, GET, PUT, DELETE 92 path (str): URL path to call 93 res_model (Type[T]): Resulting type to which the response should be deserialized 94 **kwargs (optional): Optional keyword arguments to pass with the call to request 95 96 Returns: 97 Parsed result of the call to the API, as res_model 98 """ 99 resp = self.request(method, path, **kwargs) 100 return decode_response(res_model, resp) 101 102 def request( 103 self, 104 method: str, 105 path: str, 106 endpoint: str = None, 107 headers: dict = None, 108 **kwargs, 109 ) -> requests.Response: 110 """ 111 Make a request to the AIS cluster 112 Args: 113 method (str): HTTP method, e.g. POST, GET, PUT, DELETE 114 path (str): URL path to call 115 endpoint (str): Alternative endpoint for the AIS cluster, e.g. for connecting to a specific proxy 116 headers (dict): Extra headers to be passed with the request. Content-Type and User-Agent will be overridden 117 **kwargs (optional): Optional keyword arguments to pass with the call to request 118 119 Returns: 120 Raw response from the API 121 """ 122 base = urljoin(endpoint, "v1") if endpoint else self._base_url 123 url = f"{base}/{path.lstrip('/')}" 124 if headers is None: 125 headers = {} 126 headers[HEADER_CONTENT_TYPE] = JSON_CONTENT_TYPE 127 headers[HEADER_USER_AGENT] = f"{USER_AGENT_BASE}/{sdk_version}" 128 resp = self._session.request( 129 method, 130 url, 131 headers=headers, 132 timeout=self._timeout, 133 **kwargs, 134 ) 135 if resp.status_code < 200 or resp.status_code >= 300: 136 handle_errors(resp) 137 return resp 138 139 def get_full_url(self, path: str, params: Dict[str, Any]): 140 """ 141 Get the full URL to the path on the cluster with the parameters given 142 143 Args: 144 path: Path on the cluster 145 params: Query parameters to include 146 147 Returns: 148 URL including cluster base url and parameters 149 150 """ 151 return f"{self._base_url}/{path.lstrip('/')}?{urlencode(params)}"