velocity-python 0.0.95__py3-none-any.whl → 0.0.97__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.

Potentially problematic release.


This version of velocity-python might be problematic. Click here for more details.

velocity/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.0.95"
1
+ __version__ = version = "0.0.97"
2
2
 
3
3
  from . import aws
4
4
  from . import db
@@ -1,97 +1,224 @@
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
+ """
11
26
 
12
- def __init__(self, aws_event, aws_context, context_class=VelocityContext.Context):
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
+ """
13
37
  self.aws_event = aws_event
14
38
  self.aws_context = aws_context
15
39
  self.serve_action_default = True
16
40
  self.skip_action = False
17
41
  self.ContextClass = context_class
18
42
 
19
- def log(self, tx, message, function=None):
43
+ def log(self, tx, message: str, function: Optional[str] = None):
44
+ """
45
+ Log a message to the system log table.
46
+
47
+ Args:
48
+ tx: Database transaction object
49
+ message: The message to log
50
+ function: Optional function name, auto-detected if not provided
51
+ """
20
52
  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
53
+ function = self._get_calling_function()
33
54
 
34
55
  data = {
35
- "app_name": os.environ["ProjectName"],
56
+ "app_name": os.environ.get("ProjectName", "Unknown"),
36
57
  "referer": "SQS",
37
58
  "user_agent": "QueueHandler",
38
59
  "device_type": "Lambda",
39
60
  "function": function,
40
61
  "message": message,
41
- "sys_modified_by": "lambda:BackOfficeQueueHandler",
62
+ "sys_modified_by": "Lambda:BackOfficeQueueHandler",
42
63
  }
43
64
  tx.table("sys_log").insert(data)
65
+
66
+ def _get_calling_function(self) -> str:
67
+ """
68
+ Get the name of the calling function by inspecting the call stack.
69
+
70
+ Returns:
71
+ The name of the calling function or "<Unknown>" if not found
72
+ """
73
+ skip_functions = {"x", "log", "_transaction", "_get_calling_function"}
74
+
75
+ for idx in range(10): # Limit search to prevent infinite loops
76
+ try:
77
+ frame = sys._getframe(idx)
78
+ function_name = frame.f_code.co_name
79
+
80
+ if function_name not in skip_functions:
81
+ return function_name
82
+
83
+ except ValueError:
84
+ # No more frames in the stack
85
+ break
86
+
87
+ return "<Unknown>"
44
88
 
45
89
  def serve(self, tx):
90
+ """
91
+ Process all SQS records in the event.
92
+
93
+ Args:
94
+ tx: Database transaction object
95
+ """
46
96
  records = self.aws_event.get("Records", [])
97
+
47
98
  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
- )
99
+ self._process_record(tx, record)
100
+
101
+ def _process_record(self, tx, record: Dict[str, Any]):
102
+ """
103
+ Process a single SQS record.
104
+
105
+ Args:
106
+ tx: Database transaction object
107
+ record: Individual SQS record to process
108
+ """
109
+ attrs = record.get("attributes", {})
110
+ postdata = {}
111
+
112
+ # Parse message body if present
113
+ body = record.get("body")
114
+ if body:
61
115
  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
