velocity-python 0.0.116__tar.gz → 0.0.117__tar.gz

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.

Files changed (86) hide show
  1. {velocity_python-0.0.116 → velocity_python-0.0.117}/PKG-INFO +1 -1
  2. {velocity_python-0.0.116 → velocity_python-0.0.117}/pyproject.toml +1 -1
  3. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/aws/amplify.py +8 -3
  5. velocity_python-0.0.117/src/velocity/aws/handlers/base_handler.py +245 -0
  6. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/aws/handlers/context.py +58 -41
  7. velocity_python-0.0.117/src/velocity/aws/handlers/exceptions.py +16 -0
  8. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/aws/handlers/lambda_handler.py +12 -77
  9. velocity_python-0.0.117/src/velocity/aws/handlers/sqs_handler.py +108 -0
  10. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/__init__.py +1 -1
  11. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/engine.py +30 -26
  12. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/exceptions.py +3 -0
  13. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/result.py +30 -22
  14. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/row.py +3 -1
  15. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/table.py +2 -1
  16. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/exceptions.py +35 -18
  17. velocity_python-0.0.117/src/velocity/db/servers/postgres/__init__.py +17 -0
  18. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/postgres/sql.py +32 -13
  19. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/tablehelper.py +117 -91
  20. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/utils.py +61 -46
  21. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity_python.egg-info/PKG-INFO +1 -1
  22. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity_python.egg-info/SOURCES.txt +2 -0
  23. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_cursor_rowcount_fix.py +33 -33
  24. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_db_utils.py +72 -46
  25. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_fix.py +43 -13
  26. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_oconv.py +3 -1
  27. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_original_error.py +26 -8
  28. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_payment_profile_sorting.py +79 -32
  29. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_postgres.py +14 -13
  30. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_process_error_robustness.py +132 -76
  31. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_response.py +3 -3
  32. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_result_caching.py +77 -75
  33. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_result_sql_aware.py +37 -35
  34. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_row_get_missing_column.py +32 -28
  35. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_tablehelper.py +248 -200
  36. velocity_python-0.0.116/src/velocity/aws/handlers/sqs_handler.py +0 -224
  37. velocity_python-0.0.116/src/velocity/db/servers/postgres/__init__.py +0 -19
  38. {velocity_python-0.0.116 → velocity_python-0.0.117}/LICENSE +0 -0
  39. {velocity_python-0.0.116 → velocity_python-0.0.117}/README.md +0 -0
  40. {velocity_python-0.0.116 → velocity_python-0.0.117}/setup.cfg +0 -0
  41. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/app/__init__.py +0 -0
  42. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/app/invoices.py +0 -0
  43. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/app/orders.py +0 -0
  44. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/app/payments.py +0 -0
  45. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/app/purchase_orders.py +0 -0
  46. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/aws/__init__.py +0 -0
  47. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/aws/handlers/__init__.py +0 -0
  48. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/aws/handlers/response.py +0 -0
  49. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/__init__.py +0 -0
  50. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/column.py +0 -0
  51. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/database.py +0 -0
  52. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/decorators.py +0 -0
  53. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/sequence.py +0 -0
  54. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/core/transaction.py +0 -0
  55. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/__init__.py +0 -0
  56. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/mysql.py +0 -0
  57. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/mysql_reserved.py +0 -0
  58. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/postgres/operators.py +0 -0
  59. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/postgres/reserved.py +0 -0
  60. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/postgres/types.py +0 -0
  61. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/sqlite.py +0 -0
  62. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/sqlite_reserved.py +0 -0
  63. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/sqlserver.py +0 -0
  64. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/db/servers/sqlserver_reserved.py +0 -0
  65. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/__init__.py +0 -0
  66. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/conv/__init__.py +0 -0
  67. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/conv/iconv.py +0 -0
  68. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/conv/oconv.py +0 -0
  69. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/db.py +0 -0
  70. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/export.py +0 -0
  71. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/format.py +0 -0
  72. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/mail.py +0 -0
  73. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/merge.py +0 -0
  74. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/timer.py +0 -0
  75. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity/misc/tools.py +0 -0
  76. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  77. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity_python.egg-info/requires.txt +0 -0
  78. {velocity_python-0.0.116 → velocity_python-0.0.117}/src/velocity_python.egg-info/top_level.txt +0 -0
  79. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_db.py +0 -0
  80. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_email_processing.py +0 -0
  81. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_format.py +0 -0
  82. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_iconv.py +0 -0
  83. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_merge.py +0 -0
  84. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_spreadsheet_functions.py +0 -0
  85. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_sql_builder.py +0 -0
  86. {velocity_python-0.0.116 → velocity_python-0.0.117}/tests/test_timer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.116
