everysk-lib 1.10.2__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- everysk/__init__.py +30 -0
- everysk/_version.py +683 -0
- everysk/api/__init__.py +61 -0
- everysk/api/api_requestor.py +167 -0
- everysk/api/api_resources/__init__.py +23 -0
- everysk/api/api_resources/api_resource.py +371 -0
- everysk/api/api_resources/calculation.py +779 -0
- everysk/api/api_resources/custom_index.py +42 -0
- everysk/api/api_resources/datastore.py +81 -0
- everysk/api/api_resources/file.py +42 -0
- everysk/api/api_resources/market_data.py +223 -0
- everysk/api/api_resources/parser.py +66 -0
- everysk/api/api_resources/portfolio.py +43 -0
- everysk/api/api_resources/private_security.py +42 -0
- everysk/api/api_resources/report.py +65 -0
- everysk/api/api_resources/report_template.py +39 -0
- everysk/api/api_resources/tests.py +115 -0
- everysk/api/api_resources/worker_execution.py +64 -0
- everysk/api/api_resources/workflow.py +65 -0
- everysk/api/api_resources/workflow_execution.py +93 -0
- everysk/api/api_resources/workspace.py +42 -0
- everysk/api/http_client.py +63 -0
- everysk/api/tests.py +32 -0
- everysk/api/utils.py +262 -0
- everysk/config.py +451 -0
- everysk/core/_tests/serialize/test_json.py +336 -0
- everysk/core/_tests/serialize/test_orjson.py +295 -0
- everysk/core/_tests/serialize/test_pickle.py +48 -0
- everysk/core/cloud_function/main.py +78 -0
- everysk/core/cloud_function/tests.py +86 -0
- everysk/core/compress.py +245 -0
- everysk/core/datetime/__init__.py +12 -0
- everysk/core/datetime/calendar.py +144 -0
- everysk/core/datetime/date.py +424 -0
- everysk/core/datetime/date_expression.py +299 -0
- everysk/core/datetime/date_mixin.py +1475 -0
- everysk/core/datetime/date_settings.py +30 -0
- everysk/core/datetime/datetime.py +713 -0
- everysk/core/exceptions.py +435 -0
- everysk/core/fields.py +1176 -0
- everysk/core/firestore.py +555 -0
- everysk/core/fixtures/_settings.py +29 -0
- everysk/core/fixtures/other/_settings.py +18 -0
- everysk/core/fixtures/user_agents.json +88 -0
- everysk/core/http.py +691 -0
- everysk/core/lists.py +92 -0
- everysk/core/log.py +709 -0
- everysk/core/number.py +37 -0
- everysk/core/object.py +1469 -0
- everysk/core/redis.py +1021 -0
- everysk/core/retry.py +51 -0
- everysk/core/serialize.py +674 -0
- everysk/core/sftp.py +414 -0
- everysk/core/signing.py +53 -0
- everysk/core/slack.py +127 -0
- everysk/core/string.py +199 -0
- everysk/core/tests.py +240 -0
- everysk/core/threads.py +199 -0
- everysk/core/undefined.py +70 -0
- everysk/core/unittests.py +73 -0
- everysk/core/workers.py +241 -0
- everysk/sdk/__init__.py +23 -0
- everysk/sdk/base.py +98 -0
- everysk/sdk/brutils/cnpj.py +391 -0
- everysk/sdk/brutils/cnpj_pd.py +129 -0
- everysk/sdk/engines/__init__.py +26 -0
- everysk/sdk/engines/cache.py +185 -0
- everysk/sdk/engines/compliance.py +37 -0
- everysk/sdk/engines/cryptography.py +69 -0
- everysk/sdk/engines/expression.cp312-win_amd64.pyd +0 -0
- everysk/sdk/engines/expression.pyi +55 -0
- everysk/sdk/engines/helpers.cp312-win_amd64.pyd +0 -0
- everysk/sdk/engines/helpers.pyi +26 -0
- everysk/sdk/engines/lock.py +120 -0
- everysk/sdk/engines/market_data.py +244 -0
- everysk/sdk/engines/settings.py +19 -0
- everysk/sdk/entities/__init__.py +23 -0
- everysk/sdk/entities/base.py +784 -0
- everysk/sdk/entities/base_list.py +131 -0
- everysk/sdk/entities/custom_index/base.py +209 -0
- everysk/sdk/entities/custom_index/settings.py +29 -0
- everysk/sdk/entities/datastore/base.py +160 -0
- everysk/sdk/entities/datastore/settings.py +17 -0
- everysk/sdk/entities/fields.py +375 -0
- everysk/sdk/entities/file/base.py +215 -0
- everysk/sdk/entities/file/settings.py +63 -0
- everysk/sdk/entities/portfolio/base.py +248 -0
- everysk/sdk/entities/portfolio/securities.py +241 -0
- everysk/sdk/entities/portfolio/security.py +580 -0
- everysk/sdk/entities/portfolio/settings.py +97 -0
- everysk/sdk/entities/private_security/base.py +226 -0
- everysk/sdk/entities/private_security/settings.py +17 -0
- everysk/sdk/entities/query.py +603 -0
- everysk/sdk/entities/report/base.py +214 -0
- everysk/sdk/entities/report/settings.py +23 -0
- everysk/sdk/entities/script.py +310 -0
- everysk/sdk/entities/secrets/base.py +128 -0
- everysk/sdk/entities/secrets/script.py +119 -0
- everysk/sdk/entities/secrets/settings.py +17 -0
- everysk/sdk/entities/settings.py +48 -0
- everysk/sdk/entities/tags.py +174 -0
- everysk/sdk/entities/worker_execution/base.py +307 -0
- everysk/sdk/entities/worker_execution/settings.py +63 -0
- everysk/sdk/entities/workflow_execution/base.py +113 -0
- everysk/sdk/entities/workflow_execution/settings.py +32 -0
- everysk/sdk/entities/workspace/base.py +99 -0
- everysk/sdk/entities/workspace/settings.py +27 -0
- everysk/sdk/settings.py +67 -0
- everysk/sdk/tests.py +105 -0
- everysk/sdk/worker_base.py +47 -0
- everysk/server/__init__.py +9 -0
- everysk/server/applications.py +63 -0
- everysk/server/endpoints.py +516 -0
- everysk/server/example_api.py +69 -0
- everysk/server/middlewares.py +80 -0
- everysk/server/requests.py +62 -0
- everysk/server/responses.py +119 -0
- everysk/server/routing.py +64 -0
- everysk/server/settings.py +36 -0
- everysk/server/tests.py +36 -0
- everysk/settings.py +98 -0
- everysk/sql/__init__.py +9 -0
- everysk/sql/connection.py +232 -0
- everysk/sql/model.py +376 -0
- everysk/sql/query.py +417 -0
- everysk/sql/row_factory.py +63 -0
- everysk/sql/settings.py +49 -0
- everysk/sql/utils.py +129 -0
- everysk/tests.py +23 -0
- everysk/utils.py +81 -0
- everysk/version.py +15 -0
- everysk_lib-1.10.2.dist-info/.gitignore +5 -0
- everysk_lib-1.10.2.dist-info/METADATA +326 -0
- everysk_lib-1.10.2.dist-info/RECORD +137 -0
- everysk_lib-1.10.2.dist-info/WHEEL +5 -0
- everysk_lib-1.10.2.dist-info/licenses/LICENSE.txt +9 -0
- everysk_lib-1.10.2.dist-info/top_level.txt +2 -0
everysk/core/http.py
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# (C) Copyright 2023 EVERYSK TECHNOLOGIES
|
|
4
|
+
#
|
|
5
|
+
# This is an unpublished work containing confidential and proprietary
|
|
6
|
+
# information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
|
|
7
|
+
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
|
|
8
|
+
#
|
|
9
|
+
###############################################################################
|
|
10
|
+
import json
|
|
11
|
+
import ssl
|
|
12
|
+
import tempfile
|
|
13
|
+
import time
|
|
14
|
+
from os.path import dirname
|
|
15
|
+
from random import SystemRandom
|
|
16
|
+
from types import TracebackType
|
|
17
|
+
from typing import Any, Self
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
from everysk.config import settings
|
|
22
|
+
from everysk.core.compress import compress
|
|
23
|
+
from everysk.core.exceptions import HttpError, InvalidArgumentError
|
|
24
|
+
from everysk.core.fields import BoolField, DictField, Field, IntField, ListField, StrField
|
|
25
|
+
from everysk.core.log import Logger, _get_gcp_headers
|
|
26
|
+
from everysk.core.object import BaseObject, BaseObjectConfig
|
|
27
|
+
from everysk.core.serialize import loads
|
|
28
|
+
|
|
29
|
+
log = Logger(name='everysk-lib-core-http-log')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
###############################################################################
|
|
33
|
+
# HttpConnectionConfig Class Implementation
|
|
34
|
+
###############################################################################
|
|
35
|
+
class HttpConnectionConfig(BaseObjectConfig):
|
|
36
|
+
# Number of connections to keep opened in the pool.
|
|
37
|
+
pool_connections_min = IntField(default=20, min_size=1)
|
|
38
|
+
|
|
39
|
+
# Maximum number of connections in the pool.
|
|
40
|
+
pool_connections_max = IntField(default=100)
|
|
41
|
+
|
|
42
|
+
# Timeout to wait before closing idle connections.
|
|
43
|
+
pool_connections_timeout = IntField(default=5)
|
|
44
|
+
|
|
45
|
+
# If the connection should follow the redirects automatic or raise HTTP Redirect error.
|
|
46
|
+
follow_redirects = BoolField(default=True)
|
|
47
|
+
|
|
48
|
+
# If the connection accepts HTTP 1 protocol.
|
|
49
|
+
use_http1 = BoolField(default=True)
|
|
50
|
+
|
|
51
|
+
# If the connection accepts HTTP 2 protocol.
|
|
52
|
+
use_http2 = BoolField(default=True)
|
|
53
|
+
|
|
54
|
+
# Keep the full path to the file with the user agents.
|
|
55
|
+
user_agents_file = StrField()
|
|
56
|
+
|
|
57
|
+
# List of user agents to be used in the requests.
|
|
58
|
+
user_agents = ListField()
|
|
59
|
+
|
|
60
|
+
# Activate/deactivate the use of the verify flag on HTTP requests.
|
|
61
|
+
# By default this is defined in settings.HTTP_REQUESTS_VERIFY
|
|
62
|
+
# but could be defined in the class configuration too.
|
|
63
|
+
ssl_verify = BoolField(default=settings.HTTP_DEFAULT_SSL_VERIFY)
|
|
64
|
+
|
|
65
|
+
# Limit for retries
|
|
66
|
+
retry_limit = IntField(default=settings.HTTP_DEFAULT_RETRY_LIMIT)
|
|
67
|
+
|
|
68
|
+
# Times that randrange will use to do the next retry
|
|
69
|
+
retry_end_seconds = IntField(default=settings.HTTP_DEFAULT_RETRY_END_SECONDS)
|
|
70
|
+
retry_start_seconds = IntField(default=settings.HTTP_DEFAULT_RETRY_START_SECONDS)
|
|
71
|
+
|
|
72
|
+
def __after_init__(self) -> None:
|
|
73
|
+
# Load the user agents from the file
|
|
74
|
+
if self.user_agents_file is None:
|
|
75
|
+
base_dir = dirname(__file__)
|
|
76
|
+
self.user_agents_file = f'{base_dir}/fixtures/user_agents.json'
|
|
77
|
+
|
|
78
|
+
if self.user_agents is None:
|
|
79
|
+
with open(self.user_agents_file, encoding='utf-8') as fd:
|
|
80
|
+
self.user_agents = json.load(fd)
|
|
81
|
+
|
|
82
|
+
def get_client(self, certificate: str = None) -> httpx.Client:
|
|
83
|
+
"""
|
|
84
|
+
Creates and returns an instance of an `httpx.Client` with the specified configuration.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
certificate (str, optional): Path to the SSL certificate file to be used for
|
|
88
|
+
verifying the connection. Defaults to None.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
httpx.Client: A configured HTTP client instance.
|
|
92
|
+
"""
|
|
93
|
+
limits = httpx.Limits(
|
|
94
|
+
max_keepalive_connections=self.pool_connections_min,
|
|
95
|
+
max_connections=self.pool_connections_max,
|
|
96
|
+
keepalive_expiry=self.pool_connections_timeout,
|
|
97
|
+
)
|
|
98
|
+
return httpx.Client(
|
|
99
|
+
follow_redirects=self.follow_redirects,
|
|
100
|
+
limits=limits,
|
|
101
|
+
http1=self.use_http1,
|
|
102
|
+
http2=self.use_http2,
|
|
103
|
+
verify=self.get_ssl_verify(certificate=certificate),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def get_ssl_verify(self, certificate: str = None) -> bool | ssl.SSLContext:
|
|
107
|
+
"""
|
|
108
|
+
Determines the SSL verification context or flag for HTTP requests.
|
|
109
|
+
|
|
110
|
+
If a certificate is provided, it creates an SSL context with the specified
|
|
111
|
+
certificate loaded. Otherwise, it returns the SSL verification setting
|
|
112
|
+
based on the `settings.HTTP_REQUESTS_VERIFY` configuration.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
certificate (str, optional): The name of the certificate to use for
|
|
116
|
+
creating the SSL context. Defaults to None.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Union[ssl.SSLContext, bool]: An SSL context object if a certificate is
|
|
120
|
+
provided, otherwise a boolean indicating whether SSL verification is
|
|
121
|
+
enabled.
|
|
122
|
+
"""
|
|
123
|
+
if certificate:
|
|
124
|
+
certificate_name = self._get_certificate_file_name(certificate=certificate)
|
|
125
|
+
ctx = ssl.create_default_context()
|
|
126
|
+
ctx.load_cert_chain(certfile=certificate_name)
|
|
127
|
+
return ctx
|
|
128
|
+
|
|
129
|
+
return self.ssl_verify if settings.HTTP_REQUESTS_VERIFY is Undefined else settings.HTTP_REQUESTS_VERIFY
|
|
130
|
+
|
|
131
|
+
def get_random_agent(self) -> str:
|
|
132
|
+
"""Return a random user agent from the list of user agents."""
|
|
133
|
+
random = SystemRandom()
|
|
134
|
+
return random.choice(self.user_agents)
|
|
135
|
+
|
|
136
|
+
def _get_certificate_file_name(self, certificate: str) -> str:
|
|
137
|
+
"""Create a temporary file with the certificate content and return the file name."""
|
|
138
|
+
with tempfile.NamedTemporaryFile(delete=False, mode='w', suffix='.pem') as tmp_file:
|
|
139
|
+
tmp_file.write(certificate)
|
|
140
|
+
return tmp_file.name
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
###############################################################################
|
|
144
|
+
# HttpConnection Class Implementation
|
|
145
|
+
###############################################################################
|
|
146
|
+
class HttpConnection(BaseObject):
|
|
147
|
+
"""
|
|
148
|
+
Base class to use for HTTP connections, has two attributes:
|
|
149
|
+
- timeout: It's in and represent seconds, defaults to 30.
|
|
150
|
+
- url: It's string and will be the destination.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
class Config(HttpConnectionConfig):
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
## Private attributes
|
|
157
|
+
_client: httpx.Client = None
|
|
158
|
+
_config: Config = None # To autocomplete correctly
|
|
159
|
+
_retry_count = IntField(default=1) # Used to control how many times this connection was retry
|
|
160
|
+
|
|
161
|
+
## Public attributes
|
|
162
|
+
cert = StrField(default=None)
|
|
163
|
+
headers = DictField(default=None)
|
|
164
|
+
timeout = IntField(default=settings.HTTP_DEFAULT_TIMEOUT) # This is read timeout
|
|
165
|
+
url = StrField(default=None)
|
|
166
|
+
|
|
167
|
+
## Private methods
|
|
168
|
+
def __call__(self, **kwargs: dict) -> Self:
|
|
169
|
+
"""
|
|
170
|
+
Set the attributes of the class with the values passed as kwargs.
|
|
171
|
+
We use this method together with the with statement to set the values of the class.
|
|
172
|
+
"""
|
|
173
|
+
for key, value in kwargs.items():
|
|
174
|
+
setattr(self, key, value)
|
|
175
|
+
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
def __enter__(self) -> Self:
|
|
179
|
+
"""Create and set the HTTP client to be used in the connection so we could reuse the connection."""
|
|
180
|
+
self._client = self._config.get_client(certificate=self.cert)
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
def __exit__(
|
|
184
|
+
self,
|
|
185
|
+
__exc_type: type[BaseException] | None, # noqa: PYI063
|
|
186
|
+
__exc_value: BaseException | None,
|
|
187
|
+
__traceback: TracebackType | None,
|
|
188
|
+
) -> bool | None:
|
|
189
|
+
"""
|
|
190
|
+
Close the HTTP client to free the resources.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
bool | None: If return is False any exception will be raised.
|
|
194
|
+
"""
|
|
195
|
+
self._client.close()
|
|
196
|
+
self._client = None
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
def _clean_response(self, response: httpx.Response) -> httpx.Response:
|
|
200
|
+
"""
|
|
201
|
+
Checks status_code for response, if status_code is different than 200 throws an exception.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
response (httpx.Response): Http response from server.
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
HttpError: If something goes wrong raise exception with status_code and content.
|
|
208
|
+
"""
|
|
209
|
+
if (
|
|
210
|
+
getattr(response, 'status_code', settings.HTTP_SUCCESS_STATUS_CODES[0])
|
|
211
|
+
not in settings.HTTP_SUCCESS_STATUS_CODES
|
|
212
|
+
):
|
|
213
|
+
raise HttpError(status_code=response.status_code, msg=response.content)
|
|
214
|
+
|
|
215
|
+
return response
|
|
216
|
+
|
|
217
|
+
def _get_headers(self) -> dict:
|
|
218
|
+
try:
|
|
219
|
+
# We try to get the GCP headers to send the request
|
|
220
|
+
# The first attempt is to get from a context var
|
|
221
|
+
# If it fails we try to get from the server running
|
|
222
|
+
gcp_headers = _get_gcp_headers()
|
|
223
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
224
|
+
gcp_headers = {}
|
|
225
|
+
|
|
226
|
+
# Get the headers from the class or child classes
|
|
227
|
+
headers = self.get_headers()
|
|
228
|
+
|
|
229
|
+
# Update GCP headers with the headers from the class
|
|
230
|
+
# so if the class has the same key it will be overwritten
|
|
231
|
+
gcp_headers.update(headers)
|
|
232
|
+
|
|
233
|
+
return gcp_headers
|
|
234
|
+
|
|
235
|
+
def _get_response_from_url(self) -> httpx.Response:
|
|
236
|
+
"""Implementation will be provided in child classes to handle the connection."""
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
## Public methods
|
|
240
|
+
def get_headers(self) -> dict:
|
|
241
|
+
"""
|
|
242
|
+
Headers needed to send HTTP methods.
|
|
243
|
+
Below are the most common Headers used by browsers,
|
|
244
|
+
we use them to look less like a Bot and more like a valid access.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
dict: A dictionary containing the headers information.
|
|
248
|
+
|
|
249
|
+
Example:
|
|
250
|
+
>>> http_connection = HttpConnection()
|
|
251
|
+
>>> http_connection.get_headers()
|
|
252
|
+
{
|
|
253
|
+
'Accept-Encoding': 'gzip, deflate;q=0.9',
|
|
254
|
+
'Accept-Language': 'en-US, en;q=0.9, pt-BR;q=0.8, pt;q=0.7',
|
|
255
|
+
'Cache-control': 'no-cache',
|
|
256
|
+
'Connection': 'close',
|
|
257
|
+
'Content-Type': 'text/html; charset=UTF-8',
|
|
258
|
+
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
|
|
259
|
+
}
|
|
260
|
+
"""
|
|
261
|
+
headers = settings.HTTP_DEFAULT_HEADERS.copy()
|
|
262
|
+
if settings.HTTP_USE_RANDOM_USER_AGENT:
|
|
263
|
+
headers['User-Agent'] = self._config.get_random_agent()
|
|
264
|
+
|
|
265
|
+
if self.headers is not None:
|
|
266
|
+
headers.update(self.headers)
|
|
267
|
+
|
|
268
|
+
return headers
|
|
269
|
+
|
|
270
|
+
def get_url(self) -> str:
|
|
271
|
+
"""
|
|
272
|
+
Generate the correct url to fetch data from vendor on POST/GET requests.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
str: Containing the correct URL.
|
|
276
|
+
"""
|
|
277
|
+
return self.url
|
|
278
|
+
|
|
279
|
+
def get_timeout(self) -> httpx.Timeout:
|
|
280
|
+
"""Return the timeout to be used in the connection."""
|
|
281
|
+
return httpx.Timeout(timeout=self.timeout)
|
|
282
|
+
|
|
283
|
+
def message_error_check(self, message: str, status_code: int) -> bool: # pylint: disable=unused-argument
|
|
284
|
+
"""
|
|
285
|
+
If this method returns True, the connection will be tried again.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
message (str): The error message that occurred on the connection.
|
|
289
|
+
status_code (int): The status code of the response.
|
|
290
|
+
"""
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
def get_response(self) -> httpx.Response:
|
|
294
|
+
"""
|
|
295
|
+
Try to fetch data from self.get_url and calling self._get_response_from_url for the complete response.
|
|
296
|
+
On HttpError, if self.message_error_check is True we will try connect again for a few more times.
|
|
297
|
+
"""
|
|
298
|
+
try:
|
|
299
|
+
response = self._clean_response(self._get_response_from_url())
|
|
300
|
+
# After a success we set the value to 1 again
|
|
301
|
+
self._retry_count = 1
|
|
302
|
+
except Exception as error: # pylint: disable=broad-exception-caught
|
|
303
|
+
# Sometimes it can happen that the server is busy, if this happen the error message must be tested
|
|
304
|
+
# and must return true to enable recursion and we will try again the connection.
|
|
305
|
+
message = str(error).lower()
|
|
306
|
+
status_code = getattr(error, 'status_code', 500)
|
|
307
|
+
if self.message_error_check(message, status_code) and self._retry_count < self._config.retry_limit:
|
|
308
|
+
self._retry_count += 1
|
|
309
|
+
# As we have several processes, we use a random number to avoid collision between them.
|
|
310
|
+
random = SystemRandom()
|
|
311
|
+
time.sleep(random.randint(self._config.retry_start_seconds, self._config.retry_end_seconds))
|
|
312
|
+
if settings.EVERYSK_HTTP_LOG_RETRY:
|
|
313
|
+
log.debug(f'Retry: {self.get_url()} - {message}.')
|
|
314
|
+
|
|
315
|
+
response = self.get_response()
|
|
316
|
+
else:
|
|
317
|
+
raise error
|
|
318
|
+
|
|
319
|
+
return response
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
###############################################################################
|
|
323
|
+
# HttpGETConnection Class Implementation
|
|
324
|
+
###############################################################################
|
|
325
|
+
class HttpGETConnection(HttpConnection):
|
|
326
|
+
"""Class that implements a interface for HTTP GET connections"""
|
|
327
|
+
|
|
328
|
+
params = DictField()
|
|
329
|
+
user = StrField()
|
|
330
|
+
password = StrField()
|
|
331
|
+
method = StrField(default='GET', readonly=True)
|
|
332
|
+
|
|
333
|
+
def __init__(
|
|
334
|
+
self,
|
|
335
|
+
url: str = None,
|
|
336
|
+
headers: dict = None,
|
|
337
|
+
params: dict = None,
|
|
338
|
+
timeout: int = None,
|
|
339
|
+
user: str = None,
|
|
340
|
+
password: str = None,
|
|
341
|
+
**kwargs,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""
|
|
344
|
+
HTTP GET Connection used to fetch data from a URL.
|
|
345
|
+
Url could be passed on the constructor, set in the class or set later.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
url (str, optional): The URL to fetch data from.
|
|
349
|
+
headers (dict, optional): Extra headers added in the connection. Defaults to None.
|
|
350
|
+
params (dict, optional): Parameters added in the url as query. Defaults to None.
|
|
351
|
+
timeout (int, optional): Time when the connection stops to wait and raise an Exception. Defaults to 30 seconds.
|
|
352
|
+
user (str, optional): Username if needed for authentication. Defaults to None.
|
|
353
|
+
password (str, optional): _description_. Defaults to None.
|
|
354
|
+
"""
|
|
355
|
+
# We only sent to the super the values that are not None
|
|
356
|
+
# to not overwrite the default values or the ones that are set in the class.
|
|
357
|
+
if url is not None:
|
|
358
|
+
kwargs['url'] = url
|
|
359
|
+
if headers is not None:
|
|
360
|
+
kwargs['headers'] = headers
|
|
361
|
+
if params is not None:
|
|
362
|
+
kwargs['params'] = params
|
|
363
|
+
if timeout is not None:
|
|
364
|
+
kwargs['timeout'] = timeout
|
|
365
|
+
if user is not None:
|
|
366
|
+
kwargs['user'] = user
|
|
367
|
+
if password is not None:
|
|
368
|
+
kwargs['password'] = password
|
|
369
|
+
|
|
370
|
+
super().__init__(**kwargs)
|
|
371
|
+
|
|
372
|
+
def get_params(self) -> dict:
|
|
373
|
+
"""
|
|
374
|
+
Method used to make the correct params to pass on GET request.
|
|
375
|
+
These params will be added to the URL with & separating them.
|
|
376
|
+
"""
|
|
377
|
+
return self.params
|
|
378
|
+
|
|
379
|
+
def get_request_params(self) -> dict:
|
|
380
|
+
"""
|
|
381
|
+
Constructs and returns the parameters for an HTTP GET request.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
dict: A dictionary containing the URL, headers, parameters, and timeout for the request.
|
|
385
|
+
If a user is specified, the dictionary will also include authentication credentials.
|
|
386
|
+
"""
|
|
387
|
+
params = {
|
|
388
|
+
'url': self.get_url(),
|
|
389
|
+
'headers': self._get_headers(),
|
|
390
|
+
'params': self.get_params(),
|
|
391
|
+
'timeout': self.get_timeout(),
|
|
392
|
+
}
|
|
393
|
+
if self.user:
|
|
394
|
+
params['auth'] = (self.user, self.password)
|
|
395
|
+
|
|
396
|
+
return params
|
|
397
|
+
|
|
398
|
+
def _get_response_from_url(self) -> httpx.Response:
|
|
399
|
+
"""
|
|
400
|
+
Try to fetch data from url using GET request.
|
|
401
|
+
Note that any dictionary key whose value is None will not be added to the URL's query string.
|
|
402
|
+
"""
|
|
403
|
+
# If the client is already set and not closed we use it
|
|
404
|
+
# otherwise we create a new client and close it after the request.
|
|
405
|
+
params = self.get_request_params()
|
|
406
|
+
|
|
407
|
+
if settings.EVERYSK_HTTP_LOG_RESPONSE:
|
|
408
|
+
dct = params
|
|
409
|
+
# To remove the password in the logs
|
|
410
|
+
if 'auth' in params:
|
|
411
|
+
dct = params.copy()
|
|
412
|
+
dct['auth'] = (params['auth'][0], '***********')
|
|
413
|
+
|
|
414
|
+
log.debug('HTTP %s request: %s', self.method, self.get_url(), extra={'labels': dct})
|
|
415
|
+
|
|
416
|
+
if self._client and not self._client.is_closed:
|
|
417
|
+
response = getattr(self._client, self.method.lower())(**params)
|
|
418
|
+
else:
|
|
419
|
+
with self._config.get_client(certificate=self.cert) as client:
|
|
420
|
+
response = getattr(client, self.method.lower())(**params)
|
|
421
|
+
|
|
422
|
+
if settings.EVERYSK_HTTP_LOG_RESPONSE:
|
|
423
|
+
dct = {
|
|
424
|
+
'status_code': response.status_code,
|
|
425
|
+
'time': response.elapsed.total_seconds(),
|
|
426
|
+
'headers': response.headers,
|
|
427
|
+
'content': response.content,
|
|
428
|
+
}
|
|
429
|
+
log.debug('HTTP %s response: %s', self.method, self.get_url(), extra={'labels': dct})
|
|
430
|
+
|
|
431
|
+
return response
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
###############################################################################
|
|
435
|
+
# HttpPOSTConnection Class Implementation
|
|
436
|
+
###############################################################################
|
|
437
|
+
class HttpPOSTConnection(HttpConnection):
|
|
438
|
+
"""
|
|
439
|
+
Class that implements a interface for HTTP POST connections.
|
|
440
|
+
If self.is_json is True the POST method will be a JSON POST,
|
|
441
|
+
otherwise will be a Form POST Data.
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
is_json = BoolField(default=True)
|
|
445
|
+
payload = Field()
|
|
446
|
+
method = StrField(default='POST', readonly=True)
|
|
447
|
+
|
|
448
|
+
def __init__(
|
|
449
|
+
self,
|
|
450
|
+
url: str = None,
|
|
451
|
+
headers: dict = None,
|
|
452
|
+
payload: dict = None,
|
|
453
|
+
timeout: int = None,
|
|
454
|
+
is_json: bool = None,
|
|
455
|
+
**kwargs,
|
|
456
|
+
) -> None:
|
|
457
|
+
"""
|
|
458
|
+
HTTP POST Connection used to send data to a URL.
|
|
459
|
+
Url could be passed on the constructor, set in the class or set later.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
url (str, optional): The URL to send data.
|
|
463
|
+
headers (dict, optional): Extra headers added in the connection. Defaults to None.
|
|
464
|
+
payload (dict, optional): Data to send in the POST request. Defaults to None.
|
|
465
|
+
timeout (int, optional): Time when the connection stops to wait and raise an Exception. Defaults to 30 seconds.
|
|
466
|
+
is_json (bool, optional): If the POST method will be a JSON POST or a Form POST Data. Defaults to True.
|
|
467
|
+
"""
|
|
468
|
+
# We only sent to the super the values that are not None
|
|
469
|
+
# to not overwrite the default values or the ones that are set in the class.
|
|
470
|
+
if url is not None:
|
|
471
|
+
kwargs['url'] = url
|
|
472
|
+
if headers is not None:
|
|
473
|
+
kwargs['headers'] = headers
|
|
474
|
+
if payload is not None:
|
|
475
|
+
kwargs['payload'] = payload
|
|
476
|
+
if timeout is not None:
|
|
477
|
+
kwargs['timeout'] = timeout
|
|
478
|
+
if is_json is not None:
|
|
479
|
+
kwargs['is_json'] = is_json
|
|
480
|
+
|
|
481
|
+
super().__init__(**kwargs)
|
|
482
|
+
|
|
483
|
+
def get_payload(self) -> Any:
|
|
484
|
+
"""
|
|
485
|
+
Make the correct payload body to pass on POST request.
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
dict: With all the payload information to send alongside the request.
|
|
489
|
+
"""
|
|
490
|
+
return self.payload
|
|
491
|
+
|
|
492
|
+
def get_request_params(self) -> dict:
|
|
493
|
+
"""
|
|
494
|
+
Constructs and returns the parameters for an HTTP request.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
dict: A dictionary containing the URL (str), headers (dict), timeout (int), and payload (dict)
|
|
498
|
+
for the HTTP request. The payload is included as either JSON or
|
|
499
|
+
form data based on the `is_json` attribute.
|
|
500
|
+
"""
|
|
501
|
+
params = {'url': self.get_url(), 'headers': self._get_headers(), 'timeout': self.get_timeout()}
|
|
502
|
+
|
|
503
|
+
# Discover if the content_type header is the Default
|
|
504
|
+
# This means that the header was not changed or not set so we need to put the correct one
|
|
505
|
+
is_default_content_type = params['headers'].get('Content-Type') == settings.HTTP_DEFAULT_HEADERS['Content-Type']
|
|
506
|
+
|
|
507
|
+
# Get the payload for this request and set the correct headers and params
|
|
508
|
+
payload = self.get_payload()
|
|
509
|
+
if self.is_json:
|
|
510
|
+
params['json'] = payload
|
|
511
|
+
if is_default_content_type:
|
|
512
|
+
params['headers']['Content-Type'] = 'application/json; charset=utf-8'
|
|
513
|
+
|
|
514
|
+
elif isinstance(payload, dict):
|
|
515
|
+
params['data'] = payload
|
|
516
|
+
if is_default_content_type:
|
|
517
|
+
params['headers']['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
518
|
+
|
|
519
|
+
else:
|
|
520
|
+
# Here we accept the Content-Type that returned from the get_headers method and do not change it.
|
|
521
|
+
params['content'] = payload
|
|
522
|
+
|
|
523
|
+
return params
|
|
524
|
+
|
|
525
|
+
def _get_response_from_url(self) -> httpx.Response:
|
|
526
|
+
"""
|
|
527
|
+
Try to get/set data on url using POST request.
|
|
528
|
+
"""
|
|
529
|
+
params = self.get_request_params()
|
|
530
|
+
|
|
531
|
+
if settings.EVERYSK_HTTP_LOG_RESPONSE:
|
|
532
|
+
log.debug('HTTP %s request: %s', self.method, self.get_url(), extra={'labels': params})
|
|
533
|
+
|
|
534
|
+
# If the client is already set and not closed we use it
|
|
535
|
+
# otherwise we create a new client and close it after the request.
|
|
536
|
+
if self._client and not self._client.is_closed:
|
|
537
|
+
response = getattr(self._client, self.method.lower())(**params)
|
|
538
|
+
else:
|
|
539
|
+
with self._config.get_client(certificate=self.cert) as client:
|
|
540
|
+
response = getattr(client, self.method.lower())(**params)
|
|
541
|
+
|
|
542
|
+
if settings.EVERYSK_HTTP_LOG_RESPONSE:
|
|
543
|
+
dct = {
|
|
544
|
+
'status_code': response.status_code,
|
|
545
|
+
'time': response.elapsed.total_seconds(),
|
|
546
|
+
'headers': response.headers,
|
|
547
|
+
'content': response.content,
|
|
548
|
+
}
|
|
549
|
+
log.debug('HTTP %s response: %s', self.method, self.get_url(), extra={'labels': dct})
|
|
550
|
+
|
|
551
|
+
return response
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
###############################################################################
|
|
555
|
+
# HttpPOSTCompressedConnection Class Implementation
|
|
556
|
+
###############################################################################
|
|
557
|
+
class HttpPOSTCompressedConnection(HttpPOSTConnection):
|
|
558
|
+
def get_headers(self) -> dict:
|
|
559
|
+
"""
|
|
560
|
+
Headers needed to send HTTP Post methods.
|
|
561
|
+
"""
|
|
562
|
+
headers = super().get_headers()
|
|
563
|
+
headers['Content-Encoding'] = 'gzip'
|
|
564
|
+
return headers
|
|
565
|
+
|
|
566
|
+
def get_payload(self) -> dict:
|
|
567
|
+
"""
|
|
568
|
+
Make the correct payload body to pass on POST request.
|
|
569
|
+
"""
|
|
570
|
+
return compress(self.payload, protocol='gzip', serialize='json', use_undefined=True, add_class_path=True)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
###############################################################################
|
|
574
|
+
# HttpSDKPOSTConnection Class Implementation
|
|
575
|
+
###############################################################################
|
|
576
|
+
class HttpSDKPOSTConnection(HttpPOSTCompressedConnection):
|
|
577
|
+
is_json = BoolField(default=False, readonly=True)
|
|
578
|
+
|
|
579
|
+
class_name = StrField()
|
|
580
|
+
method_name = StrField()
|
|
581
|
+
self_obj: Any = None
|
|
582
|
+
params = DictField()
|
|
583
|
+
timeout = IntField(default=settings.EVERYSK_SDK_HTTP_DEFAULT_TIMEOUT)
|
|
584
|
+
|
|
585
|
+
def get_url(self) -> str:
|
|
586
|
+
return f'{settings.EVERYSK_SDK_URL}/{settings.EVERYSK_SDK_VERSION}/{settings.EVERYSK_SDK_ROUTE}'
|
|
587
|
+
|
|
588
|
+
def get_payload(self) -> dict:
|
|
589
|
+
"""
|
|
590
|
+
Make the correct payload body to pass on POST request.
|
|
591
|
+
"""
|
|
592
|
+
self.payload = {
|
|
593
|
+
'class_name': self.class_name,
|
|
594
|
+
'method_name': self.method_name,
|
|
595
|
+
'self_obj': self.self_obj,
|
|
596
|
+
'params': self.params,
|
|
597
|
+
}
|
|
598
|
+
return super().get_payload()
|
|
599
|
+
|
|
600
|
+
def get_headers(self) -> dict:
|
|
601
|
+
"""Headers needed to send HTTP Post methods."""
|
|
602
|
+
headers = super().get_headers()
|
|
603
|
+
everysk_api_sid = settings.EVERYSK_API_SID
|
|
604
|
+
everysk_api_token = settings.EVERYSK_API_TOKEN
|
|
605
|
+
|
|
606
|
+
if not everysk_api_sid:
|
|
607
|
+
raise InvalidArgumentError('Invalid API SID.')
|
|
608
|
+
if not everysk_api_token:
|
|
609
|
+
raise InvalidArgumentError('Invalid API TOKEN.')
|
|
610
|
+
|
|
611
|
+
headers['Authorization'] = f'Bearer {everysk_api_sid}:{everysk_api_token}'
|
|
612
|
+
|
|
613
|
+
return headers
|
|
614
|
+
|
|
615
|
+
def get_response_decode(self) -> dict:
|
|
616
|
+
"""
|
|
617
|
+
Try to fetch data from self.get_url and calling self._get_response_from_url for the complete response.
|
|
618
|
+
On HttpError, if self.message_error_check is True we will try connect again more 5 times.
|
|
619
|
+
Decompress the response.content
|
|
620
|
+
"""
|
|
621
|
+
response = self.get_response()
|
|
622
|
+
return loads(response.content, use_undefined=True, instantiate_object=True)
|
|
623
|
+
|
|
624
|
+
def message_error_check(self, message: str, status_code: int) -> bool: # pylint: disable=unused-argument
|
|
625
|
+
"""
|
|
626
|
+
If this method returns True, the connection will be tried again.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
message (str): The error message that occurred on the connection.
|
|
630
|
+
status_code (int): The status code of the response.
|
|
631
|
+
"""
|
|
632
|
+
return status_code in settings.EVERYSK_SDK_HTTP_RETRY_ERROR_CODES
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
###############################################################################
|
|
636
|
+
# HttpDELETEConnection Class Implementation
|
|
637
|
+
###############################################################################
|
|
638
|
+
class HttpDELETEConnection(HttpGETConnection):
|
|
639
|
+
"""
|
|
640
|
+
HttpDELETEConnection is a class that extends HttpGETConnection to handle HTTP DELETE requests.
|
|
641
|
+
"""
|
|
642
|
+
|
|
643
|
+
method = StrField(default='DELETE', readonly=True)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
###############################################################################
|
|
647
|
+
# HttpHEADConnection Class Implementation
|
|
648
|
+
###############################################################################
|
|
649
|
+
class HttpHEADConnection(HttpGETConnection):
|
|
650
|
+
"""
|
|
651
|
+
HttpHEADConnection is a class that extends HttpGETConnection to handle HTTP HEAD requests.
|
|
652
|
+
"""
|
|
653
|
+
|
|
654
|
+
method = StrField(default='HEAD', readonly=True)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
###############################################################################
|
|
658
|
+
# HttpOPTIONSConnection Class Implementation
|
|
659
|
+
###############################################################################
|
|
660
|
+
class HttpOPTIONSConnection(HttpGETConnection):
|
|
661
|
+
"""
|
|
662
|
+
HttpOPTIONSConnection is a class that extends HttpGETConnection to handle HTTP OPTIONS requests.
|
|
663
|
+
"""
|
|
664
|
+
|
|
665
|
+
method = StrField(default='OPTIONS', readonly=True)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
###############################################################################
|
|
669
|
+
# HttpPATCHConnection Class Implementation
|
|
670
|
+
###############################################################################
|
|
671
|
+
class HttpPATCHConnection(HttpPOSTConnection):
|
|
672
|
+
"""
|
|
673
|
+
Class that implements a interface for HTTP PATCH connections.
|
|
674
|
+
If self.is_json is True the PATCH method will be a JSON PATCH,
|
|
675
|
+
otherwise will be a Form PATCH Data.
|
|
676
|
+
"""
|
|
677
|
+
|
|
678
|
+
method = StrField(default='PATCH', readonly=True)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
###############################################################################
|
|
682
|
+
# HttpPUTConnection Class Implementation
|
|
683
|
+
###############################################################################
|
|
684
|
+
class HttpPUTConnection(HttpPOSTConnection):
|
|
685
|
+
"""
|
|
686
|
+
HttpPUTConnection is a class that extends HttpPOSTConnection to handle HTTP PUT requests.
|
|
687
|
+
If self.is_json is True the PUT method will be a JSON PUT,
|
|
688
|
+
otherwise will be a Form PUT Data.
|
|
689
|
+
"""
|
|
690
|
+
|
|
691
|
+
method = StrField(default='PUT', readonly=True)
|