velocity-python 0.0.94__py3-none-any.whl → 0.0.96__py3-none-any.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.
- velocity/__init__.py +1 -1
- velocity/aws/handlers/sqs_handler.py +197 -64
- velocity/db/servers/postgres/__init__.py +1 -0
- velocity/db/servers/postgres/sql.py +5 -5
- velocity/db/servers/tablehelper.py +5 -3
- {velocity_python-0.0.94.dist-info → velocity_python-0.0.96.dist-info}/METADATA +1 -1
- {velocity_python-0.0.94.dist-info → velocity_python-0.0.96.dist-info}/RECORD +10 -10
- {velocity_python-0.0.94.dist-info → velocity_python-0.0.96.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.94.dist-info → velocity_python-0.0.96.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.94.dist-info → velocity_python-0.0.96.dist-info}/top_level.txt +0 -0
velocity/__init__.py
CHANGED
|
@@ -1,97 +1,230 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
SQS Handler Module
|
|
3
|
+
|
|
4
|
+
This module provides a base class for handling AWS SQS events in Lambda functions.
|
|
5
|
+
It includes logging capabilities, action routing, and error handling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
2
8
|
import json
|
|
3
|
-
import sys
|
|
4
9
|
import os
|
|
10
|
+
import sys
|
|
5
11
|
import traceback
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
6
14
|
from velocity.aws import DEBUG
|
|
7
15
|
from velocity.aws.handlers import context as VelocityContext
|
|
16
|
+
from velocity.misc.format import to_json
|
|
8
17
|
|
|
9
18
|
|
|
10
19
|
class SqsHandler:
|
|
20
|
+
"""
|
|
21
|
+
Base class for handling SQS events in AWS Lambda functions.
|
|
22
|
+
|
|
23
|
+
Provides structured processing of SQS records with automatic action routing,
|
|
24
|
+
logging capabilities, and error handling hooks.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, aws_event: Dict[str, Any], aws_context: Any,
|
|
28
|
+
context_class=VelocityContext.Context):
|
|
29
|
+
"""
|
|
30
|
+
Initialize the SQS handler.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
aws_event: The AWS Lambda event containing SQS records
|
|
34
|
+
aws_context: The AWS Lambda context object
|
|
35
|
+
context_class: The context class to use for processing
|
|
36
|
+
"""
|
|
37
|
+
self.aws_event = aws_event
|
|
38
|
+
self.aws_context = aws_context
|
|
39
|
+
self.serve_action_default = True
|
|
40
|
+
self.skip_action = False
|
|
41
|
+
self.ContextClass = context_class
|
|
11
42
|
|
|
12
|
-
def __init__(self, aws_event, aws_context, context_class=VelocityContext.Context):
|
|
13
43
|
self.aws_event = aws_event
|
|
14
44
|
self.aws_context = aws_context
|
|
15
45
|
self.serve_action_default = True
|
|
16
46
|
self.skip_action = False
|
|
17
47
|
self.ContextClass = context_class
|
|
18
48
|
|
|
19
|
-
def log(self, tx, message, function=None):
|
|
49
|
+
def log(self, tx, message: str, function: Optional[str] = None):
|
|
50
|
+
"""
|
|
51
|
+
Log a message to the system log table.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
tx: Database transaction object
|
|
55
|
+
message: The message to log
|
|
56
|
+
function: Optional function name, auto-detected if not provided
|
|
57
|
+
"""
|
|
20
58
|
if not function:
|
|
21
|
-
function =
|
|
22
|
-
idx = 0
|
|
23
|
-
while True:
|
|
24
|
-
try:
|
|
25
|
-
temp = sys._getframe(idx).f_code.co_name
|
|
26
|
-
except ValueError as e:
|
|
27
|
-
break
|
|
28
|
-
if temp in ["x", "log", "_transaction"]:
|
|
29
|
-
idx += 1
|
|
30
|
-
continue
|
|
31
|
-
function = temp
|
|
32
|
-
break
|
|
59
|
+
function = self._get_calling_function()
|
|
33
60
|
|
|
34
61
|
data = {
|
|
35
|
-
"app_name": os.environ
|
|
62
|
+
"app_name": os.environ.get("ProjectName", "Unknown"),
|
|
36
63
|
"referer": "SQS",
|
|
37
64
|
"user_agent": "QueueHandler",
|
|
38
65
|
"device_type": "Lambda",
|
|
39
66
|
"function": function,
|
|
40
67
|
"message": message,
|
|
41
|
-
"sys_modified_by": "
|
|
68
|
+
"sys_modified_by": "Lambda:BackOfficeQueueHandler",
|
|
42
69
|
}
|
|
43
70
|
tx.table("sys_log").insert(data)
|
|
71
|
+
|
|
72
|
+
def _get_calling_function(self) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Get the name of the calling function by inspecting the call stack.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The name of the calling function or "<Unknown>" if not found
|
|
78
|
+
"""
|
|
79
|
+
skip_functions = {"x", "log", "_transaction", "_get_calling_function"}
|
|
80
|
+
|
|
81
|
+
for idx in range(10): # Limit search to prevent infinite loops
|
|
82
|
+
try:
|
|
83
|
+
frame = sys._getframe(idx)
|
|
84
|
+
function_name = frame.f_code.co_name
|
|
85
|
+
|
|
86
|
+
if function_name not in skip_functions:
|
|
87
|
+
return function_name
|
|
88
|
+
|
|
89
|
+
except ValueError:
|
|
90
|
+
# No more frames in the stack
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
return "<Unknown>"
|
|
44
94
|
|
|
45
95
|
def serve(self, tx):
|
|
96
|
+
"""
|
|
97
|
+
Process all SQS records in the event.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
tx: Database transaction object
|
|
101
|
+
"""
|
|
46
102
|
records = self.aws_event.get("Records", [])
|
|
103
|
+
|
|
47
104
|
for record in records:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
105
|
+
self._process_record(tx, record)
|
|
106
|
+
|
|
107
|
+
def _process_record(self, tx, record: Dict[str, Any]):
|
|
108
|
+
"""
|
|
109
|
+
Process a single SQS record.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
tx: Database transaction object
|
|
113
|
+
record: Individual SQS record to process
|
|
114
|
+
"""
|
|
115
|
+
attrs = record.get("attributes", {})
|
|
116
|
+
postdata = {}
|
|
117
|
+
|
|
118
|
+
# Parse message body if present
|
|
119
|
+
body = record.get("body")
|
|
120
|
+
if body:
|
|
61
121
|
try:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
122
|
+
postdata = json.loads(body)
|
|
123
|
+
except json.JSONDecodeError as e:
|
|
124
|
+
print(f"Failed to parse SQS message body as JSON: {e}")
|
|
125
|
+
postdata = {"raw_body": body}
|
|
126
|
+
|
|
127
|
+
# Create local context for this record
|
|
128
|
+
local_context = self.ContextClass(
|
|
129
|
+
aws_event=self.aws_event,
|
|
130
|
+
aws_context=self.aws_context,
|
|
131
|
+
args=attrs,
|
|
132
|
+
postdata=postdata,
|
|
133
|
+
response=None,
|
|
134
|
+
session=None,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
self._execute_actions(local_context)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
if hasattr(self, "onError"):
|
|
141
|
+
self.onError(
|
|
142
|
+
local_context,
|
|
143
|
+
exc=e.__class__.__name__,
|
|
144
|
+
tb=traceback.format_exc(),
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
# Re-raise if no error handler is defined
|
|
148
|
+
raise
|
|
149
|
+
|
|
150
|
+
def _execute_actions(self, local_context):
|
|
151
|
+
"""
|
|
152
|
+
Execute the appropriate actions for the given context.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
local_context: The context object for this record
|
|
156
|
+
"""
|
|
157
|
+
# Execute beforeAction hook if available
|
|
158
|
+
if hasattr(self, "beforeAction"):
|
|
159
|
+
self.beforeAction(local_context)
|
|
160
|
+
|
|
161
|
+
# Determine which actions to execute
|
|
162
|
+
actions = self._get_actions_to_execute(local_context)
|
|
163
|
+
|
|
164
|
+
# Execute the first matching action
|
|
165
|
+
for action in actions:
|
|
166
|
+
if self.skip_action:
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
if hasattr(self, action):
|
|
170
|
+
getattr(self, action)(local_context)
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
# Execute afterAction hook if available
|
|
174
|
+
if hasattr(self, "afterAction"):
|
|
175
|
+
self.afterAction(local_context)
|
|
176
|
+
|
|
177
|
+
def _get_actions_to_execute(self, local_context) -> list:
|
|
178
|
+
"""
|
|
179
|
+
Get the list of actions to execute for the given context.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
local_context: The context object for this record
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
List of action method names to try executing
|
|
186
|
+
"""
|
|
187
|
+
actions = []
|
|
188
|
+
|
|
189
|
+
# Add specific action if available
|
|
190
|
+
action = local_context.action()
|
|
191
|
+
if action:
|
|
192
|
+
action_method = self._format_action_name(action)
|
|
193
|
+
actions.append(action_method)
|
|
194
|
+
|
|
195
|
+
# Add default action if enabled
|
|
196
|
+
if self.serve_action_default:
|
|
197
|
+
actions.append("OnActionDefault")
|
|
198
|
+
|
|
199
|
+
return actions
|
|
200
|
+
|
|
201
|
+
def _format_action_name(self, action: str) -> str:
|
|
202
|
+
"""
|
|
203
|
+
Format an action string into a method name.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
action: The raw action string
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Formatted method name
|
|
210
|
+
"""
|
|
211
|
+
formatted = action.replace('-', ' ').replace('_', ' ')
|
|
212
|
+
return f"on action {formatted}".title().replace(" ", "")
|
|
89
213
|
|
|
90
214
|
def OnActionDefault(self, tx, context):
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
215
|
+
"""
|
|
216
|
+
Default action handler when no specific action is found.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
tx: Database transaction object
|
|
220
|
+
context: The context object for this record
|
|
221
|
+
"""
|
|
222
|
+
action = context.action() if hasattr(context, 'action') else 'unknown'
|
|
223
|
+
warning_message = (
|
|
224
|
+
f"[Warn] Action handler not found. Calling default action "
|
|
225
|
+
f"`SqsHandler.OnActionDefault` with the following parameters:\n"
|
|
226
|
+
f" - action: {action}\n"
|
|
227
|
+
f" - attrs: {context.args()}\n"
|
|
228
|
+
f" - postdata: {context.postdata()}"
|
|
97
229
|
)
|
|
230
|
+
print(warning_message)
|
|
@@ -256,7 +256,7 @@ class SQL:
|
|
|
256
256
|
|
|
257
257
|
# FROM clause
|
|
258
258
|
if th.foreign_keys:
|
|
259
|
-
sql_parts["FROM"].append(f"{
|
|
259
|
+
sql_parts["FROM"].append(f"{TableHelper.quote(table)} AS {TableHelper.quote(alias)}")
|
|
260
260
|
# Handle joins
|
|
261
261
|
done = []
|
|
262
262
|
for key, ref_info in th.foreign_keys.items():
|
|
@@ -270,11 +270,11 @@ class SQL:
|
|
|
270
270
|
):
|
|
271
271
|
raise ValueError(f"Invalid table alias info for {ref_table}.")
|
|
272
272
|
sql_parts["FROM"].append(
|
|
273
|
-
f"LEFT JOIN {
|
|
274
|
-
f"ON {
|
|
273
|
+
f"LEFT JOIN {TableHelper.quote(ref_table)} AS {TableHelper.quote(ref_info['alias'])} "
|
|
274
|
+
f"ON {TableHelper.quote(alias)}.{TableHelper.quote(ref_info['local_column'])} = {TableHelper.quote(ref_info['alias'])}.{TableHelper.quote(ref_info['ref_column'])}"
|
|
275
275
|
)
|
|
276
276
|
else:
|
|
277
|
-
sql_parts["FROM"].append(
|
|
277
|
+
sql_parts["FROM"].append(TableHelper.quote(table))
|
|
278
278
|
|
|
279
279
|
# WHERE
|
|
280
280
|
if where:
|
|
@@ -446,7 +446,7 @@ class SQL:
|
|
|
446
446
|
else:
|
|
447
447
|
sql_parts = []
|
|
448
448
|
sql_parts.append("UPDATE")
|
|
449
|
-
sql_parts.append(
|
|
449
|
+
sql_parts.append(TableHelper.quote(table))
|
|
450
450
|
sql_parts.append("SET " + ", ".join(set_clauses))
|
|
451
451
|
if where_clauses:
|
|
452
452
|
sql_parts.append("WHERE " + " AND ".join(where_clauses))
|
|
@@ -534,8 +534,10 @@ class TableHelper:
|
|
|
534
534
|
|
|
535
535
|
return " ".join(sql_parts), tuple(vals)
|
|
536
536
|
|
|
537
|
-
|
|
537
|
+
@classmethod
|
|
538
|
+
def quote(cls, data: Union[str, List[str]]) -> Union[str, List[str]]:
|
|
538
539
|
"""
|
|
540
|
+
Class method version of quote for backward compatibility.
|
|
539
541
|
Quotes identifiers (columns/tables) if needed, especially if they match reserved words or contain special chars.
|
|
540
542
|
|
|
541
543
|
Args:
|
|
@@ -545,7 +547,7 @@ class TableHelper:
|
|
|
545
547
|
Quoted identifier(s)
|
|
546
548
|
"""
|
|
547
549
|
if isinstance(data, list):
|
|
548
|
-
return [
|
|
550
|
+
return [cls.quote(item) for item in data]
|
|
549
551
|
|
|
550
552
|
if not isinstance(data, str):
|
|
551
553
|
raise ValueError(f"Data must be string or list, got {type(data)}")
|
|
@@ -565,7 +567,7 @@ class TableHelper:
|
|
|
565
567
|
if part.startswith('"') and part.endswith('"'):
|
|
566
568
|
quoted_parts.append(part)
|
|
567
569
|
# Quote if reserved word, contains special chars, or starts with digit
|
|
568
|
-
elif (part.upper() in
|
|
570
|
+
elif (part.upper() in cls.reserved or
|
|
569
571
|
re.search(r'[/\-\s]', part) or
|
|
570
572
|
(part and part[0].isdigit())):
|
|
571
573
|
# Escape any existing quotes in the identifier
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
velocity/__init__.py,sha256=
|
|
1
|
+
velocity/__init__.py,sha256=Vu_ESj4QSJKxwVVLSBVPrZH3Wl_7Indluy1KRK1W9_Q,106
|
|
2
2
|
velocity/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
velocity/app/invoices.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
velocity/app/orders.py,sha256=W-HAXEwY8-IFXbKh82HnMeRVZM7P-TWGEQOWtkLIzI4,6298
|
|
@@ -10,7 +10,7 @@ velocity/aws/handlers/__init__.py,sha256=xnpFZJVlC2uoeeFW4zuPST8wA8ajaQDky5Y6iXZ
|
|
|
10
10
|
velocity/aws/handlers/context.py,sha256=UIjNR83y2NSIyK8HMPX8t5tpJHFNabiZvNgmmdQL3HA,1822
|
|
11
11
|
velocity/aws/handlers/lambda_handler.py,sha256=0KrT6UIxDILzBRpoRSvwDgHpQ-vWfubcZFOCbJsewDc,6516
|
|
12
12
|
velocity/aws/handlers/response.py,sha256=LXhtizLKnVBWjtHyE0h0bk-NYDrRpj7CHa7tRz9KkC4,9324
|
|
13
|
-
velocity/aws/handlers/sqs_handler.py,sha256=
|
|
13
|
+
velocity/aws/handlers/sqs_handler.py,sha256=akkDeZzTBShh2V1bSjl4VgRHCABBMt8fz3Zj9SgooPI,7340
|
|
14
14
|
velocity/db/__init__.py,sha256=vrn2AFNAKaqTdnPwLFS0OcREcCtzUCOodlmH54U7ADg,200
|
|
15
15
|
velocity/db/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
velocity/db/core/column.py,sha256=tAr8tL3a2nyaYpNHhGl508FrY_pGZTzyYgjAV5CEBv4,4092
|
|
@@ -30,11 +30,11 @@ velocity/db/servers/sqlite.py,sha256=X210a5pENT9PiVK7f16fxXzFwEsq8fSe58Vouv2xqlk
|
|
|
30
30
|
velocity/db/servers/sqlite_reserved.py,sha256=-xmjl-Hgu6lKqkCAXq_6U8_aJX6gvaMgLMLdCt-Ej7o,3006
|
|
31
31
|
velocity/db/servers/sqlserver.py,sha256=0uGLEWRXiUhrOVTpEA1zvaKq1mcfiaCDp9r7gX-N71g,29914
|
|
32
32
|
velocity/db/servers/sqlserver_reserved.py,sha256=3LGQYU0qfvk6AbKety96gbzzfLbZ0dNHDPLxKGvvi4Q,4596
|
|
33
|
-
velocity/db/servers/tablehelper.py,sha256=
|
|
34
|
-
velocity/db/servers/postgres/__init__.py,sha256=
|
|
33
|
+
velocity/db/servers/tablehelper.py,sha256=S_759UBIqj-xn6IzwIu6wU7QSrUdLtg0-T80j23m23Y,22016
|
|
34
|
+
velocity/db/servers/postgres/__init__.py,sha256=FUvXO3R5CtKCTGRim1geisIxXbiG_aQ_VFSQX9HGsjw,529
|
|
35
35
|
velocity/db/servers/postgres/operators.py,sha256=A2T1qFwhzPl0fdXVhLZJhh5Qfx-qF8oZsDnxnq2n_V8,389
|
|
36
36
|
velocity/db/servers/postgres/reserved.py,sha256=5tKLaqFV-HrWRj-nsrxl5KGbmeM3ukn_bPZK36XEu8M,3648
|
|
37
|
-
velocity/db/servers/postgres/sql.py,sha256=
|
|
37
|
+
velocity/db/servers/postgres/sql.py,sha256=dDUcdErrAgWulrr6582p4Zf1co6-PH8MfVU2q9KRisI,41416
|
|
38
38
|
velocity/db/servers/postgres/types.py,sha256=Wa45ppVf_pdWul-jYWFRGMl6IdSq8dAp10SKnhL7osQ,3757
|
|
39
39
|
velocity/misc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
40
|
velocity/misc/db.py,sha256=MPgt-kkukKR_Wh_S_5W-MyDgaeoZ4YLoDJ54wU2ppm4,2830
|
|
@@ -47,8 +47,8 @@ velocity/misc/tools.py,sha256=_bGneHHA_BV-kUonzw5H3hdJ5AOJRCKfzhgpkFbGqIo,1502
|
|
|
47
47
|
velocity/misc/conv/__init__.py,sha256=MLYF58QHjzfDSxb1rdnmLnuEQCa3gnhzzZ30CwZVvQo,40
|
|
48
48
|
velocity/misc/conv/iconv.py,sha256=d4_BucW8HTIkGNurJ7GWrtuptqUf-9t79ObzjJ5N76U,10603
|
|
49
49
|
velocity/misc/conv/oconv.py,sha256=h5Lo05DqOQnxoD3y6Px_MQP_V-pBbWf8Hkgkb9Xp1jk,6032
|
|
50
|
-
velocity_python-0.0.
|
|
51
|
-
velocity_python-0.0.
|
|
52
|
-
velocity_python-0.0.
|
|
53
|
-
velocity_python-0.0.
|
|
54
|
-
velocity_python-0.0.
|
|
50
|
+
velocity_python-0.0.96.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
|
|
51
|
+
velocity_python-0.0.96.dist-info/METADATA,sha256=n_d34QeGMb4TaeXSH5p-RJvynnHZLHde4a3SCCK-wzY,33022
|
|
52
|
+
velocity_python-0.0.96.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
53
|
+
velocity_python-0.0.96.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
|
|
54
|
+
velocity_python-0.0.96.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|