3
+ Version: 0.0.117
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.0.116"
7
+ version = "0.0.117"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.0.116"
1
+ __version__ = version = "0.0.117"
2
2
 
3
3
  from . import aws
4
4
  from . import db
@@ -320,12 +320,17 @@ class AmplifyProject:
320
320
  "Effect": "Allow",
321
321
  "Resource": "*",
322
322
  "Action": [
323
- "ec2:DescribeInstances",
323
+ "ec2:DescribeInstances",
324
324
  "ec2:CreateNetworkInterface",
325
325
  "ec2:AttachNetworkInterface",
326
- "ec2:DescribeNetworkInterfaces",
326
+ "ec2:DescribeNetworkInterfaces",
327
327
  "ec2:DeleteNetworkInterface",
328
- "cognito-idp:*",
328
+ "ec2:DescribeSecurityGroups",
329
+ "ec2:AuthorizeSecurityGroupIngress",
330
+ "ec2:AuthorizeSecurityGroupEgress",
331
+ "ec2:RevokeSecurityGroupIngress",
332
+ "ec2:RevokeSecurityGroupEgress",
333
+ "cognito-idp:*"
329
334
  ],
330
335
  }
331
336
  ],
@@ -0,0 +1,245 @@
1
+ """
2
+ Base Handler Module
3
+
4
+ This module provides a base class for handling AWS Lambda events.
5
+ It includes common functionality shared between LambdaHandler and SqsHandler.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import traceback
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from velocity.aws.handlers import context as VelocityContext
14
+
15
+
16
+ class BaseHandler:
17
+ """
18
+ Base class for handling AWS Lambda events.
19
+
20
+ Provides common functionality including action routing, logging,
21
+ and error handling hooks that can be shared across different handler types.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ aws_event: Dict[str, Any],
27
+ aws_context: Any,
28
+ context_class=VelocityContext.Context,
29
+ ):
30
+ """
31
+ Initialize the base handler.
32
+
33
+ Args:
34
+ aws_event: The AWS Lambda event
35
+ aws_context: The AWS Lambda context object
36
+ context_class: The context class to use for processing
37
+ """
38
+ self.aws_event = aws_event
39
+ self.aws_context = aws_context
40
+ self.serve_action_default = True # Set to False to disable OnActionDefault
41
+ self.skip_action = False # Set to True to skip all actions
42
+ self.ContextClass = context_class
43
+
44
+ # Configure SSL certificates for HTTPS requests
45
+ self._update_lambda_ca_certificates()
46
+
47
+ def _update_lambda_ca_certificates(self):
48
+ """Configure SSL certificates for HTTPS requests in Lambda environments."""
49
+ # This is helpful for running HTTPS clients on lambda.
50
+ if os.path.exists("/opt/python/ca-certificates.crt"):
51
+ os.environ["REQUESTS_CA_BUNDLE"] = "/opt/python/ca-certificates.crt"
52
+ elif os.path.exists("/home/ubuntu/PyLibLayer/ca-certificates.crt"):
53
+ os.environ["REQUESTS_CA_BUNDLE"] = (
54
+ "/home/ubuntu/PyLibLayer/ca-certificates.crt"
55
+ )
56
+
57
+ def get_calling_function(self) -> str:
58
+ """
59
+ Get the name of the calling function by inspecting the call stack.
60
+
61
+ Returns:
62
+ The name of the calling function or "<Unknown>" if not found
63
+ """
64
+ skip_functions = {"x", "log", "_transaction", "get_calling_function"}
65
+
66
+ for idx in range(10): # Limit search to prevent infinite loops
67
+ try:
68
+ frame = sys._getframe(idx)
69
+ function_name = frame.f_code.co_name
70
+
71
+ if function_name not in skip_functions:
72
+ return function_name
73
+
74
+ except ValueError:
75
+ # No more frames in the stack
76
+ break
77
+
78
+ return "<Unknown>"
79
+
80
+ def format_action_name(self, action: str) -> str:
81
+ """
82
+ Format an action string into a method name.
83
+
84
+ Args:
85
+ action: The raw action string (e.g., "create-user", "delete_item")
86
+
87
+ Returns:
88
+ Formatted method name (e.g., "OnActionCreateUser", "OnActionDeleteItem")
89
+ """
90
+ if not action:
91
+ return ""
92
+
93
+ formatted = action.replace("-", " ").replace("_", " ")
94
+ return f"on action {formatted}".title().replace(" ", "")
95
+
96
+ def get_actions_to_execute(self, action: Optional[str]) -> List[str]:
97
+ """
98
+ Get the list of actions to execute.
99
+
100
+ Args:
101
+ action: The specific action to execute, if any
102
+
103
+ Returns:
104
+ List of action method names to try executing
105
+ """
106
+ actions = []
107
+
108
+ # Add specific action if available
109
+ if action:
110
+ action_method = self.format_action_name(action)
111
+ actions.append(action_method)
112
+
113
+ # Add default action if enabled
114
+ if self.serve_action_default:
115
+ actions.append("OnActionDefault")
116
+
117
+ return actions
118
+
119
+ def execute_actions(self, local_context, actions: List[str]) -> bool:
120
+ """
121
+ Execute the appropriate actions for the given context.
122
+
123
+ Args:
124
+ local_context: The context object
125
+ actions: List of action method names to try
126
+
127
+ Returns:
128
+ True if an action was executed, False otherwise
129
+ """
130
+ # Execute beforeAction hook if available
131
+ if hasattr(self, "beforeAction"):
132
+ self.beforeAction(local_context)
133
+
134
+ action_executed = False
135
+
136
+ # Execute the first matching action
137
+ for action in actions:
138
+ if self.skip_action:
139
+ break
140
+
141
+ if hasattr(self, action):
142
+ result = getattr(self, action)(local_context)
143
+
144
+ # Check for deprecated return values (LambdaHandler specific)
145
+ if result is not None and hasattr(self, "_check_deprecated_return"):
146
+ self._check_deprecated_return(action, result)
147
+
148
+ action_executed = True
149
+ break
150
+
151
+ # Execute afterAction hook if available
152
+ if hasattr(self, "afterAction"):
153
+ self.afterAction(local_context)
154
+
155
+ return action_executed
156
+
157
+ def handle_error(self, local_context, exception: Exception):
158
+ """
159
+ Handle errors that occur during action execution.
160
+
161
+ Args:
162
+ local_context: The context object
163
+ exception: The exception that occurred
164
+ """
165
+ if hasattr(self, "onError"):
166
+ self.onError(
167
+ local_context,
168
+ exc=exception.__class__.__name__,
169
+ tb=traceback.format_exc(),
170
+ )
171
+ else:
172
+ # Re-raise if no error handler is defined
173
+ raise
174
+
175
+ def log(self, tx, message: str, function: Optional[str] = None):
176
+ """
177
+ Log a message to the system log table.
178
+ This is a base implementation that should be overridden by subclasses
179
+ to provide handler-specific logging details.
180
+
181
+ Args:
182
+ tx: Database transaction object
183
+ message: The message to log
184
+ function: Optional function name, auto-detected if not provided
185
+ """
186
+ if not function:
187
+ function = self.get_calling_function()
188
+
189
+ # Base log data - subclasses should extend this
190
+ data = {
191
+ "app_name": os.environ.get("ProjectName", "Unknown"),
192
+ "function": function,
193
+ "message": message,
194
+ }
195
+
196
+ # Let subclasses add their specific fields
197
+ self._extend_log_data(data)
198
+
199
+ tx.table("sys_log").insert(data)
200
+
201
+ def _extend_log_data(self, data: Dict[str, Any]):
202
+ """
203
+ Extend log data with handler-specific fields.
204
+ To be overridden by subclasses.
205
+
206
+ Args:
207
+ data: The base log data dictionary to extend
208
+ """
209
+ # Default implementation adds basic Lambda info
210
+ data.update(
211
+ {
212
+ "user_agent": "AWS Lambda",
213
+ "device_type": "Lambda",
214
+ "sys_modified_by": "Lambda",
215
+ }
216
+ )
217
+
218
+ def OnActionDefault(self, local_context):
219
+ """
220
+ Default action handler when no specific action is found.
221
+
222
+ Args:
223
+ local_context: The context object
224
+ """
225
+ action = getattr(local_context, "action", lambda: "unknown")()
226
+ warning_message = (
227
+ f"[Warn] Action handler not found. Calling default action "
228
+ f"`{self.__class__.__name__}.OnActionDefault` with the following parameters:\n"
229
+ f" - action: {action}\n"
230
+ f" - handler: {self.__class__.__name__}"
231
+ )
232
+ print(warning_message)
233
+
234
+ def get_context_args(self) -> Dict[str, Any]:
235
+ """
236
+ Get the arguments to pass to the context constructor.
237
+ To be implemented by subclasses based on their specific needs.
238
+
239
+ Returns:
240
+ Dictionary of arguments for context initialization
241
+ """
242
+ return {
243
+ "aws_event": self.aws_event,
244
+ "aws_context": self.aws_context,
245
+ }
@@ -77,7 +77,7 @@ class Context:
77
77
 
