velocity-python 0.0.211__tar.gz → 0.0.213__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.211 → velocity_python-0.0.213}/PKG-INFO +1 -1
- {velocity_python-0.0.211 → velocity_python-0.0.213}/pyproject.toml +1 -1
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/tests/test_spreadsheet_functions.py +32 -2
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/base_handler.py +41 -4
- velocity_python-0.0.213/src/velocity/aws/tests/test_base_handler_error_response.py +62 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/export.py +117 -15
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity_python.egg-info/SOURCES.txt +1 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/LICENSE +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/README.md +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/setup.cfg +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/logging.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/tests/test_decorators.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/tests/test_table_alter.py +0 -0
- {velocity_python-0.0.211 → velocity_python-0.0.213}/tests/test_where_clause_validation.py +0 -0
|
@@ -2,6 +2,7 @@ import unittest
|
|
|
2
2
|
import base64
|
|
3
3
|
from io import BytesIO
|
|
4
4
|
from openpyxl import load_workbook
|
|
5
|
+
from openpyxl.cell.cell import MergedCell
|
|
5
6
|
from velocity.misc.export import (
|
|
6
7
|
extract,
|
|
7
8
|
autosize_columns,
|
|
@@ -73,9 +74,9 @@ class TestSpreadsheetFunctions(unittest.TestCase):
|
|
|
73
74
|
|
|
74
75
|
# Verify merged cells and styles
|
|
75
76
|
self.assertTrue(worksheet.merged_cells.ranges)
|
|
76
|
-
self.
|
|
77
|
+
self.assertIn("A1:B1", {str(cell_range) for cell_range in worksheet.merged_cells.ranges})
|
|
77
78
|
self.assertEqual(worksheet["A1"].style, "col_header")
|
|
78
|
-
self.
|
|
79
|
+
self.assertIsInstance(worksheet["B1"], MergedCell)
|
|
79
80
|
|
|
80
81
|
def test_create_spreadsheet_with_freeze_panes_and_dimensions(self):
|
|
81
82
|
"""Test creating a spreadsheet with freeze panes and custom row/column dimensions."""
|
|
@@ -97,6 +98,35 @@ class TestSpreadsheetFunctions(unittest.TestCase):
|
|
|
97
98
|
self.assertEqual(worksheet.column_dimensions["A"].width, 20)
|
|
98
99
|
self.assertEqual(worksheet.column_dimensions["B"].width, 30)
|
|
99
100
|
|
|
101
|
+
def test_create_spreadsheet_autosizes_boolean_values(self):
|
|
102
|
+
"""Test that autosizing accounts for displayed boolean values."""
|
|
103
|
+
buffer = BytesIO()
|
|
104
|
+
|
|
105
|
+
create_spreadsheet([""], [[False]], buffer)
|
|
106
|
+
buffer.seek(0)
|
|
107
|
+
workbook = load_workbook(buffer)
|
|
108
|
+
worksheet = workbook.active
|
|
109
|
+
|
|
110
|
+
self.assertFalse(worksheet["A2"].value)
|
|
111
|
+
self.assertAlmostEqual(worksheet.column_dimensions["A"].width, 8.4, places=1)
|
|
112
|
+
|
|
113
|
+
def test_create_spreadsheet_autosizes_formatted_numbers(self):
|
|
114
|
+
"""Test that autosizing uses formatted number display width."""
|
|
115
|
+
buffer = BytesIO()
|
|
116
|
+
|
|
117
|
+
create_spreadsheet(
|
|
118
|
+
["Amt"],
|
|
119
|
+
[[1234]],
|
|
120
|
+
buffer,
|
|
121
|
+
formats={"A2": "$#,##0.00"},
|
|
122
|
+
)
|
|
123
|
+
buffer.seek(0)
|
|
124
|
+
workbook = load_workbook(buffer)
|
|
125
|
+
worksheet = workbook.active
|
|
126
|
+
|
|
127
|
+
self.assertEqual(worksheet["A2"].number_format, "$#,##0.00")
|
|
128
|
+
self.assertAlmostEqual(worksheet.column_dimensions["A"].width, 13.2, places=1)
|
|
129
|
+
|
|
100
130
|
def test_get_downloadable_spreadsheet(self):
|
|
101
131
|
"""Test generating a downloadable spreadsheet encoded in base64."""
|
|
102
132
|
headers = ["Header1", "Header2"]
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/base_handler.py
RENAMED
|
@@ -288,12 +288,49 @@ class BaseHandler:
|
|
|
288
288
|
"onerror_exc": on_error_exc.__class__.__name__,
|
|
289
289
|
},
|
|
290
290
|
)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
291
|
+
else:
|
|
292
|
+
raise
|
|
293
|
+
|
|
294
|
+
if self._set_unhandled_error_response(local_context):
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
if not hasattr(self, "onError"):
|
|
298
|
+
# Non-HTTP contexts without a custom error hook should still fail fast.
|
|
295
299
|
raise exception
|
|
296
300
|
|
|
301
|
+
def _set_unhandled_error_response(self, local_context) -> bool:
|
|
302
|
+
response_getter = getattr(local_context, "response", None)
|
|
303
|
+
if not callable(response_getter):
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
response = response_getter()
|
|
307
|
+
if response is None:
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
reference_id = getattr(self.aws_context, "aws_request_id", None)
|
|
311
|
+
title = "Request Failed"
|
|
312
|
+
message = "Something went wrong while processing your request."
|
|
313
|
+
detail = "More information has been logged on the backend."
|
|
314
|
+
|
|
315
|
+
body = {
|
|
316
|
+
"message": message,
|
|
317
|
+
"detail": detail,
|
|
318
|
+
"logged": True,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
user_message = f"{message} {detail}"
|
|
322
|
+
console_message = detail
|
|
323
|
+
if reference_id:
|
|
324
|
+
body["reference_id"] = str(reference_id)
|
|
325
|
+
user_message = f"{user_message} Reference: {reference_id}."
|
|
326
|
+
console_message = f"{console_message} Reference: {reference_id}."
|
|
327
|
+
|
|
328
|
+
response.set_status(500)
|
|
329
|
+
response.set_body(body)
|
|
330
|
+
response.console(console_message, title=title)
|
|
331
|
+
response.alert(user_message, title=title)
|
|
332
|
+
return True
|
|
333
|
+
|
|
297
334
|
def _is_transient_db_disconnect(self, exc: Exception = None, *args, **kwargs) -> bool:
|
|
298
335
|
"""Return True if an exception looks like a transient DB disconnect.
|
|
299
336
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import unittest
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
from velocity.aws.handlers.base_handler import BaseHandler
|
|
6
|
+
from velocity.aws.handlers.response import Response
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DummyContext:
|
|
10
|
+
def __init__(self, response):
|
|
11
|
+
self._response = response
|
|
12
|
+
|
|
13
|
+
def response(self):
|
|
14
|
+
return self._response
|
|
15
|
+
|
|
16
|
+
def action(self):
|
|
17
|
+
return "button-click"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DummyHandler(BaseHandler):
|
|
21
|
+
def __init__(self, aws_context=None):
|
|
22
|
+
super().__init__({}, aws_context or SimpleNamespace(aws_request_id="req-123"))
|
|
23
|
+
self.on_error_calls = []
|
|
24
|
+
|
|
25
|
+
def onError(self, tx, context, exc, tb):
|
|
26
|
+
self.on_error_calls.append({"exc": exc, "tb": tb})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestBaseHandlerErrorResponse(unittest.TestCase):
|
|
30
|
+
def test_unhandled_http_error_sets_generic_response(self):
|
|
31
|
+
handler = DummyHandler()
|
|
32
|
+
response = Response()
|
|
33
|
+
context = DummyContext(response)
|
|
34
|
+
|
|
35
|
+
handler.handle_error(tx=None, local_context=context, exception=RuntimeError("secret details"))
|
|
36
|
+
|
|
37
|
+
self.assertEqual(response.status(), 500)
|
|
38
|
+
self.assertEqual(response.body["message"], "Something went wrong while processing your request.")
|
|
39
|
+
self.assertEqual(response.body["detail"], "More information has been logged on the backend.")
|
|
40
|
+
self.assertTrue(response.body["logged"])
|
|
41
|
+
self.assertEqual(response.body["reference_id"], "req-123")
|
|
42
|
+
|
|
43
|
+
rendered = response.render()
|
|
44
|
+
body = json.loads(rendered["body"])
|
|
45
|
+
self.assertEqual(rendered["statusCode"], 500)
|
|
46
|
+
self.assertEqual(len(body["actions"]), 2)
|
|
47
|
+
self.assertEqual(body["actions"][0]["action"], "console")
|
|
48
|
+
self.assertEqual(body["actions"][1]["action"], "alert")
|
|
49
|
+
self.assertIn("Reference: req-123.", body["actions"][1]["payload"]["message"])
|
|
50
|
+
self.assertNotIn("secret details", body["actions"][1]["payload"]["message"])
|
|
51
|
+
self.assertEqual(len(handler.on_error_calls), 1)
|
|
52
|
+
|
|
53
|
+
def test_unhandled_non_http_error_without_onerror_reraises(self):
|
|
54
|
+
handler = BaseHandler({}, SimpleNamespace(aws_request_id="req-456"))
|
|
55
|
+
context = DummyContext(None)
|
|
56
|
+
|
|
57
|
+
with self.assertRaises(RuntimeError):
|
|
58
|
+
handler.handle_error(tx=None, local_context=context, exception=RuntimeError("boom"))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
unittest.main()
|
|
@@ -1,24 +1,120 @@
|
|
|
1
|
-
from
|
|
1
|
+
from datetime import date, datetime, time, timedelta
|
|
2
|
+
from decimal import Decimal
|
|
2
3
|
from io import BytesIO
|
|
4
|
+
from typing import Dict, List
|
|
3
5
|
import base64
|
|
6
|
+
import re
|
|
7
|
+
|
|
4
8
|
import openpyxl
|
|
5
|
-
from openpyxl.styles import
|
|
9
|
+
from openpyxl.styles import Alignment, Border, Font, NamedStyle, Side
|
|
6
10
|
from openpyxl.utils import get_column_letter
|
|
7
11
|
|
|
8
12
|
|
|
13
|
+
NUMBER_FORMAT_RE = re.compile(r"[#0?][#0?,]*(?:\.[#0?]+)?")
|
|
14
|
+
|
|
15
|
+
|
|
9
16
|
def extract(d: dict, keys: List[str]) -> List:
|
|
10
17
|
"""Extract values from a dictionary based on a list of keys."""
|
|
11
18
|
return [d.get(key) for key in keys]
|
|
12
19
|
|
|
13
20
|
|
|
14
|
-
def
|
|
21
|
+
def _stringify_cell_value(value) -> str:
|
|
22
|
+
if value is None:
|
|
23
|
+
return ""
|
|
24
|
+
if isinstance(value, bool):
|
|
25
|
+
return "TRUE" if value else "FALSE"
|
|
26
|
+
return str(value)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _select_number_format_section(format_code: str, value) -> str:
|
|
30
|
+
sections = format_code.split(";")
|
|
31
|
+
if not sections:
|
|
32
|
+
return format_code
|
|
33
|
+
if value < 0 and len(sections) > 1:
|
|
34
|
+
return sections[1]
|
|
35
|
+
if value == 0 and len(sections) > 2:
|
|
36
|
+
return sections[2]
|
|
37
|
+
return sections[0]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _clean_number_format(format_code: str) -> str:
|
|
41
|
+
cleaned = []
|
|
42
|
+
in_quotes = False
|
|
43
|
+
idx = 0
|
|
44
|
+
while idx < len(format_code):
|
|
45
|
+
char = format_code[idx]
|
|
46
|
+
if char == '"':
|
|
47
|
+
in_quotes = not in_quotes
|
|
48
|
+
elif in_quotes:
|
|
49
|
+
cleaned.append(char)
|
|
50
|
+
elif char == "\\":
|
|
51
|
+
idx += 1
|
|
52
|
+
if idx < len(format_code):
|
|
53
|
+
cleaned.append(format_code[idx])
|
|
54
|
+
elif char in {"_", "*"}:
|
|
55
|
+
idx += 1
|
|
56
|
+
elif char == "[":
|
|
57
|
+
end = format_code.find("]", idx + 1)
|
|
58
|
+
if end == -1:
|
|
59
|
+
break
|
|
60
|
+
idx = end
|
|
61
|
+
else:
|
|
62
|
+
cleaned.append(char)
|
|
63
|
+
idx += 1
|
|
64
|
+
return "".join(cleaned)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _format_numeric_value(value, format_code: str) -> str:
|
|
68
|
+
section = _clean_number_format(_select_number_format_section(format_code, value))
|
|
69
|
+
if not section or section.lower() == "general":
|
|
70
|
+
return str(value)
|
|
71
|
+
|
|
72
|
+
match = NUMBER_FORMAT_RE.search(section)
|
|
73
|
+
if not match:
|
|
74
|
+
return str(value)
|
|
75
|
+
|
|
76
|
+
pattern = match.group(0)
|
|
77
|
+
prefix = section[: match.start()]
|
|
78
|
+
suffix = section[match.end() :]
|
|
79
|
+
percent_multiplier = section.count("%")
|
|
80
|
+
scaled_value = abs(value) * (100**percent_multiplier)
|
|
81
|
+
decimal_places = len(pattern.split(".", 1)[1]) if "." in pattern else 0
|
|
82
|
+
use_grouping = "," in pattern.split(".", 1)[0]
|
|
83
|
+
|
|
84
|
+
if use_grouping:
|
|
85
|
+
number = f"{scaled_value:,.{decimal_places}f}"
|
|
86
|
+
else:
|
|
87
|
+
number = f"{scaled_value:.{decimal_places}f}"
|
|
88
|
+
|
|
89
|
+
if value < 0 and len(format_code.split(";")) == 1 and "-" not in prefix and "(" not in prefix:
|
|
90
|
+
prefix = f"-{prefix}"
|
|
91
|
+
|
|
92
|
+
return f"{prefix}{number}{suffix}"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _display_value(cell) -> str:
|
|
96
|
+
value = cell.value
|
|
97
|
+
if value is None:
|
|
98
|
+
return ""
|
|
99
|
+
if isinstance(value, bool):
|
|
100
|
+
return "TRUE" if value else "FALSE"
|
|
101
|
+
if isinstance(value, (datetime, date, time, timedelta)):
|
|
102
|
+
return str(value)
|
|
103
|
+
if isinstance(value, (int, float, Decimal)):
|
|
104
|
+
return _format_numeric_value(value, cell.number_format)
|
|
105
|
+
return str(value)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def autosize_columns(ws, fixed: Dict[str, float] | None = None):
|
|
15
109
|
"""Autosize columns in the worksheet based on content length."""
|
|
110
|
+
fixed = fixed or {}
|
|
16
111
|
for col in ws.columns:
|
|
17
112
|
max_length = 0
|
|
18
113
|
for cell in col:
|
|
19
114
|
try:
|
|
20
|
-
|
|
21
|
-
|
|
115
|
+
display_value = _display_value(cell)
|
|
116
|
+
if len(display_value) > max_length:
|
|
117
|
+
max_length = len(display_value)
|
|
22
118
|
except Exception:
|
|
23
119
|
continue
|
|
24
120
|
adjusted_width = (max_length + 2) * 1.2
|
|
@@ -30,15 +126,20 @@ def create_spreadsheet(
|
|
|
30
126
|
headers: List[str],
|
|
31
127
|
rows: List[List],
|
|
32
128
|
fileorbuffer,
|
|
33
|
-
styles: Dict[str, str] =
|
|
34
|
-
merge: List[str] =
|
|
35
|
-
formats: Dict[str, str] =
|
|
36
|
-
named_styles: List[NamedStyle] =
|
|
129
|
+
styles: Dict[str, str] | None = None,
|
|
130
|
+
merge: List[str] | None = None,
|
|
131
|
+
formats: Dict[str, str] | None = None,
|
|
132
|
+
named_styles: List[NamedStyle] | None = None,
|
|
37
133
|
freeze_panes: str = "A2",
|
|
38
134
|
dimensions: dict = None,
|
|
39
135
|
auto_size: bool = True,
|
|
40
136
|
):
|
|
41
137
|
"""Create an Excel spreadsheet with specified headers, rows, and styles."""
|
|
138
|
+
styles = styles or {}
|
|
139
|
+
merge = merge or []
|
|
140
|
+
formats = formats or {}
|
|
141
|
+
named_styles = named_styles or []
|
|
142
|
+
|
|
42
143
|
wb = openpyxl.Workbook()
|
|
43
144
|
ws = wb.active
|
|
44
145
|
|
|
@@ -102,6 +203,9 @@ def create_spreadsheet(
|
|
|
102
203
|
# Set freeze panes
|
|
103
204
|
ws.freeze_panes = freeze_panes
|
|
104
205
|
|
|
206
|
+
for cell, format_code in formats.items():
|
|
207
|
+
ws[cell].number_format = format_code
|
|
208
|
+
|
|
105
209
|
# Auto-size columns if enabled
|
|
106
210
|
if auto_size:
|
|
107
211
|
autosize_columns(ws, fixed={})
|
|
@@ -119,8 +223,6 @@ def create_spreadsheet(
|
|
|
119
223
|
ws[cell].style = local_styles[style_name]
|
|
120
224
|
for cell_range in merge:
|
|
121
225
|
ws.merge_cells(cell_range)
|
|
122
|
-
for cell, format_code in formats.items():
|
|
123
|
-
ws[cell].number_format = format_code
|
|
124
226
|
|
|
125
227
|
# Save workbook to the provided file or buffer
|
|
126
228
|
wb.save(fileorbuffer)
|
|
@@ -129,10 +231,10 @@ def create_spreadsheet(
|
|
|
129
231
|
def get_downloadable_spreadsheet(
|
|
130
232
|
headers: List[str],
|
|
131
233
|
rows: List[List],
|
|
132
|
-
styles: Dict[str, str] =
|
|
133
|
-
merge: List[str] =
|
|
134
|
-
formats: Dict[str, str] =
|
|
135
|
-
named_styles: List[NamedStyle] =
|
|
234
|
+
styles: Dict[str, str] | None = None,
|
|
235
|
+
merge: List[str] | None = None,
|
|
236
|
+
formats: Dict[str, str] | None = None,
|
|
237
|
+
named_styles: List[NamedStyle] | None = None,
|
|
136
238
|
freeze_panes: str = "A2",
|
|
137
239
|
dimensions: dict = None,
|
|
138
240
|
auto_size: bool = True,
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity_python.egg-info/SOURCES.txt
RENAMED
|
@@ -27,6 +27,7 @@ src/velocity/aws/handlers/mixins/__init__.py
|
|
|
27
27
|
src/velocity/aws/handlers/mixins/data_service.py
|
|
28
28
|
src/velocity/aws/handlers/mixins/web_handler.py
|
|
29
29
|
src/velocity/aws/tests/__init__.py
|
|
30
|
+
src/velocity/aws/tests/test_base_handler_error_response.py
|
|
30
31
|
src/velocity/aws/tests/test_lambda_handler_json_serialization.py
|
|
31
32
|
src/velocity/aws/tests/test_response.py
|
|
32
33
|
src/velocity/db/__init__.py
|
|
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.211 → velocity_python-0.0.213}/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.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/context_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/mixins/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/aws/handlers/mixins/web_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/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
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/base/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/base/initializer.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/base/operators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/mysql/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/mysql/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/mysql/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/postgres/types.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlite/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlite/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlite/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/sql.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/servers/sqlserver/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/common.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_column.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_database.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_imports.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_result.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/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.211 → velocity_python-0.0.213}/src/velocity/db/tests/postgres/test_sequence.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/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.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_postgres_unchanged.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_result_caching.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/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.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_sql_builder.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity/db/tests/test_tablehelper.py
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/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
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/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.211 → velocity_python-0.0.213}/src/velocity/payment/braintree_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/src/velocity_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{velocity_python-0.0.211 → velocity_python-0.0.213}/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.211 → velocity_python-0.0.213}/tests/test_sys_modified_count_postgres_demo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|