velocity-python 0.0.203__tar.gz → 0.0.205__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.203 → velocity_python-0.0.205}/PKG-INFO +1 -1
- {velocity_python-0.0.203 → velocity_python-0.0.205}/pyproject.toml +1 -1
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/base_handler.py +50 -6
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/engine.py +28 -13
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/transaction.py +15 -0
- velocity_python-0.0.205/src/velocity/db/core/view.py +158 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/base/sql.py +37 -0
- velocity_python-0.0.205/src/velocity/db/tests/test_view_helper.py +97 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/LICENSE +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/README.md +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/setup.cfg +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/logging.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/tests/test_decorators.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/tests/test_table_alter.py +0 -0
- {velocity_python-0.0.203 → velocity_python-0.0.205}/tests/test_where_clause_validation.py +0 -0
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/base_handler.py
RENAMED
|
@@ -221,17 +221,61 @@ class BaseHandler:
|
|
|
221
221
|
local_context: The context object
|
|
222
222
|
exception: The exception that occurred
|
|
223
223
|
"""
|
|
224
|
+
# Always log the original exception so it isn't lost if the error-path
|
|
225
|
+
# (e.g. job-status updates) fails due to a transient DB disconnect.
|
|
226
|
+
logger.exception(
|
|
227
|
+
"Unhandled exception during action execution",
|
|
228
|
+
extra={
|
|
229
|
+
"handler": self.__class__.__name__,
|
|
230
|
+
"action": getattr(local_context, "action", lambda: None)(),
|
|
231
|
+
"tx_present": tx is not None,
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
|
|
224
235
|
if hasattr(self, "onError"):
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
236
|
+
try:
|
|
237
|
+
self.onError(
|
|
238
|
+
tx,
|
|
239
|
+
local_context,
|
|
240
|
+
exc=exception.__class__.__name__,
|
|
241
|
+
tb=traceback.format_exc(),
|
|
242
|
+
)
|
|
243
|
+
except Exception as on_error_exc:
|
|
244
|
+
if self._is_transient_db_disconnect(on_error_exc):
|
|
245
|
+
logger.warning(
|
|
246
|
+
"onError failed due to transient DB disconnect; continuing",
|
|
247
|
+
exc_info=True,
|
|
248
|
+
extra={
|
|
249
|
+
"handler": self.__class__.__name__,
|
|
250
|
+
"action": getattr(local_context, "action", lambda: None)(),
|
|
251
|
+
"original_exc": exception.__class__.__name__,
|
|
252
|
+
"onerror_exc": on_error_exc.__class__.__name__,
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
return
|
|
256
|
+
raise
|
|
231
257
|
else:
|
|
232
258
|
# Re-raise if no error handler is defined
|
|
233
259
|
raise exception
|
|
234
260
|
|
|
261
|
+
@staticmethod
|
|
262
|
+
def _is_transient_db_disconnect(exc: Exception) -> bool:
|
|
263
|
+
"""Return True if an exception looks like a transient DB disconnect.
|
|
264
|
+
|
|
265
|
+
This is intentionally message-based so it works even if the originating
|
|
266
|
+
exception type comes from psycopg2 / SQLAlchemy / wrapped Velocity errors.
|
|
267
|
+
"""
|
|
268
|
+
try:
|
|
269
|
+
from velocity.db import exceptions as db_exceptions
|
|
270
|
+
|
|
271
|
+
if isinstance(exc, db_exceptions.DbConnectionError):
|
|
272
|
+
return True
|
|
273
|
+
except Exception:
|
|
274
|
+
# Avoid import-time coupling; fall back to message matching.
|
|
275
|
+
pass
|
|
276
|
+
|
|
277
|
+
return False
|
|
278
|
+
|
|
235
279
|
def log(
|
|
236
280
|
self, tx, message: str, function: Optional[str] = None, level: str = "info"
|
|
237
281
|
):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import re
|
|
3
|
+
import time
|
|
3
4
|
from contextlib import contextmanager
|
|
4
5
|
from functools import wraps
|
|
5
6
|
from velocity.db import exceptions
|
|
@@ -175,6 +176,7 @@ class Engine:
|
|
|
175
176
|
else:
|
|
176
177
|
retry_count = 0
|
|
177
178
|
lock_timeout_count = 0
|
|
179
|
+
connection_retry_count = 0
|
|
178
180
|
while True:
|
|
179
181
|
try:
|
|
180
182
|
return function(*args, **kwds)
|
|
@@ -182,13 +184,37 @@ class Engine:
|
|
|
182
184
|
retry_count += 1
|
|
183
185
|
if retry_count > self.MAX_RETRIES:
|
|
184
186
|
raise
|
|
187
|
+
# Back off a bit to reduce contention on hot DDL.
|
|
188
|
+
time.sleep(min(2.0, 0.05 * (2**min(retry_count, 6))))
|
|
185
189
|
_tx.rollback()
|
|
186
190
|
except exceptions.DbLockTimeoutError:
|
|
187
191
|
lock_timeout_count += 1
|
|
188
192
|
if lock_timeout_count > self.MAX_RETRIES:
|
|
189
193
|
raise
|
|
194
|
+
time.sleep(min(2.0, 0.05 * (2**min(lock_timeout_count, 6))))
|
|
190
195
|
_tx.rollback()
|
|
191
196
|
continue
|
|
197
|
+
except exceptions.DbConnectionError as e:
|
|
198
|
+
# Transient disconnects can happen during maintenance / restarts.
|
|
199
|
+
# Retrying the entire top-level function is the safest option.
|
|
200
|
+
msg = str(e).strip().lower()
|
|
201
|
+
if not getattr(
|
|
202
|
+
self.sql, "is_transient_connection_error_message", lambda _m: False
|
|
203
|
+
)(msg):
|
|
204
|
+
raise
|
|
205
|
+
|
|
206
|
+
connection_retry_count += 1
|
|
207
|
+
if connection_retry_count > 6:
|
|
208
|
+
raise
|
|
209
|
+
|
|
210
|
+
# Force a reconnect on the next attempt.
|
|
211
|
+
try:
|
|
212
|
+
_tx.close()
|
|
213
|
+
except Exception:
|
|
214
|
+
_tx.connection = None
|
|
215
|
+
|
|
216
|
+
time.sleep(min(2.0, 0.1 * (2**min(connection_retry_count, 5))))
|
|
217
|
+
continue
|
|
192
218
|
except Exception:
|
|
193
219
|
raise
|
|
194
220
|
finally:
|
|
@@ -440,19 +466,8 @@ class Engine:
|
|
|
440
466
|
raise exceptions.DbDatabaseMissingError(enhanced_message) from exception
|
|
441
467
|
if re.findall(r"already exists", msg, re.M):
|
|
442
468
|
raise exceptions.DbObjectExistsError(enhanced_message) from exception
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if re.findall(r"no connection to the server", msg, re.M):
|
|
446
|
-
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
447
|
-
if re.findall(r"connection timed out", msg, re.M):
|
|
448
|
-
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
449
|
-
if re.findall(r"could not connect to server", msg, re.M):
|
|
450
|
-
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
451
|
-
if re.findall(r"cannot connect to server", msg, re.M):
|
|
452
|
-
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
453
|
-
if re.findall(r"connection already closed", msg, re.M):
|
|
454
|
-
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
455
|
-
if re.findall(r"cursor already closed", msg, re.M):
|
|
469
|
+
# Dialect-specific connection error message classification (fallback when no/unknown code).
|
|
470
|
+
if getattr(self.sql, "is_connection_error_message", lambda _m: False)(msg):
|
|
456
471
|
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
457
472
|
if "no such table:" in msg:
|
|
458
473
|
raise exceptions.DbTableMissingError(enhanced_message) from exception
|
|
@@ -2,6 +2,7 @@ import traceback
|
|
|
2
2
|
|
|
3
3
|
from velocity.db.core.row import Row
|
|
4
4
|
from velocity.db.core.table import Table
|
|
5
|
+
from velocity.db.core.view import View
|
|
5
6
|
from velocity.db.core.result import Result
|
|
6
7
|
from velocity.db.core.column import Column
|
|
7
8
|
from velocity.db.core.database import Database
|
|
@@ -47,6 +48,16 @@ class Transaction:
|
|
|
47
48
|
"""
|
|
48
49
|
Retrieves a database cursor, opening a connection if necessary.
|
|
49
50
|
"""
|
|
51
|
+
# Lazily connect, and also recover if the driver reports a closed connection.
|
|
52
|
+
# (psycopg2 uses `connection.closed != 0` to indicate closed.)
|
|
53
|
+
if self.connection is not None:
|
|
54
|
+
try:
|
|
55
|
+
if getattr(self.connection, "closed", 0):
|
|
56
|
+
self.connection = None
|
|
57
|
+
except Exception:
|
|
58
|
+
# If the driver object is in a bad state, force a reconnect.
|
|
59
|
+
self.connection = None
|
|
60
|
+
|
|
50
61
|
if not self.connection:
|
|
51
62
|
self.connection = self.engine.connect()
|
|
52
63
|
if debug:
|
|
@@ -157,6 +168,10 @@ class Transaction:
|
|
|
157
168
|
"""
|
|
158
169
|
return Table(self, tablename)
|
|
159
170
|
|
|
171
|
+
def view(self, viewname, schema=None):
|
|
172
|
+
"""Returns a View helper for the given view name."""
|
|
173
|
+
return View(self, viewname, schema=schema)
|
|
174
|
+
|
|
160
175
|
def sequence(self, name):
|
|
161
176
|
"""
|
|
162
177
|
Returns a Sequence object for the given sequence name.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from velocity.db import exceptions
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _quote_ident(identifier: str) -> str:
|
|
9
|
+
return '"' + str(identifier).replace('"', '""') + '"'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _grantee_sql(grantee: str) -> str:
|
|
13
|
+
normalized = str(grantee).strip()
|
|
14
|
+
if not normalized:
|
|
15
|
+
raise ValueError("grantee cannot be empty")
|
|
16
|
+
|
|
17
|
+
keyword = normalized.upper()
|
|
18
|
+
if keyword in {"PUBLIC", "CURRENT_USER", "SESSION_USER"}:
|
|
19
|
+
return keyword
|
|
20
|
+
|
|
21
|
+
return _quote_ident(normalized)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Grant:
|
|
26
|
+
privilege: str
|
|
27
|
+
grantee: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class View:
|
|
31
|
+
"""PostgreSQL view helper.
|
|
32
|
+
|
|
33
|
+
This is intentionally lightweight and uses catalog queries directly rather than
|
|
34
|
+
embedding view DDL in the dialect.
|
|
35
|
+
|
|
36
|
+
Primary goals:
|
|
37
|
+
- Create views in an idempotent way (CREATE OR REPLACE).
|
|
38
|
+
- If the view already exists, "enhance" it by applying missing grants.
|
|
39
|
+
|
|
40
|
+
Notes:
|
|
41
|
+
- For updating the view definition, pass replace_existing=True.
|
|
42
|
+
- Privilege checks use has_table_privilege().
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, tx, name: str, schema: str | None = None):
|
|
46
|
+
self.tx = tx
|
|
47
|
+
self.name = str(name).lower()
|
|
48
|
+
self.schema = (schema or getattr(tx.engine.sql, "default_schema", None) or "public").lower()
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def qualified_name(self) -> str:
|
|
52
|
+
return f"{_quote_ident(self.schema)}.{_quote_ident(self.name)}"
|
|
53
|
+
|
|
54
|
+
def exists(self) -> bool:
|
|
55
|
+
result = self.tx.execute(
|
|
56
|
+
"""
|
|
57
|
+
SELECT 1
|
|
58
|
+
FROM information_schema.views
|
|
59
|
+
WHERE table_schema = %s AND table_name = %s
|
|
60
|
+
LIMIT 1
|
|
61
|
+
""",
|
|
62
|
+
[self.schema, self.name],
|
|
63
|
+
)
|
|
64
|
+
try:
|
|
65
|
+
rows = result.as_dict().all()
|
|
66
|
+
except Exception:
|
|
67
|
+
rows = []
|
|
68
|
+
return bool(rows)
|
|
69
|
+
|
|
70
|
+
def create_or_replace(self, select_sql: str) -> None:
|
|
71
|
+
if self.tx.engine.schema_locked:
|
|
72
|
+
raise exceptions.DbSchemaLockedError(
|
|
73
|
+
f"Cannot create/replace view {self.schema}.{self.name}: schema is locked"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
sql = (select_sql or "").strip().rstrip(";")
|
|
77
|
+
if not sql:
|
|
78
|
+
raise ValueError("select_sql cannot be empty")
|
|
79
|
+
|
|
80
|
+
# If caller already provided CREATE VIEW, execute as-is.
|
|
81
|
+
lowered = sql.lower().lstrip()
|
|
82
|
+
if lowered.startswith("create"):
|
|
83
|
+
self.tx.execute(sql)
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
ddl = f"CREATE OR REPLACE VIEW {self.qualified_name} AS\n{sql}"
|
|
87
|
+
self.tx.execute(ddl)
|
|
88
|
+
|
|
89
|
+
def _has_privilege(self, grantee: str, privilege: str) -> bool:
|
|
90
|
+
# PUBLIC is a PostgreSQL pseudo-role for GRANT, but it's not necessarily a
|
|
91
|
+
# real role that can be passed to has_table_privilege() in all setups.
|
|
92
|
+
# GRANT itself is idempotent, so for PUBLIC we just apply it unconditionally.
|
|
93
|
+
if str(grantee).strip().upper() == "PUBLIC":
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
# has_table_privilege(role, table, privilege)
|
|
97
|
+
# Note: privilege must be like 'SELECT'.
|
|
98
|
+
result = self.tx.execute(
|
|
99
|
+
"SELECT has_table_privilege(%s, %s, %s) as ok",
|
|
100
|
+
[grantee, f"{self.schema}.{self.name}", privilege],
|
|
101
|
+
)
|
|
102
|
+
try:
|
|
103
|
+
rows = result.as_dict().all()
|
|
104
|
+
except Exception:
|
|
105
|
+
rows = []
|
|
106
|
+
row = rows[0] if rows else None
|
|
107
|
+
return bool(row.get("ok")) if isinstance(row, dict) and row else False
|
|
108
|
+
|
|
109
|
+
def grant(self, privilege: str = "SELECT", grantee: str = "PUBLIC") -> None:
|
|
110
|
+
if self.tx.engine.schema_locked:
|
|
111
|
+
raise exceptions.DbSchemaLockedError(
|
|
112
|
+
f"Cannot grant privileges on {self.schema}.{self.name}: schema is locked"
|
|
113
|
+
)
|
|
114
|
+
privilege = str(privilege).upper().strip()
|
|
115
|
+
if not privilege:
|
|
116
|
+
raise ValueError("privilege cannot be empty")
|
|
117
|
+
self.tx.execute(
|
|
118
|
+
f"GRANT {privilege} ON {self.qualified_name} TO {_grantee_sql(grantee)}"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def ensure(
|
|
122
|
+
self,
|
|
123
|
+
select_sql: str | None = None,
|
|
124
|
+
*,
|
|
125
|
+
replace_existing: bool = False,
|
|
126
|
+
grants: list[Grant] | None = None,
|
|
127
|
+
grant_public_select: bool = True,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Ensure the view exists and has required grants.
|
|
130
|
+
|
|
131
|
+
- If the view does not exist, it is created using select_sql (required).
|
|
132
|
+
- If it exists, it is not modified unless replace_existing=True.
|
|
133
|
+
- Missing privileges are granted.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
exists = self.exists()
|
|
137
|
+
|
|
138
|
+
if not exists:
|
|
139
|
+
if select_sql is None:
|
|
140
|
+
raise ValueError("select_sql is required when creating a missing view")
|
|
141
|
+
self.create_or_replace(select_sql)
|
|
142
|
+
elif replace_existing and select_sql is not None:
|
|
143
|
+
self.create_or_replace(select_sql)
|
|
144
|
+
|
|
145
|
+
desired_grants: list[Grant] = []
|
|
146
|
+
if grant_public_select:
|
|
147
|
+
desired_grants.append(Grant("SELECT", "PUBLIC"))
|
|
148
|
+
if grants:
|
|
149
|
+
desired_grants.extend(grants)
|
|
150
|
+
|
|
151
|
+
for g in desired_grants:
|
|
152
|
+
privilege = str(g.privilege).upper().strip()
|
|
153
|
+
grantee = str(g.grantee).strip()
|
|
154
|
+
if not privilege or not grantee:
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
if not self._has_privilege(grantee, privilege):
|
|
158
|
+
self.grant(privilege=privilege, grantee=grantee)
|
|
@@ -52,6 +52,43 @@ class BaseSQLDialect(ABC):
|
|
|
52
52
|
"""
|
|
53
53
|
pass
|
|
54
54
|
|
|
55
|
+
@classmethod
|
|
56
|
+
def is_connection_error_message(cls, msg: str) -> bool:
|
|
57
|
+
"""Return True if an error message indicates a connection-level failure.
|
|
58
|
+
|
|
59
|
+
Dialects should override/extend this if they can do better than message matching.
|
|
60
|
+
Engine uses this only as a fallback when no/unknown error code is available.
|
|
61
|
+
"""
|
|
62
|
+
if not msg:
|
|
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)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def is_transient_connection_error_message(cls, msg: str) -> bool:
|
|
85
|
+
"""Return True if a connection error looks transient/retryable.
|
|
86
|
+
|
|
87
|
+
Default implementation treats most low-level disconnects as transient.
|
|
88
|
+
Dialects may override to be stricter.
|
|
89
|
+
"""
|
|
90
|
+
return cls.is_connection_error_message(msg)
|
|
91
|
+
|
|
55
92
|
# Core CRUD Operations
|
|
56
93
|
@classmethod
|
|
57
94
|
@abstractmethod
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
|
|
4
|
+
from velocity.db.core.view import View
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FakeResult:
|
|
8
|
+
def __init__(self, rows):
|
|
9
|
+
self._rows = rows
|
|
10
|
+
|
|
11
|
+
def as_dict(self):
|
|
12
|
+
return self
|
|
13
|
+
|
|
14
|
+
def all(self):
|
|
15
|
+
return list(self._rows)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FakeTx:
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.executed = []
|
|
21
|
+
self.engine = SimpleNamespace(
|
|
22
|
+
schema_locked=False,
|
|
23
|
+
sql=SimpleNamespace(default_schema="public"),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Toggleable responses
|
|
27
|
+
self._view_exists = False
|
|
28
|
+
self._has_priv = False
|
|
29
|
+
|
|
30
|
+
def execute(self, sql, parms=None, *args, **kwargs):
|
|
31
|
+
self.executed.append((sql, parms))
|
|
32
|
+
|
|
33
|
+
text = str(sql)
|
|
34
|
+
if "information_schema.views" in text:
|
|
35
|
+
return FakeResult([{"ok": True}]) if self._view_exists else FakeResult([])
|
|
36
|
+
|
|
37
|
+
if "has_table_privilege" in text:
|
|
38
|
+
return FakeResult([{"ok": bool(self._has_priv)}])
|
|
39
|
+
|
|
40
|
+
return FakeResult([])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestViewHelper(unittest.TestCase):
|
|
44
|
+
def test_ensure_creates_missing_view_and_grants(self):
|
|
45
|
+
tx = FakeTx()
|
|
46
|
+
v = View(tx, "my_view")
|
|
47
|
+
|
|
48
|
+
tx._view_exists = False
|
|
49
|
+
tx._has_priv = False
|
|
50
|
+
|
|
51
|
+
v.ensure("select 1 as one")
|
|
52
|
+
|
|
53
|
+
statements = "\n".join(s for s, _ in tx.executed)
|
|
54
|
+
self.assertIn("CREATE OR REPLACE VIEW", statements)
|
|
55
|
+
self.assertIn('"public"."my_view"', statements)
|
|
56
|
+
self.assertIn("GRANT SELECT ON", statements)
|
|
57
|
+
|
|
58
|
+
def test_ensure_existing_view_does_not_replace_by_default(self):
|
|
59
|
+
tx = FakeTx()
|
|
60
|
+
v = View(tx, "my_view")
|
|
61
|
+
|
|
62
|
+
tx._view_exists = True
|
|
63
|
+
tx._has_priv = False
|
|
64
|
+
|
|
65
|
+
v.ensure("select 1 as one")
|
|
66
|
+
|
|
67
|
+
statements = "\n".join(s for s, _ in tx.executed)
|
|
68
|
+
self.assertNotIn("CREATE OR REPLACE VIEW", statements)
|
|
69
|
+
self.assertIn("GRANT SELECT ON", statements)
|
|
70
|
+
|
|
71
|
+
def test_ensure_existing_view_replaces_when_requested(self):
|
|
72
|
+
tx = FakeTx()
|
|
73
|
+
v = View(tx, "my_view")
|
|
74
|
+
|
|
75
|
+
tx._view_exists = True
|
|
76
|
+
tx._has_priv = True
|
|
77
|
+
|
|
78
|
+
v.ensure("select 1 as one", replace_existing=True)
|
|
79
|
+
|
|
80
|
+
statements = "\n".join(s for s, _ in tx.executed)
|
|
81
|
+
self.assertIn("CREATE OR REPLACE VIEW", statements)
|
|
82
|
+
|
|
83
|
+
def test_ensure_skips_grant_if_already_present(self):
|
|
84
|
+
tx = FakeTx()
|
|
85
|
+
v = View(tx, "my_view")
|
|
86
|
+
|
|
87
|
+
tx._view_exists = True
|
|
88
|
+
tx._has_priv = True
|
|
89
|
+
|
|
90
|
+
v.ensure("select 1 as one")
|
|
91
|
+
|
|
92
|
+
statements = "\n".join(s for s, _ in tx.executed)
|
|
93
|
+
self.assertNotIn("GRANT SELECT ON", statements)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
unittest.main()
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity_python.egg-info/SOURCES.txt
RENAMED
|
@@ -42,6 +42,7 @@ src/velocity/db/core/row.py
|
|
|
42
42
|
src/velocity/db/core/sequence.py
|
|
43
43
|
src/velocity/db/core/table.py
|
|
44
44
|
src/velocity/db/core/transaction.py
|
|
45
|
+
src/velocity/db/core/view.py
|
|
45
46
|
src/velocity/db/servers/__init__.py
|
|
46
47
|
src/velocity/db/servers/tablehelper.py
|
|
47
48
|
src/velocity/db/servers/base/__init__.py
|
|
@@ -83,6 +84,7 @@ src/velocity/db/tests/test_schema_locking_initializers.py
|
|
|
83
84
|
src/velocity/db/tests/test_schema_locking_simple.py
|
|
84
85
|
src/velocity/db/tests/test_sql_builder.py
|
|
85
86
|
src/velocity/db/tests/test_tablehelper.py
|
|
87
|
+
src/velocity/db/tests/test_view_helper.py
|
|
86
88
|
src/velocity/db/tests/postgres/__init__.py
|
|
87
89
|
src/velocity/db/tests/postgres/common.py
|
|
88
90
|
src/velocity/db/tests/postgres/test_column.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.203 → velocity_python-0.0.205}/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
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/context_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/mixins/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/aws/handlers/mixins/web_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/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
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/base/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/base/initializer.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/base/operators.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/mysql/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/mysql/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/mysql/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/postgres/types.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlite/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlite/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlite/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/sql.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/servers/sqlserver/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/common.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_column.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_database.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_imports.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_result.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/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.203 → velocity_python-0.0.205}/src/velocity/db/tests/postgres/test_sequence.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/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.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_postgres_unchanged.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_result_caching.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/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.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_sql_builder.py
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity/db/tests/test_tablehelper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/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.203 → velocity_python-0.0.205}/src/velocity/payment/braintree_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/src/velocity_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{velocity_python-0.0.203 → velocity_python-0.0.205}/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.203 → velocity_python-0.0.205}/tests/test_sys_modified_count_postgres_demo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|