78
78
  def update_job(self, tx, data=None):
79
79
  """Update job status and message in aws_job_activity table.
80
-
80
+
81
81
  This method only UPDATES existing jobs. For creating new jobs, use create_job.
82
82
  """
83
83
  if not data:
@@ -85,11 +85,15 @@ class Context:
85
85
  if self.postdata("job_id"):
86
86
  # Sanitize data before storing in database
87
87
  sanitized_data = self._sanitize_job_data(data)
88
- tx.table("aws_job_activity").update(sanitized_data, {"job_id": self.postdata("job_id")})
88
+ tx.table("aws_job_activity").update(
89
+ sanitized_data, {"job_id": self.postdata("job_id")}
90
+ )
89
91
  tx.commit()
90
-
91
- def create_job(self, job_data):
92
+
93
+ def create_job(self, tx, job_data=None):
92
94
  """Create a new job record in aws_job_activity table using independent transaction."""
95
+ if not job_data:
96
+ return
93
97
  sanitized_data = self._sanitize_job_data(job_data)
94
98
  tx.table("aws_job_activity").insert(sanitized_data)
95
99
  tx.commit()
@@ -98,60 +102,71 @@ class Context:
98
102
  """Sanitize sensitive data before storing in aws_job_activity table."""
99
103
  if not isinstance(data, dict):
