everysk-lib 1.9.8__cp312-cp312-musllinux_1_2_x86_64.whl → 1.9.9__cp312-cp312-musllinux_1_2_x86_64.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/core/retry.py +51 -0
- everysk/core/tests.py +138 -157
- everysk/settings.py +3 -0
- everysk/sql/connection.py +81 -33
- everysk/sql/model.py +1 -6
- everysk/sql/query.py +30 -14
- everysk/sql/row_factory.py +63 -0
- everysk/sql/settings.py +25 -2
- everysk/sql/utils.py +8 -7
- {everysk_lib-1.9.8.dist-info → everysk_lib-1.9.9.dist-info}/METADATA +4 -2
- {everysk_lib-1.9.8.dist-info → everysk_lib-1.9.9.dist-info}/RECORD +15 -13
- {everysk_lib-1.9.8.dist-info → everysk_lib-1.9.9.dist-info}/.gitignore +0 -0
- {everysk_lib-1.9.8.dist-info → everysk_lib-1.9.9.dist-info}/WHEEL +0 -0
- {everysk_lib-1.9.8.dist-info → everysk_lib-1.9.9.dist-info}/licenses/LICENSE.txt +0 -0
- {everysk_lib-1.9.8.dist-info → everysk_lib-1.9.9.dist-info}/top_level.txt +0 -0
everysk/core/retry.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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 typing import Any
|
|
11
|
+
|
|
12
|
+
from everysk.config import settings
|
|
13
|
+
from everysk.core.log import Logger
|
|
14
|
+
|
|
15
|
+
log = Logger('everysk-lib-core-retry')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def retry(
|
|
19
|
+
func: callable, params: dict, retry_count: int = 0, retries: int = 5, exceptions: tuple | Exception = Exception
|
|
20
|
+
) -> Any:
|
|
21
|
+
"""
|
|
22
|
+
Retries a function call a number of times if it raises an exception.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
func (callable): The function to be called.
|
|
26
|
+
params (dict): The parameters to be passed to the function.
|
|
27
|
+
retry_count (int, optional): The current retry count. Defaults to 0.
|
|
28
|
+
retries (int, optional): The maximum number of retries. Defaults to 5.
|
|
29
|
+
exceptions (tuple | Exception, optional): The exceptions to catch. Defaults to Exception.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
Exception: If the maximum number of retries is reached.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
return func(**params)
|
|
36
|
+
except exceptions:
|
|
37
|
+
if retry_count < retries:
|
|
38
|
+
if settings.RETRY_SHOW_LOGS:
|
|
39
|
+
msg = f'Retry {retry_count + 1} of {retries} for function {func.__name__} due to exception.'
|
|
40
|
+
log.warning(
|
|
41
|
+
msg,
|
|
42
|
+
extra={
|
|
43
|
+
'function': func.__name__,
|
|
44
|
+
'params': params,
|
|
45
|
+
'retry_count': retry_count + 1,
|
|
46
|
+
'max_retries': retries,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
return retry(func, params, retry_count + 1, retries, exceptions)
|
|
50
|
+
|
|
51
|
+
raise
|
everysk/core/tests.py
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
|
|
8
8
|
#
|
|
9
9
|
###############################################################################
|
|
10
|
-
#
|
|
10
|
+
# ruff: noqa: F401
|
|
11
11
|
|
|
12
12
|
## Remember to prefix all import with EveryskLib to avoid clash with other tests
|
|
13
13
|
|
|
@@ -17,200 +17,194 @@ try:
|
|
|
17
17
|
except ModuleNotFoundError as error:
|
|
18
18
|
# This will prevent running these tests if redis is not installed
|
|
19
19
|
if not error.args[0].startswith("No module named 'redis'"):
|
|
20
|
-
raise
|
|
20
|
+
raise
|
|
21
21
|
|
|
22
22
|
## Compress Test Cases
|
|
23
|
-
from everysk.core._tests.compress import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
FileHandlingTestCase as EveryskLibFileHandlingTestCase
|
|
30
|
-
)
|
|
23
|
+
from everysk.core._tests.compress import CompressGzipJsonTestCase as EveryskLibCompressGzipJsonTestCase
|
|
24
|
+
from everysk.core._tests.compress import CompressGzipPickleTestCase as EveryskLibCompressGzipPickleTestCase
|
|
25
|
+
from everysk.core._tests.compress import CompressTestCase as EveryskLibCompressTestCase
|
|
26
|
+
from everysk.core._tests.compress import CompressZlibJsonTestCase as EveryskLibCompressZlibJsonTestCase
|
|
27
|
+
from everysk.core._tests.compress import CompressZlibPickleTestCase as EveryskLibCompressZlibPickleTestCase
|
|
28
|
+
from everysk.core._tests.compress import FileHandlingTestCase as EveryskLibFileHandlingTestCase
|
|
31
29
|
|
|
32
30
|
## Config Test Cases
|
|
33
|
-
from everysk.core._tests.config import
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
SettingsManagerTestCase as EveryskLibSettingsManagerTestCase
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
## Date, DateTime Test Cases
|
|
40
|
-
from everysk.core.datetime.tests.date import DateTestCase as EveryskLibDateTestCase
|
|
41
|
-
from everysk.core.datetime.tests.datetime import DateTimeTestCase as EveryskLibDateTimeTestCase
|
|
42
|
-
from everysk.core.datetime.tests.date_mixin import GetHolidaysTestCase as EveryskLibDateMixinGetHolidaysTestCase
|
|
43
|
-
from everysk.core.datetime.tests.calendar import CalendarTestCase as EveryskLibCalendarTestCase
|
|
31
|
+
from everysk.core._tests.config import SettingsManagerTestCase as EveryskLibSettingsManagerTestCase
|
|
32
|
+
from everysk.core._tests.config import SettingsModulesTestCase as EveryskLibSettingsModulesTestCase
|
|
33
|
+
from everysk.core._tests.config import SettingsTestCase as EveryskLibSettingsTestCase
|
|
44
34
|
|
|
45
35
|
## Exceptions Test Cases
|
|
46
|
-
from everysk.core._tests.exceptions import
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
SDKExceptionsTestCase as EveryskLibSDKExceptionsTestCase
|
|
56
|
-
)
|
|
36
|
+
from everysk.core._tests.exceptions import BaseExceptionTestCase as EveryskLibBaseExceptionTestCase
|
|
37
|
+
from everysk.core._tests.exceptions import DefaultErrorTestCase as EveryskLibDefaultErrorTestCase
|
|
38
|
+
from everysk.core._tests.exceptions import FieldValueErrorTestCase as EveryskLibFieldValueErrorTestCase
|
|
39
|
+
from everysk.core._tests.exceptions import HandledExceptionTestCase as EveryskLibHandledExceptionTestCase
|
|
40
|
+
from everysk.core._tests.exceptions import HttpErrorTestCase as EveryskLibHttpErrorTestCase
|
|
41
|
+
from everysk.core._tests.exceptions import ReadonlyErrorTestCase as EveryskLibReadonlyErrorTestCase
|
|
42
|
+
from everysk.core._tests.exceptions import RequiredErrorTestCase as EveryskLibRequiredErrorTestCase
|
|
43
|
+
from everysk.core._tests.exceptions import SDKExceptionsTestCase as EveryskLibSDKExceptionsTestCase
|
|
44
|
+
from everysk.core._tests.exceptions import TestAPIError as EveryskLibTestAPIError
|
|
57
45
|
|
|
58
46
|
## Fields Test Cases
|
|
59
|
-
from everysk.core._tests.fields import
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
47
|
+
from everysk.core._tests.fields import BoolFieldTestCase as EveryskLibBoolFieldTestCase
|
|
48
|
+
from everysk.core._tests.fields import ChoiceFieldTestCase as EveryskLibChoiceFieldTestCase
|
|
49
|
+
from everysk.core._tests.fields import COD3770TestCase as EveryskLibCOD3770TestCase
|
|
50
|
+
from everysk.core._tests.fields import DateFieldTestCase as EveryskLibDateFieldTestCase
|
|
51
|
+
from everysk.core._tests.fields import DateTimeFieldTestCase as EveryskLibDateTimeFieldTestCase
|
|
52
|
+
from everysk.core._tests.fields import DictFieldTestCase as EveryskLibDictFieldTestCase
|
|
53
|
+
from everysk.core._tests.fields import EmailFieldTestCase as EveryskLibEmailFieldTestCase
|
|
54
|
+
from everysk.core._tests.fields import FieldTestCase as EveryskLibFieldTestCase
|
|
55
|
+
from everysk.core._tests.fields import FieldUndefinedTestCase as EveryskLibFieldUndefinedTestCase
|
|
56
|
+
from everysk.core._tests.fields import FloatFieldTestCase as EveryskLibFloatFieldTestCase
|
|
57
|
+
from everysk.core._tests.fields import IntFieldTestCase as EveryskLibIntFieldTestCase
|
|
58
|
+
from everysk.core._tests.fields import IteratorFieldTestCase as EveryskLibIteratorFieldTestCase
|
|
59
|
+
from everysk.core._tests.fields import ListFieldTestCase as EveryskLibListFieldTestCase
|
|
60
|
+
from everysk.core._tests.fields import ObjectInitPropertyTestCase as EveryskLibObjectInitPropertyTestCase
|
|
61
|
+
from everysk.core._tests.fields import SetFieldTestCase as EveryskLibSetFieldTestCase
|
|
62
|
+
from everysk.core._tests.fields import StrFieldTestCase as EveryskLibStrFieldTestCase
|
|
63
|
+
from everysk.core._tests.fields import TupleFieldTestCase as EveryskLibTupleFieldTestCase
|
|
64
|
+
from everysk.core._tests.fields import URLFieldTestCase as EveryskLibURLFieldTestCase
|
|
65
|
+
|
|
66
|
+
## Date, DateTime Test Cases
|
|
67
|
+
from everysk.core.datetime.tests.calendar import CalendarTestCase as EveryskLibCalendarTestCase
|
|
68
|
+
from everysk.core.datetime.tests.date import DateTestCase as EveryskLibDateTestCase
|
|
69
|
+
from everysk.core.datetime.tests.date_mixin import GetHolidaysTestCase as EveryskLibDateMixinGetHolidaysTestCase
|
|
70
|
+
from everysk.core.datetime.tests.datetime import DateTimeTestCase as EveryskLibDateTimeTestCase
|
|
79
71
|
|
|
80
72
|
## Firestore Test Cases
|
|
81
73
|
try:
|
|
82
74
|
from everysk.core._tests.firestore import (
|
|
83
75
|
BaseDocumentCachedConfigTestCase as EveryskLibBaseDocumentCachedConfigTestCase,
|
|
84
|
-
BaseDocumentConfigTestCase as EveryskLibBaseDocumentConfigTestCase,
|
|
85
|
-
DocumentCachedTestCase as EveryskLibDocumentCachedTestCase,
|
|
86
|
-
DocumentTestCase as EveryskLibDocumentTestCase,
|
|
87
|
-
FirestoreClientTestCase as EveryskLibFirestoreClientTestCase,
|
|
88
|
-
LoadsPaginatedTestCase as EveryskLibLoadsPaginatedTestCase
|
|
89
76
|
)
|
|
77
|
+
from everysk.core._tests.firestore import BaseDocumentConfigTestCase as EveryskLibBaseDocumentConfigTestCase
|
|
78
|
+
from everysk.core._tests.firestore import DocumentCachedTestCase as EveryskLibDocumentCachedTestCase
|
|
79
|
+
from everysk.core._tests.firestore import DocumentTestCase as EveryskLibDocumentTestCase
|
|
80
|
+
from everysk.core._tests.firestore import FirestoreClientTestCase as EveryskLibFirestoreClientTestCase
|
|
81
|
+
from everysk.core._tests.firestore import LoadsPaginatedTestCase as EveryskLibLoadsPaginatedTestCase
|
|
90
82
|
except ModuleNotFoundError as error:
|
|
91
83
|
# This will prevent running these tests if google-cloud-firestore is not installed
|
|
92
84
|
if not error.args[0].startswith("No module named 'google"):
|
|
93
|
-
raise
|
|
85
|
+
raise
|
|
94
86
|
|
|
95
87
|
## Http Test Cases
|
|
96
88
|
try:
|
|
89
|
+
from everysk.core._tests.http import HttpConnectionConfigTestCase as EveryskLibHttpConnectionConfigTestCase
|
|
90
|
+
from everysk.core._tests.http import HttpConnectionTestCase as EveryskLibHttpConnectionTestCase
|
|
91
|
+
from everysk.core._tests.http import HttpDELETEConnectionTestCase as EveryskLibHttpDELETEConnectioNTestCase
|
|
92
|
+
from everysk.core._tests.http import HttpGETConnectionTestCase as EveryskLibHttpGETConnectionTestCase
|
|
93
|
+
from everysk.core._tests.http import HttpHEADConnectionTestCase as EveryskLibHttpHEADConnectionTestCase
|
|
94
|
+
from everysk.core._tests.http import HttpOPTIONSConnectionTestCase as EveryskLibHttpOPTIONSConnectionTestCase
|
|
95
|
+
from everysk.core._tests.http import HttpPATCHConnectionTestCase as EveryskLibHttpPATCHConnectionTestCase
|
|
97
96
|
from everysk.core._tests.http import (
|
|
98
|
-
HttpConnectionTestCase as EveryskLibHttpConnectionTestCase,
|
|
99
|
-
HttpConnectionConfigTestCase as EveryskLibHttpConnectionConfigTestCase,
|
|
100
|
-
HttpGETConnectionTestCase as EveryskLibHttpGETConnectionTestCase,
|
|
101
|
-
HttpPOSTConnectionTestCase as EveryskLibHttpPOSTConnectionTestCase,
|
|
102
|
-
HttpSDKPOSTConnectionTestCase as EveryskLibHttpSDKPOSTConnectionTestCase,
|
|
103
97
|
HttpPOSTCompressedConnectionTestCase as EveryskLibHttpPOSTCompressedConnectionTestCase,
|
|
104
|
-
HttpDELETEConnectionTestCase as EveryskLibHttpDELETEConnectioNTestCase,
|
|
105
|
-
HttpHEADConnectionTestCase as EveryskLibHttpHEADConnectionTestCase,
|
|
106
|
-
HttpOPTIONSConnectionTestCase as EveryskLibHttpOPTIONSConnectionTestCase,
|
|
107
|
-
HttpPATCHConnectionTestCase as EveryskLibHttpPATCHConnectionTestCase,
|
|
108
|
-
HttpPUTConnectionTestCase as EveryskLibHttpPUTCompressedConnectionTestCase
|
|
109
98
|
)
|
|
99
|
+
from everysk.core._tests.http import HttpPOSTConnectionTestCase as EveryskLibHttpPOSTConnectionTestCase
|
|
100
|
+
from everysk.core._tests.http import HttpPUTConnectionTestCase as EveryskLibHttpPUTCompressedConnectionTestCase
|
|
101
|
+
from everysk.core._tests.http import HttpSDKPOSTConnectionTestCase as EveryskLibHttpSDKPOSTConnectionTestCase
|
|
110
102
|
except ModuleNotFoundError as error:
|
|
111
103
|
# This will prevent running these tests if requests is not installed
|
|
112
104
|
if not error.args[0].startswith("No module named 'requests'"):
|
|
113
|
-
raise
|
|
105
|
+
raise
|
|
106
|
+
|
|
107
|
+
## Lists Test Cases
|
|
108
|
+
from everysk.core._tests.lists import SlicesTestCase as EveryskLibSlicesTestCase
|
|
109
|
+
from everysk.core._tests.lists import SortListDictTestCase as EveryskLibSortListDictTestCase
|
|
110
|
+
from everysk.core._tests.lists import SplitInSlicesTestCase as EveryskLibSplitInSlicesTestCase
|
|
114
111
|
|
|
115
112
|
## Log Test Cases
|
|
116
|
-
from everysk.core._tests.log import
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
113
|
+
from everysk.core._tests.log import LoggerExtraDataTestCase as EveryskLibLoggerExtraDataTestCase
|
|
114
|
+
from everysk.core._tests.log import LoggerFormatterTestCase as EveryskLibLoggerFormatterTestCase
|
|
115
|
+
from everysk.core._tests.log import LoggerJsonTestCase as EveryskLibLoggerJsonTestCase
|
|
116
|
+
from everysk.core._tests.log import LoggerManagerTestCase as EveryskLibLoggerManagerTestCase
|
|
117
|
+
from everysk.core._tests.log import LoggerMethodsTestCase as EveryskLibLoggerMethodsTestCase
|
|
118
|
+
from everysk.core._tests.log import LoggerStackLevelTestCase as EveryskLibLoggerStackLevelTestCase
|
|
119
|
+
from everysk.core._tests.log import LoggerStdoutTestCase as EveryskLibLoggerStdoutTestCase
|
|
120
|
+
from everysk.core._tests.log import LoggerTestCase as EveryskLibLoggerTestCase
|
|
121
|
+
from everysk.core._tests.log import LoggerTraceTestCase as EveryskLibLogTraceTestCase
|
|
122
|
+
|
|
127
123
|
try:
|
|
128
|
-
|
|
124
|
+
# We need requests to run this test
|
|
125
|
+
from everysk.core._tests.log import LoggerSlackTestCase as EveryskLibLoggerSlackTestCase
|
|
129
126
|
except ModuleNotFoundError as error:
|
|
130
127
|
# This will prevent running these tests if requests is not installed
|
|
131
128
|
if not error.args[0].startswith("No module named 'requests'"):
|
|
132
|
-
raise
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
## Object Test Cases
|
|
136
|
-
from everysk.core._tests.object import (
|
|
137
|
-
BaseDictPropertyTestCase as EveryskLibBaseDictPropertyTestCase,
|
|
138
|
-
BaseDictTestCase as EveryskLibBaseDictTestCase,
|
|
139
|
-
BaseFieldTestCase as EveryskLibBaseFieldTestCase,
|
|
140
|
-
BaseObjectTestCase as EveryskLibBaseObjectTestCase,
|
|
141
|
-
ConfigHashTestCase as EveryskLibConfigHashTestCase,
|
|
142
|
-
FrozenDictTestCase as EveryskLibFrozenDictTestCase,
|
|
143
|
-
FrozenObjectTestCase as EveryskLibFrozenObjectTestCase,
|
|
144
|
-
MetaClassConfigTestCase as EveryskLibMetaClassConfigTestCase,
|
|
145
|
-
RequiredTestCase as EveryskLibRequiredTestCase,
|
|
146
|
-
ValidateTestCase as EveryskLibValidateTestCase,
|
|
147
|
-
MetaClassAttributesTestCase as EveryskLibMetaClassAttributesTestCase,
|
|
148
|
-
NpArrayTestCase as EveryskLibNpArrayTestCase,
|
|
149
|
-
AfterInitTestCase as EveryskLibAfterInitTestCase,
|
|
150
|
-
BeforeInitTestCase as EveryskLibBeforeInitTestCase,
|
|
151
|
-
SilentTestCase as EveryskLibSilentTestCase,
|
|
152
|
-
TypingCheckingTestCase as EveryskLibTypingCheckingTestCase,
|
|
153
|
-
BaseDictSuperTestCase as EveryskLibBaseDictSuperTestCase
|
|
154
|
-
)
|
|
129
|
+
raise
|
|
155
130
|
|
|
156
131
|
## Number Test Cases
|
|
157
132
|
from everysk.core._tests.number import NumberTestCase as EveryskLibNumberTestCase
|
|
158
133
|
|
|
159
|
-
##
|
|
160
|
-
from everysk.core._tests.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
from everysk.core._tests.
|
|
134
|
+
## Object Test Cases
|
|
135
|
+
from everysk.core._tests.object import AfterInitTestCase as EveryskLibAfterInitTestCase
|
|
136
|
+
from everysk.core._tests.object import BaseDictPropertyTestCase as EveryskLibBaseDictPropertyTestCase
|
|
137
|
+
from everysk.core._tests.object import BaseDictSuperTestCase as EveryskLibBaseDictSuperTestCase
|
|
138
|
+
from everysk.core._tests.object import BaseDictTestCase as EveryskLibBaseDictTestCase
|
|
139
|
+
from everysk.core._tests.object import BaseFieldTestCase as EveryskLibBaseFieldTestCase
|
|
140
|
+
from everysk.core._tests.object import BaseObjectTestCase as EveryskLibBaseObjectTestCase
|
|
141
|
+
from everysk.core._tests.object import BeforeInitTestCase as EveryskLibBeforeInitTestCase
|
|
142
|
+
from everysk.core._tests.object import ConfigHashTestCase as EveryskLibConfigHashTestCase
|
|
143
|
+
from everysk.core._tests.object import FrozenDictTestCase as EveryskLibFrozenDictTestCase
|
|
144
|
+
from everysk.core._tests.object import FrozenObjectTestCase as EveryskLibFrozenObjectTestCase
|
|
145
|
+
from everysk.core._tests.object import MetaClassAttributesTestCase as EveryskLibMetaClassAttributesTestCase
|
|
146
|
+
from everysk.core._tests.object import MetaClassConfigTestCase as EveryskLibMetaClassConfigTestCase
|
|
147
|
+
from everysk.core._tests.object import NpArrayTestCase as EveryskLibNpArrayTestCase
|
|
148
|
+
from everysk.core._tests.object import RequiredTestCase as EveryskLibRequiredTestCase
|
|
149
|
+
from everysk.core._tests.object import SilentTestCase as EveryskLibSilentTestCase
|
|
150
|
+
from everysk.core._tests.object import TypingCheckingTestCase as EveryskLibTypingCheckingTestCase
|
|
151
|
+
from everysk.core._tests.object import ValidateTestCase as EveryskLibValidateTestCase
|
|
167
152
|
|
|
168
153
|
## Redis Test Cases
|
|
169
154
|
try:
|
|
170
|
-
from everysk.core._tests.redis import
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
CacheDecoratorTestCase as EveryskLibCacheDecoratorTestCase
|
|
179
|
-
)
|
|
155
|
+
from everysk.core._tests.redis import CacheDecoratorTestCase as EveryskLibCacheDecoratorTestCase
|
|
156
|
+
from everysk.core._tests.redis import RedisCacheCompressedTestCase as EveryskLibRedisCacheCompressedTestCase
|
|
157
|
+
from everysk.core._tests.redis import RedisCacheGetSetTestCase as EveryskLibRedisCacheGetSetTestCase
|
|
158
|
+
from everysk.core._tests.redis import RedisCacheTestCase as EveryskLibRedisCacheTestCase
|
|
159
|
+
from everysk.core._tests.redis import RedisChannelTestCase as EveryskLibRedisChannelTestCase
|
|
160
|
+
from everysk.core._tests.redis import RedisClientTestCase as EveryskLibRedisClientTestCase
|
|
161
|
+
from everysk.core._tests.redis import RedisListTestCase as EveryskLibRedisListTestCase
|
|
162
|
+
from everysk.core._tests.redis import RedisLockTestCase as EveryskLibRedisLockTestCase
|
|
180
163
|
except ModuleNotFoundError as error:
|
|
181
164
|
# This will prevent running these tests if redis is not installed
|
|
182
165
|
if not error.args[0].startswith("No module named 'redis'"):
|
|
183
|
-
raise
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
## Retry Test Cases
|
|
169
|
+
from everysk.core._tests.retry import RetryTestCase as EveryskLibRetryTestCase
|
|
184
170
|
|
|
185
171
|
## Serialize Test Cases
|
|
186
|
-
from everysk.core._tests.serialize.test_json import
|
|
187
|
-
|
|
188
|
-
SerializeJsonLoadsTestCase as EveryskLibSerializeJsonLoadsTestCase
|
|
189
|
-
)
|
|
172
|
+
from everysk.core._tests.serialize.test_json import SerializeJsonDumpsTestCase as EveryskLibSerializeJsonDumpsTestCase
|
|
173
|
+
from everysk.core._tests.serialize.test_json import SerializeJsonLoadsTestCase as EveryskLibSerializeJsonLoadsTestCase
|
|
190
174
|
from everysk.core._tests.serialize.test_pickle import (
|
|
191
175
|
SerializePickleDumpsTestCase as EveryskLibSerializePickleDumpsTestCase,
|
|
192
|
-
SerializePickleLoadsTestCase as EveryskLibSerializePickleLoadsTestCase
|
|
193
176
|
)
|
|
177
|
+
from everysk.core._tests.serialize.test_pickle import (
|
|
178
|
+
SerializePickleLoadsTestCase as EveryskLibSerializePickleLoadsTestCase,
|
|
179
|
+
)
|
|
180
|
+
|
|
194
181
|
try:
|
|
195
182
|
from everysk.core._tests.serialize.test_orjson import (
|
|
196
183
|
SerializeOrjsonDumpsTestCase as EveryskLibSerializeOrjsonDumpsTestCase,
|
|
197
|
-
|
|
184
|
+
)
|
|
185
|
+
from everysk.core._tests.serialize.test_orjson import (
|
|
186
|
+
SerializeOrjsonLoadsTestCase as EveryskLibSerializeOrjsonLoadsTestCase,
|
|
198
187
|
)
|
|
199
188
|
except ModuleNotFoundError as error:
|
|
200
189
|
# This will prevent running these tests if orjson is not installed
|
|
201
190
|
if not error.args[0].startswith("No module named 'orjson'"):
|
|
202
|
-
raise
|
|
191
|
+
raise
|
|
203
192
|
|
|
204
193
|
## SFTP Test Cases
|
|
205
194
|
try:
|
|
206
|
-
from everysk.core._tests.sftp import
|
|
207
|
-
|
|
208
|
-
SFTPTestCase as EveryskLibSFTPTestCase
|
|
209
|
-
)
|
|
195
|
+
from everysk.core._tests.sftp import KnownHostsTestCase as EveryskLibKnownHostsTestCase
|
|
196
|
+
from everysk.core._tests.sftp import SFTPTestCase as EveryskLibSFTPTestCase
|
|
210
197
|
except ModuleNotFoundError as error:
|
|
211
198
|
# This will prevent running these tests if Paramiko is not installed
|
|
212
199
|
if not error.args[0].startswith("No module named 'paramiko'"):
|
|
213
|
-
raise
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
## Signing Test Cases
|
|
203
|
+
from everysk.core._tests.signing import SignTestCase as EveryskLibSignTestCase
|
|
204
|
+
from everysk.core._tests.signing import UnsignTestCase as EveryskLibUnsignTestCase
|
|
205
|
+
|
|
206
|
+
## String Test Cases
|
|
207
|
+
from everysk.core._tests.string import StringTestCase as EveryskLibStringTestCase
|
|
214
208
|
|
|
215
209
|
## Slack Test Cases
|
|
216
210
|
try:
|
|
@@ -218,14 +212,12 @@ try:
|
|
|
218
212
|
except ModuleNotFoundError as error:
|
|
219
213
|
# This will prevent running these tests if requests is not installed
|
|
220
214
|
if not error.args[0].startswith("No module named 'requests'"):
|
|
221
|
-
raise
|
|
215
|
+
raise
|
|
222
216
|
|
|
223
217
|
|
|
224
218
|
## Thread Test Cases
|
|
225
|
-
from everysk.core._tests.threads import
|
|
226
|
-
|
|
227
|
-
ThreadTestCase as EveryskLibThreadTestCase
|
|
228
|
-
)
|
|
219
|
+
from everysk.core._tests.threads import ThreadPoolTestCase as EveryskLibThreadPoolTestCase
|
|
220
|
+
from everysk.core._tests.threads import ThreadTestCase as EveryskLibThreadTestCase
|
|
229
221
|
|
|
230
222
|
## Undefined Test Cases
|
|
231
223
|
from everysk.core._tests.undefined import UndefinedTestCase as EveryskLibUndefinedTestCase
|
|
@@ -234,26 +226,15 @@ from everysk.core._tests.undefined import UndefinedTestCase as EveryskLibUndefin
|
|
|
234
226
|
from everysk.core._tests.unittests import SDKUnittestTestCase as EveryskLibSDKUnittestTestCase
|
|
235
227
|
|
|
236
228
|
## Utils Test Cases
|
|
237
|
-
from everysk.core._tests.utils import
|
|
238
|
-
|
|
239
|
-
SearchKeyTestCase as EveryskLibSearchKeyTestCase
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
## Lists Test Cases
|
|
243
|
-
from everysk.core._tests.lists import (
|
|
244
|
-
SplitInSlicesTestCase as EveryskLibSplitInSlicesTestCase,
|
|
245
|
-
SlicesTestCase as EveryskLibSlicesTestCase,
|
|
246
|
-
SortListDictTestCase as EveryskLibSortListDictTestCase
|
|
247
|
-
)
|
|
229
|
+
from everysk.core._tests.utils import BoolConverterTestCase as EveryskLibBoolConverterTestCase
|
|
230
|
+
from everysk.core._tests.utils import SearchKeyTestCase as EveryskLibSearchKeyTestCase
|
|
248
231
|
|
|
249
232
|
## Workers Test Cases
|
|
250
233
|
try:
|
|
251
|
-
from everysk.core._tests.workers import
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
WorkerGoogleTestCase as EveryskLibWorkerGoogleTestCase
|
|
255
|
-
)
|
|
234
|
+
from everysk.core._tests.workers import BaseGoogleTestCase as EveryskLibBaseGoogleTestCase
|
|
235
|
+
from everysk.core._tests.workers import TaskGoogleTestCase as EveryskLibTaskGoogleTestCase
|
|
236
|
+
from everysk.core._tests.workers import WorkerGoogleTestCase as EveryskLibWorkerGoogleTestCase
|
|
256
237
|
except ModuleNotFoundError as error:
|
|
257
238
|
# This will prevent running these tests if google-cloud-tasks is not installed
|
|
258
239
|
if not error.args[0].startswith("No module named 'google"):
|
|
259
|
-
raise
|
|
240
|
+
raise
|
everysk/settings.py
CHANGED
|
@@ -93,3 +93,6 @@ HTTP_USE_RANDOM_USER_AGENT = BoolField(default=False)
|
|
|
93
93
|
|
|
94
94
|
# Default directory to use the known_hosts file and other SFTP configurations
|
|
95
95
|
EVERYSK_SFTP_DIR = StrField(default=f'{tempfile.gettempdir()}/sftp')
|
|
96
|
+
|
|
97
|
+
# Enable to show retry logs
|
|
98
|
+
RETRY_SHOW_LOGS = BoolField(default=False)
|
everysk/sql/connection.py
CHANGED
|
@@ -7,18 +7,21 @@
|
|
|
7
7
|
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
|
|
8
8
|
#
|
|
9
9
|
###############################################################################
|
|
10
|
+
from collections.abc import Callable
|
|
10
11
|
from contextvars import ContextVar, Token
|
|
11
12
|
from os import getpid
|
|
12
13
|
from types import TracebackType
|
|
13
14
|
from typing import Literal
|
|
14
15
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
16
|
+
from psycopg import Connection, OperationalError
|
|
17
|
+
from psycopg_pool import ConnectionPool as _ConnectionPool
|
|
17
18
|
|
|
18
19
|
from everysk.config import settings
|
|
19
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
|
|
20
23
|
|
|
21
|
-
_CONNECTIONS: dict[str, ConnectionPool] = {}
|
|
24
|
+
_CONNECTIONS: dict[str, 'ConnectionPool'] = {}
|
|
22
25
|
log = Logger('everysk-lib-sql-query')
|
|
23
26
|
|
|
24
27
|
|
|
@@ -27,37 +30,49 @@ def _log(message: str, extra: dict | None = None) -> None:
|
|
|
27
30
|
log.debug(message, extra=extra)
|
|
28
31
|
|
|
29
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
|
+
|
|
30
47
|
class Transaction:
|
|
31
48
|
## Private attributes
|
|
32
49
|
_connection: Connection
|
|
33
50
|
_pool: ConnectionPool
|
|
34
51
|
_token: Token
|
|
35
|
-
_transaction: _Transaction
|
|
36
52
|
|
|
37
53
|
## Public attributes
|
|
38
|
-
connection: ContextVar[
|
|
54
|
+
connection: ContextVar[Connection] = ContextVar('postgresql-psqlpy-transaction', default=None)
|
|
39
55
|
|
|
40
56
|
def __init__(self, dsn: str | None = None) -> None:
|
|
41
57
|
self._pool: ConnectionPool = get_pool(dsn=dsn)
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
self._connection =
|
|
45
|
-
self.
|
|
46
|
-
await self._transaction.begin()
|
|
47
|
-
self._token = self.connection.set(self._transaction)
|
|
59
|
+
def __enter__(self) -> None:
|
|
60
|
+
self._connection = self._pool.getconn()
|
|
61
|
+
self._token = self.connection.set(self._connection)
|
|
48
62
|
|
|
49
63
|
return self
|
|
50
64
|
|
|
51
|
-
|
|
65
|
+
def __exit__(
|
|
52
66
|
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
|
|
53
67
|
) -> None:
|
|
54
68
|
if exc_type is None:
|
|
55
|
-
|
|
69
|
+
self._connection.commit()
|
|
56
70
|
else:
|
|
57
|
-
|
|
71
|
+
self._connection.rollback()
|
|
58
72
|
|
|
59
73
|
self.connection.reset(self._token)
|
|
60
|
-
|
|
74
|
+
# Return the connection to the pool
|
|
75
|
+
self._pool.putconn(self._connection)
|
|
61
76
|
|
|
62
77
|
return False
|
|
63
78
|
|
|
@@ -89,7 +104,7 @@ def make_connection_dsn(
|
|
|
89
104
|
return 'postgresql://{user}:{password}@{host}:{port}/{database}'.format(**options)
|
|
90
105
|
|
|
91
106
|
|
|
92
|
-
def get_pool(dsn: str | None = None) -> ConnectionPool:
|
|
107
|
+
def get_pool(dsn: str | None = None, **kwargs) -> ConnectionPool:
|
|
93
108
|
"""
|
|
94
109
|
Retrieve a database connection pool for the given DSN.
|
|
95
110
|
|
|
@@ -102,28 +117,38 @@ def get_pool(dsn: str | None = None) -> ConnectionPool:
|
|
|
102
117
|
|
|
103
118
|
Args:
|
|
104
119
|
dsn (str | None): The Data Source Name for the database connection. If None, a default DSN is used.
|
|
120
|
+
**kwargs: Additional keyword arguments to configure the connection pool.
|
|
105
121
|
|
|
106
122
|
Returns:
|
|
107
123
|
ConnectionPool: The connection pool associated with the given DSN.
|
|
108
124
|
"""
|
|
109
125
|
dsn = dsn or make_connection_dsn()
|
|
126
|
+
# https://www.psycopg.org/psycopg3/docs/api/pool.html
|
|
127
|
+
kwargs['check'] = ConnectionPool.check_connection
|
|
128
|
+
kwargs['min_size'] = kwargs.get('min_size', settings.POSTGRESQL_POOL_MIN_SIZE)
|
|
129
|
+
kwargs['max_size'] = kwargs.get('max_size', settings.POSTGRESQL_POOL_MAX_SIZE)
|
|
130
|
+
kwargs['max_idle'] = kwargs.get('max_idle', settings.POSTGRESQL_POOL_MAX_IDLE)
|
|
131
|
+
kwargs['max_lifetime'] = kwargs.get('max_lifetime', settings.POSTGRESQL_POOL_MAX_LIFETIME)
|
|
132
|
+
kwargs['max_waiting'] = kwargs.get('max_waiting', settings.POSTGRESQL_POOL_MAX_WAITING)
|
|
133
|
+
kwargs['reconnect_timeout'] = kwargs.get('reconnect_timeout', settings.POSTGRESQL_POOL_RECONNECT_TIMEOUT)
|
|
134
|
+
kwargs['timeout'] = kwargs.get('timeout', settings.POSTGRESQL_POOL_TIMEOUT)
|
|
135
|
+
kwargs['open'] = kwargs.get('open', settings.POSTGRESQL_POOL_OPEN)
|
|
136
|
+
|
|
110
137
|
key = f'{getpid()}:{hash(dsn)}'
|
|
111
138
|
if key not in _CONNECTIONS:
|
|
112
|
-
_CONNECTIONS[key] = ConnectionPool(
|
|
113
|
-
|
|
114
|
-
max_db_pool_size=settings.POSTGRESQL_POOL_MAX_SIZE,
|
|
115
|
-
ssl_mode=getattr(SslMode, settings.POSTGRESQL_CONNECTION_ENCRYPTION, None),
|
|
116
|
-
)
|
|
139
|
+
_CONNECTIONS[key] = ConnectionPool(conninfo=dsn, **kwargs)
|
|
140
|
+
|
|
117
141
|
return _CONNECTIONS[key]
|
|
118
142
|
|
|
119
143
|
|
|
120
|
-
|
|
144
|
+
def execute(
|
|
121
145
|
query: str,
|
|
122
146
|
params: dict | None = None,
|
|
123
147
|
return_type: Literal['dict', 'list'] = 'list',
|
|
124
148
|
dsn: str | None = None,
|
|
125
149
|
cls: type | None = None,
|
|
126
|
-
|
|
150
|
+
loads: Callable | None = None,
|
|
151
|
+
) -> list[dict] | list[object] | dict | None:
|
|
127
152
|
"""
|
|
128
153
|
Execute a query and return the results.
|
|
129
154
|
If return_type is a class, return a list of instances of that class.
|
|
@@ -136,25 +161,48 @@ async def execute(
|
|
|
136
161
|
return_type (Literal['dict', 'list'], optional): The type of return value. Defaults to 'list'.
|
|
137
162
|
dsn (str | None, optional): The DSN to use for the connection. Defaults to None.
|
|
138
163
|
cls (type | None, optional): The class to map the results to. Defaults to None.
|
|
164
|
+
loads (Callable | None, optional): Optional function to process each value. Defaults to None.
|
|
165
|
+
retry (int, optional): The current retry count. Defaults to 0.
|
|
139
166
|
"""
|
|
140
|
-
conn = Transaction.connection.get()
|
|
167
|
+
conn: Connection = Transaction.connection.get()
|
|
141
168
|
if not conn:
|
|
142
169
|
pool: ConnectionPool = get_pool(dsn=dsn)
|
|
143
|
-
conn =
|
|
170
|
+
conn: Connection = pool.getconn()
|
|
171
|
+
is_transactional = False
|
|
144
172
|
log_message = 'PostgreSQL query executed.'
|
|
145
173
|
else:
|
|
174
|
+
is_transactional = True
|
|
146
175
|
log_message = 'PostgreSQL query executed within transaction.'
|
|
147
176
|
|
|
148
177
|
_log(log_message, extra={'labels': {'query': query, 'params': params}})
|
|
149
|
-
result: QueryResult = await conn.execute(query, params)
|
|
150
|
-
if not Transaction.connection.get():
|
|
151
|
-
conn.close()
|
|
152
178
|
|
|
153
|
-
if cls
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
179
|
+
row_factory = cls_row(cls, loads) if cls else dict_row(loads)
|
|
180
|
+
# For transactions we let it be controlled externally by the context manager
|
|
181
|
+
try:
|
|
182
|
+
with conn.cursor(row_factory=row_factory) as cur:
|
|
183
|
+
result = retry(cur.execute, {'query': query, 'params': params}, retries=3, exceptions=OperationalError)
|
|
184
|
+
|
|
185
|
+
if result.description:
|
|
186
|
+
result = cur.fetchall()
|
|
187
|
+
else:
|
|
188
|
+
result = None
|
|
189
|
+
except Exception:
|
|
190
|
+
# On error we need to rollback
|
|
191
|
+
if not is_transactional:
|
|
192
|
+
conn.rollback()
|
|
193
|
+
raise
|
|
194
|
+
|
|
195
|
+
else:
|
|
196
|
+
# Block that only executes if no exception was raised in the try block
|
|
197
|
+
if not is_transactional:
|
|
198
|
+
conn.commit()
|
|
199
|
+
|
|
200
|
+
finally:
|
|
201
|
+
# We only return the connection to the pool if we are not in a transaction
|
|
202
|
+
if not is_transactional:
|
|
203
|
+
pool.putconn(conn)
|
|
157
204
|
|
|
158
|
-
|
|
205
|
+
if result and cls and return_type == 'dict':
|
|
206
|
+
return {row[cls._primary_key]: row for row in result}
|
|
159
207
|
|
|
160
|
-
return result
|
|
208
|
+
return result
|
everysk/sql/model.py
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
|
|
8
8
|
#
|
|
9
9
|
###############################################################################
|
|
10
|
-
import asyncio
|
|
11
10
|
import inspect
|
|
12
11
|
from copy import deepcopy
|
|
13
12
|
from types import GenericAlias, UnionType
|
|
@@ -260,12 +259,8 @@ class BaseModel(dict, metaclass=BaseModelMetaClass):
|
|
|
260
259
|
return_type: Literal['dict', 'list'] = 'list',
|
|
261
260
|
klass: type | None = None,
|
|
262
261
|
) -> Any:
|
|
263
|
-
loop = asyncio.get_event_loop()
|
|
264
262
|
kwargs = {'query': query, 'params': params, 'return_type': return_type, 'dsn': cls._dsn, 'cls': klass}
|
|
265
|
-
|
|
266
|
-
return asyncio.run_coroutine_threadsafe(execute(**kwargs), loop).result()
|
|
267
|
-
|
|
268
|
-
return loop.run_until_complete(execute(**kwargs))
|
|
263
|
+
return execute(**kwargs)
|
|
269
264
|
|
|
270
265
|
@classmethod
|
|
271
266
|
def _generate_attributes(cls) -> None:
|
everysk/sql/query.py
CHANGED
|
@@ -8,12 +8,29 @@
|
|
|
8
8
|
#
|
|
9
9
|
###############################################################################
|
|
10
10
|
import hashlib
|
|
11
|
+
from functools import partial
|
|
11
12
|
|
|
12
|
-
from
|
|
13
|
+
from psycopg.types.json import Jsonb, set_json_dumps, set_json_loads
|
|
13
14
|
|
|
14
15
|
from everysk.core.object import BaseDict
|
|
16
|
+
from everysk.core.serialize import dumps, loads
|
|
15
17
|
from everysk.sql.utils import ConditionOperator
|
|
16
18
|
|
|
19
|
+
# https://www.psycopg.org/psycopg3/docs/basic/adapt.html#json-adaptation
|
|
20
|
+
set_json_dumps(
|
|
21
|
+
partial(
|
|
22
|
+
dumps,
|
|
23
|
+
add_class_path=True,
|
|
24
|
+
date_format='%Y-%m-%d',
|
|
25
|
+
datetime_format='%Y-%m-%dT%H:%M:%S',
|
|
26
|
+
indent=None,
|
|
27
|
+
separators=(',', ':'),
|
|
28
|
+
use_undefined=True,
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
set_json_loads(partial(loads, use_undefined=True, instantiate_object=True))
|
|
32
|
+
|
|
33
|
+
|
|
17
34
|
## Constants
|
|
18
35
|
_SQL_FIELDS = {
|
|
19
36
|
'bool': 'BOOLEAN',
|
|
@@ -42,7 +59,7 @@ _SQL_CREATE_SCHEMA = 'CREATE SCHEMA IF NOT EXISTS "{schema}"'
|
|
|
42
59
|
_SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS "{schema}"."{table}" ({fields})'
|
|
43
60
|
|
|
44
61
|
# https://www.postgresqltutorial.com/postgresql-delete/
|
|
45
|
-
_SQL_DELETE = 'DELETE FROM "{schema}"."{table}" WHERE "{primary_key}" = ANY(
|
|
62
|
+
_SQL_DELETE = 'DELETE FROM "{schema}"."{table}" WHERE "{primary_key}" = ANY(%(ids)s)'
|
|
46
63
|
|
|
47
64
|
# https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-upsert/
|
|
48
65
|
# https://stackoverflow.com/a/30917361
|
|
@@ -201,11 +218,13 @@ class Query:
|
|
|
201
218
|
|
|
202
219
|
try:
|
|
203
220
|
offset = int(offset)
|
|
204
|
-
except (ValueError, TypeError):
|
|
205
|
-
|
|
221
|
+
except (ValueError, TypeError) as error:
|
|
222
|
+
msg = 'Offset must be an integer or a string representing it.'
|
|
223
|
+
raise TypeError(msg) from error
|
|
206
224
|
|
|
207
225
|
if offset < 0:
|
|
208
|
-
|
|
226
|
+
msg = 'Offset must not be negative.'
|
|
227
|
+
raise TypeError(msg)
|
|
209
228
|
|
|
210
229
|
if offset == 0:
|
|
211
230
|
return ''
|
|
@@ -296,7 +315,7 @@ class Query:
|
|
|
296
315
|
"""
|
|
297
316
|
# Create the values string
|
|
298
317
|
# We do not use " here because we are using the values as placeholders
|
|
299
|
-
values = ', '.join(f'
|
|
318
|
+
values = ', '.join(f'%({field})s' for field in fields)
|
|
300
319
|
|
|
301
320
|
# Create the SQL query
|
|
302
321
|
update = ', '.join(f'"{field}" = EXCLUDED."{field}"' for field in fields)
|
|
@@ -320,17 +339,14 @@ class Query:
|
|
|
320
339
|
Args:
|
|
321
340
|
params (dict): A dictionary of parameters to prepare.
|
|
322
341
|
"""
|
|
323
|
-
# https://
|
|
342
|
+
# https://www.psycopg.org/psycopg3/docs/basic/adapt.html#json-adaptation
|
|
324
343
|
for key, value in params.items():
|
|
325
344
|
if isinstance(value, (set, tuple)):
|
|
326
|
-
params[key] =
|
|
327
|
-
elif isinstance(value, list):
|
|
328
|
-
params[key] =
|
|
345
|
+
params[key] = Jsonb(list(value))
|
|
346
|
+
elif isinstance(value, (dict, list)):
|
|
347
|
+
params[key] = Jsonb(value)
|
|
329
348
|
elif isinstance(value, BaseDict):
|
|
330
|
-
params[key] =
|
|
331
|
-
elif isinstance(value, str):
|
|
332
|
-
# Text is needed to differentiate between VARCHAR and TEXT
|
|
333
|
-
params[key] = Text(value)
|
|
349
|
+
params[key] = Jsonb(value.to_dict())
|
|
334
350
|
|
|
335
351
|
return params
|
|
336
352
|
|
|
@@ -0,0 +1,63 @@
|
|
|
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, Iterable
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from psycopg import Cursor
|
|
14
|
+
from psycopg.rows import _get_names, no_result
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cls_row(cls: type, loads: Callable | None = None) -> Callable:
|
|
18
|
+
"""
|
|
19
|
+
Function to convert a row from a cursor to an instance of the specified class.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
cls (type): The class to instantiate for each row.
|
|
23
|
+
loads (callable | None, optional): Optional function to process each value. Defaults to None.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def inner(cursor: Cursor) -> Callable:
|
|
27
|
+
names = _get_names(cursor)
|
|
28
|
+
if names is None:
|
|
29
|
+
return no_result
|
|
30
|
+
|
|
31
|
+
def cls_row_(values: Iterable) -> Any:
|
|
32
|
+
if loads is None:
|
|
33
|
+
return cls(**dict(zip(names, values, strict=True)))
|
|
34
|
+
|
|
35
|
+
return cls(**dict(zip(names, map(loads, values), strict=True)))
|
|
36
|
+
|
|
37
|
+
return cls_row_
|
|
38
|
+
|
|
39
|
+
return inner
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def dict_row(loads: Callable | None = None) -> Callable:
|
|
43
|
+
"""
|
|
44
|
+
Function to convert a row from a cursor to a dictionary.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
loads (Callable | None): Optional function to process each value. Defaults to None.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def inner(cursor: Cursor) -> Callable:
|
|
51
|
+
names = _get_names(cursor)
|
|
52
|
+
if names is None:
|
|
53
|
+
return no_result
|
|
54
|
+
|
|
55
|
+
def dict_row_(values: Iterable) -> dict[str, Any]:
|
|
56
|
+
if loads is None:
|
|
57
|
+
return dict(zip(names, values, strict=True))
|
|
58
|
+
|
|
59
|
+
return dict(zip(names, map(loads, values), strict=True))
|
|
60
|
+
|
|
61
|
+
return dict_row_
|
|
62
|
+
|
|
63
|
+
return inner
|
everysk/sql/settings.py
CHANGED
|
@@ -17,6 +17,29 @@ POSTGRESQL_CONNECTION_PASSWORD: str = None
|
|
|
17
17
|
POSTGRESQL_CONNECTION_PORT: int = 5432
|
|
18
18
|
POSTGRESQL_CONNECTION_HOST: str = None
|
|
19
19
|
POSTGRESQL_CONNECTION_USER: str = None
|
|
20
|
-
# Disable, Allow, Prefer, Require, VerifyCA, VerifyFull
|
|
21
|
-
POSTGRESQL_CONNECTION_ENCRYPTION: str = 'Prefer'
|
|
22
20
|
POSTGRESQL_POOL_MAX_SIZE: int = 10
|
|
21
|
+
POSTGRESQL_POOL_MIN_SIZE: int = 1
|
|
22
|
+
|
|
23
|
+
# https://www.psycopg.org/psycopg3/docs/api/pool.html
|
|
24
|
+
# Maximum time, in seconds, that a connection can stay unused in the pool before being closed, and the pool shrunk.
|
|
25
|
+
# This only happens to connections more than min_size, if max_size allowed the pool to grow.
|
|
26
|
+
POSTGRESQL_POOL_MAX_IDLE: int = 60 * 5 # 5 minutes
|
|
27
|
+
|
|
28
|
+
# The maximum lifetime of a connection in the pool, in seconds. Connections used for longer get closed and replaced by
|
|
29
|
+
# a new one. The amount is reduced by a random 10% to avoid mass eviction.
|
|
30
|
+
POSTGRESQL_POOL_MAX_LIFETIME: int = 60 * 30 # 30 minutes
|
|
31
|
+
|
|
32
|
+
# Maximum number of requests that can be queued to the pool, after which new requests will fail.
|
|
33
|
+
# Raising TooManyRequests, 0 means no queue limit.
|
|
34
|
+
POSTGRESQL_POOL_MAX_WAITING: int = 0
|
|
35
|
+
|
|
36
|
+
# If the connections are opened on init or later.
|
|
37
|
+
POSTGRESQL_POOL_OPEN: bool = True
|
|
38
|
+
|
|
39
|
+
# Maximum time, in seconds, the pool will try to create a connection. If a connection attempt fails, the pool will try
|
|
40
|
+
# to reconnect a few times, using an exponential backoff and some random factor to avoid mass attempts.
|
|
41
|
+
POSTGRESQL_POOL_RECONNECT_TIMEOUT: int = 60 * 2 # 2 minutes
|
|
42
|
+
|
|
43
|
+
# The default maximum time in seconds that a client can wait to receive a connection
|
|
44
|
+
# from the pool (using connection() or getconn()).
|
|
45
|
+
POSTGRESQL_POOL_TIMEOUT: int = 30 # 30 seconds
|
everysk/sql/utils.py
CHANGED
|
@@ -17,6 +17,7 @@ _SQL_OPERATORS = {
|
|
|
17
17
|
'ilike': 'ILIKE',
|
|
18
18
|
'in': 'IN',
|
|
19
19
|
'inside': '?',
|
|
20
|
+
'insidebinary': '?|',
|
|
20
21
|
'isnotnull': 'IS NOT NULL',
|
|
21
22
|
'isnull': 'IS NULL',
|
|
22
23
|
'like': 'LIKE',
|
|
@@ -58,8 +59,8 @@ class ConditionOperator:
|
|
|
58
59
|
|
|
59
60
|
## Operators methods
|
|
60
61
|
def _operator_default(self) -> tuple[str, Any]:
|
|
61
|
-
"""Default operator method "field" <operator>
|
|
62
|
-
operation = f'{self.sql_operator}
|
|
62
|
+
"""Default operator method "field" <operator> %(field)s."""
|
|
63
|
+
operation = f'{self.sql_operator} %({self.field_operator})s'
|
|
63
64
|
sql = f'"{self.field}" {operation}'
|
|
64
65
|
return sql, self.value
|
|
65
66
|
|
|
@@ -69,9 +70,9 @@ class ConditionOperator:
|
|
|
69
70
|
return sql, f'%{value}'
|
|
70
71
|
|
|
71
72
|
def _operator_in(self) -> tuple[str, list]:
|
|
72
|
-
"""Operator method for in: "field" = ANY(
|
|
73
|
+
"""Operator method for in: "field" = ANY(%(field__in)s)."""
|
|
73
74
|
# https://www.psycopg.org/psycopg3/docs/basic/from_pg2.html#you-cannot-use-in-s-with-a-tuple
|
|
74
|
-
operation = f'ANY(
|
|
75
|
+
operation = f'ANY(%({self.field_operator})s)'
|
|
75
76
|
sql = f'"{self.field}" = {operation}'
|
|
76
77
|
if isinstance(self.value, str):
|
|
77
78
|
return sql, self.value.split(',')
|
|
@@ -98,8 +99,8 @@ class ConditionOperator:
|
|
|
98
99
|
return self._operator_like()
|
|
99
100
|
|
|
100
101
|
def _operator_nin(self) -> tuple[str, list]:
|
|
101
|
-
"""Operator method for nin: "field" != ALL(
|
|
102
|
-
operation = f'ALL(
|
|
102
|
+
"""Operator method for nin: "field" != ALL(%(field__nin)s)."""
|
|
103
|
+
operation = f'ALL(%({self.field_operator})s)'
|
|
103
104
|
sql = f'"{self.field}" != {operation}'
|
|
104
105
|
if isinstance(self.value, str):
|
|
105
106
|
return sql, self.value.split(',')
|
|
@@ -119,7 +120,7 @@ class ConditionOperator:
|
|
|
119
120
|
Example:
|
|
120
121
|
field__operator = 'age__gt'
|
|
121
122
|
value = 30
|
|
122
|
-
returns: ('"age" >
|
|
123
|
+
returns: ('"age" > %(age__gt)s', 30)
|
|
123
124
|
"""
|
|
124
125
|
method_name = f'_operator_{self.operator}'
|
|
125
126
|
if hasattr(self, method_name):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: everysk-lib
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.9
|
|
4
4
|
Summary: Generic lib to share python code on Everysk.
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
Project-URL: Homepage, https://everysk.com/
|
|
@@ -115,7 +115,9 @@ Requires-Dist: lark==1.2.2; extra == "expression"
|
|
|
115
115
|
Requires-Dist: numpy==1.26.4; extra == "expression"
|
|
116
116
|
Requires-Dist: pandas==2.1.4; extra == "expression"
|
|
117
117
|
Provides-Extra: postgresql
|
|
118
|
-
Requires-Dist:
|
|
118
|
+
Requires-Dist: psycopg-binary==3.3.0; extra == "postgresql"
|
|
119
|
+
Requires-Dist: psycopg-pool==3.3.0; extra == "postgresql"
|
|
120
|
+
Requires-Dist: psycopg==3.3.0; extra == "postgresql"
|
|
119
121
|
Dynamic: license-file
|
|
120
122
|
|
|
121
123
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
everysk/__init__.py,sha256=JeZGK9kmTaBRR6H4et-3UYE6x8D7xsFtrTnJ_tQXG_Q,1039
|
|
2
2
|
everysk/_version.py,sha256=_OPcym8X5j0fiafJLlKp8ADhMP5pHKqD2YySbH9f1PY,24501
|
|
3
3
|
everysk/config.py,sha256=PKwYhzgBUIQ2m_cS5muBRWssZa4I3B6D5Qa9eSqAu18,16653
|
|
4
|
-
everysk/settings.py,sha256=
|
|
4
|
+
everysk/settings.py,sha256=Z_wPLDcAxjd6gW2lH6J8n6Q2j-x3V_AJGJmR5NapzBo,4008
|
|
5
5
|
everysk/tests.py,sha256=-btpbhDDF2CGIIDr4vra38HrSu7kXrV3o9Hgci90lNc,844
|
|
6
6
|
everysk/utils.py,sha256=nejrDn5n75YBk3BinEiDY5JhhdB7BaHX_9wpJ20RvXs,2654
|
|
7
7
|
everysk/version.py,sha256=q8j2yDrdm4lEtGtOIt4tY4TUlctwQYJJcVqscDabikA,617
|
|
@@ -37,12 +37,13 @@ everysk/core/log.py,sha256=qFRW9bO6jaELwrJ0MIaEUm2-uYFIyTbH2bQ3j0XpPio,30239
|
|
|
37
37
|
everysk/core/number.py,sha256=Al_9LLu7hj_4fTLPTiN3dzQvxctmMxB5i9I4Z4X5Lro,1204
|
|
38
38
|
everysk/core/object.py,sha256=9XMSPDlgLtGTKJLVimRIND4a7U1e_rZrERJGZw2j_lQ,53924
|
|
39
39
|
everysk/core/redis.py,sha256=HrG58dc_3EHnti2sBaijcyN8Hq4u3p1zst6-CSK9ySQ,36664
|
|
40
|
+
everysk/core/retry.py,sha256=wLU0fNKP7cNYKkYxMTjR--YFYM0ahXWP-39O_FObwfo,1897
|
|
40
41
|
everysk/core/serialize.py,sha256=8P5C7V98tB9vAQBEdu0NqMt8lktsD-Ol5REpTG1vNlw,25155
|
|
41
42
|
everysk/core/sftp.py,sha256=01_iDxqhcAO3FFKUp-8myV7qtf3I-Mi_cLZ6KrAfsGw,15600
|
|
42
43
|
everysk/core/signing.py,sha256=vImkFEiunqD-oO3e9Oi88z0x0jEEfO1lDIbC9RYxTn8,1956
|
|
43
44
|
everysk/core/slack.py,sha256=r9QH6RRe74Jz4-8XWLqbYq21msIlsVuC4fr_VUMFZPE,4394
|
|
44
45
|
everysk/core/string.py,sha256=K1BRlRNuhqlI7gdqOi40hjTHuxm1fF-RLqIAwm74eHc,5456
|
|
45
|
-
everysk/core/tests.py,sha256=
|
|
46
|
+
everysk/core/tests.py,sha256=qrfAfUDmgCvBn99X73fL5MUTo1P33gq0ftP5CTJetCc,14491
|
|
46
47
|
everysk/core/threads.py,sha256=CcL4Nv2Zzdxt1xjo0CbLSttSU29xHgSRiWF_JXr3GAA,7167
|
|
47
48
|
everysk/core/undefined.py,sha256=sNvP9qJJi3TINPSJiIWjqKz4o5DNBBYparoTRrDtJ_0,2757
|
|
48
49
|
everysk/core/unittests.py,sha256=ujGqGHCUAI25P4UgwHn9GpZAMBdqZ-lxxGERrckthIM,3289
|
|
@@ -120,14 +121,15 @@ everysk/server/routing.py,sha256=tHmmokv8msPY37T9F9_lovLZ9NP8bC8G_kxV92aM-tg,242
|
|
|
120
121
|
everysk/server/settings.py,sha256=C9nM3PYZQOKjq9KB2DB4ojGkkTJtj5pslWrUdMGYEwk,1309
|
|
121
122
|
everysk/server/tests.py,sha256=caw6jaXM0xjdi7Y10JpqUTCyUrUmKpG3uxigLcVRdew,1885
|
|
122
123
|
everysk/sql/__init__.py,sha256=lXZAWXrI2tlzw9uvJyThaWnksZqdbemQ2tqe-e7wUiU,413
|
|
123
|
-
everysk/sql/connection.py,sha256=
|
|
124
|
-
everysk/sql/model.py,sha256=
|
|
125
|
-
everysk/sql/query.py,sha256=
|
|
126
|
-
everysk/sql/
|
|
127
|
-
everysk/sql/
|
|
128
|
-
|
|
129
|
-
everysk_lib-1.9.
|
|
130
|
-
everysk_lib-1.9.
|
|
131
|
-
everysk_lib-1.9.
|
|
132
|
-
everysk_lib-1.9.
|
|
133
|
-
everysk_lib-1.9.
|
|
124
|
+
everysk/sql/connection.py,sha256=PujKm1i5gAvxU_FVT7IDuMb38D52dII52Gk8RI0vC_0,7928
|
|
125
|
+
everysk/sql/model.py,sha256=syHHTTS4JRTj_2Mu9ugmMu4b6qUY12t3VgfE3hSxYIc,14559
|
|
126
|
+
everysk/sql/query.py,sha256=yO2EoGoUoGace_9BNKN8jeyFV30yCrAhefAvGHqIFE4,14174
|
|
127
|
+
everysk/sql/row_factory.py,sha256=FEtB4d12ACT3Bp6g2k_Ty2p2HpTkPoVqODUS1_kSXys,1936
|
|
128
|
+
everysk/sql/settings.py,sha256=g5ZVm4Y3fhWGeXgTW6D309waGRBx2S6Ima4nY6ByTZw,2103
|
|
129
|
+
everysk/sql/utils.py,sha256=Hbk9INEcz12FoBeDvHh9qh0dTSmTCWAn_S1anJOJb1o,4327
|
|
130
|
+
everysk_lib-1.9.9.dist-info/.gitignore,sha256=0A1r9HzLhR7IQ1rGPjbaW5HC6oIQ7xzVYZ1z1ZaVfmw,183
|
|
131
|
+
everysk_lib-1.9.9.dist-info/METADATA,sha256=LrZks9TN21b8JcX5EiJg2oIzB_o8mwCbusCghhXkYz4,13080
|
|
132
|
+
everysk_lib-1.9.9.dist-info/WHEEL,sha256=AwHYJA1Do1jwgPIoLQR4DiHSeYY_vU6Ht9Vljq5Yt_M,112
|
|
133
|
+
everysk_lib-1.9.9.dist-info/top_level.txt,sha256=1s1Lfhd4gXolqzkh-ay3yy-EZKPiKnJfbZwx2fybxyk,14
|
|
134
|
+
everysk_lib-1.9.9.dist-info/RECORD,,
|
|
135
|
+
everysk_lib-1.9.9.dist-info/licenses/LICENSE.txt,sha256=Q5YxWA62m0TsmpEmHeoRHg4oPu_8ektkZ3FWWm1pQWo,311
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|