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.
Files changed (137) hide show
  1. everysk/__init__.py +30 -0
  2. everysk/_version.py +683 -0
  3. everysk/api/__init__.py +61 -0
  4. everysk/api/api_requestor.py +167 -0
  5. everysk/api/api_resources/__init__.py +23 -0
  6. everysk/api/api_resources/api_resource.py +371 -0
  7. everysk/api/api_resources/calculation.py +779 -0
  8. everysk/api/api_resources/custom_index.py +42 -0
  9. everysk/api/api_resources/datastore.py +81 -0
  10. everysk/api/api_resources/file.py +42 -0
  11. everysk/api/api_resources/market_data.py +223 -0
  12. everysk/api/api_resources/parser.py +66 -0
  13. everysk/api/api_resources/portfolio.py +43 -0
  14. everysk/api/api_resources/private_security.py +42 -0
  15. everysk/api/api_resources/report.py +65 -0
  16. everysk/api/api_resources/report_template.py +39 -0
  17. everysk/api/api_resources/tests.py +115 -0
  18. everysk/api/api_resources/worker_execution.py +64 -0
  19. everysk/api/api_resources/workflow.py +65 -0
  20. everysk/api/api_resources/workflow_execution.py +93 -0
  21. everysk/api/api_resources/workspace.py +42 -0
  22. everysk/api/http_client.py +63 -0
  23. everysk/api/tests.py +32 -0
  24. everysk/api/utils.py +262 -0
  25. everysk/config.py +451 -0
  26. everysk/core/_tests/serialize/test_json.py +336 -0
  27. everysk/core/_tests/serialize/test_orjson.py +295 -0
  28. everysk/core/_tests/serialize/test_pickle.py +48 -0
  29. everysk/core/cloud_function/main.py +78 -0
  30. everysk/core/cloud_function/tests.py +86 -0
  31. everysk/core/compress.py +245 -0
  32. everysk/core/datetime/__init__.py +12 -0
  33. everysk/core/datetime/calendar.py +144 -0
  34. everysk/core/datetime/date.py +424 -0
  35. everysk/core/datetime/date_expression.py +299 -0
  36. everysk/core/datetime/date_mixin.py +1475 -0
  37. everysk/core/datetime/date_settings.py +30 -0
  38. everysk/core/datetime/datetime.py +713 -0
  39. everysk/core/exceptions.py +435 -0
  40. everysk/core/fields.py +1176 -0
  41. everysk/core/firestore.py +555 -0
  42. everysk/core/fixtures/_settings.py +29 -0
  43. everysk/core/fixtures/other/_settings.py +18 -0
  44. everysk/core/fixtures/user_agents.json +88 -0
  45. everysk/core/http.py +691 -0
  46. everysk/core/lists.py +92 -0
  47. everysk/core/log.py +709 -0
  48. everysk/core/number.py +37 -0
  49. everysk/core/object.py +1469 -0
  50. everysk/core/redis.py +1021 -0
  51. everysk/core/retry.py +51 -0
  52. everysk/core/serialize.py +674 -0
  53. everysk/core/sftp.py +414 -0
  54. everysk/core/signing.py +53 -0
  55. everysk/core/slack.py +127 -0
  56. everysk/core/string.py +199 -0
  57. everysk/core/tests.py +240 -0
  58. everysk/core/threads.py +199 -0
  59. everysk/core/undefined.py +70 -0
  60. everysk/core/unittests.py +73 -0
  61. everysk/core/workers.py +241 -0
  62. everysk/sdk/__init__.py +23 -0
  63. everysk/sdk/base.py +98 -0
  64. everysk/sdk/brutils/cnpj.py +391 -0
  65. everysk/sdk/brutils/cnpj_pd.py +129 -0
  66. everysk/sdk/engines/__init__.py +26 -0
  67. everysk/sdk/engines/cache.py +185 -0
  68. everysk/sdk/engines/compliance.py +37 -0
  69. everysk/sdk/engines/cryptography.py +69 -0
  70. everysk/sdk/engines/expression.cp312-win_amd64.pyd +0 -0
  71. everysk/sdk/engines/expression.pyi +55 -0
  72. everysk/sdk/engines/helpers.cp312-win_amd64.pyd +0 -0
  73. everysk/sdk/engines/helpers.pyi +26 -0
  74. everysk/sdk/engines/lock.py +120 -0
  75. everysk/sdk/engines/market_data.py +244 -0
  76. everysk/sdk/engines/settings.py +19 -0
  77. everysk/sdk/entities/__init__.py +23 -0
  78. everysk/sdk/entities/base.py +784 -0
  79. everysk/sdk/entities/base_list.py +131 -0
  80. everysk/sdk/entities/custom_index/base.py +209 -0
  81. everysk/sdk/entities/custom_index/settings.py +29 -0
  82. everysk/sdk/entities/datastore/base.py +160 -0
  83. everysk/sdk/entities/datastore/settings.py +17 -0
  84. everysk/sdk/entities/fields.py +375 -0
  85. everysk/sdk/entities/file/base.py +215 -0
  86. everysk/sdk/entities/file/settings.py +63 -0
  87. everysk/sdk/entities/portfolio/base.py +248 -0
  88. everysk/sdk/entities/portfolio/securities.py +241 -0
  89. everysk/sdk/entities/portfolio/security.py +580 -0
  90. everysk/sdk/entities/portfolio/settings.py +97 -0
  91. everysk/sdk/entities/private_security/base.py +226 -0
  92. everysk/sdk/entities/private_security/settings.py +17 -0
  93. everysk/sdk/entities/query.py +603 -0
  94. everysk/sdk/entities/report/base.py +214 -0
  95. everysk/sdk/entities/report/settings.py +23 -0
  96. everysk/sdk/entities/script.py +310 -0
  97. everysk/sdk/entities/secrets/base.py +128 -0
  98. everysk/sdk/entities/secrets/script.py +119 -0
  99. everysk/sdk/entities/secrets/settings.py +17 -0
  100. everysk/sdk/entities/settings.py +48 -0
  101. everysk/sdk/entities/tags.py +174 -0
  102. everysk/sdk/entities/worker_execution/base.py +307 -0
  103. everysk/sdk/entities/worker_execution/settings.py +63 -0
  104. everysk/sdk/entities/workflow_execution/base.py +113 -0
  105. everysk/sdk/entities/workflow_execution/settings.py +32 -0
  106. everysk/sdk/entities/workspace/base.py +99 -0
  107. everysk/sdk/entities/workspace/settings.py +27 -0
  108. everysk/sdk/settings.py +67 -0
  109. everysk/sdk/tests.py +105 -0
  110. everysk/sdk/worker_base.py +47 -0
  111. everysk/server/__init__.py +9 -0
  112. everysk/server/applications.py +63 -0
  113. everysk/server/endpoints.py +516 -0
  114. everysk/server/example_api.py +69 -0
  115. everysk/server/middlewares.py +80 -0
  116. everysk/server/requests.py +62 -0
  117. everysk/server/responses.py +119 -0
  118. everysk/server/routing.py +64 -0
  119. everysk/server/settings.py +36 -0
  120. everysk/server/tests.py +36 -0
  121. everysk/settings.py +98 -0
  122. everysk/sql/__init__.py +9 -0
  123. everysk/sql/connection.py +232 -0
  124. everysk/sql/model.py +376 -0
  125. everysk/sql/query.py +417 -0
  126. everysk/sql/row_factory.py +63 -0
  127. everysk/sql/settings.py +49 -0
  128. everysk/sql/utils.py +129 -0
  129. everysk/tests.py +23 -0
  130. everysk/utils.py +81 -0
  131. everysk/version.py +15 -0
  132. everysk_lib-1.10.2.dist-info/.gitignore +5 -0
  133. everysk_lib-1.10.2.dist-info/METADATA +326 -0
  134. everysk_lib-1.10.2.dist-info/RECORD +137 -0
  135. everysk_lib-1.10.2.dist-info/WHEEL +5 -0
  136. everysk_lib-1.10.2.dist-info/licenses/LICENSE.txt +9 -0
  137. everysk_lib-1.10.2.dist-info/top_level.txt +2 -0
