velocity-python 0.0.109__py3-none-any.whl → 0.0.155__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.
Files changed (120) hide show
  1. velocity/__init__.py +3 -1
  2. velocity/app/orders.py +3 -4
  3. velocity/app/tests/__init__.py +1 -0
  4. velocity/app/tests/test_email_processing.py +112 -0
  5. velocity/app/tests/test_payment_profile_sorting.py +191 -0
  6. velocity/app/tests/test_spreadsheet_functions.py +124 -0
  7. velocity/aws/__init__.py +3 -0
  8. velocity/aws/amplify.py +10 -6
  9. velocity/aws/handlers/__init__.py +2 -0
  10. velocity/aws/handlers/base_handler.py +248 -0
  11. velocity/aws/handlers/context.py +167 -2
  12. velocity/aws/handlers/exceptions.py +16 -0
  13. velocity/aws/handlers/lambda_handler.py +24 -85
  14. velocity/aws/handlers/mixins/__init__.py +16 -0
  15. velocity/aws/handlers/mixins/activity_tracker.py +181 -0
  16. velocity/aws/handlers/mixins/aws_session_mixin.py +192 -0
  17. velocity/aws/handlers/mixins/error_handler.py +192 -0
  18. velocity/aws/handlers/mixins/legacy_mixin.py +53 -0
  19. velocity/aws/handlers/mixins/standard_mixin.py +73 -0
  20. velocity/aws/handlers/response.py +1 -1
  21. velocity/aws/handlers/sqs_handler.py +28 -143
  22. velocity/aws/tests/__init__.py +1 -0
  23. velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
  24. velocity/aws/tests/test_response.py +163 -0
  25. velocity/db/__init__.py +16 -4
  26. velocity/db/core/decorators.py +20 -4
  27. velocity/db/core/engine.py +185 -839
  28. velocity/db/core/result.py +30 -24
  29. velocity/db/core/row.py +15 -3
  30. velocity/db/core/table.py +279 -40
  31. velocity/db/core/transaction.py +19 -11
  32. velocity/db/exceptions.py +42 -18
  33. velocity/db/servers/base/__init__.py +9 -0
  34. velocity/db/servers/base/initializer.py +70 -0
  35. velocity/db/servers/base/operators.py +98 -0
  36. velocity/db/servers/base/sql.py +503 -0
  37. velocity/db/servers/base/types.py +135 -0
  38. velocity/db/servers/mysql/__init__.py +73 -0
  39. velocity/db/servers/mysql/operators.py +54 -0
  40. velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
  41. velocity/db/servers/mysql/sql.py +718 -0
  42. velocity/db/servers/mysql/types.py +107 -0
  43. velocity/db/servers/postgres/__init__.py +59 -11
  44. velocity/db/servers/postgres/operators.py +34 -0
  45. velocity/db/servers/postgres/sql.py +474 -120
  46. velocity/db/servers/postgres/types.py +88 -2
  47. velocity/db/servers/sqlite/__init__.py +61 -0
  48. velocity/db/servers/sqlite/operators.py +52 -0
  49. velocity/db/servers/sqlite/reserved.py +20 -0
  50. velocity/db/servers/sqlite/sql.py +677 -0
  51. velocity/db/servers/sqlite/types.py +92 -0
  52. velocity/db/servers/sqlserver/__init__.py +73 -0
  53. velocity/db/servers/sqlserver/operators.py +47 -0
  54. velocity/db/servers/sqlserver/reserved.py +32 -0
  55. velocity/db/servers/sqlserver/sql.py +805 -0
  56. velocity/db/servers/sqlserver/types.py +114 -0
  57. velocity/db/servers/tablehelper.py +117 -91
  58. velocity/db/tests/__init__.py +1 -0
  59. velocity/db/tests/common_db_test.py +0 -0
  60. velocity/db/tests/postgres/__init__.py +1 -0
  61. velocity/db/tests/postgres/common.py +49 -0
  62. velocity/db/tests/postgres/test_column.py +29 -0
  63. velocity/db/tests/postgres/test_connections.py +25 -0
  64. velocity/db/tests/postgres/test_database.py +21 -0
  65. velocity/db/tests/postgres/test_engine.py +205 -0
  66. velocity/db/tests/postgres/test_general_usage.py +88 -0
  67. velocity/db/tests/postgres/test_imports.py +8 -0
  68. velocity/db/tests/postgres/test_result.py +19 -0
  69. velocity/db/tests/postgres/test_row.py +137 -0
  70. velocity/db/tests/postgres/test_row_comprehensive.py +720 -0
  71. velocity/db/tests/postgres/test_schema_locking.py +335 -0
  72. velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  73. velocity/db/tests/postgres/test_sequence.py +34 -0
  74. velocity/db/tests/postgres/test_sql_comprehensive.py +462 -0
  75. velocity/db/tests/postgres/test_table.py +101 -0
  76. velocity/db/tests/postgres/test_table_comprehensive.py +646 -0
  77. velocity/db/tests/postgres/test_transaction.py +106 -0
  78. velocity/db/tests/sql/__init__.py +1 -0
  79. velocity/db/tests/sql/common.py +177 -0
  80. velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  81. velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  82. velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
  83. velocity/db/tests/test_db_utils.py +221 -0
  84. velocity/db/tests/test_postgres.py +448 -0
  85. velocity/db/tests/test_postgres_unchanged.py +81 -0
  86. velocity/db/tests/test_process_error_robustness.py +292 -0
  87. velocity/db/tests/test_result_caching.py +279 -0
  88. velocity/db/tests/test_result_sql_aware.py +117 -0
  89. velocity/db/tests/test_row_get_missing_column.py +72 -0
  90. velocity/db/tests/test_schema_locking_initializers.py +226 -0
  91. velocity/db/tests/test_schema_locking_simple.py +97 -0
  92. velocity/db/tests/test_sql_builder.py +165 -0
  93. velocity/db/tests/test_tablehelper.py +486 -0
  94. velocity/db/utils.py +62 -47
  95. velocity/misc/conv/__init__.py +2 -0
  96. velocity/misc/conv/iconv.py +5 -4
  97. velocity/misc/export.py +1 -4
  98. velocity/misc/merge.py +1 -1
  99. velocity/misc/tests/__init__.py +1 -0
  100. velocity/misc/tests/test_db.py +90 -0
  101. velocity/misc/tests/test_fix.py +78 -0
  102. velocity/misc/tests/test_format.py +64 -0
  103. velocity/misc/tests/test_iconv.py +203 -0
  104. velocity/misc/tests/test_merge.py +82 -0
  105. velocity/misc/tests/test_oconv.py +144 -0
  106. velocity/misc/tests/test_original_error.py +52 -0
  107. velocity/misc/tests/test_timer.py +74 -0
  108. velocity/misc/tools.py +0 -1
  109. {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/METADATA +2 -2
  110. velocity_python-0.0.155.dist-info/RECORD +129 -0
  111. velocity/db/core/exceptions.py +0 -70
  112. velocity/db/servers/mysql.py +0 -641
  113. velocity/db/servers/sqlite.py +0 -968
  114. velocity/db/servers/sqlite_reserved.py +0 -208
  115. velocity/db/servers/sqlserver.py +0 -921
  116. velocity/db/servers/sqlserver_reserved.py +0 -314
  117. velocity_python-0.0.109.dist-info/RECORD +0 -56
  118. {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/WHEEL +0 -0
  119. {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/licenses/LICENSE +0 -0
  120. {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,248 @@
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, tx, local_context, actions: List[str]) -> bool:
120
+ """
121
+ Execute the appropriate actions for the given context.
122
+
123
+ Args:
124
+ tx: Database transaction object
125
+ local_context: The context object
126
+ actions: List of action method names to try
127
+
128
+ Returns:
129
+ True if an action was executed, False otherwise
130
+ """
131
+ # Execute beforeAction hook if available
132
+ if hasattr(self, "beforeAction"):
133
+ self.beforeAction(tx, local_context)
134
+
135
+ action_executed = False
136
+
137
+ # Execute the first matching action
138
+ for action in actions:
139
+ if self.skip_action:
140
+ break
141
+
142
+ if hasattr(self, action):
143
+ result = getattr(self, action)(tx, local_context)
144
+
145
+ # Check for deprecated return values (LambdaHandler specific)
146
+ if result is not None and hasattr(self, "_check_deprecated_return"):
147
+ self._check_deprecated_return(action, result)
148
+
149
+ action_executed = True
150
+ break
151
+
152
+ # Execute afterAction hook if available
153
+ if hasattr(self, "afterAction"):
154
+ self.afterAction(tx, local_context)
155
+
156
+ return action_executed
157
+
158
+ def handle_error(self, tx, local_context, exception: Exception):
159
+ """
160
+ Handle errors that occur during action execution.
161
+
162
+ Args:
163
+ tx: Database transaction object
164
+ local_context: The context object
165
+ exception: The exception that occurred
166
+ """
167
+ if hasattr(self, "onError"):
168
+ self.onError(
169
+ tx,
170
+ local_context,
171
+ exc=exception.__class__.__name__,
172
+ tb=traceback.format_exc(),
173
+ )
174
+ else:
175
+ # Re-raise if no error handler is defined
176
+ raise exception
177
+
178
+ def log(self, tx, message: str, function: Optional[str] = None):
179
+ """
180
+ Log a message to the system log table.
181
+ This is a base implementation that should be overridden by subclasses
182
+ to provide handler-specific logging details.
183
+
184
+ Args:
185
+ tx: Database transaction object
186
+ message: The message to log
187
+ function: Optional function name, auto-detected if not provided
188
+ """
189
+ if not function:
190
+ function = self.get_calling_function()
191
+
192
+ # Base log data - subclasses should extend this
193
+ data = {
194
+ "app_name": os.environ.get("ProjectName", "Unknown"),
195
+ "function": function,
196
+ "message": message,
197
+ }
198
+
199
+ # Let subclasses add their specific fields
200
+ self._extend_log_data(data)
201
+
202
+ tx.table("sys_log").insert(data)
203
+
204
+ def _extend_log_data(self, data: Dict[str, Any]):
205
+ """
206
+ Extend log data with handler-specific fields.
207
+ To be overridden by subclasses.
208
+
209
+ Args:
210
+ data: The base log data dictionary to extend
211
+ """
212
+ # Default implementation adds basic Lambda info
213
+ data.update(
214
+ {
215
+ "user_agent": "AWS Lambda",
216
+ "device_type": "Lambda",
217
+ "sys_modified_by": "Lambda",
218
+ }
219
+ )
220
+
221
+ def OnActionDefault(self, local_context):
222
+ """
223
+ Default action handler when no specific action is found.
224
+
225
+ Args:
226
+ local_context: The context object
227
+ """
228
+ action = getattr(local_context, "action", lambda: "unknown")()
229
+ warning_message = (
230
+ f"[Warn] Action handler not found. Calling default action "
231
+ f"`{self.__class__.__name__}.OnActionDefault` with the following parameters:\n"
232
+ f" - action: {action}\n"
233
+ f" - handler: {self.__class__.__name__}"
234
+ )
235
+ print(warning_message)
236
+
237
+ def get_context_args(self) -> Dict[str, Any]:
238
+ """
239
+ Get the arguments to pass to the context constructor.
240
+ To be implemented by subclasses based on their specific needs.
241
+
242
+ Returns:
243
+ Dictionary of arguments for context initialization
244
+ """
245
+ return {
246
+ "aws_event": self.aws_event,
247
+ "aws_context": self.aws_context,
248
+ }
@@ -1,6 +1,14 @@
1
+ import os
2
+ import boto3
3
+ import uuid
1
4
  import support.app
5
+ from velocity.misc.format import to_json
6
+ from velocity.misc.merge import deep_merge
7
+ from datetime import datetime
2
8
 
3
- engine = support.app.postgres()
9
+ import velocity.db
10
+
11
+ engine = velocity.db.postgres.initialize()
4
12
 
5
13
 
6
14
  @engine.transaction
@@ -12,7 +20,7 @@ class Context:
12
20
  self.__args = args
13
21
  self.__postdata = postdata
14
22
  self.__response = response
15
- self.__session = session
23
+ self.__session = {} if session is None else session
16
24
  self.__aws_event = aws_event
17
25
  self.__aws_context = aws_context
18
26
  self.__log = log
@@ -68,3 +76,160 @@ class Context:
68
76
  print(f"{function}: {message}")
69
77
  else:
70
78
  print(f"{message}")
79
+
80
+ def update_job(self, tx, data=None):
81
+ """Update job status and message in aws_job_activity table.
82
+
83
+ This method only UPDATES existing jobs. For creating new jobs, use create_job.
84
+ """
85
+ if not data:
86
+ return
87
+ if self.postdata("job_id"):
88
+ # Sanitize data before storing in database
89
+ sanitized_data = self._sanitize_job_data(data)
90
+ tx.table("aws_job_activity").update(
91
+ sanitized_data, {"job_id": self.postdata("job_id")}
92
+ )
93
+ tx.commit()
94
+
95
+ def create_job(self, tx, job_data=None):
96
+ """Create a new job record in aws_job_activity table using independent transaction."""
97
+ if not job_data:
98
+ return
99
+ sanitized_data = self._sanitize_job_data(job_data)
100
+ tx.table("aws_job_activity").insert(sanitized_data)
101
+ tx.commit()
102
+
103
+ def _sanitize_job_data(self, data):
104
+ """Sanitize sensitive data before storing in aws_job_activity table."""
105
+ if not isinstance(data, dict):
106
+ return data
107
+
108
+ sanitized = {}
109
+
110
+ # List of sensitive field patterns to sanitize
111
+ sensitive_patterns = [
112
+ "password",
113
+ "token",
114
+ "secret",
115
+ "key",
116
+ "credential",
117
+ "auth",
118
+ "cognito_user",
119
+ "session",
120
+ "cookie",
121
+ "authorization",
122
+ ]
123
+
124
+ for key, value in data.items():
125
+ # Check if key contains sensitive patterns
126
+ if any(pattern in key.lower() for pattern in sensitive_patterns):
127
+ sanitized[key] = "[REDACTED]" if value else value
128
+ elif key == "error" and value:
129
+ # Sanitize error messages - keep first 500 chars and remove potential sensitive info
130
+ error_str = str(value)[:500]
131
+ for pattern in sensitive_patterns:
132
+ if pattern in error_str.lower():
133
+ # Replace potential sensitive values with placeholder
134
+ import re
135
+
136
+ # Remove patterns like password=value, token=value, etc.
137
+ error_str = re.sub(
138
+ rf"{pattern}[=:]\s*[^\s,\]}}]+",
139
+ f"{pattern}=[REDACTED]",
140
+ error_str,
141
+ flags=re.IGNORECASE,
142
+ )
143
+ sanitized[key] = error_str
144
+ elif key == "traceback" and value:
145
+ # Sanitize traceback - keep structure but remove sensitive values
146
+ tb_str = str(value)
147
+ for pattern in sensitive_patterns:
148
+ if pattern in tb_str.lower():
149
+ import re
150
+
151
+ # Remove patterns like password=value, token=value, etc.
152
+ tb_str = re.sub(
153
+ rf"{pattern}[=:]\s*[^\s,\]}}]+",
154
+ f"{pattern}=[REDACTED]",
155
+ tb_str,
156
+ flags=re.IGNORECASE,
157
+ )
158
+ # Limit traceback size to prevent DB bloat
159
+ sanitized[key] = tb_str[:2000]
160
+ elif key == "message" and value:
161
+ # Sanitize message field
162
+ message_str = str(value)
163
+ for pattern in sensitive_patterns:
164
+ if pattern in message_str.lower():
165
+ import re
166
+
167
+ message_str = re.sub(
168
+ rf"{pattern}[=:]\s*[^\s,\]}}]+",
169
+ f"{pattern}=[REDACTED]",
170
+ message_str,
171
+ flags=re.IGNORECASE,
172
+ )
173
+ sanitized[key] = message_str[:1000] # Limit message size
174
+ else:
175
+ # For other fields, copy as-is but check for nested dicts
176
+ if isinstance(value, dict):
177
+ sanitized[key] = self._sanitize_job_data(value)
178
+ elif isinstance(value, str) and len(value) > 5000:
179
+ # Limit very large string fields
180
+ sanitized[key] = value[:5000] + "...[TRUNCATED]"
181
+ else:
182
+ sanitized[key] = value
183
+
184
+ return sanitized
185
+
186
+ def enqueue(self, action, payload={}, user=None, suppress_job_activity=False):
187
+ """
188
+ Enqueue jobs to SQS with independent job activity tracking.
189
+
190
+ This method uses its own transaction for aws_job_activity updates to ensure
191
+ job tracking is never rolled back with other operations.
192
+ """
193
+ batch_id = str(uuid.uuid4())
194
+ results = {"batch_id": batch_id}
195
+ queue = boto3.resource("sqs").get_queue_by_name(
196
+ QueueName=os.environ["SqsWorkQueue"]
197
+ )
198
+ if isinstance(payload, dict):
199
+ payload = [payload]
200
+ messages = []
201
+ if user is None:
202
+ user = self.session().get("email_address") or "EnqueueTasks"
203
+ for item in payload:
204
+ message = {"action": action, "payload": item}
205
+ id = str(uuid.uuid4()).split("-")[0]
206
+ if suppress_job_activity:
207
+ messages.append({"Id": id, "MessageBody": to_json(message)})
208
+ else:
209
+ message["job_id"] = id
210
+ # Use separate transaction for job activity - this should never be rolled back
211
+ self.create_job(
212
+ {
213
+ "action": action,
214
+ "initial_timestamp": datetime.now(),
215
+ "created_by": user,
216
+ "sys_modified_by": user,
217
+ "payload": to_json(message),
218
+ "batch_id": str(batch_id),
219
+ "job_id": id,
220
+ "status": "Initialized",
221
+ "message": "Job Initialized",
222
+ }
223
+ )
224
+ messages.append({"Id": id, "MessageBody": to_json(message)})
225
+
226
+ if len(messages) == 10:
227
+ result = queue.send_messages(Entries=messages)
228
+ results = deep_merge(results, result)
229
+ messages.clear()
230
+
231
+ if messages:
232
+ result = queue.send_messages(Entries=messages)
233
+ results = deep_merge(results, result)
234
+
235
+ 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"}
@@ -1,23 +1,17 @@
1
- from velocity.misc.format import to_json
2
1
  import json
3
- import sys
4
- import os
5
- import traceback
6
- from support.app import DEBUG
7
- from support.app import helpers, AlertError, enqueue
8
-
9
- from velocity.aws.handlers import Response
10
-
2
+ from velocity.aws.handlers.base_handler import BaseHandler
3
+ from velocity.aws.handlers.response import Response
11
4
  from . import context
12
5
 
6
+ # TODO: helpers import needs to be resolved - may need to pass table name instead
7
+ # from some_app import helpers
8
+
13
9
 
14
- class LambdaHandler:
10
+ class LambdaHandler(BaseHandler):
15
11
  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
12
+ super().__init__(aws_event, aws_context, context_class)
20
13
 
14
+ # LambdaHandler-specific initialization
21
15
  requestContext = aws_event.get("requestContext") or {}
22
16
  identity = requestContext.get("identity") or {}
23
17
  headers = aws_event.get("headers") or {}
@@ -48,36 +42,6 @@ class LambdaHandler:
48
42
  else:
49
43
  self.session["device_type"] = "unknown"
50
44
 
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
45
  def serve(self, tx):
82
46
  response = Response()
83
47
  body = self.aws_event.get("body")
@@ -85,7 +49,7 @@ class LambdaHandler:
85
49
  if isinstance(body, str) and len(body) > 0:
86
50
  try:
87
51
  postdata = json.loads(body)
88
- except:
52
+ except (json.JSONDecodeError, TypeError):
89
53
  postdata = {"raw_body": body}
90
54
  elif isinstance(body, dict):
91
55
  postdata = body
@@ -93,7 +57,7 @@ class LambdaHandler:
93
57
  try:
94
58
  new = "\n".join(body)
95
59
  postdata = json.loads(new)
96
- except:
60
+ except (json.JSONDecodeError, TypeError):
97
61
  postdata = {"raw_body": body}
98
62
 
99
63
  req_params = self.aws_event.get("queryStringParameters") or {}
@@ -106,43 +70,20 @@ class LambdaHandler:
106
70
  session=self.session,
107
71
  log=lambda message, function=None: self.log(message, function),
108
72
  )
73
+
74
+ # Determine action from postdata or query parameters
75
+ action = postdata.get("action") or req_params.get("action")
76
+
77
+ # Get the list of actions to execute
78
+ actions = self.get_actions_to_execute(action)
79
+
80
+ # Use BaseHandler's execute_actions method
109
81
  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())
82
+ self.execute_actions(tx, local_context, actions)
136
83
  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
-
145
- return response.render()
84
+ self.handle_error(tx, local_context, e)
85
+
86
+ return local_context.response().render()
146
87
 
147
88
  def track(self, tx, data={}, user=None):
148
89
  data = data.copy()
@@ -155,7 +96,8 @@ class LambdaHandler:
155
96
  "sys_modified_by": self.session["email_address"],
156
97
  }