100
104
  return data
101
-
105
+
102
106
  sanitized = {}
103
-
107
+
104
108
  # List of sensitive field patterns to sanitize
105
109
  sensitive_patterns = [
106
- 'password', 'token', 'secret', 'key', 'credential', 'auth',
107
- 'cognito_user', 'session', 'cookie', 'authorization'
110
+ "password",
111
+ "token",
112
+ "secret",
113
+ "key",
114
+ "credential",
115
+ "auth",
116
+ "cognito_user",
117
+ "session",
118
+ "cookie",
119
+ "authorization",
108
120
  ]
109
-
121
+
110
122
  for key, value in data.items():
111
123
  # Check if key contains sensitive patterns
112
124
  if any(pattern in key.lower() for pattern in sensitive_patterns):
113
125
  sanitized[key] = "[REDACTED]" if value else value
114
- elif key == 'error' and value:
126
+ elif key == "error" and value:
115
127
  # Sanitize error messages - keep first 500 chars and remove potential sensitive info
116
128
  error_str = str(value)[:500]
117
129
  for pattern in sensitive_patterns:
118
130
  if pattern in error_str.lower():
119
131
  # Replace potential sensitive values with placeholder
120
132
  import re
133
+
121
134
  # Remove patterns like password=value, token=value, etc.
122
135
  error_str = re.sub(
123
- rf'{pattern}[=:]\s*[^\s,\]}}]+',
124
- f'{pattern}=[REDACTED]',
125
- error_str,
126
- flags=re.IGNORECASE
136
+ rf"{pattern}[=:]\s*[^\s,\]}}]+",
137
+ f"{pattern}=[REDACTED]",
138
+ error_str,
139
+ flags=re.IGNORECASE,
127
140
  )
128
141
  sanitized[key] = error_str
129
- elif key == 'traceback' and value:
142
+ elif key == "traceback" and value:
130
143
  # Sanitize traceback - keep structure but remove sensitive values
131
144
  tb_str = str(value)
