velocity-python 0.0.198__tar.gz → 0.0.200__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.198 → velocity_python-0.0.200}/PKG-INFO +1 -1
- {velocity_python-0.0.198 → velocity_python-0.0.200}/pyproject.toml +1 -1
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/lambda_handler.py +92 -53
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/mixins/web_handler.py +31 -4
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/conv/iconv.py +25 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- velocity_python-0.0.200/tests/test_iconv_money_to_cents.py +20 -0
- velocity_python-0.0.200/tests/test_lambda_handler_auth.py +77 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/LICENSE +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/README.md +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/setup.cfg +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/logging.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/tests/test_decorators.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/tests/test_table_alter.py +0 -0
- {velocity_python-0.0.198 → velocity_python-0.0.200}/tests/test_where_clause_validation.py +0 -0
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
@@ -18,6 +18,12 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
|
|
19
19
|
class LambdaHandler(BaseHandler):
|
|
20
20
|
user_table: Optional[str] = None
|
|
21
|
+
# Auth behavior for HTTP-invoked lambdas.
|
|
22
|
+
# - required: must have Cognito identity and a DB user
|
|
23
|
+
# - optional: Cognito identity may be absent; DB user lookup is skipped unless present
|
|
24
|
+
# - none: no Cognito lookup; no DB user lookup
|
|
25
|
+
auth_mode: str = "required"
|
|
26
|
+
require_db_user: bool = True
|
|
21
27
|
def __init__(
|
|
22
28
|
self,
|
|
23
29
|
aws_event,
|
|
@@ -42,18 +48,44 @@ class LambdaHandler(BaseHandler):
|
|
|
42
48
|
logger.debug("starting LamdaHandler.beforeAction")
|
|
43
49
|
|
|
44
50
|
|
|
45
|
-
context.perf.start("get_cognito_user")
|
|
46
|
-
self.cognito_user = context.get_cognito_user(self.aws_event)
|
|
47
|
-
context.perf.log("get_cognito_user")
|
|
48
51
|
self.current_user = {}
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
auth_mode = getattr(self, "auth_mode", "required") or "required"
|
|
54
|
+
require_db_user = bool(getattr(self, "require_db_user", True))
|
|
55
|
+
|
|
51
56
|
session = context.session() or {}
|
|
57
|
+
|
|
58
|
+
public_actions = getattr(self, "public_actions", None)
|
|
52
59
|
try:
|
|
53
|
-
|
|
54
|
-
session["email_address"] = email_address
|
|
60
|
+
current_action = context.action()
|
|
55
61
|
except Exception:
|
|
56
|
-
|
|
62
|
+
current_action = None
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
current_action
|
|
66
|
+
and isinstance(public_actions, (list, tuple, set))
|
|
67
|
+
and current_action in public_actions
|
|
68
|
+
):
|
|
69
|
+
auth_mode = "none"
|
|
70
|
+
require_db_user = False
|
|
71
|
+
|
|
72
|
+
if auth_mode == "none":
|
|
73
|
+
self.cognito_user = None
|
|
74
|
+
else:
|
|
75
|
+
context.perf.start("get_cognito_user")
|
|
76
|
+
if auth_mode == "optional":
|
|
77
|
+
self.cognito_user = context.get_cognito_user_optional(self.aws_event)
|
|
78
|
+
else:
|
|
79
|
+
self.cognito_user = context.get_cognito_user(self.aws_event)
|
|
80
|
+
context.perf.log("get_cognito_user")
|
|
81
|
+
|
|
82
|
+
logger.debug("DEBUG: !!! cognito_user %s", self.cognito_user)
|
|
83
|
+
if isinstance(self.cognito_user, dict):
|
|
84
|
+
try:
|
|
85
|
+
email_address = self.cognito_user["attributes"]["email"]
|
|
86
|
+
session["email_address"] = email_address
|
|
87
|
+
except Exception:
|
|
88
|
+
logger.warning("Unable to read email from Cognito user", exc_info=True)
|
|
57
89
|
|
|
58
90
|
logger.info(
|
|
59
91
|
"Starting action",
|
|
@@ -65,27 +97,32 @@ class LambdaHandler(BaseHandler):
|
|
|
65
97
|
},
|
|
66
98
|
)
|
|
67
99
|
|
|
68
|
-
if
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
raise Exception(
|
|
83
|
-
"A valid user with permission is required to access this function [DB]"
|
|
100
|
+
if require_db_user:
|
|
101
|
+
if not session.get("email_address"):
|
|
102
|
+
raise Exception("A valid user is required to access this function [Auth]")
|
|
103
|
+
|
|
104
|
+
if not self.user_table:
|
|
105
|
+
logger.warning(
|
|
106
|
+
"user_table not configured; cannot validate user %s",
|
|
107
|
+
session.get("email_address"),
|
|
108
|
+
)
|
|
109
|
+
raise Exception("User table not configured; cannot validate user [Config]")
|
|
110
|
+
|
|
111
|
+
context.perf.start("user lookup")
|
|
112
|
+
row = tx.table(self.user_table).find(
|
|
113
|
+
{"email_address": session.get("email_address")}
|
|
84
114
|
)
|
|
85
|
-
|
|
115
|
+
context.perf.log("user lookup")
|
|
116
|
+
if not row:
|
|
117
|
+
raise Exception(
|
|
118
|
+
"A valid user with permission is required to access this function [DB]"
|
|
119
|
+
)
|
|
120
|
+
self.current_user = row.to_dict()
|
|
86
121
|
|
|
87
|
-
temp = copy.deepcopy(context.postdata())
|
|
88
|
-
temp
|
|
122
|
+
temp = copy.deepcopy(context.postdata() or {})
|
|
123
|
+
payload = temp.get("payload")
|
|
124
|
+
if isinstance(payload, dict):
|
|
125
|
+
payload.pop("cognito_user", None)
|
|
89
126
|
logger.debug(
|
|
90
127
|
"Events.OnAction %s",
|
|
91
128
|
temp.get("action"),
|
|
@@ -123,9 +160,11 @@ class LambdaHandler(BaseHandler):
|
|
|
123
160
|
|
|
124
161
|
logger.debug("starting BackOfficeEvents.onError")
|
|
125
162
|
|
|
126
|
-
temp = copy.deepcopy(context.postdata())
|
|
163
|
+
temp = copy.deepcopy(context.postdata() or {})
|
|
127
164
|
logger.debug("starting BackOfficeEvents.log")
|
|
128
|
-
temp
|
|
165
|
+
payload = temp.get("payload")
|
|
166
|
+
if isinstance(payload, dict):
|
|
167
|
+
payload.pop("cognito_user", None)
|
|
129
168
|
logger.error(
|
|
130
169
|
"Events.OnError %s",
|
|
131
170
|
temp.get("action"),
|
|
@@ -189,10 +228,16 @@ class LambdaHandler(BaseHandler):
|
|
|
189
228
|
sanitized_payload["sys_modified_by"] = session.get("email_address") or "system"
|
|
190
229
|
|
|
191
230
|
if not email:
|
|
192
|
-
|
|
231
|
+
# Tracking should be best-effort and never break user flows.
|
|
232
|
+
logger.warning("Tracking email could not be resolved; skipping tracking write")
|
|
233
|
+
return
|
|
193
234
|
|
|
194
235
|
table_name = context_obj.get_tracking_table(email)
|
|
195
236
|
|
|
237
|
+
if not table_name:
|
|
238
|
+
logger.warning("Tracking table could not be resolved; skipping tracking write")
|
|
239
|
+
return
|
|
240
|
+
|
|
196
241
|
try:
|
|
197
242
|
tx.table(table_name).insert(sanitized_payload)
|
|
198
243
|
except Exception as exc: # pragma: no cover - best effort logging
|
|
@@ -200,30 +245,24 @@ class LambdaHandler(BaseHandler):
|
|
|
200
245
|
|
|
201
246
|
def validate(self, user_required=True):
|
|
202
247
|
session = self.context.session() if self.context else {}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
assert attrs["email"]
|
|
222
|
-
assert attrs["sub"]
|
|
223
|
-
assert session["sub"]
|
|
224
|
-
assert session["sub"] == attrs["sub"]
|
|
225
|
-
except:
|
|
226
|
-
raise Exception("User Authentication Error [Cognito]")
|
|
248
|
+
session_sub = session.get("sub")
|
|
249
|
+
cognito_user = getattr(self, "cognito_user", None)
|
|
250
|
+
|
|
251
|
+
if not user_required:
|
|
252
|
+
# Optional-auth flows: if there's no user in session, treat as anonymous.
|
|
253
|
+
if not session_sub:
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
if not isinstance(cognito_user, dict):
|
|
258
|
+
raise Exception("Missing Cognito user")
|
|
259
|
+
attrs = cognito_user.get("attributes") or {}
|
|
260
|
+
email = attrs.get("email")
|
|
261
|
+
sub = attrs.get("sub")
|
|
262
|
+
if not email or not sub or not session_sub or session_sub != sub:
|
|
263
|
+
raise Exception("User mismatch")
|
|
264
|
+
except Exception:
|
|
265
|
+
raise Exception("User Authentication Error [Cognito]")
|
|
227
266
|
|
|
228
267
|
def _summarize_postdata(self, postdata):
|
|
229
268
|
"""Extract key fields from postdata for logging without storing entire payload"""
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/mixins/web_handler.py
RENAMED
|
@@ -484,7 +484,7 @@ class ButtonHandler:
|
|
|
484
484
|
module_name = None # Override in subclass (e.g., 'buttons')
|
|
485
485
|
|
|
486
486
|
@classmethod
|
|
487
|
-
def _get_button_module(cls,
|
|
487
|
+
def _get_button_module(cls, *args, **kwargs):
|
|
488
488
|
"""
|
|
489
489
|
Load button-specific handler module if it exists.
|
|
490
490
|
|
|
@@ -495,6 +495,19 @@ class ButtonHandler:
|
|
|
495
495
|
The loaded module or None if not found
|
|
496
496
|
|
|
497
497
|
"""
|
|
498
|
+
# NOTE: This helper is called from Lambda handlers that are often wrapped by
|
|
499
|
+
# velocity's transaction decorators. Those wrappers can inject extra positional
|
|
500
|
+
# arguments (including duplicate tx objects). To stay robust across versions,
|
|
501
|
+
# we accept *args and locate the handler name from strings.
|
|
502
|
+
|
|
503
|
+
handler = kwargs.get("handler")
|
|
504
|
+
if not handler:
|
|
505
|
+
string_args = [value for value in args if isinstance(value, str)]
|
|
506
|
+
handler = string_args[-1] if string_args else None
|
|
507
|
+
|
|
508
|
+
if not handler:
|
|
509
|
+
raise AlertError("Button handler name missing in request payload")
|
|
510
|
+
|
|
498
511
|
if not cls.module_name:
|
|
499
512
|
raise AlertError("ButtonHandler.module_name not set in subclass. This is a configuration issue that needs to be handled by the developer.")
|
|
500
513
|
try:
|
|
@@ -503,7 +516,7 @@ class ButtonHandler:
|
|
|
503
516
|
raise AlertError(f"Unable to import ButtonHandler module `{handler}`. Please ensure the code has been deployed.")
|
|
504
517
|
|
|
505
518
|
@classmethod
|
|
506
|
-
def _get_button_function(cls,
|
|
519
|
+
def _get_button_function(cls, *args, **kwargs):
|
|
507
520
|
"""
|
|
508
521
|
Get a specific function from a button handler module.
|
|
509
522
|
|
|
@@ -514,7 +527,21 @@ class ButtonHandler:
|
|
|
514
527
|
Returns:
|
|
515
528
|
The function object or None if not found
|
|
516
529
|
"""
|
|
517
|
-
|
|
530
|
+
handler = kwargs.get("handler")
|
|
531
|
+
action = kwargs.get("action")
|
|
532
|
+
|
|
533
|
+
if not handler or not action:
|
|
534
|
+
string_args = [value for value in args if isinstance(value, str)]
|
|
535
|
+
if len(string_args) >= 2:
|
|
536
|
+
handler = handler or string_args[-2]
|
|
537
|
+
action = action or string_args[-1]
|
|
538
|
+
|
|
539
|
+
if not handler:
|
|
540
|
+
raise AlertError("Button handler name missing in request payload")
|
|
541
|
+
if not action:
|
|
542
|
+
raise AlertError("Button action missing in request payload")
|
|
543
|
+
|
|
544
|
+
module = cls._get_button_module(handler=handler)
|
|
518
545
|
|
|
519
546
|
# Convert action name: "refresh-data" -> "RefreshData"
|
|
520
547
|
func_name = action.replace("-", " ").title().replace(" ", "")
|
|
@@ -558,7 +585,7 @@ class ButtonHandler:
|
|
|
558
585
|
action = dataset["action"]
|
|
559
586
|
|
|
560
587
|
# Resolve the function using the helper
|
|
561
|
-
func = self._get_button_function(tx, handler, action)
|
|
588
|
+
func = self.__class__._get_button_function(tx, handler, action)
|
|
562
589
|
|
|
563
590
|
# Execute the button handler
|
|
564
591
|
# Don't return its result to avoid triggering base handler warnings
|
|
@@ -229,6 +229,31 @@ def money(data: str) -> Optional[Decimal]:
|
|
|
229
229
|
return None
|
|
230
230
|
|
|
231
231
|
|
|
232
|
+
def money_to_cents(value) -> int:
|
|
233
|
+
"""Convert a dollars-like value to integer cents.
|
|
234
|
+
|
|
235
|
+
This is intentionally permissive and mirrors common app-layer behavior:
|
|
236
|
+
- None/"" => 0
|
|
237
|
+
- int/float/Decimal/str => dollars * 100, truncated toward 0
|
|
238
|
+
|
|
239
|
+
Notes:
|
|
240
|
+
- If you already have cents, do not pass them here.
|
|
241
|
+
"""
|
|
242
|
+
if value is None:
|
|
243
|
+
return 0
|
|
244
|
+
if isinstance(value, int):
|
|
245
|
+
# Ambiguous (could be dollars or cents). Many apps pass dollars.
|
|
246
|
+
# Treat as dollars to preserve historical behavior.
|
|
247
|
+
return int(Decimal(str(value)) * 100)
|
|
248
|
+
if isinstance(value, (float, Decimal)):
|
|
249
|
+
return int(Decimal(str(value)) * 100)
|
|
250
|
+
|
|
251
|
+
raw = str(value).strip()
|
|
252
|
+
if raw == "":
|
|
253
|
+
return 0
|
|
254
|
+
return int(Decimal(raw) * 100)
|
|
255
|
+
|
|
256
|
+
|
|
232
257
|
def round_to(
|
|
233
258
|
precision: int, data: Optional[Union[str, float, Decimal]] = None
|
|
234
259
|
) -> Union[Decimal, Callable[[Union[str, float, Decimal]], Optional[Decimal]]]:
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity_python.egg-info/SOURCES.txt
RENAMED
|
@@ -136,7 +136,9 @@ src/velocity_python.egg-info/dependency_links.txt
|
|
|
136
136
|
src/velocity_python.egg-info/requires.txt
|
|
137
137
|
src/velocity_python.egg-info/top_level.txt
|
|
138
138
|
tests/test_decorators.py
|
|
139
|
+
tests/test_iconv_money_to_cents.py
|
|
139
140
|
tests/test_lambda_handler.py
|
|
141
|
+
tests/test_lambda_handler_auth.py
|
|
140
142
|
tests/test_mixins_import.py
|
|
141
143
|
tests/test_sys_modified_count_postgres_demo.py
|
|
142
144
|
tests/test_table_alter.py
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import decimal
|
|
2
|
+
|
|
3
|
+
from velocity.misc.conv import iconv
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_money_to_cents_none_and_empty():
|
|
7
|
+
assert iconv.money_to_cents(None) == 0
|
|
8
|
+
assert iconv.money_to_cents("") == 0
|
|
9
|
+
assert iconv.money_to_cents(" ") == 0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_money_to_cents_numeric_types():
|
|
13
|
+
assert iconv.money_to_cents(10) == 1000
|
|
14
|
+
assert iconv.money_to_cents(10.5) == 1050
|
|
15
|
+
assert iconv.money_to_cents(decimal.Decimal("10.50")) == 1050
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_money_to_cents_string_values():
|
|
19
|
+
assert iconv.money_to_cents("10") == 1000
|
|
20
|
+
assert iconv.money_to_cents("10.50") == 1050
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from velocity.aws.handlers.lambda_handler import LambdaHandler
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _Perf:
|
|
7
|
+
def start(self, *args, **kwargs):
|
|
8
|
+
return None
|
|
9
|
+
|
|
10
|
+
def log(self, *args, **kwargs):
|
|
11
|
+
return None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _Context:
|
|
15
|
+
def __init__(self, action=None, postdata=None, session=None):
|
|
16
|
+
self._action = action
|
|
17
|
+
self._postdata = postdata
|
|
18
|
+
self._session = session if isinstance(session, dict) else {}
|
|
19
|
+
self.perf = _Perf()
|
|
20
|
+
|
|
21
|
+
def action(self):
|
|
22
|
+
return self._action
|
|
23
|
+
|
|
24
|
+
def args(self):
|
|
25
|
+
return {}
|
|
26
|
+
|
|
27
|
+
def postdata(self):
|
|
28
|
+
return self._postdata
|
|
29
|
+
|
|
30
|
+
def session(self):
|
|
31
|
+
return self._session
|
|
32
|
+
|
|
33
|
+
def get_cognito_user(self, aws_event): # pragma: no cover
|
|
34
|
+
raise AssertionError("get_cognito_user should not be called")
|
|
35
|
+
|
|
36
|
+
def get_cognito_user_optional(self, aws_event): # pragma: no cover
|
|
37
|
+
raise AssertionError("get_cognito_user_optional should not be called")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _Handler(LambdaHandler):
|
|
41
|
+
def _enhanced_before_action(self, tx, context):
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def _enhanced_error_handler(self, tx, context, exc, tb):
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestLambdaHandlerAuthModes(unittest.TestCase):
|
|
49
|
+
def test_auth_mode_none_skips_cognito(self):
|
|
50
|
+
h = _Handler(aws_event={}, aws_context=type("C", (), {"aws_request_id": "rid"})())
|
|
51
|
+
h.auth_mode = "none"
|
|
52
|
+
h.require_db_user = False
|
|
53
|
+
h.beforeAction(tx=object(), context=_Context(action="anything", postdata={}))
|
|
54
|
+
|
|
55
|
+
def test_public_actions_bypass_auth_and_db_user(self):
|
|
56
|
+
h = _Handler(aws_event={}, aws_context=type("C", (), {"aws_request_id": "rid"})())
|
|
57
|
+
h.auth_mode = "required"
|
|
58
|
+
h.require_db_user = True
|
|
59
|
+
h.user_table = "users"
|
|
60
|
+
h.public_actions = ["public-action"]
|
|
61
|
+
|
|
62
|
+
# Should not call cognito, should not require session email
|
|
63
|
+
h.beforeAction(tx=object(), context=_Context(action="public-action", postdata={}))
|
|
64
|
+
|
|
65
|
+
def test_onerror_does_not_require_payload(self):
|
|
66
|
+
h = _Handler(aws_event={}, aws_context=type("C", (), {"aws_request_id": "rid"})())
|
|
67
|
+
h.onError(tx=object(), context=_Context(action="anything", postdata={}), exc="E", tb="TB")
|
|
68
|
+
h.onError(
|
|
69
|
+
tx=object(),
|
|
70
|
+
context=_Context(action="anything", postdata={"payload": "not-a-dict"}),
|
|
71
|
+
exc="E",
|
|
72
|
+
tb="TB",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/app/tests/test_email_processing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/base_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/context_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/mixins/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/aws/handlers/sqs_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/base/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/base/initializer.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/base/operators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/mysql/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/mysql/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/mysql/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/postgres/types.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlite/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlite/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlite/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/sql.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/servers/sqlserver/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/common.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_column.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_database.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_imports.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_result.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_row.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_sequence.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/postgres/test_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_postgres_unchanged.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_result_caching.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_result_sql_aware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_sql_builder.py
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/db/tests/test_tablehelper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/misc/tests/test_original_error.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity/payment/braintree_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/src/velocity_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.198 → velocity_python-0.0.200}/tests/test_sys_modified_count_postgres_demo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|