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
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
from typing import Any, Self
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Do not use this class, use the constant Undefined that is already defined in the python builtins. ##
|
|
14
|
+
class UndefinedType:
|
|
15
|
+
"""
|
|
16
|
+
The UndefinedType class is used to represent an undefined value in the Everysk framework.
|
|
17
|
+
This class is designed to be immutable and to always represent the same undefined value.
|
|
18
|
+
We use it as default values for function parameters and class attributes to indicate that
|
|
19
|
+
the value is not set and to differentiate it from None or other possible values.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
default_error_message = 'This object is immutable.'
|
|
23
|
+
default_parse_string = '__UNDEFINED_VALUE__'
|
|
24
|
+
default_repr_string = '<Undefined value>'
|
|
25
|
+
block = False
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
if self.block:
|
|
29
|
+
raise NotImplementedError('Do not use this class, use the constant Undefined.')
|
|
30
|
+
|
|
31
|
+
def __bool__(self) -> bool:
|
|
32
|
+
"""Undefined object is always False"""
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
def __copy__(self) -> Self:
|
|
36
|
+
"""To keep consistence, this object will always be the same."""
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def __delattr__(self, name: str) -> None:
|
|
40
|
+
"""We could not delete attributes from this object."""
|
|
41
|
+
raise AttributeError(self.default_error_message)
|
|
42
|
+
|
|
43
|
+
def __deepcopy__(self, memo: dict | None = None) -> Self:
|
|
44
|
+
"""To keep consistence, this object will always be the same."""
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def __eq__(self, value: object) -> bool:
|
|
48
|
+
"""For an object created from the UndefinedType class to be equal to another, the classes must be equal."""
|
|
49
|
+
return isinstance(value, type(self))
|
|
50
|
+
|
|
51
|
+
def __getattr__(self, name: str) -> Any:
|
|
52
|
+
"""Undefined object don't have attributes."""
|
|
53
|
+
raise AttributeError(self.default_error_message)
|
|
54
|
+
|
|
55
|
+
def __hash__(self) -> int:
|
|
56
|
+
"""Must return an int that is used as hash for this object."""
|
|
57
|
+
return id(self)
|
|
58
|
+
|
|
59
|
+
def __repr__(self) -> str:
|
|
60
|
+
"""Fixed to be the same every time."""
|
|
61
|
+
return self.default_repr_string
|
|
62
|
+
|
|
63
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
64
|
+
"""We can't set any attribute to this object."""
|
|
65
|
+
if self.block:
|
|
66
|
+
raise AttributeError(self.default_error_message)
|
|
67
|
+
|
|
68
|
+
def __str__(self) -> str:
|
|
69
|
+
"""Fixed to be the same every time."""
|
|
70
|
+
return self.default_repr_string
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# (C) Copyright 2025 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
|
+
__all__ = ['TestCase', 'mock', 'skip', 'skipUnless', 'SkipTest', 'skipIf']
|
|
11
|
+
|
|
12
|
+
import difflib
|
|
13
|
+
import pprint
|
|
14
|
+
from time import time
|
|
15
|
+
from typing import Any
|
|
16
|
+
from unittest import TestCase as PythonTestCase, mock, skip, skipUnless, SkipTest, skipIf
|
|
17
|
+
from unittest.util import _common_shorten_repr
|
|
18
|
+
from warnings import warn
|
|
19
|
+
|
|
20
|
+
from everysk.config import settings
|
|
21
|
+
from everysk.core.object import BaseDict
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
###############################################################################
|
|
25
|
+
# TestCase Class Implementation
|
|
26
|
+
###############################################################################
|
|
27
|
+
class TestCase(PythonTestCase):
|
|
28
|
+
|
|
29
|
+
def _callTestMethod(self, method: callable) -> None:
|
|
30
|
+
# For some tests the time could not pass 1 second, gzip tests for example.
|
|
31
|
+
# So we use mock to fix the time for the tests.
|
|
32
|
+
original_time = time()
|
|
33
|
+
with mock.patch('time.time', return_value=original_time):
|
|
34
|
+
# We could not use super here because the stacklevel would be wrong
|
|
35
|
+
if method() is not None:
|
|
36
|
+
warn(
|
|
37
|
+
f'It is deprecated to return a value that is not None from a test case ({method})',
|
|
38
|
+
DeprecationWarning,
|
|
39
|
+
stacklevel=3
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def assertDictEqual(self, d1: dict | BaseDict, d2: dict | BaseDict, msg: str = None):
|
|
43
|
+
# pylint: disable=protected-access
|
|
44
|
+
self.assertIsInstance(d1, (dict, BaseDict), 'First argument is not a dictionary')
|
|
45
|
+
self.assertIsInstance(d2, (dict, BaseDict), 'Second argument is not a dictionary')
|
|
46
|
+
|
|
47
|
+
# We need to ensure that both objects are of the same type to proceed
|
|
48
|
+
if isinstance(d1, dict) and isinstance(d2, BaseDict):
|
|
49
|
+
d1 = type(d2)(**d1)
|
|
50
|
+
|
|
51
|
+
elif isinstance(d2, dict) and isinstance(d1, BaseDict):
|
|
52
|
+
d2 = type(d1)(**d2)
|
|
53
|
+
|
|
54
|
+
if d1 != d2:
|
|
55
|
+
standardMsg = '%s != %s' % _common_shorten_repr(d1, d2) # pylint: disable=consider-using-f-string, invalid-name
|
|
56
|
+
diff = ('\n' + '\n'.join(difflib.ndiff(
|
|
57
|
+
pprint.pformat(d1).splitlines(),
|
|
58
|
+
pprint.pformat(d2).splitlines())))
|
|
59
|
+
standardMsg = self._truncateMessage(standardMsg, diff) # pylint: disable=invalid-name
|
|
60
|
+
self.fail(self._formatMessage(msg, standardMsg))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
###############################################################################
|
|
64
|
+
# EveryskMagicMock Class Implementation
|
|
65
|
+
###############################################################################
|
|
66
|
+
class EveryskMagicMock(mock.MagicMock): # pylint: disable=too-many-ancestors
|
|
67
|
+
|
|
68
|
+
def __init__(self, *args: Any, **kw: Any) -> None:
|
|
69
|
+
super().__init__(*args, **kw)
|
|
70
|
+
# To avoid infinite recursion inside serialize.dumps we need to remove this method
|
|
71
|
+
delattr(self, settings.SERIALIZE_CONVERT_METHOD_NAME)
|
|
72
|
+
|
|
73
|
+
mock.MagicMock = EveryskMagicMock
|
everysk/core/workers.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
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 traceback
|
|
11
|
+
from typing import Any
|
|
12
|
+
from uuid import uuid4
|
|
13
|
+
|
|
14
|
+
from google.api_core.exceptions import (
|
|
15
|
+
AlreadyExists,
|
|
16
|
+
DeadlineExceeded,
|
|
17
|
+
ServiceUnavailable,
|
|
18
|
+
TooManyRequests
|
|
19
|
+
)
|
|
20
|
+
from google.cloud.tasks_v2 import (
|
|
21
|
+
CloudTasksClient,
|
|
22
|
+
CreateTaskRequest,
|
|
23
|
+
HttpMethod,
|
|
24
|
+
PauseQueueRequest,
|
|
25
|
+
ResumeQueueRequest,
|
|
26
|
+
Task,
|
|
27
|
+
Queue
|
|
28
|
+
)
|
|
29
|
+
from google.protobuf import duration_pb2
|
|
30
|
+
|
|
31
|
+
from everysk.core.compress import compress, decompress
|
|
32
|
+
from everysk.config import settings
|
|
33
|
+
from everysk.core.fields import IntField, StrField
|
|
34
|
+
from everysk.core.log import Logger
|
|
35
|
+
from everysk.core.object import BaseObject
|
|
36
|
+
from everysk.core.string import import_from_string
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
log = Logger('everysk-workers')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
###############################################################################
|
|
43
|
+
# BaseGoogle Class Implementation
|
|
44
|
+
###############################################################################
|
|
45
|
+
class BaseGoogle(BaseObject):
|
|
46
|
+
## Private attributes
|
|
47
|
+
_gtask: CloudTasksClient = None
|
|
48
|
+
|
|
49
|
+
## Public attributes
|
|
50
|
+
google_task_project = StrField(default=settings.EVERYSK_GOOGLE_CLOUD_PROJECT)
|
|
51
|
+
google_task_location = StrField(default=settings.EVERYSK_GOOGLE_CLOUD_LOCATION)
|
|
52
|
+
worker_id = StrField(required=True)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def gtask(self) -> CloudTasksClient:
|
|
56
|
+
# pylint: disable=protected-access
|
|
57
|
+
if self.__class__._gtask is None:
|
|
58
|
+
self.__class__._gtask = CloudTasksClient()
|
|
59
|
+
|
|
60
|
+
return self.__class__._gtask
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
###############################################################################
|
|
64
|
+
# TaskGoogle Class Implementation
|
|
65
|
+
###############################################################################
|
|
66
|
+
class TaskGoogle(BaseGoogle):
|
|
67
|
+
## Public attributes
|
|
68
|
+
google_task_name = StrField() # Must be a unique name if None is used, auto create this
|
|
69
|
+
timeout = IntField(default=600, min_size=15, max_size=1800) # https://cloud.google.com/tasks/docs/reference/rest/v2/projects.locations.queues.tasks
|
|
70
|
+
worker_url = StrField(required=True)
|
|
71
|
+
|
|
72
|
+
## Private methods
|
|
73
|
+
def __init__(self, **kwargs) -> None:
|
|
74
|
+
super().__init__(**kwargs)
|
|
75
|
+
# This facilitate to init it over pickle
|
|
76
|
+
self._received_kwargs = kwargs
|
|
77
|
+
if self.google_task_name is None:
|
|
78
|
+
self.google_task_name = f'{self.__class__.__name__}-{uuid4()}'
|
|
79
|
+
|
|
80
|
+
## GTask methods
|
|
81
|
+
def gtask_queue_path(self) -> str:
|
|
82
|
+
return self.gtask.queue_path(
|
|
83
|
+
project=self.google_task_project,
|
|
84
|
+
location=self.google_task_location,
|
|
85
|
+
queue=self.worker_id
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def gtask_task_path(self) -> str:
|
|
89
|
+
return self.gtask.task_path(
|
|
90
|
+
project=self.google_task_project,
|
|
91
|
+
location=self.google_task_location,
|
|
92
|
+
queue=self.worker_id,
|
|
93
|
+
task=self.google_task_name
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def gtask_create_task_request(self, body: dict) -> CreateTaskRequest:
|
|
97
|
+
# https://cloud.google.com/tasks/docs/creating-http-target-tasks#createtask_method
|
|
98
|
+
if self.timeout is not None:
|
|
99
|
+
deadline = duration_pb2.Duration() # pylint: disable=no-member
|
|
100
|
+
deadline.FromSeconds(self.timeout)
|
|
101
|
+
|
|
102
|
+
task = {
|
|
103
|
+
'name': self.gtask_task_path(),
|
|
104
|
+
'dispatch_deadline': deadline,
|
|
105
|
+
'http_request': {
|
|
106
|
+
'http_method': HttpMethod.POST,
|
|
107
|
+
'url': f'{self.worker_url}/worker/{self.worker_id}',
|
|
108
|
+
'headers': self.get_headers(),
|
|
109
|
+
'body': compress(body, serialize='pickle')
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return CreateTaskRequest(parent=self.gtask_queue_path(), task=task)
|
|
113
|
+
|
|
114
|
+
## Public methods
|
|
115
|
+
def get_headers(self) -> dict:
|
|
116
|
+
"""
|
|
117
|
+
Return the headers that must be used to receive the task.
|
|
118
|
+
Remember that body must be always a byte object, then on child
|
|
119
|
+
classes do like de example bellow.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
>>> def get_headers(self) -> dict:
|
|
123
|
+
... headers = super().get_headers()
|
|
124
|
+
... headers['new_header'] = 'bla bla'
|
|
125
|
+
... return headers
|
|
126
|
+
"""
|
|
127
|
+
return {'Content-type': 'application/octet-stream'}
|
|
128
|
+
|
|
129
|
+
def run(self) -> str:
|
|
130
|
+
""" This must return a str object to be used on worker logs """
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
def send_start(self) -> Queue:
|
|
134
|
+
resume_queue_request = ResumeQueueRequest(name=self.gtask_queue_path())
|
|
135
|
+
return self.gtask.resume_queue(request=resume_queue_request)
|
|
136
|
+
|
|
137
|
+
def send_pause(self) -> Queue:
|
|
138
|
+
pause_queue_request = PauseQueueRequest(name=self.gtask_queue_path())
|
|
139
|
+
return self.gtask.pause_queue(request=pause_queue_request)
|
|
140
|
+
|
|
141
|
+
def save(self, timeout: float = 30.0, retry_times: int = 0) -> Task:
|
|
142
|
+
"""
|
|
143
|
+
Saves this task on Google Cloud Tasks to be executed.
|
|
144
|
+
We will only retry if the Task Error is in (DeadlineExceeded, ServiceUnavailable, TooManyRequests).
|
|
145
|
+
Be careful, if the task name is random retry could duplicate the task.
|
|
146
|
+
# https://everysk.atlassian.net/browse/COD-1546
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
timeout (float, optional): Time to wait until the task is done. Defaults to 30.0.
|
|
150
|
+
retry_times (int, optional): Retry times if the task fails. Defaults to 0.
|
|
151
|
+
"""
|
|
152
|
+
# Timeout could not be greater than 30 seconds
|
|
153
|
+
timeout = min(timeout, 30.0)
|
|
154
|
+
|
|
155
|
+
body = {'cls': self.get_full_doted_class_path(), 'kwargs': self._received_kwargs}
|
|
156
|
+
task = None
|
|
157
|
+
try:
|
|
158
|
+
task = self.gtask.create_task(request=self.gtask_create_task_request(body), timeout=timeout)
|
|
159
|
+
|
|
160
|
+
except AlreadyExists:
|
|
161
|
+
log.debug('Google task already exists: %s', self.gtask_task_path())
|
|
162
|
+
|
|
163
|
+
except (DeadlineExceeded, ServiceUnavailable, TooManyRequests) as error:
|
|
164
|
+
if retry_times > 0:
|
|
165
|
+
retry_times = retry_times - 1
|
|
166
|
+
task = self.save(timeout=timeout, retry_times=retry_times)
|
|
167
|
+
else:
|
|
168
|
+
raise error
|
|
169
|
+
|
|
170
|
+
return task
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
###############################################################################
|
|
174
|
+
# WorkerGoogle Class Implementation
|
|
175
|
+
###############################################################################
|
|
176
|
+
class WorkerGoogle(BaseGoogle):
|
|
177
|
+
"""
|
|
178
|
+
Example:
|
|
179
|
+
from flask import Flask, request
|
|
180
|
+
from everysk.core.fields import StrField
|
|
181
|
+
from everysk.core.workers import WorkerGoogle
|
|
182
|
+
|
|
183
|
+
WORKER_ID = 'worker-id'
|
|
184
|
+
|
|
185
|
+
class FirestoreSaveWorker(WorkerGoogle):
|
|
186
|
+
google_task_location: StrField(default='location', readonly=True)
|
|
187
|
+
google_task_project: StrField(default='project', readonly=True)
|
|
188
|
+
worker_id: StrField(default=WORKER_ID, readonly=True)
|
|
189
|
+
|
|
190
|
+
## Flask app
|
|
191
|
+
app = Flask(__name__)
|
|
192
|
+
|
|
193
|
+
@app.route(f'/worker/{WORKER_ID}', methods=['POST'])
|
|
194
|
+
def firestore_save():
|
|
195
|
+
return FirestoreSaveWorker.worker_run(
|
|
196
|
+
headers=request.headers,
|
|
197
|
+
data=request.data
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
## Main run function
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
app.run(host=settings.FLASK_HOST, port=settings.FLASK_PORT, debug=settings.DEBUG)
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
## Public methods
|
|
206
|
+
def check_google_task(self, headers: dict) -> bool:
|
|
207
|
+
""" Check Google Task readers to ensure that the request come from then. """
|
|
208
|
+
queue_name = headers.get('X-Cloudtasks-Queuename', None)
|
|
209
|
+
user_agent = headers.get('User-Agent', None)
|
|
210
|
+
return queue_name == self.worker_id and user_agent == 'Google-Cloud-Tasks'
|
|
211
|
+
|
|
212
|
+
def run_task(self, cls: str, kwargs: dict) -> Any:
|
|
213
|
+
"""
|
|
214
|
+
Run the task, if pause is activated from the Tread, stops
|
|
215
|
+
until the self.worker_start message arrives.
|
|
216
|
+
"""
|
|
217
|
+
cls = import_from_string(cls)
|
|
218
|
+
task = cls(**kwargs)
|
|
219
|
+
return task.run()
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def worker_run(cls, headers: dict, data: bytes, worker_id: str = None) -> None:
|
|
223
|
+
if worker_id is None:
|
|
224
|
+
worker = cls()
|
|
225
|
+
else:
|
|
226
|
+
worker = cls(worker_id=worker_id)
|
|
227
|
+
|
|
228
|
+
if not worker.check_google_task(headers):
|
|
229
|
+
message = {'error': True, 'message': f"Couldn't validate Google headers - {headers}"}
|
|
230
|
+
log.error(message['message'])
|
|
231
|
+
return message
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
post_data = decompress(data, serialize='pickle')
|
|
235
|
+
result = worker.run_task(**post_data)
|
|
236
|
+
return {'error': False, 'result': result}
|
|
237
|
+
|
|
238
|
+
except Exception as error: #pylint: disable=broad-exception-caught
|
|
239
|
+
message = f'Worker {cls.worker_id} error: {str(error)}'
|
|
240
|
+
log.error('Worker %s error: %s', cls.worker_id, traceback.format_exc())
|
|
241
|
+
return {'error': True, 'message': message}
|
everysk/sdk/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# (C) Copyright 2025 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
|
+
from everysk.core.string import import_from_string
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
###############################################################################
|
|
14
|
+
# Private Functions Implementation
|
|
15
|
+
###############################################################################
|
|
16
|
+
def __getattr__(_name: str):
|
|
17
|
+
from everysk.config import settings # pylint: disable=import-outside-toplevel
|
|
18
|
+
modules = settings.EVERYSK_SDK_MODULES_PATH
|
|
19
|
+
|
|
20
|
+
if _name in modules:
|
|
21
|
+
return import_from_string(modules[_name])
|
|
22
|
+
|
|
23
|
+
raise AttributeError(f"cannot import name '{_name}' from everysk.sdk")
|
everysk/sdk/base.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
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 inspect
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from everysk.core.exceptions import HttpError, SDKError
|
|
14
|
+
from everysk.core.http import HttpSDKPOSTConnection, httpx
|
|
15
|
+
from everysk.core.object import BaseDict, BaseObject
|
|
16
|
+
from everysk.core.string import import_from_string
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
###############################################################################
|
|
20
|
+
# Public Functions Implementation
|
|
21
|
+
###############################################################################
|
|
22
|
+
def handler_input_args(input_args: Any) -> Any:
|
|
23
|
+
"""
|
|
24
|
+
This function handles the input arguments and returns the dictionary.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
input_args (Any): The input arguments to be handled.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Any: The parsed object.
|
|
31
|
+
"""
|
|
32
|
+
ret: Any = input_args
|
|
33
|
+
if isinstance(input_args, dict):
|
|
34
|
+
ret = BaseDict()
|
|
35
|
+
for key, value in input_args.items():
|
|
36
|
+
ret[key] = handler_input_args(value)
|
|
37
|
+
|
|
38
|
+
elif isinstance(input_args, list):
|
|
39
|
+
ret = type(input_args)()
|
|
40
|
+
for item in input_args:
|
|
41
|
+
ret.append(handler_input_args(item))
|
|
42
|
+
|
|
43
|
+
return ret
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
###############################################################################
|
|
47
|
+
# BaseSDK Class Implementation
|
|
48
|
+
###############################################################################
|
|
49
|
+
class BaseSDK(BaseObject):
|
|
50
|
+
"""
|
|
51
|
+
A base class for SDK classes.
|
|
52
|
+
|
|
53
|
+
This class provides a base implementation for SDK classes.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def get_response(cls, **kwargs: dict) -> Any:
|
|
58
|
+
"""
|
|
59
|
+
Get a response from an SDK method.
|
|
60
|
+
|
|
61
|
+
This method sends an HTTP POST request to a remote service and returns the response as a dictionary.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
**kwargs (dict): Keyword arguments used to configure the HTTP request and SDK behavior.
|
|
65
|
+
|
|
66
|
+
Keyword Args:
|
|
67
|
+
class_name (str, optional): The name of the SDK class making the request. Defaults to the class name of the calling class.
|
|
68
|
+
method_name (str, optional): The name of the SDK method making the request. Defaults to the name of the calling function.
|
|
69
|
+
self_obj (object, optional): An instance of the calling SDK class, if applicable. Defaults to None.
|
|
70
|
+
params (dict, optional): Additional parameters to include in the HTTP request. Defaults to an empty dictionary.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Any: The response from the remote service.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
SDKError: If there is an issue with the SDK operation.
|
|
77
|
+
"""
|
|
78
|
+
# Set default values for keyword arguments if not provided
|
|
79
|
+
kwargs.setdefault('class_name', cls.__name__)
|
|
80
|
+
kwargs.setdefault('method_name', inspect.stack()[1].function)
|
|
81
|
+
kwargs.setdefault('self_obj', None)
|
|
82
|
+
kwargs.setdefault('params', {})
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
response: httpx.Response = HttpSDKPOSTConnection(**kwargs).get_response_decode()
|
|
86
|
+
except HttpError as error:
|
|
87
|
+
raise SDKError(error.msg) from error
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
response
|
|
91
|
+
and isinstance(response, (dict, BaseDict))
|
|
92
|
+
and 'error_message' in response
|
|
93
|
+
and 'error_module' in response
|
|
94
|
+
):
|
|
95
|
+
error_module = import_from_string(response['error_module'])
|
|
96
|
+
raise error_module(response['error_message'])
|
|
97
|
+
|
|
98
|
+
return response
|