Coverage for filip/clients/base_http_client.py: 85%
111 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-11-20 16:54 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-11-20 16:54 +0000
1"""
2Base http client module
3"""
4import logging
5from pydantic import AnyHttpUrl
6from typing import Dict, ByteString, List, IO, Tuple, Union
7import requests
9from filip.models.base import FiwareHeader
10from filip.utils import validate_http_url
13class BaseHttpClient:
14 """
15 Base client for all derived api-clients.
17 Args:
18 session: request session object. This is required for reusing
19 the same connection
20 reuse_session (bool):
21 fiware_header: Fiware header object required for multi tenancy
22 **kwargs: Optional arguments that ``request`` takes.
24 """
25 def __init__(self,
26 url: Union[AnyHttpUrl, str] = None,
27 *,
28 session: requests.Session = None,
29 fiware_header: Union[Dict, FiwareHeader] = None,
30 **kwargs):
32 self.logger = logging.getLogger(
33 name=f"{self.__class__.__name__}")
34 self.logger.addHandler(logging.NullHandler())
35 self.logger.debug("Creating %s", self.__class__.__name__)
37 if url:
38 self.logger.debug("Checking url style...")
39 self.base_url = validate_http_url(url)
41 if session:
42 self.session = session
43 self._external_session = True
44 else:
45 self.session = None
46 self._headers = {}
48 if not fiware_header:
49 self.fiware_headers = FiwareHeader()
50 else:
51 self.fiware_headers = fiware_header
53 self.headers.update(kwargs.pop('headers', {}))
54 self.kwargs: Dict = kwargs
56 # Context Manager Protocol
57 def __enter__(self):
58 if not self.session:
59 self.session = requests.Session()
60 self.headers.update(self.fiware_headers.model_dump(by_alias=True))
61 self._external_session = False
62 return self
64 def __exit__(self, exc_type, exc_val, exc_tb):
65 self.close()
67 @property
68 def fiware_headers(self) -> FiwareHeader:
69 """
70 Get fiware header
72 Returns:
73 FiwareHeader
74 """
75 return self._fiware_headers
77 @fiware_headers.setter
78 def fiware_headers(self, headers: Union[Dict, FiwareHeader]) -> None:
79 """
80 Sets new fiware header
82 Args:
83 headers (Dict, FiwareHeader): New headers either as FiwareHeader
84 object or as dict.
86 Example:
87 {fiware-service: "MyService",
88 fiware-servicepath: "/MyServicePath"}
90 Returns:
91 None
92 """
93 if isinstance(headers, FiwareHeader):
94 self._fiware_headers = headers
95 elif isinstance(headers, dict):
96 self._fiware_headers = FiwareHeader.model_validate(headers)
97 elif isinstance(headers, str):
98 self._fiware_headers = FiwareHeader.model_validate_json(headers)
99 else:
100 raise TypeError(f'Invalid headers! {type(headers)}')
101 self.headers.update(self.fiware_headers.model_dump(by_alias=True))
103 @property
104 def fiware_service(self) -> str:
105 """
106 Get current fiware service
107 Returns:
108 str
109 """
110 return self.fiware_headers.service
112 @fiware_service.setter
113 def fiware_service(self, service: str) -> None:
114 """
115 Set new fiware service
116 Args:
117 service:
119 Returns:
120 None
121 """
122 self._fiware_headers.service = service
123 self.headers.update(self.fiware_headers.model_dump(by_alias=True))
125 @property
126 def fiware_service_path(self) -> str:
127 """
128 Get current fiware service path
129 Returns:
130 str
131 """
132 return self.fiware_headers.service_path
134 @fiware_service_path.setter
135 def fiware_service_path(self, service_path: str) -> None:
136 """
137 Set new fiware service path
138 Args:
139 service_path (str): New fiware service path. Must start with '/'
141 Returns:
142 None
143 """
144 self._fiware_headers.service_path = service_path
145 self.headers.update(self.fiware_headers.model_dump(by_alias=True))
147 @property
148 def headers(self):
149 """
150 Return current session headers
151 Returns:
152 dict with headers
153 """
154 if self.session:
155 return self.session.headers
156 return self._headers
158 # modification to requests api
159 def get(self,
160 url: str,
161 params: Union[Dict, List[Tuple], ByteString] = None,
162 **kwargs) -> requests.Response:
163 """
164 Sends a GET request either using the provided session or the single
165 session.
167 Args:
168 url (str): URL for the new :class:`Request` object.
169 params (optional): (optional) Dictionary, list of tuples or bytes
170 to send in the query string for the :class:`Request`.
171 **kwargs: Optional arguments that ``request`` takes.
173 Returns:
174 requests.Response
175 """
177 kwargs.update({k: v for k, v in self.kwargs.items()
178 if k not in kwargs.keys()})
180 if self.session:
181 return self.session.get(url=url, params=params, **kwargs)
182 return requests.get(url=url, params=params, **kwargs)
184 def options(self, url: str, **kwargs) -> requests.Response:
185 """
186 Sends an OPTIONS request either using the provided session or the
187 single session.
189 Args:
190 url (str):
191 **kwargs: Optional arguments that ``request`` takes.
193 Returns:
194 requests.Response
195 """
196 kwargs.update({k: v for k, v in self.kwargs.items()
197 if k not in kwargs.keys()})
199 if self.session:
200 return self.session.options(url=url, **kwargs)
201 return requests.options(url=url, **kwargs)
203 def head(self, url: str,
204 params: Union[Dict, List[Tuple], ByteString] = None,
205 **kwargs) -> requests.Response:
206 """
207 Sends a HEAD request either using the provided session or the
208 single session.
210 Args:
211 url (str): URL for the new :class:`Request` object.
212 params (optional): Dictionary, list of tuples or bytes
213 to send in the query string for the :class:`Request`.
214 **kwargs: Optional arguments that ``request`` takes.
216 Returns:
217 requests.Response
218 """
219 kwargs.update({k: v for k, v in self.kwargs.items()
220 if k not in kwargs.keys()})
222 if self.session:
223 return self.session.head(url=url, params=params, **kwargs)
224 return requests.head(url=url, params=params, **kwargs)
226 def post(self,
227 url: str,
228 data: Union[Dict, ByteString, List[Tuple], IO, str] = None,
229 json: Dict = None,
230 **kwargs) -> requests.Response:
231 """
232 Sends a POST request either using the provided session or the
233 single session.
235 Args:
236 url: URL for the new :class:`Request` object.
237 data: Dictionary, list of tuples, bytes, or file-like object to
238 send in the body of the :class:`Request`.
239 json: A JSON serializable Python object to send in the
240 body of the :class:`Request`.
241 **kwargs: Optional arguments that ``request`` takes.
243 Returns:
245 """
246 kwargs.update({k: v for k, v in self.kwargs.items()
247 if k not in kwargs.keys()})
249 if self.session:
250 return self.session.post(url=url, data=data, json=json, **kwargs)
251 return requests.post(url=url, data=data, json=json, **kwargs)
253 def put(self,
254 url: str,
255 data: Union[Dict, ByteString, List[Tuple], IO, str] = None,
256 json: Dict = None,
257 **kwargs) -> requests.Response:
258 """
259 Sends a PUT request either using the provided session or the
260 single session.
262 Args:
263 url: URL for the new :class:`Request` object.
264 data (Union[Dict, ByteString, List[Tuple], IO]):
265 Dictionary, list of tuples, bytes, or file-like
266 object to send in the body of the :class:`Request`.
267 json (Dict): A JSON serializable Python object to send in the
268 body of the :class:`Request`..
269 **kwargs: Optional arguments that ``request`` takes.
271 Returns:
272 request.Response
273 """
274 kwargs.update({k: v for k, v in self.kwargs.items()
275 if k not in kwargs.keys()})
277 if self.session:
278 return self.session.put(url=url, data=data, json=json, **kwargs)
279 return requests.put(url=url, data=data, json=json, **kwargs)
281 def patch(self,
282 url: str,
283 data: Union[Dict, ByteString, List[Tuple], IO, str] = None,
284 json: Dict = None,
285 **kwargs) -> requests.Response:
286 """
287 Sends a PATCH request either using the provided session or the
288 single session.
290 Args:
291 url: URL for the new :class:`Request` object.
292 data (Union[Dict, ByteString, List[Tuple], IO]):
293 Dictionary, list of tuples, bytes, or file-like
294 object to send in the body of the :class:`Request`.
295 json (Dict): A JSON serializable Python object to send in the
296 body of the :class:`Request`..
297 **kwargs: Optional arguments that ``request`` takes.
299 Returns:
300 request.Response
301 """
302 kwargs.update({k: v for k, v in self.kwargs.items()
303 if k not in kwargs.keys()})
305 if self.session:
306 return self.session.patch(url=url, data=data, json=json, **kwargs)
307 return requests.patch(url=url, data=data, json=json, **kwargs)
309 def delete(self, url: str, **kwargs) -> requests.Response:
310 """
311 Sends a DELETE request either using the provided session or the
312 single session.
314 Args:
315 url (str): URL for the new :class:`Request` object.
316 **kwargs: Optional arguments that ``request`` takes.
318 Returns:
319 request.Response
320 """
321 kwargs.update({k: v for k, v in self.kwargs.items()
322 if k not in kwargs.keys()})
324 if self.session:
325 return self.session.delete(url=url, **kwargs)
326 return requests.delete(url=url, **kwargs)
328 def log_error(self,
329 err: requests.RequestException,
330 msg: str = None) -> None:
331 """
332 Outputs the error messages from the client request function. If
333 additional information is available in the server response this will
334 be forwarded to the logging output.
336 Note:
337 The user is responsible to setup the logging system
339 Args:
340 err: Request Error
341 msg: error message from calling function
343 Returns:
344 None
345 """
346 if err.response is not None:
347 if err.response.text and msg:
348 self.logger.error("%s \n Reason: %s", msg, err.response.text)
349 elif err.response.text and not msg:
350 self.logger.error("%s", err.response.text)
351 elif not err.response and msg:
352 self.logger.error("%s \n Reason: %s", msg, err)
353 else:
354 self.logger.error(err)
356 def close(self) -> None:
357 """
358 Close http session
359 Returns:
360 None
361 """
362 if self.session and not self._external_session:
363 self.session.close()