velocity-python 0.0.185__tar.gz → 0.0.186__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.185 → velocity_python-0.0.186}/PKG-INFO +1 -1
- {velocity_python-0.0.185 → velocity_python-0.0.186}/pyproject.toml +1 -1
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/mixins/data_service.py +272 -249
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.185 → velocity_python-0.0.186}/LICENSE +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/README.md +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/setup.cfg +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/logging.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity_python.egg-info/SOURCES.txt +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/tests/test_decorators.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/tests/test_table_alter.py +0 -0
- {velocity_python-0.0.185 → velocity_python-0.0.186}/tests/test_where_clause_validation.py +0 -0
{velocity_python-0.0.185 → velocity_python-0.0.186}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
@@ -58,319 +58,322 @@ class DataServiceMixin:
|
|
|
58
58
|
else self._pg_types.get(column_info["type_name"], "string")
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def read_hook(self, tx, table_name, sys_id, row, context):
|
|
61
|
+
def _call_rwx_hook(self, hook_name, table, *args, **kwargs):
|
|
64
62
|
"""
|
|
65
|
-
|
|
63
|
+
Call a table-specific RWX hook if it exists.
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
table_name: Name of the table
|
|
70
|
-
sys_id: Record ID
|
|
71
|
-
row: Dictionary of record data
|
|
72
|
-
context: Request context
|
|
73
|
-
"""
|
|
74
|
-
pass
|
|
75
|
-
|
|
76
|
-
def write_hook(self, tx, table_name, sys_id, incoming, context):
|
|
77
|
-
"""
|
|
78
|
-
Called before writing an object. Override to add validation/transformation.
|
|
65
|
+
This method tries to load a hook from the rwx package and call it.
|
|
66
|
+
If the hook doesn't exist, execution continues silently.
|
|
79
67
|
|
|
80
68
|
Args:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
context: Request context
|
|
69
|
+
hook_name: Name of the hook (e.g., 'before_write', 'after_read')
|
|
70
|
+
table: Table name (used to load table-specific module)
|
|
71
|
+
*args: Arguments to pass to the hook
|
|
72
|
+
**kwargs: Keyword arguments to pass to the hook
|
|
86
73
|
"""
|
|
87
|
-
|
|
74
|
+
try:
|
|
75
|
+
m = importlib.import_module(f".{table}", "rwx")
|
|
76
|
+
if hasattr(m, hook_name):
|
|
77
|
+
getattr(m, hook_name)(*args, **kwargs)
|
|
78
|
+
except ImportError:
|
|
79
|
+
# rwx package not available, continue without hooks
|
|
80
|
+
pass
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
"""
|
|
91
|
-
Called after querying objects. Override to transform results.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
tx: Database transaction
|
|
95
|
-
table_name: Name of the table
|
|
96
|
-
params: Query parameters
|
|
97
|
-
data: Query results
|
|
98
|
-
context: Request context
|
|
99
|
-
"""
|
|
100
|
-
pass
|
|
82
|
+
# ========== Hook Methods (Override These) ==========
|
|
101
83
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
84
|
+
def read_hook(self, tx, table, sys_id, context):
|
|
85
|
+
row = {}
|
|
86
|
+
if not sys_id:
|
|
87
|
+
raise Exception("An object id was not provided for read operation")
|
|
88
|
+
if sys_id == "@new":
|
|
89
|
+
row = {}
|
|
90
|
+
self._call_rwx_hook("on_new", "common", tx, "common", row, context)
|
|
91
|
+
self._call_rwx_hook("on_new", table, tx, table, row, context)
|
|
92
|
+
return row
|
|
93
|
+
sys_id = int(sys_id)
|
|
94
|
+
self._call_rwx_hook("before_read", "common", tx, "common", sys_id, context)
|
|
95
|
+
self._call_rwx_hook("before_read", table, tx, table, sys_id, context)
|
|
96
|
+
row = tx.table(table).find(sys_id)
|
|
97
|
+
row = row.to_dict() if row else {}
|
|
98
|
+
self._call_rwx_hook("after_read", "common", tx, "common", sys_id, row, context)
|
|
99
|
+
self._call_rwx_hook("after_read", table, tx, table, sys_id, row, context)
|
|
100
|
+
return row
|
|
101
|
+
|
|
102
|
+
def find_hook(self, tx, table, where, context):
|
|
103
|
+
row = {}
|
|
104
|
+
if not where:
|
|
105
|
+
raise Exception("An query predicate was not provided for this read operation")
|
|
106
|
+
self._call_rwx_hook("before_find", "common", tx, "common", where, context)
|
|
107
|
+
self._call_rwx_hook("before_find", table, tx, table, where, context)
|
|
108
|
+
row = tx.table(table).find(where)
|
|
109
|
+
self._call_rwx_hook("after_find", "common", tx, "common", where, row, context)
|
|
110
|
+
self._call_rwx_hook("after_find", table, tx, table, where, row, context)
|
|
111
|
+
if row:
|
|
112
|
+
row = row.to_dict()
|
|
113
|
+
return row
|
|
114
|
+
|
|
115
|
+
def write_hook(self, tx, table, sys_id, incoming, context):
|
|
116
|
+
row = {}
|
|
117
|
+
incoming.pop("sys_id", None)
|
|
118
|
+
if sys_id == "@new":
|
|
119
|
+
self._call_rwx_hook("before_new", "common", tx, "common", sys_id, incoming, context)
|
|
120
|
+
self._call_rwx_hook("before_new", table, tx, table, sys_id, incoming, context)
|
|
121
|
+
row = tx.table(table).new()
|
|
122
|
+
sys_id = row["sys_id"]
|
|
123
|
+
self._call_rwx_hook("after_new", "common", tx, "common", sys_id, row, context)
|
|
124
|
+
self._call_rwx_hook("after_new", table, tx, table, sys_id, row, context)
|
|
125
|
+
elif sys_id:
|
|
126
|
+
sys_id = int(sys_id)
|
|
127
|
+
else:
|
|
128
|
+
raise Exception("Object sys_id was not supplied on write operation.")
|
|
129
|
+
self._call_rwx_hook("before_write", "common", tx, "common", sys_id, incoming, context)
|
|
130
|
+
self._call_rwx_hook("before_write", table, tx, table, sys_id, incoming, context)
|
|
131
|
+
if not row:
|
|
132
|
+
row = tx.table(table).get(sys_id)
|
|
133
|
+
row.update(incoming)
|
|
134
|
+
self._call_rwx_hook("after_write", "common", tx, "common", sys_id, row, context)
|
|
135
|
+
self._call_rwx_hook("after_write", table, tx, table, sys_id, row, context)
|
|
136
|
+
|
|
137
|
+
return row.to_dict()
|
|
138
|
+
|
|
139
|
+
def query_hook(self, tx, table, payload, context):
|
|
140
|
+
self._call_rwx_hook("before_query", "common", tx, "common", payload, context)
|
|
141
|
+
self._call_rwx_hook("before_query", table, tx, table, payload, context)
|
|
142
|
+
params = payload.get("params", {})
|
|
143
|
+
result = tx.table(payload["obj"]).select(**params)
|
|
144
|
+
if payload.get("result_format") == "excel":
|
|
145
|
+
data = {
|
|
146
|
+
"headers": payload.get(
|
|
147
|
+
"headers", [x.replace("_", " ").title() for x in result.headers]
|
|
148
|
+
),
|
|
149
|
+
"rows": result.as_list().all(),
|
|
150
|
+
}
|
|
151
|
+
else:
|
|
152
|
+
data = {
|
|
153
|
+
"rows": result.all(),
|
|
154
|
+
"config": {
|
|
155
|
+
"lastFetch": datetime.datetime.now(),
|
|
156
|
+
"query": result.sql,
|
|
157
|
+
"format": payload.get("result_format"),
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
if payload.get("count"):
|
|
161
|
+
data["count"] = tx.table(payload["obj"]).count(where=params.get("where", None))
|
|
162
|
+
if payload.get("headers"):
|
|
163
|
+
data["columns"] = [
|
|
164
|
+
{
|
|
165
|
+
"field": x["name"],
|
|
166
|
+
"headerName": x["name"].replace("_", " ").title(),
|
|
167
|
+
"type": self._get_field_type(x),
|
|
168
|
+
}
|
|
169
|
+
for x in result.columns.values()
|
|
170
|
+
]
|
|
171
|
+
self._call_rwx_hook("after_query", "common", tx, "common", data, payload, context)
|
|
172
|
+
self._call_rwx_hook("after_query", table, tx, table, data, payload, context)
|
|
173
|
+
return data
|
|
174
|
+
|
|
175
|
+
def delete_hook(self, tx, table, sys_id, context):
|
|
176
|
+
if sys_id:
|
|
177
|
+
sys_id = int(sys_id)
|
|
178
|
+
self._call_rwx_hook("before_delete", "common", tx, "common", sys_id, context)
|
|
179
|
+
self._call_rwx_hook("before_delete", table, tx, table, sys_id, context)
|
|
180
|
+
row = tx.table(table).find(sys_id)
|
|
181
|
+
if row:
|
|
182
|
+
row.clear()
|
|
183
|
+
self._call_rwx_hook("after_delete", "common", tx, "common", sys_id, context)
|
|
184
|
+
self._call_rwx_hook("after_delete", table, tx, table, sys_id, context)
|
|
185
|
+
|
|
113
186
|
|
|
114
187
|
# ========== CRUD Action Methods ==========
|
|
115
188
|
|
|
116
189
|
def OnActionReadObject(self, tx, context):
|
|
117
|
-
"""
|
|
118
|
-
Read a single object by sys_id.
|
|
119
|
-
|
|
120
|
-
Payload:
|
|
121
|
-
tableName: str - Name of the database table
|
|
122
|
-
object: dict - Object containing sys_id field
|
|
123
|
-
"""
|
|
124
190
|
payload = context.payload()
|
|
125
|
-
|
|
191
|
+
|
|
126
192
|
# Validate required parameters
|
|
127
193
|
if "tableName" not in payload:
|
|
128
194
|
raise ValueError("Missing required parameter 'tableName' in payload")
|
|
129
195
|
if "object" not in payload:
|
|
130
196
|
raise ValueError("Missing required parameter 'object' in payload")
|
|
131
|
-
|
|
197
|
+
|
|
132
198
|
table_name = payload["tableName"]
|
|
133
199
|
obj = payload["object"]
|
|
134
|
-
|
|
200
|
+
|
|
135
201
|
if not table_name:
|
|
136
202
|
raise ValueError("Parameter 'tableName' cannot be empty")
|
|
137
203
|
if not obj:
|
|
138
204
|
raise ValueError("Parameter 'object' cannot be empty")
|
|
205
|
+
|
|
206
|
+
row = self.read_hook(
|
|
207
|
+
tx,
|
|
208
|
+
table_name,
|
|
209
|
+
obj.get("sys_id"),
|
|
210
|
+
context,
|
|
211
|
+
)
|
|
139
212
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
row = {}
|
|
147
|
-
else:
|
|
148
|
-
sys_id = int(sys_id)
|
|
149
|
-
row = tx.table(table_name).find(sys_id)
|
|
150
|
-
row = row.to_dict() if row else {}
|
|
151
|
-
|
|
152
|
-
# Call hook for custom logic
|
|
153
|
-
self.read_hook(tx, table_name, sys_id, row, context)
|
|
154
|
-
|
|
155
|
-
context.response().set_body({
|
|
156
|
-
"object": row,
|
|
157
|
-
"lastFetch": datetime.datetime.now(),
|
|
158
|
-
})
|
|
159
|
-
|
|
213
|
+
context.response().set_body(
|
|
214
|
+
{
|
|
215
|
+
"object": row,
|
|
216
|
+
"lastFetch": datetime.datetime.now(),
|
|
217
|
+
}
|
|
218
|
+
)
|
|
160
219
|
if row:
|
|
161
220
|
context.response().load_object(row)
|
|
162
221
|
else:
|
|
163
222
|
message = f"Object {obj.get('sys_id')} was not found in the database. You may create it as a new object."
|
|
164
223
|
context.response().toast(message, "warning")
|
|
165
|
-
|
|
224
|
+
|
|
166
225
|
def OnActionFindObject(self, tx, context):
|
|
167
|
-
"""
|
|
168
|
-
Find a single object by query predicate.
|
|
169
|
-
|
|
170
|
-
Payload:
|
|
171
|
-
tableName: str - Name of the database table
|
|
172
|
-
query: dict - Query containing 'where' clause
|
|
173
|
-
"""
|
|
174
226
|
payload = context.payload()
|
|
175
|
-
|
|
227
|
+
|
|
176
228
|
# Validate required parameters
|
|
177
229
|
if "tableName" not in payload:
|
|
178
230
|
raise ValueError("Missing required parameter 'tableName' in payload")
|
|
179
231
|
if "query" not in payload:
|
|
180
232
|
raise ValueError("Missing required parameter 'query' in payload")
|
|
181
|
-
|
|
233
|
+
|
|
182
234
|
table_name = payload["tableName"]
|
|
183
235
|
query = payload["query"]
|
|
184
|
-
|
|
236
|
+
|
|
185
237
|
if not table_name:
|
|
186
238
|
raise ValueError("Parameter 'tableName' cannot be empty")
|
|
187
239
|
if not query or "where" not in query:
|
|
188
240
|
raise ValueError("Parameter 'query' must contain 'where' clause")
|
|
189
|
-
|
|
190
|
-
row =
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
241
|
+
|
|
242
|
+
row = self.find_hook(
|
|
243
|
+
tx,
|
|
244
|
+
table_name,
|
|
245
|
+
query["where"],
|
|
246
|
+
context,
|
|
247
|
+
)
|
|
248
|
+
context.response().set_body(
|
|
249
|
+
{
|
|
250
|
+
"object": row,
|
|
251
|
+
"lastFetch": datetime.datetime.now(),
|
|
252
|
+
}
|
|
253
|
+
)
|
|
202
254
|
context.response().load_object(row)
|
|
203
|
-
|
|
255
|
+
|
|
204
256
|
def OnActionWriteObject(self, tx, context):
|
|
205
|
-
"""
|
|
206
|
-
Write (insert or update) an object.
|
|
207
|
-
|
|
208
|
-
Payload:
|
|
209
|
-
tableName: str - Name of the database table
|
|
210
|
-
object: dict - Object data to write
|
|
211
|
-
"""
|
|
212
257
|
payload = context.payload()
|
|
213
|
-
|
|
258
|
+
|
|
214
259
|
# Validate required parameters
|
|
215
260
|
if "tableName" not in payload:
|
|
216
261
|
raise ValueError("Missing required parameter 'tableName' in payload")
|
|
217
262
|
if "object" not in payload:
|
|
218
263
|
raise ValueError("Missing required parameter 'object' in payload")
|
|
219
|
-
|
|
264
|
+
|
|
220
265
|
table_name = payload["tableName"]
|
|
221
266
|
obj = payload["object"]
|
|
222
|
-
|
|
267
|
+
|
|
223
268
|
if not table_name:
|
|
224
269
|
raise ValueError("Parameter 'tableName' cannot be empty")
|
|
225
270
|
if not obj or not isinstance(obj, dict):
|
|
226
271
|
raise ValueError("Parameter 'object' must be a non-empty dictionary")
|
|
227
|
-
|
|
272
|
+
|
|
228
273
|
# Ensure the object has at least some data
|
|
229
274
|
incoming = obj.copy()
|
|
230
275
|
if not any(value is not None for value in incoming.values()):
|
|
231
276
|
raise ValueError("Parameter 'object' cannot contain only None values")
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
277
|
+
|
|
278
|
+
print(
|
|
279
|
+
f"WRITE_LOG: Writing to table {table_name} with sys_id {incoming.get('sys_id')}"
|
|
280
|
+
)
|
|
281
|
+
print(f"WRITE_LOG: Object keys: {list(incoming.keys())}")
|
|
282
|
+
|
|
238
283
|
try:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
context.response().set_body(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
284
|
+
row = self.write_hook(
|
|
285
|
+
tx,
|
|
286
|
+
table_name,
|
|
287
|
+
incoming.get("sys_id"),
|
|
288
|
+
incoming,
|
|
289
|
+
context,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if not row:
|
|
293
|
+
print(f"WARNING: self.write_hook returned empty row for table {table_name}")
|
|
294
|
+
row = {}
|
|
295
|
+
|
|
296
|
+
context.response().set_body(
|
|
297
|
+
{
|
|
298
|
+
"object": row,
|
|
299
|
+
"lastFetch": datetime.datetime.now(),
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
print(f"WRITE_LOG: Successfully wrote to {table_name}")
|
|
303
|
+
|
|
257
304
|
except Exception as e:
|
|
258
|
-
|
|
259
|
-
|
|
305
|
+
print(f"ERROR in OnActionWriteObject: {e}")
|
|
306
|
+
print(f"table_name={table_name}, incoming_keys={list(incoming.keys())}")
|
|
307
|
+
raise
|
|
308
|
+
context.response().load_object(row)
|
|
309
|
+
|
|
310
|
+
# Query a table for rows. If the requested result format is excel, then return the
|
|
311
|
+
# data as a downloadedable excel file. If the requested result format is raw,
|
|
312
|
+
# then return the data as rows for the application front end to handle as it wishes.
|
|
313
|
+
# Otherwise, return the data as a dataset to be populated into a datatable,
|
|
314
|
+
# and load the data into 'store.repo' as such.
|
|
315
|
+
# @param self
|
|
316
|
+
# @param tx
|
|
317
|
+
# @param args
|
|
318
|
+
# @param postdata
|
|
319
|
+
# @param response
|
|
320
|
+
#
|
|
321
|
+
# Payload parameters
|
|
322
|
+
|
|
260
323
|
def OnActionQuery(self, tx, context):
|
|
261
|
-
"""
|
|
262
|
-
Query table for multiple rows.
|
|
263
|
-
|
|
264
|
-
Payload:
|
|
265
|
-
obj: str - Table name to query
|
|
266
|
-
params: dict - Query parameters (where, orderby, limit, offset)
|
|
267
|
-
result_format: str - 'excel', 'raw', or 'datatable' (default)
|
|
268
|
-
datatable: str - Name for datatable in response (defaults to obj)
|
|
269
|
-
count: bool - Include total count in response
|
|
270
|
-
headers: bool - Include column headers in response
|
|
271
|
-
"""
|
|
272
324
|
payload = context.payload()
|
|
273
|
-
|
|
325
|
+
|
|
274
326
|
# Validate required parameters
|
|
275
327
|
if "obj" not in payload:
|
|
276
328
|
raise ValueError("Missing required parameter 'obj' in payload")
|
|
277
|
-
|
|
329
|
+
|
|
278
330
|
table = payload["obj"]
|
|
279
331
|
if not table:
|
|
280
332
|
raise ValueError("Parameter 'obj' cannot be empty")
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
result = tx.table(table).select(**params)
|
|
284
|
-
|
|
333
|
+
|
|
334
|
+
data = self.query_hook(tx, table, payload, context)
|
|
285
335
|
if payload.get("result_format") == "excel":
|
|
286
|
-
headers = payload.get(
|
|
287
|
-
"headers", [x.replace("_", " ").title() for x in result.headers]
|
|
288
|
-
)
|
|
289
|
-
rows = result.as_list().all()
|
|
290
|
-
|
|
291
336
|
filebuffer = BytesIO()
|
|
292
|
-
export.create_spreadsheet(headers, rows, filebuffer)
|
|
293
|
-
context.response().file_download(
|
|
294
|
-
"filename": payload.get("filename", "temp_file.xls"),
|
|
295
|
-
"data": base64.b64encode(filebuffer.getvalue()).decode(),
|
|
296
|
-
})
|
|
297
|
-
return
|
|
298
|
-
|
|
299
|
-
data = {
|
|
300
|
-
"rows": result.all(),
|
|
301
|
-
"config": {
|
|
302
|
-
"lastFetch": datetime.datetime.now(),
|
|
303
|
-
"query": result.sql,
|
|
304
|
-
"format": payload.get("result_format"),
|
|
305
|
-
},
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if payload.get("count"):
|
|
309
|
-
data["count"] = tx.table(table).count(where=params.get("where", None))
|
|
310
|
-
|
|
311
|
-
if payload.get("headers"):
|
|
312
|
-
data["columns"] = [
|
|
337
|
+
export.create_spreadsheet(data["headers"], data["rows"], filebuffer)
|
|
338
|
+
context.response().file_download(
|
|
313
339
|
{
|
|
314
|
-
"
|
|
315
|
-
"
|
|
316
|
-
"type": self._get_field_type(x),
|
|
340
|
+
"filename": payload.get("filename", "temp_file.xls"),
|
|
341
|
+
"data": base64.b64encode(filebuffer.getvalue()).decode(),
|
|
317
342
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
# Call hook after query
|
|
322
|
-
self.query_hook(tx, table, params, data, context)
|
|
323
|
-
|
|
343
|
+
)
|
|
344
|
+
return
|
|
324
345
|
if payload.get("result_format") == "raw":
|
|
325
346
|
context.response().set_body(data)
|
|
326
347
|
else:
|
|
327
|
-
context.response().set_table(
|
|
328
|
-
payload.get("datatable", payload.get("obj")): data
|
|
329
|
-
|
|
330
|
-
|
|
348
|
+
context.response().set_table(
|
|
349
|
+
{payload.get("datatable", payload.get("obj")): data}
|
|
350
|
+
)
|
|
351
|
+
|
|
331
352
|
def OnActionDeleteObject(self, tx, context):
|
|
332
|
-
"""
|
|
333
|
-
Delete one or more objects.
|
|
334
|
-
|
|
335
|
-
Payload:
|
|
336
|
-
tableName: str - Name of the database table
|
|
337
|
-
deleteList: list - List of sys_id values to delete (optional)
|
|
338
|
-
object: dict - Single object with sys_id to delete (optional)
|
|
339
|
-
"""
|
|
340
353
|
payload = context.payload()
|
|
341
|
-
|
|
354
|
+
|
|
342
355
|
# Validate required parameters
|
|
343
356
|
if "tableName" not in payload:
|
|
344
357
|
raise ValueError("Missing required parameter 'tableName' in payload")
|
|
345
|
-
|
|
358
|
+
|
|
346
359
|
table_name = payload["tableName"]
|
|
347
360
|
if not table_name:
|
|
348
361
|
raise ValueError("Parameter 'tableName' cannot be empty")
|
|
349
|
-
|
|
362
|
+
|
|
350
363
|
table = tx.table(table_name)
|
|
351
364
|
deleteList = []
|
|
352
|
-
|
|
353
365
|
if "deleteList" in payload:
|
|
354
366
|
deleteList.extend(payload.get("deleteList"))
|
|
355
|
-
|
|
356
367
|
if "object" in payload:
|
|
357
368
|
obj = payload["object"]
|
|
358
369
|
if obj and obj.get("sys_id"):
|
|
359
370
|
deleteList.append(obj.get("sys_id"))
|
|
360
371
|
|
|
361
372
|
for sys_id in deleteList:
|
|
362
|
-
# Call hook before delete
|
|
363
373
|
self.delete_hook(tx, table_name, sys_id, context)
|
|
364
374
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
obj.clear()
|
|
368
|
-
context.response().toast(f"Object {sys_id} deleted", "success")
|
|
369
|
-
else:
|
|
370
|
-
context.response().toast(f"Object {sys_id} not found", "warning")
|
|
371
|
-
|
|
372
|
-
if not deleteList:
|
|
373
|
-
context.response().toast("No items were selected.", "warning")
|
|
375
|
+
if not (deleteList):
|
|
376
|
+
context.response().toast(f"No items were selected.", "warning")
|
|
374
377
|
|
|
375
378
|
def OnActionGetTables(self, tx, context):
|
|
376
379
|
"""Get list of all tables in the database."""
|
|
@@ -534,20 +537,63 @@ class RWXHookSystem:
|
|
|
534
537
|
"""
|
|
535
538
|
RWX (Read/Write/eXecute) hook system for table-specific business logic.
|
|
536
539
|
|
|
537
|
-
This
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
540
|
+
This system allows you to create table-specific hook modules in an 'rwx' package
|
|
541
|
+
and have them automatically called during CRUD operations. This is useful for
|
|
542
|
+
adding custom business logic (validation, permissions, data enrichment) for
|
|
543
|
+
specific tables without modifying the generic DataServiceMixin.
|
|
544
|
+
|
|
545
|
+
How it works:
|
|
546
|
+
1. Create a Python package called 'rwx' in your Lambda handler
|
|
547
|
+
2. For each table that needs custom logic, create a module (e.g., rwx/admin_users.py)
|
|
548
|
+
3. Define hook functions in the module (before_read, after_write, before_delete, etc.)
|
|
549
|
+
4. Subclass RWXHookSystem and set hook_module_name = 'rwx'
|
|
550
|
+
5. Use it from your handler class
|
|
551
|
+
|
|
552
|
+
Example structure:
|
|
553
|
+
src/
|
|
554
|
+
index.py
|
|
555
|
+
rwx/
|
|
556
|
+
__init__.py
|
|
557
|
+
admin_users.py # hooks for admin_users table
|
|
558
|
+
donors.py # hooks for donors table
|
|
559
|
+
|
|
560
|
+
Example hook in rwx/admin_users.py:
|
|
561
|
+
def before_write(tx, table, sys_id, incoming, context):
|
|
562
|
+
# Validate incoming data
|
|
563
|
+
if not incoming.get('email_address'):
|
|
564
|
+
raise ValueError("Email is required")
|
|
565
|
+
# Modify data
|
|
566
|
+
incoming['description'] = f"{incoming['full_name']} ({incoming['email_address']})"
|
|
567
|
+
|
|
568
|
+
def after_read(tx, table, sys_id, row, context):
|
|
569
|
+
# Enrich response data
|
|
570
|
+
context.response().set_body({'extra_data': 'value'})
|
|
571
|
+
|
|
572
|
+
Hook signatures:
|
|
573
|
+
- on_new(tx, table, row, context) - When creating new empty object
|
|
574
|
+
- before_read(tx, table, sys_id, context) - Before reading an object
|
|
575
|
+
- after_read(tx, table, sys_id, row, context) - After reading an object
|
|
576
|
+
- before_find(tx, table, query, context) - Before finding by query
|
|
577
|
+
- after_find(tx, table, query, row, context) - After finding by query
|
|
578
|
+
- before_new(tx, table, sys_id, incoming, context) - Before creating new object
|
|
579
|
+
- after_new(tx, table, sys_id, row, context) - After creating new object
|
|
580
|
+
- before_write(tx, table, sys_id, incoming, context) - Before write (insert/update)
|
|
581
|
+
- after_write(tx, table, sys_id, row, context) - After write (insert/update)
|
|
582
|
+
- before_delete(tx, table, sys_id, context) - Before deleting an object
|
|
583
|
+
- after_delete(tx, table, sys_id, context) - After deleting an object
|
|
584
|
+
- before_query(tx, table, payload, context) - Before querying objects
|
|
585
|
+
- after_query(tx, table, data, payload, context) - After querying objects
|
|
586
|
+
|
|
587
|
+
Usage in a Lambda handler:
|
|
588
|
+
from velocity.aws.handlers.mixins import DataServiceMixin, RWXHookSystem
|
|
542
589
|
|
|
543
590
|
class MyRWXSystem(RWXHookSystem):
|
|
544
|
-
hook_module_name = 'rwx' #
|
|
591
|
+
hook_module_name = 'rwx' # Name of your rwx package
|
|
545
592
|
|
|
593
|
+
@engine.transaction
|
|
546
594
|
class HttpEventHandler(DataServiceMixin, LambdaHandler):
|
|
547
|
-
def
|
|
548
|
-
|
|
549
|
-
'read', tx, table_name, sys_id, row, context
|
|
550
|
-
)
|
|
595
|
+
def __init__(self, aws_event, aws_context):
|
|
596
|
+
super().__init__(aws_event, aws_context)
|
|
551
597
|
"""
|
|
552
598
|
|
|
553
599
|
hook_module_name = None # Override in subclass (e.g., 'rwx')
|
|
@@ -569,29 +615,6 @@ class RWXHookSystem:
|
|
|
569
615
|
if module and hasattr(module, hook_name):
|
|
570
616
|
return getattr(module, hook_name)(*args, **kwargs)
|
|
571
617
|
return None
|
|
572
|
-
|
|
573
|
-
@classmethod
|
|
574
|
-
def call_hooks(cls, operation, tx, table_name, *args, context=None):
|
|
575
|
-
"""
|
|
576
|
-
Call before/after hooks for an operation.
|
|
577
|
-
|
|
578
|
-
Args:
|
|
579
|
-
operation: 'read', 'write', 'delete', 'query'
|
|
580
|
-
tx: Database transaction
|
|
581
|
-
table_name: Name of the table
|
|
582
|
-
*args: Operation-specific arguments
|
|
583
|
-
context: Request context (keyword only)
|
|
584
|
-
"""
|
|
585
|
-
# Call before hook
|
|
586
|
-
cls._call_hook(f'before_{operation}', table_name, tx, table_name, *args, context)
|
|
587
|
-
|
|
588
|
-
# Note: after hooks should be called by the application after the operation
|
|
589
|
-
# This method is just for the before hook pattern
|
|
590
|
-
|
|
591
|
-
@classmethod
|
|
592
|
-
def call_after_hook(cls, operation, tx, table_name, *args, context=None):
|
|
593
|
-
"""Call after hook for an operation"""
|
|
594
|
-
cls._call_hook(f'after_{operation}', table_name, tx, table_name, *args, context)
|
|
595
618
|
|
|
596
619
|
|
|
597
620
|
def apply_sys_modified_by(incoming, context):
|