- )
116
+ postdata = json.loads(body)
117
+ except json.JSONDecodeError as e:
118
+ print(f"Failed to parse SQS message body as JSON: {e}")
119
+ postdata = {"raw_body": body}
120
+
121
+ # Create local context for this record
122
+ local_context = self.ContextClass(
123
+ aws_event=self.aws_event,
124
+ aws_context=self.aws_context,
125
+ args=attrs,
126
+ postdata=postdata,
127
+ response=None,
128
+ session=None,
129
+ )
130
+
131
+ try:
132
+ self._execute_actions(local_context)
133
+ except Exception as e:
134
+ if hasattr(self, "onError"):
135
+ self.onError(
136
+ local_context,
137
+ exc=e.__class__.__name__,
138
+ tb=traceback.format_exc(),
139
+ )
140
+ else:
141
+ # Re-raise if no error handler is defined
142
+ raise
143
+
144
+ def _execute_actions(self, local_context):
145
+ """
146
+ Execute the appropriate actions for the given context.
147
+
148
+ Args:
149
+ local_context: The context object for this record
150
+ """
151
+ # Execute beforeAction hook if available
152
+ if hasattr(self, "beforeAction"):
153
+ self.beforeAction(local_context)
154
+
155
+ # Determine which actions to execute
156
+ actions = self._get_actions_to_execute(local_context)
157
+
158
+ # Execute the first matching action
159
+ for action in actions:
160
+ if self.skip_action:
161
+ return
162
+
163
+ if hasattr(self, action):
164
+ getattr(self, action)(local_context)
165
+ break
166
+
167
+ # Execute afterAction hook if available
168
+ if hasattr(self, "afterAction"):
169
+ self.afterAction(local_context)
170
+
171
+ def _get_actions_to_execute(self, local_context) -> list:
172
+ """
173
+ Get the list of actions to execute for the given context.
174
+
175
+ Args:
176
+ local_context: The context object for this record
177
+
178
+ Returns:
179
+ List of action method names to try executing
180
+ """
181
+ actions = []
182
+
183
+ # Add specific action if available
184
+ action = local_context.action()
185
+ if action:
186
+ action_method = self._format_action_name(action)
187
+ actions.append(action_method)
188
+
189
+ # Add default action if enabled
190
+ if self.serve_action_default:
191
+ actions.append("OnActionDefault")
192
+
193
+ return actions
194
+
195
+ def _format_action_name(self, action: str) -> str:
196
+ """
197
+ Format an action string into a method name.
198
+
199
+ Args:
200
+ action: The raw action string
201
+
202
+ Returns:
203
+ Formatted method name
204
+ """
205
+ formatted = action.replace('-', ' ').replace('_', ' ')
206
+ return f"on action {formatted}".title().replace(" ", "")
89
207
 
90
208
  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
- """
209
+ """
210
+ Default action handler when no specific action is found.
211
+
212
+ Args:
213
+ tx: Database transaction object
214
+ context: The context object for this record
215
+ """
216
+ action = context.action() if hasattr(context, 'action') else 'unknown'
217
+ warning_message = (
218
+ f"[Warn] Action handler not found. Calling default action "
219
+ f"`SqsHandler.OnActionDefault` with the following parameters:\n"
220
+ f" - action: {action}\n"
221
+ f" - attrs: {context.args()}\n"
222
+ f" - postdata: {context.postdata()}"
97
223
  )
224
+ print(warning_message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.95
3
+ Version: 0.0.97
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=Ia6_qjrZBVx6x83-y9sI4W7UBXzNOKPSIofuJb7lVrk,106
1
+ velocity/__init__.py,sha256=INVMQ4GO-JInxsuAsrayUJ5jvsooS1cSwDflvVJjvPE,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=nqt8NMOc5yO-L6CZo7NjgR8Q4KPKTDFBO-0eHu6oxkY,7149
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
@@ -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.95.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
51
- velocity_python-0.0.95.dist-info/METADATA,sha256=0ve_k4tFITuVudvOEsARj3hIPyuaAhTCEWXhTiTUA3g,33022
52
- velocity_python-0.0.95.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- velocity_python-0.0.95.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
54
- velocity_python-0.0.95.dist-info/RECORD,,
50
+ velocity_python-0.0.97.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
51
+ velocity_python-0.0.97.dist-info/METADATA,sha256=vQvxtwSQIBj6yC8li-uFlArx8F6IqyhMd3oH4sXB8go,33022
52
+ velocity_python-0.0.97.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
+ velocity_python-0.0.97.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
54
+ velocity_python-0.0.97.dist-info/RECORD,,