157
98
  )
158
- tx.table(helpers.get_tracking_table(user or self.session)).insert(data)
99
+ # TODO: Fix undefined helpers reference
100
+ # tx.table(helpers.get_tracking_table(user or self.session)).insert(data)
159
101
 
160
102
  def OnActionDefault(self, tx, context):
161
103
  context.response().set_body(
@@ -164,6 +106,3 @@ class LambdaHandler:
164
106
 
165
107
  def OnActionTracking(self, tx, context):
166
108
  self.track(tx, context.payload().get("data", {}))
167
-
168
- def enqueue(self, tx, action, payload={}):
169
- enqueue(tx, action, payload, self.session["email_address"])
@@ -0,0 +1,16 @@
1
+ """
2
+ Mixins for AWS Lambda handlers.
3
+
4
+ This package provides reusable mixins for common Lambda handler functionality:
5
+ - ActivityTracker: Standardized activity logging and tracking
6
+ - ErrorHandler: Standardized error handling and notifications
7
+ - StandardMixin: Combined mixin for most common use cases
8
+ - LegacyMixin: Backward-compatible enhanced tracking for existing handlers
9
+ """
10
+
11
+ from .activity_tracker import ActivityTracker
12
+ from .error_handler import ErrorHandler
13
+ from .standard_mixin import StandardMixin
14
+ from .legacy_mixin import LegacyMixin
15
+
16
+ __all__ = ['ActivityTracker', 'ErrorHandler', 'StandardMixin', 'LegacyMixin']