velocity-python 0.0.132__tar.gz → 0.0.134__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.

Potentially problematic release.


This version of velocity-python might be problematic. Click here for more details.

Files changed (130) hide show
  1. {velocity_python-0.0.132 → velocity_python-0.0.134}/PKG-INFO +1 -1
  2. {velocity_python-0.0.132 → velocity_python-0.0.134}/pyproject.toml +1 -1
  3. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/__init__.py +1 -1
  4. velocity_python-0.0.134/src/velocity/app/tests/__init__.py +1 -0
  5. velocity_python-0.0.134/src/velocity/aws/tests/__init__.py +1 -0
  6. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/decorators.py +20 -3
  7. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/engine.py +33 -7
  8. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/exceptions.py +7 -0
  9. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/base/initializer.py +2 -1
  10. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/mysql/__init__.py +13 -4
  11. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/postgres/__init__.py +14 -4
  12. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlite/__init__.py +13 -4
  13. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlserver/__init__.py +13 -4
  14. velocity_python-0.0.134/src/velocity/db/tests/__init__.py +1 -0
  15. velocity_python-0.0.134/src/velocity/db/tests/postgres/__init__.py +1 -0
  16. velocity_python-0.0.134/src/velocity/db/tests/postgres/common.py +49 -0
  17. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_column.py +29 -0
  18. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_connections.py +25 -0
  19. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_database.py +21 -0
  20. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_engine.py +205 -0
  21. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_general_usage.py +88 -0
  22. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_imports.py +8 -0
  23. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_result.py +19 -0
  24. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_row.py +137 -0
  25. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_schema_locking.py +335 -0
  26. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  27. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_sequence.py +34 -0
  28. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_table.py +101 -0
  29. velocity_python-0.0.134/src/velocity/db/tests/postgres/test_transaction.py +106 -0
  30. velocity_python-0.0.134/src/velocity/db/tests/sql/__init__.py +1 -0
  31. velocity_python-0.0.134/src/velocity/db/tests/sql/common.py +177 -0
  32. velocity_python-0.0.134/src/velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  33. velocity_python-0.0.134/src/velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  34. velocity_python-0.0.134/src/velocity/db/tests/test_schema_locking_initializers.py +226 -0
  35. velocity_python-0.0.134/src/velocity/db/tests/test_schema_locking_simple.py +97 -0
  36. velocity_python-0.0.134/src/velocity/misc/__init__.py +0 -0
  37. velocity_python-0.0.134/src/velocity/misc/tests/__init__.py +1 -0
  38. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/misc}/tests/test_db.py +1 -1
  39. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/misc}/tests/test_format.py +1 -1
  40. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/misc}/tests/test_iconv.py +1 -1
  41. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/misc}/tests/test_merge.py +1 -1
  42. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/misc}/tests/test_oconv.py +1 -1
  43. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/misc}/tests/test_timer.py +1 -1
  44. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity_python.egg-info/PKG-INFO +1 -1
  45. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity_python.egg-info/SOURCES.txt +50 -24
  46. {velocity_python-0.0.132 → velocity_python-0.0.134}/LICENSE +0 -0
  47. {velocity_python-0.0.132 → velocity_python-0.0.134}/README.md +0 -0
  48. {velocity_python-0.0.132 → velocity_python-0.0.134}/setup.cfg +0 -0
  49. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/app/__init__.py +0 -0
  50. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/app/invoices.py +0 -0
  51. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/app/orders.py +0 -0
  52. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/app/payments.py +0 -0
  53. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/app/purchase_orders.py +0 -0
  54. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/app}/tests/test_email_processing.py +0 -0
  55. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/app}/tests/test_payment_profile_sorting.py +0 -0
  56. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/app}/tests/test_spreadsheet_functions.py +0 -0
  57. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/__init__.py +0 -0
  58. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/amplify.py +0 -0
  59. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/__init__.py +0 -0
  60. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/base_handler.py +0 -0
  61. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/context.py +0 -0
  62. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/exceptions.py +0 -0
  63. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  64. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  65. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/mixins/activity_tracker.py +0 -0
  66. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/mixins/error_handler.py +0 -0
  67. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/mixins/legacy_mixin.py +0 -0
  68. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/mixins/standard_mixin.py +0 -0
  69. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/response.py +0 -0
  70. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  71. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/aws}/tests/test_lambda_handler_json_serialization.py +0 -0
  72. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/aws}/tests/test_response.py +0 -0
  73. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/__init__.py +0 -0
  74. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/__init__.py +0 -0
  75. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/column.py +0 -0
  76. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/database.py +0 -0
  77. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/result.py +0 -0
  78. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/row.py +0 -0
  79. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/sequence.py +0 -0
  80. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/table.py +0 -0
  81. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/core/transaction.py +0 -0
  82. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/__init__.py +0 -0
  83. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/base/__init__.py +0 -0
  84. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/base/operators.py +0 -0
  85. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/base/sql.py +0 -0
  86. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/base/types.py +0 -0
  87. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/mysql/operators.py +0 -0
  88. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/mysql/reserved.py +0 -0
  89. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/mysql/sql.py +0 -0
  90. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/mysql/types.py +0 -0
  91. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/postgres/operators.py +0 -0
  92. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/postgres/reserved.py +0 -0
  93. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/postgres/sql.py +0 -0
  94. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/postgres/types.py +0 -0
  95. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlite/operators.py +0 -0
  96. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  97. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlite/sql.py +0 -0
  98. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlite/types.py +0 -0
  99. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  100. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  101. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  102. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/sqlserver/types.py +0 -0
  103. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/servers/tablehelper.py +0 -0
  104. /velocity_python-0.0.132/src/velocity/misc/__init__.py → /velocity_python-0.0.134/src/velocity/db/tests/common_db_test.py +0 -0
  105. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_cursor_rowcount_fix.py +0 -0
  106. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_db_utils.py +0 -0
  107. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_postgres.py +0 -0
  108. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_postgres_unchanged.py +0 -0
  109. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_process_error_robustness.py +0 -0
  110. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_result_caching.py +0 -0
  111. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_result_sql_aware.py +0 -0
  112. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_row_get_missing_column.py +0 -0
  113. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_sql_builder.py +0 -0
  114. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/db}/tests/test_tablehelper.py +0 -0
  115. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/db/utils.py +0 -0
  116. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/conv/__init__.py +0 -0
  117. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/conv/iconv.py +0 -0
  118. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/conv/oconv.py +0 -0
  119. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/db.py +0 -0
  120. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/export.py +0 -0
  121. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/format.py +0 -0
  122. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/mail.py +0 -0
  123. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/merge.py +0 -0
  124. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/misc}/tests/test_fix.py +0 -0
  125. {velocity_python-0.0.132 → velocity_python-0.0.134/src/velocity/misc}/tests/test_original_error.py +0 -0
  126. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/timer.py +0 -0
  127. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity/misc/tools.py +0 -0
  128. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  129. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity_python.egg-info/requires.txt +0 -0
  130. {velocity_python-0.0.132 → velocity_python-0.0.134}/src/velocity_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.132
