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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.0.94"
1
+ __version__ = version = "0.0.96"
2
2
 
3
3
  from . import aws
4
4
  from . import db
@@ -1,97 +1,230 @@
1
- from velocity.misc.format import to_json
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 = "<Unknown>"
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["ProjectName"],
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": "lambda:BackOfficeQueueHandler",
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
- attrs = record.get("attributes")
49
- postdata = {}
50
- if record.get("body"):
51
- postdata = json.loads(record.get("body"))
52
-
53
- local_context = self.ContextClass(
54
- aws_event=self.aws_event,
55
- aws_context=self.aws_context,
56
- args=attrs,
57
- postdata=postdata,
58
- response=None,
59
- session=None,
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
- if hasattr(self, "beforeAction"):
63
- self.beforeAction(local_context)
64
- actions = []
65
- action = local_context.action()
66
- if action:
67
- actions.append(
68
- f"on action {action.replace('-', ' ').replace('_', ' ')}".title().replace(
69
- " ", ""
70
- )
71
- )
72
- if self.serve_action_default:
73
- actions.append("OnActionDefault")
74
- for action in actions:
75
- if self.skip_action:
76
- return
77
- if hasattr(self, action):
78
- getattr(self, action)(local_context)
79
- break
80
- if hasattr(self, "afterAction"):
81
- self.afterAction(local_context)
82
- except Exception as e:
83
- if hasattr(self, "onError"):
84
- self.onError(
85
- local_context,
86
- exc=e.__class__.__name__,
87
- tb=traceback.format_exc(),
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
- print(
92
- f"""
93
- [Warn] Action handler not found. Calling default action `SqsHandler.OnActionDefault` with the following parameters for attrs, and postdata:
94
- attrs: {str(context.args())}
95
- postdata: {str(context.postdata())}
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)
@@ -4,6 +4,7 @@ from .sql import SQL
4
4
  from velocity.db.core import engine
5
5
 
6
6
 
7
+
7
8
  def initialize(config=None, **kwargs):
8
9
  if not config:
9
10
  # Keep the default config inside this function.
@@ -256,7 +256,7 @@ class SQL:
256
256
 
257
257
  # FROM clause
258
258
  if th.foreign_keys:
259
- sql_parts["FROM"].append(f"{th.quote(table)} AS {th.quote(alias)}")
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 {th.quote(ref_table)} AS {th.quote(ref_info['alias'])} "
274
- f"ON {th.quote(alias)}.{th.quote(ref_info['local_column'])} = {th.quote(ref_info['alias'])}.{th.quote(ref_info['ref_column'])}"
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(th.quote(table))
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(th.quote(table))
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
- def quote(self, data: Union[str, List[str]]) -> Union[str, List[str]]:
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 [self.quote(item) for item in data]
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 self.reserved or
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.94
3
+ Version: 0.0.96
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <contact@example.com>
6
6
  License: MIT
@@ -1,4 +1,4 @@
1
- velocity/__init__.py,sha256=PsMnrSNELgyykj-XBC8NxUNJMeeGI2aQYvJjUNWcpzQ,106
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=YBqrEkA6EfkQUVk_kwsSI-HjFJO8-JqYco-p0UYDNXE,3368
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=IW17pedialg2xNORv2uAqNA9SB3aomDtB1HRxAEvBmA,21936
34
- velocity/db/servers/postgres/__init__.py,sha256=Z0zJib46hz1zfEAMG0rQBlJ6lp47LW7t0_63xMS0zrI,528
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=OoVctgZ3C2sUu6PeMFVCbmQHyfWY-KNmBlCX2PkvaGA,41326
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.94.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
51
- velocity_python-0.0.94.dist-info/METADATA,sha256=2mDuaDS-hMWf4ZCUraBa8v0dCi9e64800byK_pShRu8,33022
52
- velocity_python-0.0.94.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- velocity_python-0.0.94.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
54
- velocity_python-0.0.94.dist-info/RECORD,,
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,,