132
145
  for pattern in sensitive_patterns:
133
146
  if pattern in tb_str.lower():
134
147
  import re
148
+
135
149
  # Remove patterns like password=value, token=value, etc.
136
150
  tb_str = re.sub(
137
- rf'{pattern}[=:]\s*[^\s,\]}}]+',
138
- f'{pattern}=[REDACTED]',
139
- tb_str,
140
- flags=re.IGNORECASE
151
+ rf"{pattern}[=:]\s*[^\s,\]}}]+",
152
+ f"{pattern}=[REDACTED]",
153
+ tb_str,
154
+ flags=re.IGNORECASE,
141
155
  )
142
156
  # Limit traceback size to prevent DB bloat
143
157
  sanitized[key] = tb_str[:2000]
144
- elif key == 'message' and value:
158
+ elif key == "message" and value:
145
159
  # Sanitize message field
146
160
  message_str = str(value)
147
161
  for pattern in sensitive_patterns:
148
162
  if pattern in message_str.lower():
149
163
  import re
164
+
150
165
  message_str = re.sub(
151
- rf'{pattern}[=:]\s*[^\s,\]}}]+',
152
- f'{pattern}=[REDACTED]',
153
- message_str,
154
- flags=re.IGNORECASE
166
+ rf"{pattern}[=:]\s*[^\s,\]}}]+",
167
+ f"{pattern}=[REDACTED]",
168
+ message_str,
169
+ flags=re.IGNORECASE,
155
170
  )
156
171
  sanitized[key] = message_str[:1000] # Limit message size
157
172
  else:
@@ -163,13 +178,13 @@ class Context:
163
178
  sanitized[key] = value[:5000] + "...[TRUNCATED]"
164
179
  else:
165
180
  sanitized[key] = value
166
-
181
+
167
182
  return sanitized
168
183
 
169
184
  def enqueue(self, action, payload={}, user=None, suppress_job_activity=False):
170
185
  """
171
186
  Enqueue jobs to SQS with independent job activity tracking.
172
-
187
+
173
188
  This method uses its own transaction for aws_job_activity updates to ensure
174
189
  job tracking is never rolled back with other operations.
175
190
  """
@@ -181,7 +196,7 @@ class Context:
181
196
  if isinstance(payload, dict):
182
197
  payload = [payload]
183
198
  messages = []
184
- if user==None:
199
+ if user == None:
185
200
  user = self.session.get("email_address") or "EnqueueTasks"
186
201
  for item in payload:
187
202
  message = {"action": action, "payload": item}
@@ -191,26 +206,28 @@ class Context:
191
206
  else:
192
207
  message["job_id"] = id
193
208
  # Use separate transaction for job activity - this should never be rolled back
194
- self.create_job({
195
- "action": action,
196
- "initial_timestamp": datetime.now(),
197
- "created_by": user,
198
- "sys_modified_by": user,
199
- "payload": to_json(message),
200
- "batch_id": str(batch_id),
201
- "job_id": id,
202
- "status": "Initialized",
203
- "message": f"Job Initialized"
204
- })
209
+ self.create_job(
210
+ {
211
+ "action": action,
212
+ "initial_timestamp": datetime.now(),
213
+ "created_by": user,
214
+ "sys_modified_by": user,
215
+ "payload": to_json(message),
216
+ "batch_id": str(batch_id),
217
+ "job_id": id,
218
+ "status": "Initialized",
219
+ "message": f"Job Initialized",
220
+ }
221
+ )
205
222
  messages.append({"Id": id, "MessageBody": to_json(message)})
206
-
223
+
207
224
  if len(messages) == 10:
208
225
  result = queue.send_messages(Entries=messages)
209
226
  results = deep_merge(results, result)
210
227
  messages.clear()
211
-
228
+
212
229
  if messages:
213
230
  result = queue.send_messages(Entries=messages)
214
231
  results = deep_merge(results, result)
215
-
232
+
216
233
  return results
@@ -0,0 +1,16 @@
1
+ class AppError(Exception):
2
+ pass
3
+
4
+
5
+ class AccessError(AppError):
6
+ pass
7
+
8
+
9
+ class AlertError(AppError):
10
+ def get_payload(self):
11
+ data = self.args[0]
12
+ if isinstance(data, str):
13
+ return {"message": data}
14
+ elif isinstance(data, dict):
15
+ return data
16
+ return {"message": "No message was supplied to AlertError"}
@@ -3,21 +3,20 @@ import json
3
3
  import sys
