Coretex
utils.py
1 # Copyright (C) 2023 Coretex LLC
2 
3 # This file is part of Coretex.ai
4 
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
14 
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
17 
18 from typing import Optional, Any, Dict, List, Union, Tuple, BinaryIO
19 
20 import io
21 import time
22 import random
23 import logging
24 
25 from urllib.parse import urlsplit, urlunsplit, SplitResult
26 
27 from .network_response import NetworkResponse
28 
29 
30 logger = logging.getLogger("coretexpylib")
31 
32 
33 RequestBodyType = Dict[str, Any]
34 RequestFormType = List[Tuple[str, Tuple[str, Union[bytes, BinaryIO], str]]]
35 
36 
37 def logFilesData(files: Optional[RequestFormType]) -> List[Dict[str, Any]]:
38  if files is None:
39  return []
40 
41  debugFilesData: List[Dict[str, Any]] = []
42 
43  for paramName, (fileName, fileData, mimeType) in files:
44  if isinstance(fileData, bytes):
45  fileSize = len(fileData)
46  else:
47  fileSize = fileData.seek(0, io.SEEK_END)
48  fileData.seek(0)
49 
50  debugFilesData.append({
51  "paramName": paramName,
52  "fileName": fileName,
53  "fileSize": fileSize, # Dump file size instead of file data because file data can be in GBs
54  "mimeType": mimeType
55  })
56 
57  return debugFilesData
58 
59 
60 def logRequestFailure(endpoint: str, response: NetworkResponse) -> None:
61  if not response.hasFailed():
62  raise ValueError(f">> [Coretex] Invalid response status code: \"{response.statusCode}\"")
63 
64  logger.debug(f">> [Coretex] Request to \"{endpoint}\" failed with status code: {response.statusCode}")
65 
66  try:
67  responseJson = response.getJson(dict)
68 
69  message = responseJson.get("message")
70  if message is not None:
71  logger.debug(f"\tResponse: {message}")
72  else:
73  logger.debug(f"\tResponse: {responseJson}")
74  except (ValueError, TypeError):
75  logger.debug(f"\tResponse: {response.getContent()!r}")
76 
77 
78 def baseUrl(url: str) -> str:
79  result = urlsplit(url)
80  parsed = SplitResult(result.scheme, result.netloc, "", "", "")
81 
82  return urlunsplit(parsed)
83 
84 
85 def getDelayBeforeRetry(retry: int) -> int:
86  # retry starts from 0 so we add +1/+2 to start/end
87  # to make it have proper delay
88 
89  start = (retry + 1) ** 2
90  end = (retry + 2) ** 2
91 
92  return random.randint(start, end)
93 
94 
95 def sleepBeforeRetry(retry: int, endpoint: str) -> None:
96  delay = getDelayBeforeRetry(retry)
97  logger.debug(f">> [Coretex] Waiting for {delay} seconds before retrying failed \"{endpoint}\" request")
98 
99  time.sleep(delay)
100 
101 
102 def getTimeoutForRetry(retry: int, timeout: Tuple[int, int], maxTimeout: Tuple[int, int]) -> Tuple[int, int]:
103  connectTimeout, readTimeout = timeout
104  maxConnectTimeout, maxReadTimeout = maxTimeout
105 
106  return (
107  min(connectTimeout * retry, maxConnectTimeout),
108  min(readTimeout * retry, maxReadTimeout)
109  )