velocity-python 0.0.202__tar.gz → 0.0.204__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.202 → velocity_python-0.0.204}/PKG-INFO +1 -1
- {velocity_python-0.0.202 → velocity_python-0.0.204}/pyproject.toml +1 -1
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/engine.py +61 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/transaction.py +15 -0
- velocity_python-0.0.204/src/velocity/db/core/view.py +158 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/sql.py +2 -2
- velocity_python-0.0.204/src/velocity/db/tests/test_view_helper.py +97 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/tests/test_table_alter.py +5 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/LICENSE +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/README.md +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/setup.cfg +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/logging.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/tests/test_decorators.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.0.202 → velocity_python-0.0.204}/tests/test_where_clause_validation.py +0 -0
|
@@ -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,35 @@ 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 self._is_transient_connection_error_message(msg):
|
|
202
|
+
raise
|
|
203
|
+
|
|
204
|
+
connection_retry_count += 1
|
|
205
|
+
if connection_retry_count > 6:
|
|
206
|
+
raise
|
|
207
|
+
|
|
208
|
+
# Force a reconnect on the next attempt.
|
|
209
|
+
try:
|
|
210
|
+
_tx.close()
|
|
211
|
+
except Exception:
|
|
212
|
+
_tx.connection = None
|
|
213
|
+
|
|
214
|
+
time.sleep(min(2.0, 0.1 * (2**min(connection_retry_count, 5))))
|
|
215
|
+
continue
|
|
192
216
|
except Exception:
|
|
193
217
|
raise
|
|
194
218
|
finally:
|
|
@@ -442,6 +466,13 @@ class Engine:
|
|
|
442
466
|
raise exceptions.DbObjectExistsError(enhanced_message) from exception
|
|
443
467
|
if re.findall(r"server closed the connection unexpectedly", msg, re.M):
|
|
444
468
|
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
469
|
+
if re.findall(r"ssl syscall error: eof detected", msg, re.M):
|
|
470
|
+
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
471
|
+
if re.findall(r"ssl syscall error: connection reset by peer", msg, re.M):
|
|
472
|
+
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
473
|
+
if re.findall(r"eof detected", msg, re.M):
|
|
474
|
+
# Be conservative: EOFs typically indicate a transient disconnect.
|
|
475
|
+
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
445
476
|
if re.findall(r"no connection to the server", msg, re.M):
|
|
446
477
|
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
447
478
|
if re.findall(r"connection timed out", msg, re.M):
|
|
@@ -450,6 +481,16 @@ class Engine:
|
|
|
450
481
|
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
451
482
|
if re.findall(r"cannot connect to server", msg, re.M):
|
|
452
483
|
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
484
|
+
if re.findall(r"terminating connection due to administrator command", msg, re.M):
|
|
485
|
+
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
486
|
+
if re.findall(r"connection reset by peer", msg, re.M):
|
|
487
|
+
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
488
|
+
if re.findall(r"broken pipe", msg, re.M):
|
|
489
|
+
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
490
|
+
if re.findall(r"could not receive data from server", msg, re.M):
|
|
491
|
+
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
492
|
+
if re.findall(r"could not send data to server", msg, re.M):
|
|
493
|
+
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
453
494
|
if re.findall(r"connection already closed", msg, re.M):
|
|
454
495
|
raise exceptions.DbConnectionError(enhanced_message) from exception
|
|
455
496
|
if re.findall(r"cursor already closed", msg, re.M):
|
|
@@ -471,6 +512,26 @@ class Engine:
|
|
|
471
512
|
# If we can't classify it, re-raise with enhanced message
|
|
472
513
|
raise type(exception)(enhanced_message) from exception
|
|
473
514
|
|
|
515
|
+
def _is_transient_connection_error_message(self, msg: str) -> bool:
|
|
516
|
+
"""Return True if this looks like a retryable, transient connection drop.
|
|
517
|
+
|
|
518
|
+
Keep this intentionally conservative: authentication/config issues should not be retried.
|
|
519
|
+
"""
|
|
520
|
+
needles = (
|
|
521
|
+
"ssl syscall error",
|
|
522
|
+
"eof detected",
|
|
523
|
+
"server closed the connection unexpectedly",
|
|
524
|
+
"connection reset by peer",
|
|
525
|
+
"broken pipe",
|
|
526
|
+
"could not receive data from server",
|
|
527
|
+
"could not send data to server",
|
|
528
|
+
"terminating connection due to administrator command",
|
|
529
|
+
"connection already closed",
|
|
530
|
+
"cursor already closed",
|
|
531
|
+
"no connection to the server",
|
|
532
|
+
)
|
|
533
|
+
return any(n in msg for n in needles)
|
|
534
|
+
|
|
474
535
|
def _format_sql_with_params(self, sql, parameters):
|
|
475
536
|
"""
|
|
476
537
|
Format SQL query with parameters merged for easy copy-paste debugging.
|
|
@@ -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)
|
|
@@ -1412,14 +1412,14 @@ class SQL(BaseSQLDialect):
|
|
|
1412
1412
|
col_type = "TIMESTAMP"
|
|
1413
1413
|
sql.append(
|
|
1414
1414
|
f"ALTER TABLE {TableHelper.quote(table)} "
|
|
1415
|
-
f"ADD {TableHelper.quote(col_name_clean)} {col_type} {null_clause};"
|
|
1415
|
+
f"ADD COLUMN IF NOT EXISTS {TableHelper.quote(col_name_clean)} {col_type} {null_clause};"
|
|
1416
1416
|
)
|
|
1417
1417
|
else:
|
|
1418
1418
|
# Normal code path: rely on your `TYPES.get_type(...)` logic
|
|
1419
1419
|
col_type = TYPES.get_type(val)
|
|
1420
1420
|
sql.append(
|
|
1421
1421
|
f"ALTER TABLE {TableHelper.quote(table)} "
|
|
1422
|
-
f"ADD {TableHelper.quote(col_name_clean)} {col_type} {null_clause};"
|
|
1422
|
+
f"ADD COLUMN IF NOT EXISTS {TableHelper.quote(col_name_clean)} {col_type} {null_clause};"
|
|
1423
1423
|
)
|
|
1424
1424
|
|
|
1425
1425
|
final_sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
@@ -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.202 → velocity_python-0.0.204}/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
|
|
@@ -5,6 +5,7 @@ from velocity.db.core.table import Table
|
|
|
5
5
|
from velocity.db.servers.postgres import types as postgres_types
|
|
6
6
|
from velocity.db.servers.mysql import types as mysql_types
|
|
7
7
|
from velocity.db.servers.sqlserver import types as sqlserver_types
|
|
8
|
+
from velocity.db.servers.postgres.sql import SQL as PostgresSQL
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class RecordingSQL:
|
|
@@ -97,6 +98,10 @@ class FakeTable(Table):
|
|
|
97
98
|
|
|
98
99
|
|
|
99
100
|
class TableAlterTests(unittest.TestCase):
|
|
101
|
+
def test_postgres_alter_add_is_idempotent(self):
|
|
102
|
+
sql, _vals = PostgresSQL.alter_add("aws_api_activity", {"user_id": str})
|
|
103
|
+
self.assertIn("ADD COLUMN IF NOT EXISTS", sql.upper())
|
|
104
|
+
|
|
100
105
|
def test_postgres_type_change_uses_python_type(self):
|
|
101
106
|
sql = RecordingSQL("PostGreSQL", postgres_types)
|
|
102
107
|
table = FakeTable(
|
|
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.202 → velocity_python-0.0.204}/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.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/base_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/context_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/mixins/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/aws/handlers/mixins/web_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/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.202 → velocity_python-0.0.204}/src/velocity/db/servers/base/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/base/initializer.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/base/operators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/mysql/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/mysql/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/mysql/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/postgres/types.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlite/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlite/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlite/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/sql.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/servers/sqlserver/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/common.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_column.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_database.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_imports.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_result.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/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.202 → velocity_python-0.0.204}/src/velocity/db/tests/postgres/test_sequence.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/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.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_postgres_unchanged.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_result_caching.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/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.202 → velocity_python-0.0.204}/src/velocity/db/tests/test_sql_builder.py
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/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.202 → velocity_python-0.0.204}/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.202 → velocity_python-0.0.204}/src/velocity/payment/braintree_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/src/velocity_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{velocity_python-0.0.202 → velocity_python-0.0.204}/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.202 → velocity_python-0.0.204}/tests/test_sys_modified_count_postgres_demo.py
RENAMED
|
File without changes
|
|
File without changes
|