4
4
  import os
5
5
  import traceback
6
- from support.app import DEBUG
7
- from support.app import helpers, AlertError
8
-
9
- from velocity.aws.handlers import Response
10
-
6
+ from velocity.aws.handlers.base_handler import BaseHandler
7
+ from velocity.aws.handlers.response import Response
8
+ from velocity.aws.handlers.exceptions import AlertError
11
9
  from . import context
12
10
 
11
+ # TODO: helpers import needs to be resolved - may need to pass table name instead
12
+ # from some_app import helpers
13
13
 
14
- class LambdaHandler:
14
+
15
+ class LambdaHandler(BaseHandler):
15
16
  def __init__(self, aws_event, aws_context, context_class=context.Context):
16
- self.aws_event = aws_event
17
- self.aws_context = aws_context
18
- self.serve_action_default = True # Set to False to disable OnActionDefault
19
- self.skip_action = False # Set to True to skip all actions
17
+ super().__init__(aws_event, aws_context, context_class)
20
18
 
19
+ # LambdaHandler-specific initialization
21
20
  requestContext = aws_event.get("requestContext") or {}
22
21
  identity = requestContext.get("identity") or {}
23
22
  headers = aws_event.get("headers") or {}
@@ -48,36 +47,6 @@ class LambdaHandler:
48
47
  else:
49
48
  self.session["device_type"] = "unknown"
50
49
 
51
- self.ContextClass = context_class
52
-
53
- def log(self, tx, message, function=None):
54
- if not function:
55
- function = "<Unknown>"
56
- idx = 0
57
- while True:
58
- try:
59
- temp = sys._getframe(idx).f_code.co_name
60
- except ValueError as e:
61
- break
62
- if temp in ["x", "log", "_transaction"]:
63
- idx += 1
64
- continue
65
- function = temp
66
- break
67
-
68
- data = {
69
- "app_name": os.environ["ProjectName"],
70
- "source_ip": self.session["source_ip"],
71
- "referer": self.session["referer"],
72
- "user_agent": self.session["user_agent"],
73
- "device_type": self.session["device_type"],
74
- "function": function,
75
- "message": message,
76
- }
77
- if "email_address" in self.session:
78
- data["sys_modified_by"] = self.session["email_address"]
79
- tx.table("sys_log").insert(data)
80
-
81
50
  def serve(self, tx):
82
51
  response = Response()
83
52
  body = self.aws_event.get("body")
@@ -104,45 +73,11 @@ class LambdaHandler:
104
73
  postdata=postdata,
105
74
  response=response,
106
75
  session=self.session,
107
- log=lambda message, function=None: self.log(message, function),
76
+ log=lambda message, function=None: self.log(tx, message, function),
108
77
  )
109
- try:
110
- if hasattr(self, "beforeAction"):
111
- self.beforeAction(local_context)
112
- actions = []
113
- action = postdata.get("action", req_params.get("action"))
114
- if action:
115
- actions.append(
116
- f"on action {action.replace('-', ' ').replace('_', ' ')}".title().replace(
117
- " ", ""
118
- )
119
- )
120
- if self.serve_action_default:
121
- actions.append("OnActionDefault")
122
- for action in actions:
123
- if self.skip_action:
124
- break
125
- if hasattr(self, action):
126
- result = getattr(self, action)(local_context)
127
- if result is not None:
128
- raise Exception(
129
- f"Deprecated Feature Error: Action {action} returned a response but this is not allowed. Use Repsonse object instead: {type(result)}"
130
- )
131
- break
132
- if hasattr(self, "afterAction"):
133
- self.afterAction(local_context)
134
- except AlertError as e:
135
- response.alert(e.get_payload())
136
- except Exception as e:
137
- response.exception()
138
- if hasattr(self, "onError"):
139
- self.onError(
140
- local_context,
141
- exc=e.__class__.__name__,
142
- tb=traceback.format_exc(),
143
- )
144
78
 
145
- return response.render()
79
+ # Use BaseHandler's execute_actions method
80
+ return self.execute_actions(tx, local_context, postdata, req_params)
146
81
 
147
82
  def track(self, tx, data={}, user=None):
148
83
  data = data.copy()