velocity-python 0.0.116__py3-none-any.whl → 0.0.118__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 +3 -1
- velocity/aws/__init__.py +3 -0
- velocity/aws/amplify.py +10 -6
- velocity/aws/handlers/__init__.py +2 -0
- velocity/aws/handlers/base_handler.py +245 -0
- velocity/aws/handlers/context.py +58 -41
- velocity/aws/handlers/exceptions.py +16 -0
- velocity/aws/handlers/lambda_handler.py +15 -84
- velocity/aws/handlers/response.py +1 -1
- velocity/aws/handlers/sqs_handler.py +23 -144
- velocity/db/__init__.py +16 -1
- velocity/db/core/decorators.py +0 -1
- velocity/db/core/engine.py +33 -31
- velocity/db/core/exceptions.py +3 -0
- velocity/db/core/result.py +30 -24
- velocity/db/core/row.py +3 -1
- velocity/db/core/table.py +6 -5
- velocity/db/exceptions.py +35 -18
- velocity/db/servers/mysql.py +2 -3
- velocity/db/servers/postgres/__init__.py +10 -12
- velocity/db/servers/postgres/sql.py +36 -17
- velocity/db/servers/sqlite.py +2 -2
- velocity/db/servers/sqlserver.py +3 -3
- velocity/db/servers/tablehelper.py +117 -91
- velocity/db/utils.py +62 -47
- velocity/misc/conv/__init__.py +2 -0
- velocity/misc/conv/iconv.py +5 -4
- velocity/misc/export.py +1 -4
- velocity/misc/merge.py +1 -1
- velocity/misc/tools.py +0 -1
- {velocity_python-0.0.116.dist-info → velocity_python-0.0.118.dist-info}/METADATA +1 -1
- velocity_python-0.0.118.dist-info/RECORD +58 -0
- velocity_python-0.0.116.dist-info/RECORD +0 -56
- {velocity_python-0.0.116.dist-info → velocity_python-0.0.118.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.116.dist-info → velocity_python-0.0.118.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.116.dist-info → velocity_python-0.0.118.dist-info}/top_level.txt +0 -0
velocity/__init__.py
CHANGED
velocity/aws/__init__.py
CHANGED
velocity/aws/amplify.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import boto3
|
|
2
|
+
import time
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
class AmplifyProject:
|
|
@@ -325,7 +326,12 @@ class AmplifyProject:
|
|
|
325
326
|
"ec2:AttachNetworkInterface",
|
|
326
327
|
"ec2:DescribeNetworkInterfaces",
|
|
327
328
|
"ec2:DeleteNetworkInterface",
|
|
328
|
-
"
|
|
329
|
+
"ec2:DescribeSecurityGroups",
|
|
330
|
+
"ec2:AuthorizeSecurityGroupIngress",
|
|
331
|
+
"ec2:AuthorizeSecurityGroupEgress",
|
|
332
|
+
"ec2:RevokeSecurityGroupIngress",
|
|
333
|
+
"ec2:RevokeSecurityGroupEgress",
|
|
334
|
+
"cognito-idp:*"
|
|
329
335
|
],
|
|
330
336
|
}
|
|
331
337
|
],
|
|
@@ -389,10 +395,9 @@ class AmplifyProject:
|
|
|
389
395
|
except Exception:
|
|
390
396
|
pass
|
|
391
397
|
|
|
392
|
-
def check_policies(
|
|
393
|
-
self,
|
|
394
|
-
):
|
|
398
|
+
def check_policies(self, function):
|
|
395
399
|
# Attach a role policy
|
|
400
|
+
iam_client = boto3.client("iam")
|
|
396
401
|
response = iam_client.list_attached_role_policies(
|
|
397
402
|
RoleName=function["Role"].split("/")[1]
|
|
398
403
|
)
|
|
@@ -426,7 +431,7 @@ class AmplifyProject:
|
|
|
426
431
|
print(f"\n🔧 Applying {len(env_vars)} environment variables...")
|
|
427
432
|
for function_name in self.list_lambda_functions_filtered(branch):
|
|
428
433
|
print(f"➡️ Updating Lambda function: {function_name}")
|
|
429
|
-
self.
|
|
434
|
+
self.update_lambda_function(function_name, env_vars)
|
|
430
435
|
|
|
431
436
|
print(
|
|
432
437
|
"✅ Environment variables successfully applied to matching Lambda functions.\n"
|
|
@@ -435,7 +440,6 @@ class AmplifyProject:
|
|
|
435
440
|
|
|
436
441
|
def main():
|
|
437
442
|
app_id = "d3c209q3ri53mk"
|
|
438
|
-
branch = "demo"
|
|
439
443
|
app = AmplifyProject(app_id)
|
|
440
444
|
print(app.list_backend_branches())
|
|
441
445
|
|
|
@@ -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
|
+
}
|
velocity/aws/handlers/context.py
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
107
|
-
|
|
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 ==
|
|
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
|
|
124
|
-
f
|
|
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 ==
|
|
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
|
|
138
|
-
f
|
|
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 ==
|
|
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
|
|
152
|
-
f
|
|
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
|
|
199
|
+
if user is 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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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": "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"}
|
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
from velocity.misc.format import to_json
|
|
2
1
|
import json
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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
|
-
|
|
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
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
|
|
10
|
+
class LambdaHandler(BaseHandler):
|
|
15
11
|
def __init__(self, aws_event, aws_context, context_class=context.Context):
|
|
16
|
-
|
|
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 {}
|
|
@@ -104,45 +68,11 @@ class LambdaHandler:
|
|
|
104
68
|
postdata=postdata,
|
|
105
69
|
response=response,
|
|
106
70
|
session=self.session,
|
|
107
|
-
log=lambda message, function=None: self.log(message, function),
|
|
71
|
+
log=lambda message, function=None: self.log(tx, message, function),
|
|
108
72
|
)
|
|
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
73
|
|
|
145
|
-
|
|
74
|
+
# Use BaseHandler's execute_actions method
|
|
75
|
+
return self.execute_actions(tx, local_context, postdata, req_params)
|
|
146
76
|
|
|
147
77
|
def track(self, tx, data={}, user=None):
|
|
148
78
|
data = data.copy()
|
|
@@ -155,7 +85,8 @@ class LambdaHandler:
|
|
|
155
85
|
"sys_modified_by": self.session["email_address"],
|
|
156
86
|
}
|
|
157
87
|
)
|
|
158
|
-
|
|
88
|
+
# TODO: Fix undefined helpers reference
|
|
89
|
+
# tx.table(helpers.get_tracking_table(user or self.session)).insert(data)
|
|
159
90
|
|
|
160
91
|
def OnActionDefault(self, tx, context):
|
|
161
92
|
context.response().set_body(
|