velocity-python 0.0.205__tar.gz → 0.0.207__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.205 → velocity_python-0.0.207}/PKG-INFO +1 -1
- {velocity_python-0.0.205 → velocity_python-0.0.207}/pyproject.toml +1 -1
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/mixins/data_service.py +137 -3
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/decorators.py +14 -1
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/base/sql.py +3 -23
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/mysql/sql.py +39 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/sql.py +31 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlite/sql.py +18 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/sql.py +34 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.205 → velocity_python-0.0.207}/LICENSE +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/README.md +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/setup.cfg +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/logging.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity_python.egg-info/SOURCES.txt +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_decorators.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_table_alter.py +0 -0
- {velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_where_clause_validation.py +0 -0
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
@@ -9,6 +9,7 @@ import base64
|
|
|
9
9
|
import datetime
|
|
10
10
|
import importlib
|
|
11
11
|
import logging
|
|
12
|
+
import re
|
|
12
13
|
from io import BytesIO
|
|
13
14
|
|
|
14
15
|
from velocity.misc import export
|
|
@@ -152,8 +153,116 @@ class DataServiceMixin:
|
|
|
152
153
|
self._call_rwx_hook("before_query", "common", tx, table, payload, context)
|
|
153
154
|
self._call_rwx_hook("before_query", table, tx, table, payload, context)
|
|
154
155
|
params = payload.get("params", {})
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
result_format = payload.get("result_format")
|
|
157
|
+
|
|
158
|
+
# Clear any previous swallowed-error details so we only report errors
|
|
159
|
+
# related to this query.
|
|
160
|
+
if hasattr(tx, "_last_return_default_error"):
|
|
161
|
+
tx._last_return_default_error = None
|
|
162
|
+
|
|
163
|
+
obj = payload["obj"]
|
|
164
|
+
|
|
165
|
+
def _normalize_identifier(identifier: str):
|
|
166
|
+
if identifier is None:
|
|
167
|
+
return None
|
|
168
|
+
identifier = str(identifier).strip()
|
|
169
|
+
if not identifier:
|
|
170
|
+
return None
|
|
171
|
+
if identifier[0] == identifier[-1] and identifier[0] in {'"', "'"}:
|
|
172
|
+
identifier = identifier[1:-1]
|
|
173
|
+
return identifier
|
|
174
|
+
|
|
175
|
+
def _extract_requested_column_names(columns_spec):
|
|
176
|
+
if not columns_spec:
|
|
177
|
+
return []
|
|
178
|
+
if isinstance(columns_spec, str):
|
|
179
|
+
columns_spec = [columns_spec]
|
|
180
|
+
if not isinstance(columns_spec, (list, tuple)):
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
requested = []
|
|
184
|
+
simple_identifier = re.compile(
|
|
185
|
+
r"^([A-Za-z_][A-Za-z0-9_]*)(\\.([A-Za-z_][A-Za-z0-9_]*))?$"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
for col in columns_spec:
|
|
189
|
+
if not isinstance(col, str):
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
raw = col.strip()
|
|
193
|
+
if not raw or raw == "*":
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
# If this looks like an expression (functions, casts, aliases, etc.),
|
|
197
|
+
# skip strict validation and let the DB error (which we will surface).
|
|
198
|
+
lowered = raw.lower()
|
|
199
|
+
if (
|
|
200
|
+
"(" in raw
|
|
201
|
+
or ")" in raw
|
|
202
|
+
or " over " in lowered
|
|
203
|
+
or "::" in raw
|
|
204
|
+
or " as " in lowered
|
|
205
|
+
or "+" in raw
|
|
206
|
+
or "-" in raw
|
|
207
|
+
or "/" in raw
|
|
208
|
+
or "*" in raw
|
|
209
|
+
or "||" in raw
|
|
210
|
+
):
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# Allow simple identifiers, optionally qualified (table.column).
|
|
214
|
+
match = simple_identifier.match(raw)
|
|
215
|
+
if not match:
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
colname = match.group(3) or match.group(1)
|
|
219
|
+
colname = _normalize_identifier(colname)
|
|
220
|
+
if colname:
|
|
221
|
+
requested.append(colname)
|
|
222
|
+
|
|
223
|
+
return requested
|
|
224
|
+
|
|
225
|
+
# Optional: detect missing columns before running the query.
|
|
226
|
+
requested_cols = _extract_requested_column_names(params.get("columns"))
|
|
227
|
+
if requested_cols:
|
|
228
|
+
available_cols = {c.lower() for c in tx.table(obj).sys_columns()}
|
|
229
|
+
missing = sorted(
|
|
230
|
+
{c for c in requested_cols if c.lower() not in available_cols},
|
|
231
|
+
key=lambda v: v.lower(),
|
|
232
|
+
)
|
|
233
|
+
if missing:
|
|
234
|
+
message = (
|
|
235
|
+
f"Query failed for '{obj}': requested columns not present in the database: "
|
|
236
|
+
+ ", ".join(missing)
|
|
237
|
+
)
|
|
238
|
+
context.response().toast(message, "error")
|
|
239
|
+
error_payload = {
|
|
240
|
+
"type": "missing-columns",
|
|
241
|
+
"message": message,
|
|
242
|
+
"missing": missing,
|
|
243
|
+
}
|
|
244
|
+
if result_format == "excel":
|
|
245
|
+
return {
|
|
246
|
+
"headers": payload.get("headers", []),
|
|
247
|
+
"rows": [],
|
|
248
|
+
"error": error_payload,
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
"rows": [],
|
|
252
|
+
"config": {
|
|
253
|
+
"lastFetch": datetime.datetime.now(),
|
|
254
|
+
"query": None,
|
|
255
|
+
"format": result_format,
|
|
256
|
+
"error": error_payload,
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
result = tx.table(obj).select(**params)
|
|
261
|
+
|
|
262
|
+
swallowed_error = getattr(tx, "_last_return_default_error", None)
|
|
263
|
+
if swallowed_error:
|
|
264
|
+
tx._last_return_default_error = None
|
|
265
|
+
if result_format == "excel":
|
|
157
266
|
data = {
|
|
158
267
|
"headers": payload.get(
|
|
159
268
|
"headers", [x.replace("_", " ").title() for x in result.headers]
|
|
@@ -166,9 +275,34 @@ class DataServiceMixin:
|
|
|
166
275
|
"config": {
|
|
167
276
|
"lastFetch": datetime.datetime.now(),
|
|
168
277
|
"query": result.sql,
|
|
169
|
-
"format":
|
|
278
|
+
"format": result_format,
|
|
170
279
|
},
|
|
171
280
|
}
|
|
281
|
+
|
|
282
|
+
# If the DB call failed but was swallowed (return_default), surface a reason.
|
|
283
|
+
# Common symptoms are empty rows + null SQL.
|
|
284
|
+
if swallowed_error and result_format == "excel":
|
|
285
|
+
error_message = swallowed_error.get("message") or "Unknown database error"
|
|
286
|
+
context.response().toast(
|
|
287
|
+
f"Query failed for '{obj}': {error_message.splitlines()[0]}",
|
|
288
|
+
"error",
|
|
289
|
+
)
|
|
290
|
+
data["error"] = {"type": "db-error", **swallowed_error}
|
|
291
|
+
elif swallowed_error and isinstance(data, dict) and data.get("config") is not None:
|
|
292
|
+
error_message = swallowed_error.get("message") or "Unknown database error"
|
|
293
|
+
context.response().toast(
|
|
294
|
+
f"Query failed for '{obj}': {error_message.splitlines()[0]}",
|
|
295
|
+
"error",
|
|
296
|
+
)
|
|
297
|
+
data["config"].update(
|
|
298
|
+
{
|
|
299
|
+
"query": data["config"].get("query") or None,
|
|
300
|
+
"error": {
|
|
301
|
+
"type": "db-error",
|
|
302
|
+
**swallowed_error,
|
|
303
|
+
},
|
|
304
|
+
}
|
|
305
|
+
)
|
|
172
306
|
if payload.get("count"):
|
|
173
307
|
data["count"] = tx.table(payload["obj"]).count(
|
|
174
308
|
where=params.get("where", None)
|
|
@@ -105,8 +105,21 @@ def return_default(
|
|
|
105
105
|
result = func(self, *args, **kwds)
|
|
106
106
|
if result is None:
|
|
107
107
|
result = default
|
|
108
|
-
except func.exceptions:
|
|
108
|
+
except func.exceptions as e:
|
|
109
109
|
self.tx.rollback_savepoint(sp, cursor=self.cursor())
|
|
110
|
+
|
|
111
|
+
# Capture swallowed exceptions for upstream diagnostics.
|
|
112
|
+
# This decorator intentionally returns a default value instead of
|
|
113
|
+
# raising, but consumers (e.g. API handlers) may still want to
|
|
114
|
+
# surface a reason to the caller.
|
|
115
|
+
try:
|
|
116
|
+
self.tx._last_return_default_error = {
|
|
117
|
+
"type": e.__class__.__name__,
|
|
118
|
+
"message": str(e),
|
|
119
|
+
"function": f"{func.__module__}.{getattr(func, '__qualname__', func.__name__)}",
|
|
120
|
+
}
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
110
123
|
return default
|
|
111
124
|
self.tx.release_savepoint(sp, cursor=self.cursor())
|
|
112
125
|
return result
|
|
@@ -59,35 +59,15 @@ class BaseSQLDialect(ABC):
|
|
|
59
59
|
Dialects should override/extend this if they can do better than message matching.
|
|
60
60
|
Engine uses this only as a fallback when no/unknown error code is available.
|
|
61
61
|
"""
|
|
62
|
-
|
|
63
|
-
return False
|
|
64
|
-
m = str(msg).strip().lower()
|
|
65
|
-
needles = (
|
|
66
|
-
"server closed the connection unexpectedly",
|
|
67
|
-
"no connection to the server",
|
|
68
|
-
"connection timed out",
|
|
69
|
-
"could not connect to server",
|
|
70
|
-
"cannot connect to server",
|
|
71
|
-
"connection already closed",
|
|
72
|
-
"cursor already closed",
|
|
73
|
-
"ssl syscall error",
|
|
74
|
-
"eof detected",
|
|
75
|
-
"connection reset by peer",
|
|
76
|
-
"broken pipe",
|
|
77
|
-
"terminating connection due to administrator command",
|
|
78
|
-
"could not receive data from server",
|
|
79
|
-
"could not send data to server",
|
|
80
|
-
)
|
|
81
|
-
return any(n in m for n in needles)
|
|
62
|
+
return False
|
|
82
63
|
|
|
83
64
|
@classmethod
|
|
84
65
|
def is_transient_connection_error_message(cls, msg: str) -> bool:
|
|
85
66
|
"""Return True if a connection error looks transient/retryable.
|
|
86
67
|
|
|
87
|
-
Default implementation
|
|
88
|
-
Dialects may override to be stricter.
|
|
68
|
+
Default implementation is disabled; dialects must implement this.
|
|
89
69
|
"""
|
|
90
|
-
return
|
|
70
|
+
return False
|
|
91
71
|
|
|
92
72
|
# Core CRUD Operations
|
|
93
73
|
@classmethod
|
|
@@ -72,6 +72,45 @@ class SQL(BaseSQLDialect):
|
|
|
72
72
|
error_msg = getattr(e, "msg", None)
|
|
73
73
|
return error_code, error_msg
|
|
74
74
|
|
|
75
|
+
@classmethod
|
|
76
|
+
def is_connection_error_message(cls, msg: str) -> bool:
|
|
77
|
+
if not msg:
|
|
78
|
+
return False
|
|
79
|
+
m = str(msg).strip().lower()
|
|
80
|
+
|
|
81
|
+
# Common MySQL connector / server disconnects.
|
|
82
|
+
needles = (
|
|
83
|
+
"mysql server has gone away",
|
|
84
|
+
"lost connection to mysql server",
|
|
85
|
+
"can't connect to mysql server",
|
|
86
|
+
"connection refused",
|
|
87
|
+
"connection reset by peer",
|
|
88
|
+
"broken pipe",
|
|
89
|
+
"connection timed out",
|
|
90
|
+
"read timed out",
|
|
91
|
+
"write timed out",
|
|
92
|
+
"server shutdown",
|
|
93
|
+
"too many connections",
|
|
94
|
+
"is dead or not enabled",
|
|
95
|
+
)
|
|
96
|
+
return any(n in m for n in needles)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def is_transient_connection_error_message(cls, msg: str) -> bool:
|
|
100
|
+
if not cls.is_connection_error_message(msg):
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
# Do not treat auth/config problems as transient.
|
|
104
|
+
m = str(msg).strip().lower()
|
|
105
|
+
non_transient = (
|
|
106
|
+
"access denied",
|
|
107
|
+
"authentication",
|
|
108
|
+
"unknown database",
|
|
109
|
+
"unknown mysql server host",
|
|
110
|
+
"bad handshake",
|
|
111
|
+
)
|
|
112
|
+
return not any(n in m for n in non_transient)
|
|
113
|
+
|
|
75
114
|
@classmethod
|
|
76
115
|
def select(
|
|
77
116
|
cls,
|
|
@@ -77,6 +77,37 @@ class SQL(BaseSQLDialect):
|
|
|
77
77
|
error_mesg = getattr(e, "pgerror", None)
|
|
78
78
|
return error_code, error_mesg
|
|
79
79
|
|
|
80
|
+
@classmethod
|
|
81
|
+
def is_connection_error_message(cls, msg: str) -> bool:
|
|
82
|
+
if not msg:
|
|
83
|
+
return False
|
|
84
|
+
m = str(msg).strip().lower()
|
|
85
|
+
needles = (
|
|
86
|
+
"server closed the connection unexpectedly",
|
|
87
|
+
"no connection to the server",
|
|
88
|
+
"connection timed out",
|
|
89
|
+
"could not connect to server",
|
|
90
|
+
"cannot connect to server",
|
|
91
|
+
"could not translate host name",
|
|
92
|
+
"connection already closed",
|
|
93
|
+
"cursor already closed",
|
|
94
|
+
"ssl syscall error",
|
|
95
|
+
"eof detected",
|
|
96
|
+
"connection reset by peer",
|
|
97
|
+
"broken pipe",
|
|
98
|
+
"terminating connection due to administrator command",
|
|
99
|
+
"could not receive data from server",
|
|
100
|
+
"could not send data to server",
|
|
101
|
+
"the database system is starting up",
|
|
102
|
+
"the database system is shutting down",
|
|
103
|
+
)
|
|
104
|
+
return any(n in m for n in needles)
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def is_transient_connection_error_message(cls, msg: str) -> bool:
|
|
108
|
+
# For Postgres, low-level disconnects/restarts are typically transient.
|
|
109
|
+
return cls.is_connection_error_message(msg)
|
|
110
|
+
|
|
80
111
|
@staticmethod
|
|
81
112
|
def _validate_where_string(where):
|
|
82
113
|
"""
|
|
@@ -72,6 +72,24 @@ class SQL(BaseSQLDialect):
|
|
|
72
72
|
# SQLite exceptions don't have error codes like other databases
|
|
73
73
|
return None, str(e)
|
|
74
74
|
|
|
75
|
+
@classmethod
|
|
76
|
+
def is_connection_error_message(cls, msg: str) -> bool:
|
|
77
|
+
if not msg:
|
|
78
|
+
return False
|
|
79
|
+
m = str(msg).strip().lower()
|
|
80
|
+
needles = (
|
|
81
|
+
"unable to open database file",
|
|
82
|
+
"disk i/o error",
|
|
83
|
+
"readonly database",
|
|
84
|
+
"file is not a database",
|
|
85
|
+
)
|
|
86
|
+
return any(n in m for n in needles)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def is_transient_connection_error_message(cls, msg: str) -> bool:
|
|
90
|
+
# SQLite connection/file errors are typically not transient in-process.
|
|
91
|
+
return False
|
|
92
|
+
|
|
75
93
|
@classmethod
|
|
76
94
|
def select(
|
|
77
95
|
cls,
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/sql.py
RENAMED
|
@@ -74,6 +74,40 @@ class SQL(BaseSQLDialect):
|
|
|
74
74
|
error_message = getattr(e, "message", None) or str(e)
|
|
75
75
|
return error_number, error_message
|
|
76
76
|
|
|
77
|
+
@classmethod
|
|
78
|
+
def is_connection_error_message(cls, msg: str) -> bool:
|
|
79
|
+
if not msg:
|
|
80
|
+
return False
|
|
81
|
+
m = str(msg).strip().lower()
|
|
82
|
+
|
|
83
|
+
# pyodbc/pymssql/pytds typically surface these in message text.
|
|
84
|
+
needles = (
|
|
85
|
+
"communication link failure",
|
|
86
|
+
"transport-level error",
|
|
87
|
+
"tcp provider",
|
|
88
|
+
"a connection was successfully established with the server, but then an error occurred during the login process",
|
|
89
|
+
"connection is broken",
|
|
90
|
+
"connection was forcibly closed",
|
|
91
|
+
"connection reset",
|
|
92
|
+
"login timeout expired",
|
|
93
|
+
"server is not found or not accessible",
|
|
94
|
+
"could not open a connection",
|
|
95
|
+
"network-related",
|
|
96
|
+
"connection timed out",
|
|
97
|
+
"broken pipe",
|
|
98
|
+
)
|
|
99
|
+
return any(n in m for n in needles)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def is_transient_connection_error_message(cls, msg: str) -> bool:
|
|
103
|
+
if not cls.is_connection_error_message(msg):
|
|
104
|
+
return False
|
|
105
|
+
m = str(msg).strip().lower()
|
|
106
|
+
# "login failed" is usually credentials/permissions, not transient.
|
|
107
|
+
if "login failed" in m:
|
|
108
|
+
return False
|
|
109
|
+
return True
|
|
110
|
+
|
|
77
111
|
@classmethod
|
|
78
112
|
def select(
|
|
79
113
|
cls,
|
|
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.205 → velocity_python-0.0.207}/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.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/base_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/context_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/mixins/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/aws/handlers/mixins/web_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/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.205 → velocity_python-0.0.207}/src/velocity/db/servers/base/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/base/initializer.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/base/operators.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/mysql/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/mysql/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/mysql/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/postgres/types.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlite/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlite/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlite/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/servers/sqlserver/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/common.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_column.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_database.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_imports.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_result.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/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.205 → velocity_python-0.0.207}/src/velocity/db/tests/postgres/test_sequence.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/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.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_postgres_unchanged.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_result_caching.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/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.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_sql_builder.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_tablehelper.py
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity/db/tests/test_view_helper.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
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/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.205 → velocity_python-0.0.207}/src/velocity/payment/braintree_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/src/velocity_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.205 → velocity_python-0.0.207}/tests/test_sys_modified_count_postgres_demo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|