3
+ Version: 0.0.134
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.0.132"
7
+ version = "0.0.134"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.0.132"
1
+ __version__ = version = "0.0.134"
2
2
 
3
3
  from . import aws
4
4
  from . import db
@@ -0,0 +1 @@
1
+ # App module tests
@@ -0,0 +1 @@
1
+ # AWS module tests
@@ -99,7 +99,8 @@ def return_default(
99
99
 
100
100
  def create_missing(func):
101
101
  """
102
- If the function call fails with DbColumnMissingError or DbTableMissingError, tries to create them and re-run.
102
+ If the function call fails with DbColumnMissingError or DbTableMissingError,
103
+ tries to create them and re-run (only if schema is not locked).
103
104
  """
104
105
 
105
106
  @wraps(func)
@@ -109,8 +110,16 @@ def create_missing(func):
109
110
  result = func(self, *args, **kwds)
110
111
  self.tx.release_savepoint(sp, cursor=self.cursor())
111
112
  return result
112
- except exceptions.DbColumnMissingError:
113
+ except exceptions.DbColumnMissingError as e:
113
114
  self.tx.rollback_savepoint(sp, cursor=self.cursor())
115
+
116
+ # Check if schema is locked
117
+ if self.tx.engine.schema_locked:
118
+ raise exceptions.DbSchemaLockedError(
119
+ f"Cannot create missing column: schema is locked. Original error: {e}"
120
+ ) from e
121
+
122
+ # Existing logic for automatic creation
114
123
  data = {}
115
124
  if "pk" in kwds:
116
125
  data.update(kwds["pk"])
@@ -121,8 +130,16 @@ def create_missing(func):
121
130
  data.update(arg)
122
131
  self.alter(data)
123
132
  return func(self, *args, **kwds)
124
- except exceptions.DbTableMissingError:
133
+ except exceptions.DbTableMissingError as e:
125
134
  self.tx.rollback_savepoint(sp, cursor=self.cursor())
135
+
136
+ # Check if schema is locked
137
+ if self.tx.engine.schema_locked:
138
+ raise exceptions.DbSchemaLockedError(
139
+ f"Cannot create missing table: schema is locked. Original error: {e}"
140
+ ) from e
141
+
142
+ # Existing logic for automatic creation
126
143
  data = {}
127
144
  if "pk" in kwds:
128
145
  data.update(kwds["pk"])
@@ -1,5 +1,7 @@
1
1
  import inspect
2
2
  import re
3
+ import os
4
+ from contextlib import contextmanager
3
5
  from functools import wraps
4
6
  from velocity.db import exceptions
5
7
  from velocity.db.core.transaction import Transaction
@@ -17,11 +19,12 @@ class Engine:
17
19
 
18
20
  MAX_RETRIES = 100
19
21
 
20
- def __init__(self, driver, config, sql, connect_timeout=5):
22
+ def __init__(self, driver, config, sql, connect_timeout=5, schema_locked=False):
21
23
  self.__config = config
22
24
  self.__sql = sql
23
25
  self.__driver = driver
24
26
  self.__connect_timeout = connect_timeout
27
+ self.__schema_locked = schema_locked
25
28
 
26
29
  def __str__(self):
27
30
  return f"[{self.sql.server}] engine({self.config})"
@@ -205,6 +208,29 @@ class Engine:
205
208
  def sql(self):
206
209
  return self.__sql
207
210
 
211
+ @property
212
+ def schema_locked(self):
213
+ """Returns True if schema modifications are locked."""
214
+ return self.__schema_locked
215
+
216
+ def lock_schema(self):
217
+ """Lock schema to prevent automatic modifications."""
218
+ self.__schema_locked = True
219
+
220
+ def unlock_schema(self):
221
+ """Unlock schema to allow automatic modifications."""
222
+ self.__schema_locked = False
223
+
224
+ @contextmanager
225
+ def unlocked_schema(self):
226
+ """Temporarily unlock schema for automatic creation."""
227
+ original_state = self.__schema_locked
228
+ self.__schema_locked = False
229
+ try:
230
+ yield self
231
+ finally:
232
+ self.__schema_locked = original_state
233
+
208
234
  @property
209
235
  def version(self):
210
236
  """
@@ -353,12 +379,12 @@ class Engine:
353
379
  if sql:
354
380
  formatted_sql_info = f" sql={self._format_sql_with_params(sql, parameters)}"
355
381
 
356
- logger.warning(
357
- "Database error caught. Attempting to transform: code=%s message=%s%s",
358
- error_code,
359
- error_message,
360
- formatted_sql_info,
361
- )
382
+ # logger.warning(
383
+ # "Database error caught. Attempting to transform: code=%s message=%s%s",
384
+ # error_code,
385
+ # error_message,
386
+ # formatted_sql_info,
387
+ # )
362
388
 
363
389
  # Direct error code mapping
364
390
  if error_code in self.sql.ApplicationErrorCodes:
@@ -93,6 +93,12 @@ class DbTransactionError(DbException):
93
93
  pass
94
94
 
95
95
 
96
+ class DbSchemaLockedError(DbApplicationError):
97
+ """Raised when attempting to modify schema while schema is locked."""
98
+
99
+ pass
100
+
101
+
96
102
  class DuplicateRowsFoundError(Exception):
97
103
  """Multiple rows found when expecting single result."""
98
104
 
@@ -125,5 +131,6 @@ __all__ = [
125
131
  "DbDataIntegrityError",
126
132
  "DbQueryError",
127
133
  "DbTransactionError",
134
+ "DbSchemaLockedError",
128
135
  "DuplicateRowsFoundError",
129
136
  ]
@@ -16,12 +16,13 @@ class BaseInitializer(ABC):
16
16
 
17
17
  @staticmethod
18
18
  @abstractmethod
19
- def initialize(config: Optional[Dict[str, Any]] = None, **kwargs) -> engine.Engine:
19
+ def initialize(config: Optional[Dict[str, Any]] = None, schema_locked: bool = False, **kwargs) -> engine.Engine:
20
20
  """
21
21
  Initialize a database engine with the appropriate driver and configuration.
22
22
 
23
23
  Args:
24
24
  config: Configuration dictionary (can be None)
25
+ schema_locked: Boolean to lock schema modifications (default: False)
25
26
  **kwargs: Additional configuration parameters
26
27
 
27
28
  Returns:
@@ -8,12 +8,13 @@ class MySQLInitializer(BaseInitializer):
8
8
  """MySQL database initializer."""
9
9
 
10
10
  @staticmethod
11
- def initialize(config=None, **kwargs):
11
+ def initialize(config=None, schema_locked=False, **kwargs):
12
12
  """
13
13
  Initialize MySQL engine with mysql.connector driver.
14
14
 
15
15
  Args:
16
16
  config: Configuration dictionary
17
+ schema_locked: Boolean to lock schema modifications
17
18
  **kwargs: Additional configuration parameters
18
19
 
19
20
  Returns:
@@ -55,10 +56,18 @@ class MySQLInitializer(BaseInitializer):
55
56
  required_keys = ["database", "host", "user"]
56
57
  MySQLInitializer._validate_required_config(final_config, required_keys)
57
58
 
58
- return engine.Engine(mysql.connector, final_config, SQL)
59
+ # Check for environment variable override for schema locking
60
+ if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
61
+ schema_locked = True
62
+
63
+ return engine.Engine(mysql.connector, final_config, SQL, schema_locked=schema_locked)
59
64
 
60
65
 
61
66
  # Maintain backward compatibility
62
- def initialize(config=None, **kwargs):
67
+ def initialize(config=None, schema_locked=False, **kwargs):
63
68
  """Backward compatible initialization function."""
64
- return MySQLInitializer.initialize(config, **kwargs)
69
+ # Check for environment variable override for schema locking
70
+ if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
71
+ schema_locked = True
72
+
73
+ return MySQLInitializer.initialize(config, schema_locked, **kwargs)
@@ -9,12 +9,13 @@ class PostgreSQLInitializer(BaseInitializer):
9
9
  """PostgreSQL database initializer."""
10
10
 
11
11
  @staticmethod
12
- def initialize(config=None, **kwargs):
12
+ def initialize(config=None, schema_locked=False, **kwargs):
13
13
  """
14
14
  Initialize PostgreSQL engine with psycopg2 driver.
15
15
 
16
16
  Args:
17
17
  config: Configuration dictionary
18
+ schema_locked: Boolean to lock schema modifications
18
19
  **kwargs: Additional configuration parameters
19
20
 
20
21
  Returns:
@@ -39,11 +40,15 @@ class PostgreSQLInitializer(BaseInitializer):
39
40
  required_keys = ["database", "host", "user", "password"]
40
41
  PostgreSQLInitializer._validate_required_config(final_config, required_keys)
41
42
 
42
- return engine.Engine(psycopg2, final_config, SQL)
43
+ # Check for environment variable override for schema locking
44
+ if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
45
+ schema_locked = True
46
+
47
+ return engine.Engine(psycopg2, final_config, SQL, schema_locked=schema_locked)
43
48
 
44
49
 
45
50
  # Maintain backward compatibility
46
- def initialize(config=None, **kwargs):
51
+ def initialize(config=None, schema_locked=False, **kwargs):
47
52
  """Backward compatible initialization function - matches original behavior exactly."""
48
53
  konfig = {
49
54
  "database": os.environ["DBDatabase"],
@@ -54,4 +59,9 @@ def initialize(config=None, **kwargs):
54
59
  }
55
60
  konfig.update(config or {})
56
61
  konfig.update(kwargs)
57
- return engine.Engine(psycopg2, konfig, SQL)
62
+
63
+ # Check for environment variable override for schema locking
64
+ if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
65
+ schema_locked = True
66
+
67
+ return engine.Engine(psycopg2, konfig, SQL, schema_locked=schema_locked)
@@ -9,12 +9,13 @@ class SQLiteInitializer(BaseInitializer):
9
9
  """SQLite database initializer."""
10
10
 
11
11
  @staticmethod
12
- def initialize(config=None, **kwargs):
12
+ def initialize(config=None, schema_locked=False, **kwargs):
13
13
  """
14
14
  Initialize SQLite engine with sqlite3 driver.
15
15
 
16
16
  Args:
17
17
  config: Configuration dictionary
18
+ schema_locked: Boolean to lock schema modifications
18
19
  **kwargs: Additional configuration parameters
19
20
 
20
21
  Returns:
@@ -43,10 +44,18 @@ class SQLiteInitializer(BaseInitializer):
43
44
  required_keys = ["database"]
44
45
  SQLiteInitializer._validate_required_config(final_config, required_keys)
45
46
 
46
- return engine.Engine(sqlite3, final_config, SQL)
47
+ # Check for environment variable override for schema locking
48
+ if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
49
+ schema_locked = True
50
+
51
+ return engine.Engine(sqlite3, final_config, SQL, schema_locked=schema_locked)
47
52
 
48
53
 
49
54
  # Maintain backward compatibility
50
- def initialize(config=None, **kwargs):
55
+ def initialize(config=None, schema_locked=False, **kwargs):
51
56
  """Backward compatible initialization function."""
52
- return SQLiteInitializer.initialize(config, **kwargs)
57
+ # Check for environment variable override for schema locking
58
+ if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
59
+ schema_locked = True
60
+
61
+ return SQLiteInitializer.initialize(config, schema_locked, **kwargs)
@@ -8,12 +8,13 @@ class SQLServerInitializer(BaseInitializer):
8
8
  """SQL Server database initializer."""
9
9
 
10
10
  @staticmethod
11
- def initialize(config=None, **kwargs):
11
+ def initialize(config=None, schema_locked=False, **kwargs):
12
12
  """
13
13
  Initialize SQL Server engine with pytds driver.
14
14
 
15
15
  Args:
16
16
  config: Configuration dictionary
17
+ schema_locked: Boolean to lock schema modifications
17
18
  **kwargs: Additional configuration parameters
18
19
 
19
20
  Returns:
@@ -55,10 +56,18 @@ class SQLServerInitializer(BaseInitializer):
55
56
  required_keys = ["database", "server", "user"]
56
57
  SQLServerInitializer._validate_required_config(final_config, required_keys)
57
58
 
58
- return engine.Engine(pytds, final_config, SQL)
59
+ # Check for environment variable override for schema locking
60
+ if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
61
+ schema_locked = True
62
+
63
+ return engine.Engine(pytds, final_config, SQL, schema_locked=schema_locked)
59
64
 
60
65
 
61
66
  # Maintain backward compatibility
62
- def initialize(config=None, **kwargs):
67
+ def initialize(config=None, schema_locked=False, **kwargs):
63
68
  """Backward compatible initialization function."""
64
- return SQLServerInitializer.initialize(config, **kwargs)
69
+ # Check for environment variable override for schema locking
70
+ if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
71
+ schema_locked = True
72
+
73
+ return SQLServerInitializer.initialize(config, schema_locked, **kwargs)
@@ -0,0 +1 @@
1
+ # Database module tests
@@ -0,0 +1 @@
1
+ # PostgreSQL tests
@@ -0,0 +1,49 @@
1
+ import unittest
2
+ from velocity.db.servers import postgres
3
+ import env
4
+ env.set()
5
+
6
+ test_db = "test_db_postgres"
7
+ engine = postgres.initialize(database=test_db)
8
+
9
+
10
+ class CommonPostgresTest(unittest.TestCase):
11
+ """
12
+ Base test class for PostgreSQL tests following the common pattern.
13
+ All PostgreSQL tests should inherit from this class.
14
+ """
15
+
16
+ @classmethod
17
+ def setUpClass(cls):
18
+ """Set up the test database and create any common tables."""
19
+ @engine.transaction
20
+ def setup(tx):
21
+ tx.switch_to_database("postgres")
22
+ tx.execute(f"drop database if exists {test_db}", single=True)
23
+
24
+ # Create the test database
25
+ db = tx.database(test_db)
26
+ if not db.exists():
27
+ db.create()
28
+ db.switch()
29
+
30
+ # Call subclass-specific table creation with commit
31
+ if hasattr(cls, 'create_test_tables'):
32
+ cls.create_test_tables(tx)
33
+
34
+ setup()
35
+
36
+ @classmethod
37
+ def tearDownClass(cls):
38
+ """Clean up the test database."""
39
+ @engine.transaction
40
+ def cleanup(tx):
41
+ tx.switch_to_database("postgres")
42
+ tx.execute(f"drop database if exists {test_db}", single=True)
43
+
44
+ cleanup()
45
+
46
+ @classmethod
47
+ def create_test_tables(cls, tx):
48
+ """Override this method in subclasses to create test-specific tables."""
49
+ pass
@@ -0,0 +1,29 @@
1
+ import unittest
2
+ from velocity.db.core.column import Column
3
+ from velocity.db.exceptions import DbColumnMissingError
4
+ from .common import CommonPostgresTest, engine, test_db
5
+
6
+
7
+ @engine.transaction
8
+ @engine.transaction
9
+ class TestColumn(CommonPostgresTest):
10
+
11
+ @classmethod
12
+ def create_test_tables(cls, tx):
13
+ """Create test tables for column tests."""
14
+ tx.table("mock_table").create(
15
+ columns={
16
+ "column1": int,
17
+ "column2": str,
18
+ "column3": str,
19
+ }
20
+ )
21
+
22
+ def test_init(self, tx):
23
+ column = tx.table("mock_table").column("column1")
24
+ self.assertIsInstance(column, Column)
25
+ self.assertEqual(column.name, "column1")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ unittest.main()
@@ -0,0 +1,25 @@
1
+ import unittest
2
+ from .common import CommonPostgresTest, engine, test_db
3
+
4
+
5
+ @engine.transaction
6
+ @engine.transaction
7
+ class TestConnections(CommonPostgresTest):
8
+
9
+ @classmethod
10
+ def create_test_tables(cls, tx):
11
+ """Create test tables for connection tests."""
12
+ tx.table("mock_table").create(
13
+ columns={
14
+ "column1": int,
15
+ "column2": str,
16
+ "column3": str,
17
+ }
18
+ )
19
+ def test_init(self, tx):
20
+ # Test the initialization of the Database object
21
+ assert tx.table("mock_table").exists()
22
+
23
+
24
+ if __name__ == "__main__":
25
+ unittest.main()
@@ -0,0 +1,21 @@
1
+ import unittest
2
+ from velocity.db.core.database import Database
3
+ from .common import CommonPostgresTest, engine, test_db
4
+
5
+
6
+ @engine.transaction
7
+ @engine.transaction
8
+ class TestDatabase(CommonPostgresTest):
9
+
10
+ @classmethod
11
+ def create_test_tables(cls, tx):
12
+ """No special tables needed for database tests."""
13
+ pass
14
+
15
+ def test_init(self, tx):
16
+ # Test the initialization of the Database object
17
+ tx.database
18
+
19
+
20
+ if __name__ == "__main__":
21
+ unittest.main()
@@ -0,0 +1,205 @@
1
+ import os
2
+ import datetime
3
+ import unittest
4
+ from velocity.db.core.engine import Engine
5
+ from velocity.db.core.transaction import Transaction
6
+ from .common import CommonPostgresTest, engine, test_db
7
+
8
+
9
+ @engine.transaction
10
+ class TestEngine(CommonPostgresTest):
11
+ @classmethod
12
+ def create_test_tables(cls, tx):
13
+ """No special tables needed for engine tests."""
14
+ pass
15
+
16
+ def test_engine_init(self, tx):
17
+ import psycopg2
18
+ from velocity.db.servers.postgres import SQL
19
+
20
+ # Test the engine instance from common test
21
+ assert engine.sql == SQL
22
+ assert engine.driver == psycopg2
23
+
24
+ def test_engine_attributes(self, tx):
25
+ import psycopg2
26
+ from velocity.db.servers.postgres import SQL
27
+
28
+ # Test private attributes
29
+ assert engine._Engine__sql == SQL
30
+ assert engine._Engine__driver == psycopg2
31
+
32
+ def test_connect(self, tx):
33
+ import psycopg2
34
+
35
+ assert engine.connect() != None
36
+ conn = engine.connect()
37
+ self.assertIsInstance(conn, psycopg2.extensions.connection)
38
+
39
+ def test_other_stuff(self, tx):
40
+ assert engine.version[:10] == "PostgreSQL"
41
+
42
+ timestamp = engine.timestamp
43
+ self.assertIsInstance(timestamp, datetime.datetime)
44
+ assert engine.user == os.environ["DBUser"]
45
+ assert test_db in engine.databases
46
+ assert "public" in engine.schemas
47
+ assert "information_schema" in engine.schemas
48
+ assert "public" == engine.current_schema
49
+ assert engine.current_database == test_db
50
+ assert [] == engine.views
51
+ assert [] == engine.tables
52
+
53
+ def test_process_error(self, tx):
54
+ local_engine = Engine(None, None, None) # Replace None with appropriate arguments
55
+ with self.assertRaises(
56
+ Exception
57
+ ): # Replace Exception with the specific exception raised by process_error
58
+ local_engine.process_error(sql_stmt=None, sql_params=None)
59
+ # Add additional assertions as needed
60
+
61
+ def test_transaction_injection_1(self, tx):
62
+ @engine.transaction
63
+ def function():
64
+ pass
65
+
66
+ function()
67
+
68
+ @engine.transaction
69
+ def function(_tx):
70
+ pass
71
+
72
+ with self.assertRaises(NameError):
73
+ function()
74
+
75
+ @engine.transaction
76
+ def function(tx):
77
+ pass
78
+
79
+ with self.assertRaises(TypeError):
80
+ function(tx=3)
81
+ with self.assertRaises(TypeError):
82
+ function(tx=None)
83
+
84
+ def test_transaction_injection_function(self, tx):
85
+ with engine.transaction() as original:
86
+
87
+ @engine.transaction
88
+ def function(tx):
89
+ assert tx != None
90
+ assert tx != original
91
+ self.assertIsInstance(tx, Transaction)
92
+
93
+ function()
94
+
95
+ @engine.transaction
96
+ def function(tx):
97
+ assert tx != None
98
+ assert tx == original
99
+ self.assertIsInstance(tx, Transaction)
100
+
101
+ function(original)
102
+
103
+ @engine.transaction
104
+ def function(tx=None):
105
+ assert tx != None
106
+ assert tx != original
107
+ self.assertIsInstance(tx, Transaction)
108
+
109
+ function()
110
+
111
+ @engine.transaction
112
+ def function(tx=None):
113
+ assert tx != None
114
+ assert tx == original
115
+ self.assertIsInstance(tx, Transaction)
116
+
117
+ function(original)
118
+
119
+ @engine.transaction
120
+ def function(tx=None):
121
+ assert tx != None
122
+ assert tx == original
123
+ self.assertIsInstance(tx, Transaction)
124
+
125
+ function(tx=original)
126
+
127
+ @engine.transaction
128
+ def function(tx, a, b):
129
+ assert tx != None
130
+ assert tx != original
131
+ self.assertIsInstance(tx, Transaction)
132
+
133
+ function(1, 2)
134
+
135
+ @engine.transaction
136
+ def function(tx, a, b):
137
+ assert tx != None
138
+ assert tx == original
139
+ self.assertIsInstance(tx, Transaction)
140
+
141
+ function(original, 1, 2)
142
+
143
+ @engine.transaction
144
+ def function(tx, a, b):
145
+ assert tx != None
146
+ assert tx == original
147
+ self.assertIsInstance(tx, Transaction)
148
+
149
+ function(tx=original, a=1, b=2)
150
+
151
+ @engine.transaction
152
+ def function(tx, src, b):
153
+ assert tx != None
154
+ assert tx != src
155
+ self.assertIsInstance(tx, Transaction)
156
+
157
+ function(src=original, b=2)
158
+
159
+ @engine.transaction
160
+ def function(a, tx, b):
161
+ assert tx != None
162
+ assert tx != original
163
+ self.assertIsInstance(tx, Transaction)
164
+
165
+ function(1, 2)
166
+
167
+ @engine.transaction
168
+ def function(a, tx, b):
169
+ assert tx != None
170
+ assert tx == original
171
+ self.assertIsInstance(tx, Transaction)
172
+
173
+ function(1, original, 2)
174
+
175
+ def test_transaction_injection_class(self, tx):
176
+ test_class = self
177
+ with engine.transaction() as original:
178
+
179
+ @engine.transaction
180
+ class TestClass:
181
+ @engine.transaction
182
+ def __init__(self, tx):
183
+ assert tx != None
184
+ assert tx != original
185
+ test_class.assertIsInstance(tx, Transaction)
186
+
187
+ def first_method(self, tx):
188
+ assert tx != None
189
+ assert tx != original
190
+ test_class.assertIsInstance(tx, Transaction)
191
+
192
+ def second_method(self, tx):
193
+ assert tx != None
194
+ assert tx == original
195
+ test_class.assertIsInstance(tx, Transaction)
196
+
197
+ tc = TestClass()
198
+
199
+ tc.first_method()
200
+ tc.second_method(original)
201
+ self.assertRaises(AssertionError, tc.second_method)
202
+
203
+
204
+ if __name__ == "__main__":
205
+ unittest.main()