@@ -0,0 +1,62 @@
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__ = ['JSONRequest', 'Request']
11
+
12
+ from starlette.requests import Request as _Request
13
+
14
+ from everysk.core.compress import decompress
15
+ from everysk.core.log import Logger
16
+ from everysk.core.serialize import loads
17
+
18
+
19
+ log = Logger(__name__)
20
+
21
+
22
+ ###############################################################################
23
+ # Request Class Implementation
24
+ ###############################################################################
25
+ class Request(_Request):
26
+
27
+ async def body(self) -> bytes:
28
+ """
29
+ Method to return the body of the request.
30
+ If we receive a request with a Content-Encoding header, we decompress the body.
31
+ """
32
+ # If self._body is set then body was already read.
33
+ if not hasattr(self, '_body'):
34
+ body = await super().body()
35
+ # We only decompress the body if it is not empty.
36
+ if body:
37
+ content_encoding = self.headers.get('Content-Encoding', '').lower()
38
+ if 'gzip' in content_encoding:
39
+ try:
40
+ body = decompress(body, protocol='gzip', serialize=None )
41
+ except Exception: # pylint: disable=broad-except
42
+ log.error('Error decompressing the request body.', extra={'labels': {'body': body}})
43
+
44
+ self._body = body # pylint: disable=attribute-defined-outside-init
45
+ return self._body
46
+
47
+
48
+ ###############################################################################
49
+ # JSONRequest Class Implementation
50
+ ###############################################################################
51
+ class JSONRequest(Request):
52
+
53
+ async def json(self):
54
+ """
55
+ Method to return the JSON content of the request body.
56
+ It uses the loads function from the core.serialize module to parse the body.
57
+ """
58
+ if not hasattr(self, '_json'):
59
+ body = await self.body()
60
+ self._json = loads(body, protocol='json', use_undefined=True) # pylint: disable=attribute-defined-outside-init
61
+
62
+ return self._json
@@ -0,0 +1,119 @@
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__ = [
11
+ 'FileResponse',
12
+ 'HTMLResponse',
13
+ 'JSONResponse',
14
+ 'PlainTextResponse',
15
+ 'RedirectResponse',
16
+ 'Response',
17
+ 'StreamingResponse',
18
+ ]
19
+
20
+ from typing import Any, Literal
21
+
22
+ from starlette.background import BackgroundTask
23
+ from starlette.responses import (
24
+ FileResponse,
25
+ HTMLResponse,
26
+ PlainTextResponse,
27
+ RedirectResponse,
28
+ Response,
29
+ StreamingResponse,
30
+ )
31
+
32
+ from everysk.core.object import BaseObject
33
+ from everysk.core.serialize import dumps
34
+
35
+
36
+ ###############################################################################
37
+ # JSONResponse Class Implementation
38
+ ###############################################################################
39
+ class DumpsParams(BaseObject):
40
+ add_class_path: bool = True
41
+ date_format: str | None = None
42
+ datetime_format: str | None = None
43
+ indent: int | None = None
44
+ protocol: Literal['json', 'orjson'] = 'json'
45
+ return_type: Literal['bytes', 'str'] = 'bytes'
46
+ separators: tuple[str] = (',', ':')
47
+ sort_keys: bool = False
48
+ use_undefined: bool = True
49
+ decode_bytes: bool = True
50
+
51
+ def __init__(
52
+ self,
53
+ add_class_path: bool = True,
54
+ date_format: str | None = None,
55
+ datetime_format: str | None = None,
56
+ indent: int | None = None,
57
+ protocol: Literal['json', 'orjson'] = 'json',
58
+ return_type: Literal['bytes', 'str'] = 'bytes',
59
+ separators: tuple[str] = (',', ':'),
60
+ sort_keys: bool = False,
61
+ use_undefined: bool = True,
62
+ decode_bytes: bool = True,
63
+ **kwargs,
64
+ ):
65
+ super().__init__(
66
+ add_class_path=add_class_path,
67
+ date_format=date_format,
68
+ datetime_format=datetime_format,
69
+ indent=indent,
70
+ protocol=protocol,
71
+ return_type=return_type,
72
+ separators=separators,
73
+ sort_keys=sort_keys,
74
+ use_undefined=use_undefined,
75
+ decode_bytes=decode_bytes,
76
+ **kwargs,
77
+ )
78
+
79
+ def to_dict(self): # pylint: disable=arguments-differ
80
+ dct = super().to_dict(add_class_path=True, recursion=True)
81
+ # we remove all private keys because we don't want them in the serialized output
82
+ return {key: value for key, value in dct.items() if not key.startswith('_')}
83
+
84
+
85
+ class JSONResponse(Response):
86
+ ## Public attributes
87
+ media_type = 'application/json; charset=UTF-8'
88
+ serialize_dumps_params: DumpsParams = DumpsParams()
89
+
90
+ def __init__(
91
+ self,
92
+ content: Any = None,
93
+ status_code: int = 200,
94
+ headers: dict[str, str] | None = None,
95
+ media_type: str | None = None,
96
+ background: BackgroundTask | None = None,
97
+ serialize_dumps_params: DumpsParams | None = None
98
+ ) -> None:
99
+ # Must be before the render method that runs inside init
100
+ if serialize_dumps_params:
101
+ self.serialize_dumps_params = serialize_dumps_params
102
+
103
+ super().__init__(
104
+ content=content,
105
+ status_code=status_code,
106
+ headers=headers,
107
+ media_type=media_type,
108
+ background=background
109
+ )
110
+
111
+ def render(self, content: Any) -> bytes:
112
+ """
113
+ Serialize the content to JSON format using the dumps function from the core.serialize module.
114
+
115
+ Args:
116
+ content (Any): The content to be serialized.
117
+ """
118
+ # with indent=None and separators=(",", ":"), the JSON will be minified
119
+ return dumps(content, **self.serialize_dumps_params.to_dict())
@@ -0,0 +1,64 @@
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__ = ['Route', 'RouteLazy']
11
+
12
+ from starlette.routing import Route, compile_path
13
+ from starlette.types import Receive, Scope, Send
14
+
15
+ from everysk.core.string import import_from_string
16
+
17
+
18
+ ###############################################################################
19
+ # RouteLazy Class Implementation
20
+ ###############################################################################
21
+ class RouteLazy(Route):
22
+ app: callable = None
23
+ endpoint: str = None
24
+ include_in_schema: bool = True
25
+ methods: set = None
26
+ name: str = None
27
+ path: str = None
28
+
29
+ def __init__(self, path: str, endpoint: str, name: str | None = None) -> None: # pylint: disable=super-init-not-called
30
+ """
31
+ Route class that will lazy load the endpoint class.
32
+
33
+ Args:
34
+ path (str): The path of the route always start with '/'.
35
+ endpoint (str): Full doted class path of the endpoint.
36
+ name (str | None, optional): A name for this endpoint. Defaults to the same value in endpoint.
37
+
38
+ Raises:
39
+ ValueError: If the path doesn't start with '/'.
40
+ """
41
+ if not path.startswith('/'):
42
+ raise ValueError("Routed paths must start with '/'")
43
+
44
+ self.path = path
45
+ self.endpoint = endpoint
46
+ self.name = endpoint if name is None else name
47
+ self.path_regex, self.path_format, self.param_convertors = compile_path(path)
48
+
49
+ async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:
50
+ """
51
+ Method to handle the incoming request and execute it.
52
+ If the endpoint is a string, it will import the endpoint class and execute it.
53
+
54
+ Args:
55
+ scope (Scope): ASGI scope dictionary.
56
+ receive (Receive): ASGI receive data.
57
+ send (Send): ASGI send data.
58
+ """
59
+ if isinstance(self.endpoint, str):
60
+ self.app = import_from_string(self.endpoint)
61
+ else:
62
+ self.app = self.endpoint
63
+
64
+ await self.app(scope, receive, send)
@@ -0,0 +1,36 @@
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
+
11
+ EVERYSK_SERVER_DEBUG: bool = False
12
+
13
+ # Used to check access on JSONEndpoints
14
+ EVERYSK_SERVER_REST_KEY_NAME: str = 'X-Rest-Key'
15
+ EVERYSK_SERVER_REST_KEY_VALUE: str = '12345'
16
+
17
+ # Used by the GZipMiddleware to compress the response
18
+ EVERYSK_SERVER_GZIP_MINIMUM_SIZE: int = 1024
19
+ EVERYSK_SERVER_GZIP_COMPRESS_LEVEL: int = 9
20
+
21
+ # The codes that will be logged by the Logger
22
+ EVERYSK_SERVER_CODES_LOG: tuple = (500,)
23
+
24
+ # URL to use in the redirect response
25
+ EVERYSK_SERVER_REDIRECT_URL: str = None
26
+
27
+ # To enable GZipMiddleware
28
+ EVERYSK_SERVER_GZIP_MIDDLEWARE_ENABLED: bool = True
29
+
30
+ # To enable SecurityMiddleware
31
+ EVERYSK_SERVER_SECURITY_MIDDLEWARE_ENABLED: bool = True
32
+
33
+ # Size of the error message to log in the HTTP error response
34
+ # If the error message is larger than this size, it will be truncated
35
+ # and the full message will be in the extra context of the log
36
+ EVERYSK_SERVER_HTTP_ERROR_MESSAGE_SIZE: int = 256
@@ -0,0 +1,36 @@
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
+ # pylint: disable=unused-import
11
+
12
+ try:
13
+ from everysk.server._tests.applications import ServerApplication as EveryskLibServerApplication
14
+
15
+ from everysk.server._tests.endpoints import (
16
+ BaseEndpointTestCase as EveryskLibBaseEndpointTestCase,
17
+ BaseEndpointTestCaseAsync as EveryskLibBaseEndpointTestCaseAsync,
18
+ DumpsParamsTestCase as EveryskLibDumpsParamsTestCase,
19
+ HealthCheckEndpointTestCaseAsync as EveryskLibHealthCheckEndpointTestCaseAsync,
20
+ JSONEndpointTestCase as EveryskLibJSONEndpointTestCase,
21
+ JSONEndpointTestCaseAsync as EveryskLibJSONEndpointTestCaseAsync,
22
+ NotAllowedMethodsTestCaseAsync as EveryskLibNotAllowedMethodsTestCaseAsync,
23
+ RedirectEndpointTestCase as EveryskLibRedirectEndpointTestCase,
24
+ RedirectEndpointTestCaseAsync as EveryskLibRedirectEndpointTestCaseAsync,
25
+ )
26
+
27
+ from everysk.server._tests.middlewares import (
28
+ GZipMiddlewareTestCaseAsync as EveryskLibGZipMiddlewareTestCaseAsync,
29
+ SecurityHeadersMiddlewareTestCaseAsync as EveryskLibSecurityHeadersMiddlewareTestCaseAsync,
30
+ UpdateMiddlewaresTestCase as EveryskLibUpdateMiddlewaresTestCase
31
+ )
32
+ from everysk.server._tests.routing import RouteLazyTestCaseAsync as EveryskLibRouteLazyTestCaseAsync
33
+ except ModuleNotFoundError as error:
34
+ # This will prevent running these tests if requests is not installed
35
+ if not error.args[0].startswith("No module named 'starlette'"):
36
+ raise error
everysk/settings.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 tempfile
11
+
12
+ from everysk.core.fields import BoolField, ChoiceField, DictField, FloatField, IntField, ListField, StrField
13
+
14
+ ## Project settings
15
+ DEBUG = BoolField(default=True)
16
+
17
+ # PROD | DEV | LOCAL
18
+ PROFILE = ChoiceField(default='DEV', choices=('PROD', 'DEV', 'LOCAL'))
19
+
20
+ ## Redis
21
+ REDIS_HOST = StrField(default='0.0.0.0') # noqa: S104
22
+ REDIS_PORT = IntField(default=6379)
23
+ REDIS_NAMESPACE = StrField()
24
+ REDIS_HEALTH_CHECK_INTERVAL = IntField(default=30) # seconds
25
+ REDIS_RETRY_ATTEMPTS = IntField(default=3)
26
+ REDIS_RETRY_BACKOFF_MIN = FloatField(default=0.05) # Minimum backoff between each retry in seconds
27
+ REDIS_RETRY_BACKOFF_MAX = FloatField(default=0.5) # Maximum backoff between each retry in seconds
28
+ REDIS_RETRY_EXTRA_ERROR_LIST = ListField() # Added more errors to retry
29
+ REDIS_SOCKET_KEEPALIVE = BoolField(default=True)
30
+ REDIS_SOCKET_TIMEOUT = IntField(default=120) # seconds
31
+ REDIS_SHOW_LOGS = BoolField(default=False)
32
+
33
+ ## Everysk SIGNING key is used to verify the integrity of the data
34
+ EVERYSK_SIGNING_KEY = StrField(default=None)
35
+
36
+ ## Google Cloud
37
+ EVERYSK_GOOGLE_CLOUD_LOCATION = StrField(default='us-central1')
38
+ EVERYSK_GOOGLE_CLOUD_PROJECT = StrField()
39
+
40
+ ## Logger settings
41
+ # This is the default app server to infer the headers and payload
42
+ LOGGING_APP_SERVER = StrField(default='flask')
43
+
44
+ # This is the default string to create the Google Cloud Trace ID
45
+ LOGGING_GOOGLE_CLOUD_TRACE_ID = StrField(default='projects/{EVERYSK_GOOGLE_CLOUD_PROJECT}/traces')
46
+
47
+ # This is the default Formatter for the logs
48
+ LOGGING_JSON = BoolField(default=False)
49
+
50
+ ## Slack URL to send messages
51
+ SLACK_URL = StrField()
52
+
53
+ ## Activate/deactivate HTTP Log for every request/response
54
+ EVERYSK_HTTP_LOG_RESPONSE = BoolField(default=False)
55
+
56
+ ## Activate/deactivate HTTP Log for retry connections
57
+ EVERYSK_HTTP_LOG_RETRY = BoolField(default=False)
58
+
59
+ ## List of HTTP Success status codes that do not raise HTTPError
60
+ HTTP_SUCCESS_STATUS_CODES = ListField(default=[200, 201, 202, 204, 303], readonly=True)
61
+
62
+ ## HTTP default headers used to send requests
63
+ HTTP_DEFAULT_HEADERS = DictField(
64
+ default={
65
+ 'Accept-Encoding': 'gzip',
66
+ 'Accept-Language': 'en-US, en;q=0.9, pt-BR;q=0.8, pt;q=0.7',
67
+ 'Cache-control': 'no-cache',
68
+ 'Content-Type': 'text/html; charset=UTF-8',
69
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', # noqa: E501
70
+ },
71
+ readonly=True,
72
+ )
73
+ HTTP_DEFAULT_TIMEOUT = IntField(default=30)
74
+ HTTP_DEFAULT_SSL_VERIFY = BoolField(default=True)
75
+ HTTP_DEFAULT_RETRY_LIMIT = IntField(default=5)
76
+ HTTP_DEFAULT_RETRY_END_SECONDS = IntField(default=30)
77
+ HTTP_DEFAULT_RETRY_START_SECONDS = IntField(default=5)
78
+
79
+ ## Serialization settings
80
+ SERIALIZE_CONVERT_METHOD_NAME = StrField(default='to_native')
81
+ SERIALIZE_DATE_KEY = StrField(default='__date__')
82
+ SERIALIZE_DATETIME_KEY = StrField('__datetime__')
83
+ SERIALIZE_UNDEFINED_KEY = StrField('__undefined__')
84
+ SERIALIZE_USE_UNDEFINED = BoolField(default=False)
85
+
86
+ ## Activate/deactivate the use of the verify flag on HTTP requests.
87
+ # By default the value is Undefined and the value comes from the self._config.ssl_verify
88
+ # attribute in the HttpConnection class, if it is defined we use it.
89
+ HTTP_REQUESTS_VERIFY = BoolField(default=Undefined)
90
+
91
+ # Activate/deactivate the use of random user agents on HTTP requests
92
+ HTTP_USE_RANDOM_USER_AGENT = BoolField(default=False)
93
+
94
+ # Default directory to use the known_hosts file and other SFTP configurations
95
+ EVERYSK_SFTP_DIR = StrField(default=f'{tempfile.gettempdir()}/sftp')
96
+
97
+ # Enable to show retry logs
98
+ RETRY_SHOW_LOGS = BoolField(default=False)
@@ -0,0 +1,9 @@
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
+ ###############################################################################
@@ -0,0 +1,232 @@
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 collections.abc import Callable
11
+ from contextvars import ContextVar, Token
12
+ from os import getpid
13
+ from types import TracebackType
14
+ from typing import Literal
15
+
16
+ from psycopg import Connection, OperationalError
17
+ from psycopg_pool import ConnectionPool as _ConnectionPool
18
+
19
+ from everysk.config import settings
20
+ from everysk.core.log import Logger
21
+ from everysk.core.retry import retry
22
+ from everysk.sql.row_factory import cls_row, dict_row
23
+
24
+ _CONNECTIONS: dict[str, 'ConnectionPool'] = {}
25
+ log = Logger('everysk-lib-sql-query')
26
+
27
+
28
+ def _log(message: str, extra: dict | None = None) -> None:
29
+ if settings.POSTGRESQL_LOG_QUERIES:
30
+ log.debug(message, extra=extra)
31
+
32
+
33
+ class ConnectionPool(_ConnectionPool):
34
+ def __del__(self) -> None:
35
+ # To close the connections when the pool is deleted
36
+ # https://everysk.atlassian.net/browse/COD-8885
37
+ try:
38
+ return super().__del__()
39
+ except RuntimeError:
40
+ # The connection is already closed or discarded because we cannot join the current thread
41
+ # RuntimeError: cannot join current thread
42
+ pass
43
+
44
+ return None
45
+
46
+
47
+ class Transaction:
48
+ """
49
+ Context manager for PostgreSQL transactions.
50
+
51
+ Usage:
52
+
53
+ >>> from everysk.sql.connection import Transaction, execute
54
+ >>> with Transaction():
55
+ ... result = execute("SELECT * FROM my_table WHERE id = %s", params={"id": 1})
56
+ ... # Perform other database operations within the transaction
57
+
58
+ """
59
+
60
+ ## Private attributes
61
+ _connection: Connection
62
+ _pool: ConnectionPool
63
+ _token: Token
64
+
65
+ ## Public attributes
66
+ connection: ContextVar[Connection] = ContextVar('postgresql-psqlpy-transaction', default=None)
67
+
68
+ def __init__(self, dsn: str | None = None) -> None:
69
+ self._pool: ConnectionPool = get_pool(dsn=dsn)
70
+
71
+ def __enter__(self) -> None:
72
+ self._connection = self._pool.getconn()
73
+ self._token = self.connection.set(self._connection)
74
+
75
+ return self
76
+
77
+ def __exit__(
78
+ self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
79
+ ) -> None:
80
+ if exc_type is None:
81
+ self._connection.commit()
82
+ else:
83
+ self._connection.rollback()
84
+
85
+ self.connection.reset(self._token)
86
+ # Return the connection to the pool
87
+ self._pool.putconn(self._connection)
88
+
89
+ return False
90
+
91
+
92
+ def make_connection_dsn(
93
+ host: str | None = None,
94
+ port: int | None = None,
95
+ user: str | None = None,
96
+ password: str | None = None,
97
+ database: str | None = None,
98
+ sslmode: str | None = None,
99
+ ) -> str:
100
+ """
101
+ Create a PostgreSQL connection DSN from settings.
102
+ Supports both TCP and Unix socket connections.
103
+ If parameters are provided, they override the settings.
104
+
105
+ Args:
106
+ host (str | None): The database host. If None, uses the setting.
107
+ port (int | None): The database port. If None, uses the setting.
108
+ user (str | None): The database user. If None, uses the setting.
109
+ password (str | None): The database password. If None, uses the setting.
110
+ database (str | None): The database name. If None, uses the setting.
111
+ sslmode (str | None): The SSL mode for connection. If None, uses the setting if defined.
112
+ """
113
+ sslmode: str | None = sslmode or settings.POSTGRESQL_CONNECTION_SSLMODE
114
+ options: dict[str, str | int] = {
115
+ 'host': host or settings.POSTGRESQL_CONNECTION_HOST,
116
+ 'port': port or settings.POSTGRESQL_CONNECTION_PORT,
117
+ 'user': user or settings.POSTGRESQL_CONNECTION_USER,
118
+ 'password': password or settings.POSTGRESQL_CONNECTION_PASSWORD,
119
+ 'database': database or settings.POSTGRESQL_CONNECTION_DATABASE,
120
+ 'sslmode': f'?sslmode={sslmode}' if sslmode else '',
121
+ }
122
+
123
+ # Handle Unix socket connections
124
+ if options['host'].startswith('/'):
125
+ return 'postgresql:///{database}?host={host}&user={user}&password={password}'.format(**options)
126
+
127
+ # Standard TCP connection
128
+ return 'postgresql://{user}:{password}@{host}:{port}/{database}{sslmode}'.format(**options)
129
+
130
+
131
+ def get_pool(dsn: str | None = None, **kwargs) -> ConnectionPool:
132
+ """
133
+ Retrieve a database connection pool for the given DSN.
134
+
135
+ If no DSN is provided, a default DSN is generated. The connection pool is cached
136
+ based on the process ID and DSN hash to ensure reuse within the same process.
137
+ If a pool for the given key does not exist, a new one is created with the specified
138
+ maximum size and SSL mode.
139
+
140
+ Importantly, this is necessary because connections cannot be shared between processes.
141
+
142
+ Args:
143
+ dsn (str | None): The Data Source Name for the database connection. If None, a default DSN is used.
144
+ **kwargs: Additional keyword arguments to configure the connection pool.
145
+
146
+ Returns:
147
+ ConnectionPool: The connection pool associated with the given DSN.
148
+ """
149
+ dsn = dsn or make_connection_dsn()
150
+ # https://www.psycopg.org/psycopg3/docs/api/pool.html
151
+ kwargs['check'] = ConnectionPool.check_connection
152
+ kwargs['min_size'] = kwargs.get('min_size', settings.POSTGRESQL_POOL_MIN_SIZE)
153
+ kwargs['max_size'] = kwargs.get('max_size', settings.POSTGRESQL_POOL_MAX_SIZE)
154
+ kwargs['max_idle'] = kwargs.get('max_idle', settings.POSTGRESQL_POOL_MAX_IDLE)
155
+ kwargs['max_lifetime'] = kwargs.get('max_lifetime', settings.POSTGRESQL_POOL_MAX_LIFETIME)
156
+ kwargs['max_waiting'] = kwargs.get('max_waiting', settings.POSTGRESQL_POOL_MAX_WAITING)
157
+ kwargs['reconnect_timeout'] = kwargs.get('reconnect_timeout', settings.POSTGRESQL_POOL_RECONNECT_TIMEOUT)
158
+ kwargs['timeout'] = kwargs.get('timeout', settings.POSTGRESQL_POOL_TIMEOUT)
159
+ kwargs['open'] = kwargs.get('open', settings.POSTGRESQL_POOL_OPEN)
160
+
161
+ key = f'{getpid()}:{hash(dsn)}'
162
+ if key not in _CONNECTIONS:
163
+ _CONNECTIONS[key] = ConnectionPool(conninfo=dsn, **kwargs)
164
+
165
+ return _CONNECTIONS[key]
166
+
167
+
168
+ def execute(
169
+ query: str,
170
+ params: dict | None = None,
171
+ return_type: Literal['dict', 'list'] = 'list',
172
+ dsn: str | None = None,
173
+ cls: type | None = None,
174
+ loads: Callable | None = None,
175
+ ) -> list[dict] | list[object] | dict | None:
176
+ """
177
+ Execute a query and return the results.
178
+ If return_type is a class, return a list of instances of that class.
179
+ If return_type is a string, return a dictionary keyed by that string.
180
+ Otherwise, return a list of dictionaries.
181
+
182
+ Args:
183
+ query (str): The SQL query to execute.
184
+ params (dict | None, optional): The parameters to include in the query. Defaults to None.
185
+ return_type (Literal['dict', 'list'], optional): The type of return value. Defaults to 'list'.
186
+ dsn (str | None, optional): The DSN to use for the connection. Defaults to None.
187
+ cls (type | None, optional): The class to map the results to. Defaults to None.
188
+ loads (Callable | None, optional): Optional function to process each value. Defaults to None.
189
+ retry (int, optional): The current retry count. Defaults to 0.
190
+ """
191
+ conn: Connection = Transaction.connection.get()
192
+ if not conn:
193
+ pool: ConnectionPool = get_pool(dsn=dsn)
194
+ conn: Connection = pool.getconn()
195
+ is_transactional = False
196
+ log_message = 'PostgreSQL query executed.'
197
+ else:
198
+ is_transactional = True
199
+ log_message = 'PostgreSQL query executed within transaction.'
200
+
201
+ _log(log_message, extra={'labels': {'query': query, 'params': params}})
202
+
203
+ row_factory = cls_row(cls, loads) if cls else dict_row(loads)
204
+ # For transactions we let it be controlled externally by the context manager
205
+ try:
206
+ with conn.cursor(row_factory=row_factory) as cur:
207
+ result = retry(cur.execute, {'query': query, 'params': params}, retries=3, exceptions=OperationalError)
208
+
209
+ if result.description:
210
+ result = cur.fetchall()
211
+ else:
212
+ result = None
213
+ except Exception:
214
+ # On error we need to rollback
215
+ if not is_transactional:
216
+ conn.rollback()
217
+ raise
218
+
219
+ else:
220
+ # Block that only executes if no exception was raised in the try block
221
+ if not is_transactional:
222
+ conn.commit()
223
+
224
+ finally:
225
+ # We only return the connection to the pool if we are not in a transaction
226
+ if not is_transactional:
227
+ pool.putconn(conn)
228
+
229
+ if result and cls and return_type == 'dict':
230
+ return {row[cls._primary_key]: row for row in result}
231
+
232
+ return result