velocity-python 0.0.195__tar.gz → 0.0.197__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.
- {velocity_python-0.0.195 → velocity_python-0.0.197}/PKG-INFO +1 -1
- {velocity_python-0.0.195 → velocity_python-0.0.197}/pyproject.toml +1 -1
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/base_handler.py +28 -27
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/context.py +88 -19
- velocity_python-0.0.197/src/velocity/aws/handlers/context_factory.py +27 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/lambda_handler.py +48 -81
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/mixins/web_handler.py +50 -49
- velocity_python-0.0.197/src/velocity/aws/handlers/perf.py +42 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/sqs_handler.py +12 -5
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/LICENSE +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/README.md +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/setup.cfg +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/logging.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/tests/test_decorators.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/tests/test_table_alter.py +0 -0
- {velocity_python-0.0.195 → velocity_python-0.0.197}/tests/test_where_clause_validation.py +0 -0
{velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/base_handler.py
RENAMED
|
@@ -8,11 +8,11 @@ It includes common functionality shared between LambdaHandler and SqsHandler.
|
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
10
|
import sys
|
|
11
|
-
import time
|
|
12
11
|
import traceback
|
|
13
12
|
from typing import Any, Dict, List, Optional
|
|
14
13
|
|
|
15
14
|
from velocity.aws.handlers import context as VelocityContext
|
|
15
|
+
from velocity.aws.handlers.context_factory import ContextFactory
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
18
18
|
|
|
@@ -28,6 +28,7 @@ class BaseHandler:
|
|
|
28
28
|
self,
|
|
29
29
|
aws_event: Dict[str, Any],
|
|
30
30
|
aws_context: Any,
|
|
31
|
+
context_factory: Optional[ContextFactory] = None,
|
|
31
32
|
context_class=VelocityContext.Context,
|
|
32
33
|
):
|
|
33
34
|
"""
|
|
@@ -42,10 +43,21 @@ class BaseHandler:
|
|
|
42
43
|
self.aws_context = aws_context
|
|
43
44
|
self.serve_action_default = True # Set to False to disable OnActionDefault
|
|
44
45
|
self.skip_action = False # Set to True to skip all actions
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.
|
|
46
|
+
if context_factory is None:
|
|
47
|
+
context_factory = ContextFactory(context_class=context_class)
|
|
48
|
+
self.context_factory = context_factory
|
|
49
|
+
self.context = None
|
|
50
|
+
|
|
51
|
+
def create_context(self, *, args, postdata, response, session):
|
|
52
|
+
self.context = self.context_factory.create(
|
|
53
|
+
aws_event=self.aws_event,
|
|
54
|
+
aws_context=self.aws_context,
|
|
55
|
+
args=args,
|
|
56
|
+
postdata=postdata,
|
|
57
|
+
response=response,
|
|
58
|
+
session=session,
|
|
59
|
+
)
|
|
60
|
+
return self.context
|
|
49
61
|
|
|
50
62
|
def _update_lambda_ca_certificates(self):
|
|
51
63
|
"""Configure SSL certificates for HTTPS requests in Lambda environments."""
|
|
@@ -131,16 +143,14 @@ class BaseHandler:
|
|
|
131
143
|
Returns:
|
|
132
144
|
True if an action was executed, False otherwise
|
|
133
145
|
"""
|
|
134
|
-
|
|
146
|
+
local_context.perf.start("execute_actions total")
|
|
135
147
|
|
|
136
148
|
# Execute beforeAction hook if available
|
|
137
149
|
if hasattr(self, "beforeAction"):
|
|
138
|
-
|
|
150
|
+
local_context.perf.start("beforeAction")
|
|
139
151
|
self.beforeAction(tx, local_context)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
"Timing: beforeAction %.2f ms",
|
|
143
|
-
before_elapsed,
|
|
152
|
+
local_context.perf.log(
|
|
153
|
+
"beforeAction",
|
|
144
154
|
extra={"action": getattr(local_context, "action", lambda: None)()},
|
|
145
155
|
)
|
|
146
156
|
|
|
@@ -152,13 +162,10 @@ class BaseHandler:
|
|
|
152
162
|
break
|
|
153
163
|
|
|
154
164
|
if hasattr(self, action):
|
|
155
|
-
|
|
165
|
+
local_context.perf.start(action)
|
|
156
166
|
result = getattr(self, action)(tx, local_context)
|
|
157
|
-
|
|
158
|
-
logger.info(
|
|
159
|
-
"Timing: %s %.2f ms",
|
|
167
|
+
local_context.perf.log(
|
|
160
168
|
action,
|
|
161
|
-
action_elapsed,
|
|
162
169
|
extra={"action": getattr(local_context, "action", lambda: None)()},
|
|
163
170
|
)
|
|
164
171
|
if result is not None:
|
|
@@ -179,8 +186,6 @@ class BaseHandler:
|
|
|
179
186
|
diagnostic_info["dataset"] = local_context.dataset()
|
|
180
187
|
except Exception as e:
|
|
181
188
|
diagnostic_info["diagnostic_error"] = str(e)
|
|
182
|
-
"""
|
|
183
|
-
Get the list of actions to execute.
|
|
184
189
|
logger.warning(
|
|
185
190
|
"Action %s returned an unexpected non-None result (%r).",
|
|
186
191
|
action,
|
|
@@ -192,19 +197,15 @@ class BaseHandler:
|
|
|
192
197
|
|
|
193
198
|
# Execute afterAction hook if available
|
|
194
199
|
if hasattr(self, "afterAction"):
|
|
195
|
-
|
|
200
|
+
local_context.perf.start("afterAction")
|
|
196
201
|
self.afterAction(tx, local_context)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"Timing: afterAction %.2f ms",
|
|
200
|
-
after_elapsed,
|
|
202
|
+
local_context.perf.log(
|
|
203
|
+
"afterAction",
|
|
201
204
|
extra={"action": getattr(local_context, "action", lambda: None)()},
|
|
202
205
|
)
|
|
203
206
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
"Timing: execute_actions total %.2f ms",
|
|
207
|
-
total_elapsed,
|
|
207
|
+
local_context.perf.log(
|
|
208
|
+
"execute_actions total",
|
|
208
209
|
extra={"action": getattr(local_context, "action", lambda: None)()},
|
|
209
210
|
)
|
|
210
211
|
|
|
@@ -8,6 +8,7 @@ from velocity.misc.format import to_json
|
|
|
8
8
|
from velocity.misc.merge import deep_merge
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from velocity.aws.handlers.exceptions import AlertError
|
|
11
|
+
from velocity.aws.handlers.perf import PerfTimer
|
|
11
12
|
from botocore.exceptions import ClientError
|
|
12
13
|
import hashlib
|
|
13
14
|
import velocity
|
|
@@ -24,16 +25,16 @@ logger = get_logger("velocity.aws.handlers.context")
|
|
|
24
25
|
@engine.transaction
|
|
25
26
|
class Context:
|
|
26
27
|
|
|
27
|
-
def __init__(
|
|
28
|
-
self, aws_event, aws_context, args, postdata, response, session, log=None
|
|
29
|
-
):
|
|
28
|
+
def __init__(self, aws_event, aws_context, args, postdata, response, session):
|
|
30
29
|
self.__args = args
|
|
31
30
|
self.__postdata = postdata
|
|
32
31
|
self.__response = response
|
|
32
|
+
if session is None:
|
|
33
|
+
session = self._build_session(aws_event)
|
|
33
34
|
self.__session = {} if session is None else session
|
|
34
35
|
self.__aws_event = aws_event
|
|
35
36
|
self.__aws_context = aws_context
|
|
36
|
-
self.
|
|
37
|
+
self.perf = PerfTimer(False, logger)
|
|
37
38
|
self._job_record_cache = {}
|
|
38
39
|
self._job_cancelled_flag = False
|
|
39
40
|
self._feature_flags_cache = None
|
|
@@ -81,13 +82,84 @@ class Context:
|
|
|
81
82
|
def dataset(self):
|
|
82
83
|
return self.payload().get("dataset", {})
|
|
83
84
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
def update_postdata(self, postdata):
|
|
86
|
+
self.__postdata = postdata
|
|
87
|
+
|
|
88
|
+
def parse_postdata(self):
|
|
89
|
+
body = self.__aws_event.get("body")
|
|
90
|
+
postdata = {}
|
|
91
|
+
if isinstance(body, str) and len(body) > 0:
|
|
92
|
+
try:
|
|
93
|
+
postdata = json.loads(body)
|
|
94
|
+
except (json.JSONDecodeError, TypeError):
|
|
95
|
+
postdata = {"raw_body": body}
|
|
96
|
+
elif isinstance(body, dict):
|
|
97
|
+
postdata = body
|
|
98
|
+
elif isinstance(body, list) and len(body) > 0:
|
|
99
|
+
try:
|
|
100
|
+
new = "\n".join(body)
|
|
101
|
+
postdata = json.loads(new)
|
|
102
|
+
except (json.JSONDecodeError, TypeError):
|
|
103
|
+
postdata = {"raw_body": body}
|
|
104
|
+
return postdata
|
|
105
|
+
|
|
106
|
+
def _is_performance_timing_enabled(self, postdata=None) -> bool:
|
|
107
|
+
if postdata is None:
|
|
108
|
+
postdata = self.__postdata
|
|
109
|
+
|
|
110
|
+
if not isinstance(postdata, dict):
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
def normalize(val):
|
|
114
|
+
if isinstance(val, bool):
|
|
115
|
+
return val
|
|
116
|
+
if val is None:
|
|
117
|
+
return False
|
|
118
|
+
return str(val).strip().lower() in {"true", "1", "yes", "y"}
|
|
119
|
+
|
|
120
|
+
if normalize(postdata.get("log_performance_timing")):
|
|
121
|
+
return True
|
|
122
|
+
payload = postdata.get("payload")
|
|
123
|
+
if isinstance(payload, dict) and normalize(payload.get("log_performance_timing")):
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def configure_perf(self, postdata=None):
|
|
128
|
+
enabled = self._is_performance_timing_enabled(postdata=postdata)
|
|
129
|
+
self.perf.set_enabled(enabled)
|
|
130
|
+
return enabled
|
|
131
|
+
|
|
132
|
+
def _build_session(self, aws_event):
|
|
133
|
+
request_context = aws_event.get("requestContext") or {}
|
|
134
|
+
identity = request_context.get("identity") or {}
|
|
135
|
+
headers = aws_event.get("headers") or {}
|
|
136
|
+
auth = identity.get("cognitoAuthenticationProvider")
|
|
137
|
+
session = {
|
|
138
|
+
"authentication_provider": identity.get("cognitoAuthenticationProvider"),
|
|
139
|
+
"authentication_type": identity.get("cognitoAuthenticationType"),
|
|
140
|
+
"cognito_user": identity.get("user"),
|
|
141
|
+
"is_desktop": headers.get("CloudFront-Is-Desktop-Viewer") == "true",
|
|
142
|
+
"is_mobile": headers.get("CloudFront-Is-Mobile-Viewer") == "true",
|
|
143
|
+
"is_smart_tv": headers.get("CloudFront-Is-SmartTV-Viewer") == "true",
|
|
144
|
+
"is_tablet": headers.get("CloudFront-Is-Tablet-Viewer") == "true",
|
|
145
|
+
"origin": headers.get("origin"),
|
|
146
|
+
"path": aws_event.get("path"),
|
|
147
|
+
"referer": headers.get("Referer"),
|
|
148
|
+
"source_ip": identity.get("sourceIp"),
|
|
149
|
+
"user_agent": identity.get("userAgent"),
|
|
150
|
+
"sub": auth.split(":")[-1] if auth else None,
|
|
151
|
+
}
|
|
152
|
+
if session.get("is_mobile"):
|
|
153
|
+
session["device_type"] = "mobile"
|
|
154
|
+
elif session.get("is_desktop"):
|
|
155
|
+
session["device_type"] = "desktop"
|
|
156
|
+
elif session.get("is_tablet"):
|
|
157
|
+
session["device_type"] = "tablet"
|
|
158
|
+
elif session.get("is_smart_tv"):
|
|
159
|
+
session["device_type"] = "smart_tv"
|
|
89
160
|
else:
|
|
90
|
-
|
|
161
|
+
session["device_type"] = "unknown"
|
|
162
|
+
return session
|
|
91
163
|
|
|
92
164
|
def update_job(self, tx, data=None):
|
|
93
165
|
"""Update job status and message in aws_job_activity table.
|
|
@@ -478,7 +550,7 @@ class Context:
|
|
|
478
550
|
return None
|
|
479
551
|
|
|
480
552
|
def get_cognito_user(self, aws_event):
|
|
481
|
-
|
|
553
|
+
self.perf.start("get_cognito_user total")
|
|
482
554
|
provider = (
|
|
483
555
|
aws_event.get("requestContext", {})
|
|
484
556
|
.get("identity", {})
|
|
@@ -504,24 +576,22 @@ class Context:
|
|
|
504
576
|
raise AlertError("Incomplete Cognito identity provider data") from None
|
|
505
577
|
|
|
506
578
|
try:
|
|
507
|
-
|
|
579
|
+
self.perf.start("cognito admin_get_user")
|
|
508
580
|
response = cognito_client.admin_get_user(
|
|
509
581
|
UserPoolId=user_pool_id,
|
|
510
582
|
Username=user_sub,
|
|
511
583
|
)
|
|
512
|
-
|
|
513
|
-
logger.info("Timing: cognito admin_get_user %.2f ms", admin_elapsed)
|
|
584
|
+
self.perf.log("cognito admin_get_user")
|
|
514
585
|
resolved_username = response.get("Username")
|
|
515
586
|
except cognito_client.exceptions.UserNotFoundException:
|
|
516
587
|
try:
|
|
517
|
-
|
|
588
|
+
self.perf.start("cognito list_users")
|
|
518
589
|
list_response = cognito_client.list_users(
|
|
519
590
|
UserPoolId=user_pool_id,
|
|
520
591
|
Filter=f'sub = "{user_sub}"',
|
|
521
592
|
Limit=1,
|
|
522
593
|
)
|
|
523
|
-
|
|
524
|
-
logger.info("Timing: cognito list_users %.2f ms", list_elapsed)
|
|
594
|
+
self.perf.log("cognito list_users")
|
|
525
595
|
except ClientError as exc:
|
|
526
596
|
message = exc.response.get("Error", {}).get("Message", "Unknown error")
|
|
527
597
|
raise AlertError(
|
|
@@ -560,8 +630,7 @@ class Context:
|
|
|
560
630
|
"sub": attributes.get("sub", user_sub),
|
|
561
631
|
}
|
|
562
632
|
|
|
563
|
-
|
|
564
|
-
logger.info("Timing: get_cognito_user total %.2f ms", overall_elapsed)
|
|
633
|
+
self.perf.log("get_cognito_user total")
|
|
565
634
|
|
|
566
635
|
return user
|
|
567
636
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Type
|
|
2
|
+
|
|
3
|
+
from velocity.aws.handlers import context as VelocityContext
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContextFactory:
|
|
7
|
+
def __init__(self, context_class: Optional[Type[VelocityContext.Context]] = None):
|
|
8
|
+
self.context_class = context_class or VelocityContext.Context
|
|
9
|
+
|
|
10
|
+
def create(
|
|
11
|
+
self,
|
|
12
|
+
*,
|
|
13
|
+
aws_event: Dict[str, Any],
|
|
14
|
+
aws_context: Any,
|
|
15
|
+
args: Dict[str, Any],
|
|
16
|
+
postdata: Dict[str, Any],
|
|
17
|
+
response: Any,
|
|
18
|
+
session: Optional[Dict[str, Any]],
|
|
19
|
+
) -> VelocityContext.Context:
|
|
20
|
+
return self.context_class(
|
|
21
|
+
aws_event=aws_event,
|
|
22
|
+
aws_context=aws_context,
|
|
23
|
+
args=args,
|
|
24
|
+
postdata=postdata,
|
|
25
|
+
response=response,
|
|
26
|
+
session=session,
|
|
27
|
+
)
|
{velocity_python-0.0.195 → velocity_python-0.0.197}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
@@ -4,9 +4,10 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import pprint
|
|
6
6
|
import time
|
|
7
|
-
from typing import Optional
|
|
7
|
+
from typing import Optional
|
|
8
8
|
|
|
9
9
|
from velocity.aws.handlers.base_handler import BaseHandler
|
|
10
|
+
from velocity.aws.handlers.context_factory import ContextFactory
|
|
10
11
|
from velocity.aws.handlers.response import Response
|
|
11
12
|
from velocity.logging import configure_logging
|
|
12
13
|
from . import context
|
|
@@ -21,44 +22,16 @@ class LambdaHandler(BaseHandler):
|
|
|
21
22
|
self,
|
|
22
23
|
aws_event,
|
|
23
24
|
aws_context,
|
|
24
|
-
|
|
25
|
+
context_factory: Optional[ContextFactory] = None,
|
|
26
|
+
context_class: type = context.Context,
|
|
25
27
|
):
|
|
26
28
|
self.start = time.time()
|
|
27
|
-
super().__init__(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
identity = requestContext.get("identity") or {}
|
|
34
|
-
headers = aws_event.get("headers") or {}
|
|
35
|
-
auth = identity.get("cognitoAuthenticationProvider")
|
|
36
|
-
session = {
|
|
37
|
-
"authentication_provider": identity.get("cognitoAuthenticationProvider"),
|
|
38
|
-
"authentication_type": identity.get("cognitoAuthenticationType"),
|
|
39
|
-
"cognito_user": identity.get("user"),
|
|
40
|
-
"is_desktop": headers.get("CloudFront-Is-Desktop-Viewer") == "true",
|
|
41
|
-
"is_mobile": headers.get("CloudFront-Is-Mobile-Viewer") == "true",
|
|
42
|
-
"is_smart_tv": headers.get("CloudFront-Is-SmartTV-Viewer") == "true",
|
|
43
|
-
"is_tablet": headers.get("CloudFront-Is-Tablet-Viewer") == "true",
|
|
44
|
-
"origin": headers.get("origin"),
|
|
45
|
-
"path": aws_event.get("path"),
|
|
46
|
-
"referer": headers.get("Referer"),
|
|
47
|
-
"source_ip": identity.get("sourceIp"),
|
|
48
|
-
"user_agent": identity.get("userAgent"),
|
|
49
|
-
"sub": auth.split(":")[-1] if auth else None,
|
|
50
|
-
}
|
|
51
|
-
if session.get("is_mobile"):
|
|
52
|
-
session["device_type"] = "mobile"
|
|
53
|
-
elif session.get("is_desktop"):
|
|
54
|
-
session["device_type"] = "desktop"
|
|
55
|
-
elif session.get("is_tablet"):
|
|
56
|
-
session["device_type"] = "tablet"
|
|
57
|
-
elif session.get("is_smart_tv"):
|
|
58
|
-
session["device_type"] = "smart_tv"
|
|
59
|
-
else:
|
|
60
|
-
session["device_type"] = "unknown"
|
|
61
|
-
return session
|
|
29
|
+
super().__init__(
|
|
30
|
+
aws_event,
|
|
31
|
+
aws_context,
|
|
32
|
+
context_factory=context_factory,
|
|
33
|
+
context_class=context_class,
|
|
34
|
+
)
|
|
62
35
|
|
|
63
36
|
|
|
64
37
|
def beforeAction(self, tx, context):
|
|
@@ -69,16 +42,16 @@ class LambdaHandler(BaseHandler):
|
|
|
69
42
|
logger.debug("starting LamdaHandler.beforeAction")
|
|
70
43
|
|
|
71
44
|
|
|
72
|
-
|
|
45
|
+
context.perf.start("get_cognito_user")
|
|
73
46
|
self.cognito_user = context.get_cognito_user(self.aws_event)
|
|
74
|
-
|
|
75
|
-
logger.info("Timing: get_cognito_user %.2f ms", cognito_elapsed)
|
|
47
|
+
context.perf.log("get_cognito_user")
|
|
76
48
|
self.current_user = {}
|
|
77
49
|
|
|
78
50
|
logger.debug("DEBUG: !!! cognito_user %s", self.cognito_user)
|
|
51
|
+
session = context.session() or {}
|
|
79
52
|
try:
|
|
80
53
|
email_address = self.cognito_user["attributes"]["email"]
|
|
81
|
-
|
|
54
|
+
session["email_address"] = email_address
|
|
82
55
|
except Exception:
|
|
83
56
|
logger.warning("Unable to read email from Cognito user", exc_info=True)
|
|
84
57
|
|
|
@@ -88,24 +61,23 @@ class LambdaHandler(BaseHandler):
|
|
|
88
61
|
"action": context.action(),
|
|
89
62
|
"context_args": context.args(),
|
|
90
63
|
"summary": self._summarize_postdata(context.postdata()),
|
|
91
|
-
"session_email":
|
|
64
|
+
"session_email": session.get("email_address"),
|
|
92
65
|
},
|
|
93
66
|
)
|
|
94
67
|
|
|
95
68
|
if not self.user_table:
|
|
96
69
|
logger.warning(
|
|
97
70
|
"user_table not configured; skipping DB lookup for %s",
|
|
98
|
-
|
|
71
|
+
session.get("email_address"),
|
|
99
72
|
)
|
|
100
73
|
raise Exception(
|
|
101
74
|
"User table not configured; cannot validate user [Config]"
|
|
102
75
|
)
|
|
103
|
-
|
|
76
|
+
context.perf.start("user lookup")
|
|
104
77
|
row = tx.table(self.user_table).find(
|
|
105
|
-
{"email_address":
|
|
78
|
+
{"email_address": session.get("email_address")}
|
|
106
79
|
)
|
|
107
|
-
|
|
108
|
-
logger.info("Timing: user lookup %.2f ms", lookup_elapsed)
|
|
80
|
+
context.perf.log("user lookup")
|
|
109
81
|
if not row:
|
|
110
82
|
raise Exception(
|
|
111
83
|
"A valid user with permission is required to access this function [DB]"
|
|
@@ -163,32 +135,18 @@ class LambdaHandler(BaseHandler):
|
|
|
163
135
|
|
|
164
136
|
def serve(self, tx):
|
|
165
137
|
response = Response()
|
|
166
|
-
body = self.aws_event.get("body")
|
|
167
|
-
postdata = {}
|
|
168
|
-
if isinstance(body, str) and len(body) > 0:
|
|
169
|
-
try:
|
|
170
|
-
postdata = json.loads(body)
|
|
171
|
-
except (json.JSONDecodeError, TypeError):
|
|
172
|
-
postdata = {"raw_body": body}
|
|
173
|
-
elif isinstance(body, dict):
|
|
174
|
-
postdata = body
|
|
175
|
-
elif isinstance(body, list) and len(body) > 0:
|
|
176
|
-
try:
|
|
177
|
-
new = "\n".join(body)
|
|
178
|
-
postdata = json.loads(new)
|
|
179
|
-
except (json.JSONDecodeError, TypeError):
|
|
180
|
-
postdata = {"raw_body": body}
|
|
181
|
-
|
|
182
138
|
req_params = self.aws_event.get("queryStringParameters") or {}
|
|
183
|
-
local_context = self.
|
|
184
|
-
aws_event=self.aws_event,
|
|
185
|
-
aws_context=self.aws_context,
|
|
139
|
+
local_context = self.create_context(
|
|
186
140
|
args=req_params,
|
|
187
|
-
postdata=
|
|
141
|
+
postdata={},
|
|
188
142
|
response=response,
|
|
189
|
-
session=
|
|
190
|
-
log=lambda message, function=None: self.log(tx, message, function),
|
|
143
|
+
session=None,
|
|
191
144
|
)
|
|
145
|
+
local_context.perf.start("parse body")
|
|
146
|
+
postdata = local_context.parse_postdata()
|
|
147
|
+
local_context.update_postdata(postdata)
|
|
148
|
+
local_context.configure_perf(postdata=postdata)
|
|
149
|
+
local_context.perf.log("parse body")
|
|
192
150
|
|
|
193
151
|
# Determine action from postdata or query parameters
|
|
194
152
|
action = postdata.get("action") or req_params.get("action")
|
|
@@ -197,21 +155,29 @@ class LambdaHandler(BaseHandler):
|
|
|
197
155
|
actions = self.get_actions_to_execute(action)
|
|
198
156
|
|
|
199
157
|
# Use BaseHandler's execute_actions method
|
|
158
|
+
local_context.perf.start("execute_actions total (serve)")
|
|
200
159
|
try:
|
|
201
160
|
self.execute_actions(tx, local_context, actions)
|
|
202
161
|
except Exception as e:
|
|
203
162
|
self.handle_error(tx, local_context, e)
|
|
204
|
-
|
|
205
|
-
|
|
163
|
+
local_context.perf.log("execute_actions total (serve)")
|
|
164
|
+
|
|
165
|
+
local_context.perf.start("response.render")
|
|
166
|
+
rendered = local_context.response().render()
|
|
167
|
+
local_context.perf.log("response.render")
|
|
168
|
+
|
|
169
|
+
return rendered
|
|
206
170
|
|
|
207
171
|
def track(self, tx, data=None, user=None, context_obj=None):
|
|
172
|
+
context_obj = context_obj or self.context
|
|
173
|
+
session = context_obj.session() if context_obj else {}
|
|
208
174
|
sanitized_payload = context.sanitize_tracking_payload(data or {})
|
|
209
175
|
sanitized_payload.update(
|
|
210
176
|
{
|
|
211
|
-
"source_ip":
|
|
212
|
-
"referer":
|
|
213
|
-
"user_agent":
|
|
214
|
-
"device_type":
|
|
177
|
+
"source_ip": session.get("source_ip"),
|
|
178
|
+
"referer": session.get("referer"),
|
|
179
|
+
"user_agent": session.get("user_agent"),
|
|
180
|
+
"device_type": session.get("device_type"),
|
|
215
181
|
}
|
|
216
182
|
)
|
|
217
183
|
|
|
@@ -220,7 +186,7 @@ class LambdaHandler(BaseHandler):
|
|
|
220
186
|
if email:
|
|
221
187
|
sanitized_payload["sys_modified_by"] = email
|
|
222
188
|
elif not sanitized_payload.get("sys_modified_by"):
|
|
223
|
-
sanitized_payload["sys_modified_by"] =
|
|
189
|
+
sanitized_payload["sys_modified_by"] = session.get("email_address") or "system"
|
|
224
190
|
|
|
225
191
|
if not email:
|
|
226
192
|
raise Exception(f"Tracking email could not be resolved for tracking.")
|
|
@@ -233,19 +199,20 @@ class LambdaHandler(BaseHandler):
|
|
|
233
199
|
self.log(tx, f"Failed to write tracking record: {exc}", "track")
|
|
234
200
|
|
|
235
201
|
def validate(self, user_required=True):
|
|
202
|
+
session = self.context.session() if self.context else {}
|
|
236
203
|
if user_required:
|
|
237
204
|
try:
|
|
238
205
|
attrs = self.cognito_user["attributes"]
|
|
239
206
|
assert attrs
|
|
240
207
|
assert attrs["email"]
|
|
241
208
|
assert attrs["sub"]
|
|
242
|
-
assert
|
|
243
|
-
assert
|
|
209
|
+
assert session["sub"]
|
|
210
|
+
assert session["sub"] == attrs["sub"]
|
|
244
211
|
except:
|
|
245
212
|
raise Exception("User Authentication Error [Cognito]")
|
|
246
213
|
else:
|
|
247
214
|
try:
|
|
248
|
-
if not
|
|
215
|
+
if not session["sub"]:
|
|
249
216
|
# User is not signed in. If user_required
|
|
250
217
|
# is false, then simply return
|
|
251
218
|
return
|
|
@@ -253,8 +220,8 @@ class LambdaHandler(BaseHandler):
|
|
|
253
220
|
assert attrs
|
|
254
221
|
assert attrs["email"]
|
|
255
222
|
assert attrs["sub"]
|
|
256
|
-
assert
|
|
257
|
-
assert
|
|
223
|
+
assert session["sub"]
|
|
224
|
+
assert session["sub"] == attrs["sub"]
|
|
258
225
|
except:
|
|
259
226
|
raise Exception("User Authentication Error [Cognito]")
|
|
260
227
|
|