velocity-python 0.0.131__py3-none-any.whl → 0.0.134__py3-none-any.whl

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 (88) hide show
  1. velocity/__init__.py +1 -1
  2. velocity/app/tests/__init__.py +1 -0
  3. velocity/app/tests/test_email_processing.py +112 -0
  4. velocity/app/tests/test_payment_profile_sorting.py +191 -0
  5. velocity/app/tests/test_spreadsheet_functions.py +124 -0
  6. velocity/aws/tests/__init__.py +1 -0
  7. velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
  8. velocity/aws/tests/test_response.py +163 -0
  9. velocity/db/core/decorators.py +20 -3
  10. velocity/db/core/engine.py +33 -7
  11. velocity/db/exceptions.py +7 -0
  12. velocity/db/servers/base/__init__.py +9 -0
  13. velocity/db/servers/base/initializer.py +70 -0
  14. velocity/db/servers/base/operators.py +98 -0
  15. velocity/db/servers/base/sql.py +503 -0
  16. velocity/db/servers/base/types.py +135 -0
  17. velocity/db/servers/mysql/__init__.py +73 -0
  18. velocity/db/servers/mysql/operators.py +54 -0
  19. velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
  20. velocity/db/servers/mysql/sql.py +569 -0
  21. velocity/db/servers/mysql/types.py +107 -0
  22. velocity/db/servers/postgres/__init__.py +52 -2
  23. velocity/db/servers/postgres/operators.py +34 -0
  24. velocity/db/servers/postgres/sql.py +4 -3
  25. velocity/db/servers/postgres/types.py +88 -2
  26. velocity/db/servers/sqlite/__init__.py +61 -0
  27. velocity/db/servers/sqlite/operators.py +52 -0
  28. velocity/db/servers/sqlite/reserved.py +20 -0
  29. velocity/db/servers/sqlite/sql.py +530 -0
  30. velocity/db/servers/sqlite/types.py +92 -0
  31. velocity/db/servers/sqlserver/__init__.py +73 -0
  32. velocity/db/servers/sqlserver/operators.py +47 -0
  33. velocity/db/servers/sqlserver/reserved.py +32 -0
  34. velocity/db/servers/sqlserver/sql.py +625 -0
  35. velocity/db/servers/sqlserver/types.py +114 -0
  36. velocity/db/tests/__init__.py +1 -0
  37. velocity/db/tests/common_db_test.py +0 -0
  38. velocity/db/tests/postgres/__init__.py +1 -0
  39. velocity/db/tests/postgres/common.py +49 -0
  40. velocity/db/tests/postgres/test_column.py +29 -0
  41. velocity/db/tests/postgres/test_connections.py +25 -0
  42. velocity/db/tests/postgres/test_database.py +21 -0
  43. velocity/db/tests/postgres/test_engine.py +205 -0
  44. velocity/db/tests/postgres/test_general_usage.py +88 -0
  45. velocity/db/tests/postgres/test_imports.py +8 -0
  46. velocity/db/tests/postgres/test_result.py +19 -0
  47. velocity/db/tests/postgres/test_row.py +137 -0
  48. velocity/db/tests/postgres/test_schema_locking.py +335 -0
  49. velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  50. velocity/db/tests/postgres/test_sequence.py +34 -0
  51. velocity/db/tests/postgres/test_table.py +101 -0
  52. velocity/db/tests/postgres/test_transaction.py +106 -0
  53. velocity/db/tests/sql/__init__.py +1 -0
  54. velocity/db/tests/sql/common.py +177 -0
  55. velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  56. velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  57. velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
  58. velocity/db/tests/test_db_utils.py +221 -0
  59. velocity/db/tests/test_postgres.py +212 -0
  60. velocity/db/tests/test_postgres_unchanged.py +81 -0
  61. velocity/db/tests/test_process_error_robustness.py +292 -0
  62. velocity/db/tests/test_result_caching.py +279 -0
  63. velocity/db/tests/test_result_sql_aware.py +117 -0
  64. velocity/db/tests/test_row_get_missing_column.py +72 -0
  65. velocity/db/tests/test_schema_locking_initializers.py +226 -0
  66. velocity/db/tests/test_schema_locking_simple.py +97 -0
  67. velocity/db/tests/test_sql_builder.py +165 -0
  68. velocity/db/tests/test_tablehelper.py +486 -0
  69. velocity/misc/tests/__init__.py +1 -0
  70. velocity/misc/tests/test_db.py +90 -0
  71. velocity/misc/tests/test_fix.py +78 -0
  72. velocity/misc/tests/test_format.py +64 -0
  73. velocity/misc/tests/test_iconv.py +203 -0
  74. velocity/misc/tests/test_merge.py +82 -0
  75. velocity/misc/tests/test_oconv.py +144 -0
  76. velocity/misc/tests/test_original_error.py +52 -0
  77. velocity/misc/tests/test_timer.py +74 -0
  78. {velocity_python-0.0.131.dist-info → velocity_python-0.0.134.dist-info}/METADATA +1 -1
  79. velocity_python-0.0.134.dist-info/RECORD +125 -0
  80. velocity/db/servers/mysql.py +0 -640
  81. velocity/db/servers/sqlite.py +0 -968
  82. velocity/db/servers/sqlite_reserved.py +0 -208
  83. velocity/db/servers/sqlserver.py +0 -921
  84. velocity/db/servers/sqlserver_reserved.py +0 -314
  85. velocity_python-0.0.131.dist-info/RECORD +0 -62
  86. {velocity_python-0.0.131.dist-info → velocity_python-0.0.134.dist-info}/WHEEL +0 -0
  87. {velocity_python-0.0.131.dist-info → velocity_python-0.0.134.dist-info}/licenses/LICENSE +0 -0
  88. {velocity_python-0.0.131.dist-info → velocity_python-0.0.134.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,503 @@
1
+ """
2
+ Abstract base class for SQL dialect implementations.
3
+ """
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any, Dict, List, Optional, Tuple, Union
6
+ from collections.abc import Mapping, Sequence
7
+
8
+
9
+ class BaseSQLDialect(ABC):
10
+ """
11
+ Abstract base class that defines the interface all database SQL dialects must implement.
12
+
13
+ This class ensures consistency across all database implementations and makes it clear
14
+ what methods each database dialect needs to provide.
15
+ """
16
+
17
+ # Database server identifier - must be set by subclasses
18
+ server: str = ""
19
+
20
+ # Column metadata identifiers - database specific
21
+ type_column_identifier: str = ""
22
+ is_nullable: str = ""
23
+
24
+ # Default schema name for this database
25
+ default_schema: str = ""
26
+
27
+ # Error code classifications - must be set by subclasses
28
+ ApplicationErrorCodes: List[str] = []
29
+ DatabaseMissingErrorCodes: List[str] = []
30
+ TableMissingErrorCodes: List[str] = []
31
+ ColumnMissingErrorCodes: List[str] = []
32
+ ForeignKeyMissingErrorCodes: List[str] = []
33
+ ConnectionErrorCodes: List[str] = []
34
+ DuplicateKeyErrorCodes: List[str] = []
35
+ RetryTransactionCodes: List[str] = []
36
+ TruncationErrorCodes: List[str] = []
37
+ LockTimeoutErrorCodes: List[str] = []
38
+ DatabaseObjectExistsErrorCodes: List[str] = []
39
+ DataIntegrityErrorCodes: List[str] = []
40
+
41
+ @classmethod
42
+ @abstractmethod
43
+ def get_error(cls, e: Exception) -> Optional[str]:
44
+ """
45
+ Extract error information from database exception.
46
+
47
+ Args:
48
+ e: Database exception
49
+
50
+ Returns:
51
+ Error code or message, or None if not applicable
52
+ """
53
+ pass
54
+
55
+ # Core CRUD Operations
56
+ @classmethod
57
+ @abstractmethod
58
+ def select(
59
+ cls,
60
+ tx: Any,
61
+ columns: Optional[Union[str, List[str]]] = None,
62
+ table: Optional[str] = None,
63
+ where: Optional[Union[str, Dict, List]] = None,
64
+ orderby: Optional[Union[str, List, Dict]] = None,
65
+ groupby: Optional[Union[str, List]] = None,
66
+ having: Optional[Union[str, Dict, List]] = None,
67
+ start: Optional[int] = None,
68
+ qty: Optional[int] = None,
69
+ lock: Optional[bool] = None,
70
+ skip_locked: Optional[bool] = None,
71
+ ) -> Tuple[str, List[Any]]:
72
+ """
73
+ Generate a SELECT statement.
74
+
75
+ Returns:
76
+ Tuple of (sql_string, parameters)
77
+ """
78
+ pass
79
+
80
+ @classmethod
81
+ @abstractmethod
82
+ def insert(cls, table: str, data: Dict[str, Any]) -> Tuple[str, List[Any]]:
83
+ """
84
+ Generate an INSERT statement.
85
+
86
+ Args:
87
+ table: Table name
88
+ data: Dictionary of column names to values
89
+
90
+ Returns:
91
+ Tuple of (sql_string, parameters)
92
+ """
93
+ pass
94
+
95
+ @classmethod
96
+ @abstractmethod
97
+ def update(
98
+ cls,
99
+ tx: Any,
100
+ table: str,
101
+ data: Dict[str, Any],
102
+ where: Optional[Union[str, Dict, List]] = None,
103
+ pk: Optional[Dict[str, Any]] = None,
104
+ excluded: bool = False
105
+ ) -> Tuple[str, List[Any]]:
106
+ """
107
+ Generate an UPDATE statement.
108
+
109
+ Args:
110
+ tx: Database transaction
111
+ table: Table name
112
+ data: Dictionary of columns to update
113
+ where: WHERE clause conditions
114
+ pk: Primary key conditions to merge with where
115
+ excluded: If True, creates EXCLUDED.col expressions for upserts
116
+
117
+ Returns:
118
+ Tuple of (sql_string, parameters)
119
+ """
120
+ pass
121
+
122
+ @classmethod
123
+ @abstractmethod
124
+ def delete(cls, tx: Any, table: str, where: Union[str, Dict, List]) -> Tuple[str, List[Any]]:
125
+ """
126
+ Generate a DELETE statement.
127
+
128
+ Args:
129
+ tx: Database transaction
130
+ table: Table name
131
+ where: WHERE clause conditions
132
+
133
+ Returns:
134
+ Tuple of (sql_string, parameters)
135
+ """
136
+ pass
137
+
138
+ @classmethod
139
+ @abstractmethod
140
+ def merge(
141
+ cls,
142
+ tx: Any,
143
+ table: str,
144
+ data: Dict[str, Any],
145
+ pk: Dict[str, Any],
146
+ on_conflict_do_nothing: bool,
147
+ on_conflict_update: bool
148
+ ) -> Tuple[str, List[Any]]:
149
+ """
150
+ Generate an UPSERT/MERGE statement.
151
+
152
+ Args:
153
+ tx: Database transaction
154
+ table: Table name
155
+ data: Data to insert/update
156
+ pk: Primary key columns
157
+ on_conflict_do_nothing: If True, ignore conflicts
158
+ on_conflict_update: If True, update on conflicts
159
+
160
+ Returns:
161
+ Tuple of (sql_string, parameters)
162
+ """
163
+ pass
164
+
165
+ # Database Metadata Operations
166
+ @classmethod
167
+ @abstractmethod
168
+ def version(cls) -> str:
169
+ """Get database version query."""
170
+ pass
171
+
172
+ @classmethod
173
+ @abstractmethod
174
+ def timestamp(cls) -> str:
175
+ """Get current timestamp query."""
176
+ pass
177
+
178
+ @classmethod
179
+ @abstractmethod
180
+ def user(cls) -> str:
181
+ """Get current user query."""
182
+ pass
183
+
184
+ @classmethod
185
+ @abstractmethod
186
+ def databases(cls) -> str:
187
+ """Get list of databases query."""
188
+ pass
189
+
190
+ @classmethod
191
+ @abstractmethod
192
+ def schemas(cls) -> str:
193
+ """Get list of schemas query."""
194
+ pass
195
+
196
+ @classmethod
197
+ @abstractmethod
198
+ def current_schema(cls) -> str:
199
+ """Get current schema query."""
200
+ pass
201
+
202
+ @classmethod
203
+ @abstractmethod
204
+ def current_database(cls) -> str:
205
+ """Get current database query."""
206
+ pass
207
+
208
+ @classmethod
209
+ @abstractmethod
210
+ def tables(cls, system: bool = False) -> str:
211
+ """
212
+ Get list of tables query.
213
+
214
+ Args:
215
+ system: Include system tables
216
+ """
217
+ pass
218
+
219
+ @classmethod
220
+ @abstractmethod
221
+ def views(cls, system: bool = False) -> str:
222
+ """
223
+ Get list of views query.
224
+
225
+ Args:
226
+ system: Include system views
227
+ """
228
+ pass
229
+
230
+ # Database Structure Operations
231
+ @classmethod
232
+ @abstractmethod
233
+ def create_database(cls, name: str) -> str:
234
+ """Generate CREATE DATABASE statement."""
235
+ pass
236
+
237
+ @classmethod
238
+ @abstractmethod
239
+ def drop_database(cls, name: str) -> str:
240
+ """Generate DROP DATABASE statement."""
241
+ pass
242
+
243
+ @classmethod
244
+ @abstractmethod
245
+ def create_table(cls, name: str, columns: Dict[str, Any] = None, drop: bool = False) -> str:
246
+ """
247
+ Generate CREATE TABLE statement.
248
+
249
+ Args:
250
+ name: Table name
251
+ columns: Column definitions
252
+ drop: Drop table if exists first
253
+ """
254
+ pass
255
+
256
+ @classmethod
257
+ @abstractmethod
258
+ def drop_table(cls, name: str) -> str:
259
+ """Generate DROP TABLE statement."""
260
+ pass
261
+
262
+ @classmethod
263
+ @abstractmethod
264
+ def truncate(cls, table: str) -> str:
265
+ """Generate TRUNCATE statement."""
266
+ pass
267
+
268
+ # Column Operations
269
+ @classmethod
270
+ @abstractmethod
271
+ def columns(cls, name: str) -> str:
272
+ """Get table columns query."""
273
+ pass
274
+
275
+ @classmethod
276
+ @abstractmethod
277
+ def column_info(cls, table: str, name: str) -> str:
278
+ """Get column information query."""
279
+ pass
280
+
281
+ @classmethod
282
+ @abstractmethod
283
+ def drop_column(cls, table: str, name: str, cascade: bool = True) -> str:
284
+ """Generate DROP COLUMN statement."""
285
+ pass
286
+
287
+ @classmethod
288
+ @abstractmethod
289
+ def alter_add(cls, table: str, columns: Dict[str, Any], null_allowed: bool = True) -> str:
290
+ """Generate ALTER TABLE ADD COLUMN statement."""
291
+ pass
292
+
293
+ @classmethod
294
+ @abstractmethod
295
+ def alter_drop(cls, table: str, columns: List[str]) -> str:
296
+ """Generate ALTER TABLE DROP COLUMN statement."""
297
+ pass
298
+
299
+ @classmethod
300
+ @abstractmethod
301
+ def alter_column_by_type(cls, table: str, column: str, value: str, nullable: bool = True) -> str:
302
+ """Generate ALTER COLUMN statement by type."""
303
+ pass
304
+
305
+ @classmethod
306
+ @abstractmethod
307
+ def alter_column_by_sql(cls, table: str, column: str, value: str) -> str:
308
+ """Generate ALTER COLUMN statement by SQL."""
309
+ pass
310
+
311
+ @classmethod
312
+ @abstractmethod
313
+ def rename_column(cls, table: str, orig: str, new: str) -> str:
314
+ """Generate RENAME COLUMN statement."""
315
+ pass
316
+
317
+ @classmethod
318
+ @abstractmethod
319
+ def rename_table(cls, table: str, new: str) -> str:
320
+ """Generate RENAME TABLE statement."""
321
+ pass
322
+
323
+ # Key Operations
324
+ @classmethod
325
+ @abstractmethod
326
+ def primary_keys(cls, table: str) -> str:
327
+ """Get primary key columns query."""
328
+ pass
329
+
330
+ @classmethod
331
+ @abstractmethod
332
+ def foreign_key_info(
333
+ cls,
334
+ table: Optional[str] = None,
335
+ column: Optional[str] = None,
336
+ schema: Optional[str] = None
337
+ ) -> str:
338
+ """Get foreign key information query."""
339
+ pass
340
+
341
+ @classmethod
342
+ @abstractmethod
343
+ def create_foreign_key(
344
+ cls,
345
+ table: str,
346
+ columns: List[str],
347
+ key_to_table: str,
348
+ key_to_columns: List[str],
349
+ name: Optional[str] = None,
350
+ schema: Optional[str] = None,
351
+ ) -> str:
352
+ """Generate CREATE FOREIGN KEY statement."""
353
+ pass
354
+
355
+ @classmethod
356
+ @abstractmethod
357
+ def drop_foreign_key(
358
+ cls,
359
+ table: str,
360
+ columns: List[str],
361
+ key_to_table: Optional[str] = None,
362
+ key_to_columns: Optional[List[str]] = None,
363
+ name: Optional[str] = None,
364
+ schema: Optional[str] = None,
365
+ ) -> str:
366
+ """Generate DROP FOREIGN KEY statement."""
367
+ pass
368
+
369
+ # Index Operations
370
+ @classmethod
371
+ @abstractmethod
372
+ def create_index(
373
+ cls,
374
+ tx: Any,
375
+ table: Optional[str] = None,
376
+ columns: Optional[List[str]] = None,
377
+ unique: bool = False,
378
+ direction: Optional[str] = None,
379
+ where: Optional[str] = None,
380
+ name: Optional[str] = None,
381
+ schema: Optional[str] = None,
382
+ trigram: Optional[bool] = None,
383
+ lower: Optional[bool] = None,
384
+ ) -> str:
385
+ """Generate CREATE INDEX statement."""
386
+ pass
387
+
388
+ @classmethod
389
+ @abstractmethod
390
+ def drop_index(
391
+ cls,
392
+ table: Optional[str] = None,
393
+ columns: Optional[List[str]] = None,
394
+ name: Optional[str] = None,
395
+ schema: Optional[str] = None,
396
+ trigram: Optional[bool] = None,
397
+ ) -> str:
398
+ """Generate DROP INDEX statement."""
399
+ pass
400
+
401
+ @classmethod
402
+ @abstractmethod
403
+ def indexes(cls, table: str) -> str:
404
+ """Get table indexes query."""
405
+ pass
406
+
407
+ # Transaction Operations
408
+ @classmethod
409
+ @abstractmethod
410
+ def create_savepoint(cls, sp: str) -> str:
411
+ """Generate SAVEPOINT statement."""
412
+ pass
413
+
414
+ @classmethod
415
+ @abstractmethod
416
+ def release_savepoint(cls, sp: str) -> str:
417
+ """Generate RELEASE SAVEPOINT statement."""
418
+ pass
419
+
420
+ @classmethod
421
+ @abstractmethod
422
+ def rollback_savepoint(cls, sp: str) -> str:
423
+ """Generate ROLLBACK TO SAVEPOINT statement."""
424
+ pass
425
+
426
+ # View Operations
427
+ @classmethod
428
+ @abstractmethod
429
+ def create_view(cls, name: str, query: str, temp: bool = False, silent: bool = True) -> str:
430
+ """Generate CREATE VIEW statement."""
431
+ pass
432
+
433
+ @classmethod
434
+ @abstractmethod
435
+ def drop_view(cls, name: str, silent: bool = True) -> str:
436
+ """Generate DROP VIEW statement."""
437
+ pass
438
+
439
+ # Sequence/Identity Operations
440
+ @classmethod
441
+ @abstractmethod
442
+ def last_id(cls, table: str) -> str:
443
+ """Get last inserted ID query."""
444
+ pass
445
+
446
+ @classmethod
447
+ @abstractmethod
448
+ def current_id(cls, table: str) -> str:
449
+ """Get current sequence value query."""
450
+ pass
451
+
452
+ @classmethod
453
+ @abstractmethod
454
+ def set_id(cls, table: str, start: int) -> str:
455
+ """Generate set sequence value statement."""
456
+ pass
457
+
458
+ @classmethod
459
+ @abstractmethod
460
+ def set_sequence(cls, table: str, next_value: int) -> str:
461
+ """Generate set sequence next value statement."""
462
+ pass
463
+
464
+ # Utility Operations
465
+ @classmethod
466
+ @abstractmethod
467
+ def massage_data(cls, data: Dict[str, Any]) -> Dict[str, Any]:
468
+ """
469
+ Massage data before insert/update operations.
470
+ Database-specific data transformations.
471
+ """
472
+ pass
473
+
474
+ @classmethod
475
+ @abstractmethod
476
+ def alter_trigger(cls, table: str, state: str = "ENABLE", name: str = "USER") -> str:
477
+ """Generate ALTER TRIGGER statement."""
478
+ pass
479
+
480
+ @classmethod
481
+ @abstractmethod
482
+ def missing(
483
+ cls,
484
+ tx: Any,
485
+ table: str,
486
+ list_values: List[Any],
487
+ column: str = "SYS_ID",
488
+ where: Optional[Union[str, Dict, List]] = None,
489
+ ) -> Tuple[str, List[Any]]:
490
+ """
491
+ Generate query to find missing values from a list.
492
+
493
+ Args:
494
+ tx: Database transaction
495
+ table: Table name
496
+ list_values: List of values to check
497
+ column: Column to check against
498
+ where: Additional WHERE conditions
499
+
500
+ Returns:
501
+ Tuple of (sql_string, parameters)
502
+ """
503
+ pass
@@ -0,0 +1,135 @@
1
+ """
2
+ Abstract base class for database type mapping implementations.
3
+ """
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any, Type, Union
6
+ import datetime
7
+ import decimal
8
+
9
+
10
+ class BaseTypes(ABC):
11
+ """
12
+ Abstract base class that defines the interface for database type mappings.
13
+
14
+ Each database implementation should provide concrete implementations of these
15
+ type mapping methods to handle conversion between Python types and SQL types.
16
+ """
17
+
18
+ # Basic SQL type constants - should be overridden by subclasses
19
+ TEXT: str = "TEXT"
20
+ INTEGER: str = "INTEGER"
21
+ NUMERIC: str = "NUMERIC"
22
+ BOOLEAN: str = "BOOLEAN"
23
+ DATE: str = "DATE"
24
+ TIME: str = "TIME"
25
+ DATETIME: str = "DATETIME"
26
+ TIMESTAMP: str = "TIMESTAMP"
27
+ BINARY: str = "BINARY"
28
+ BIGINT: str = "BIGINT"
29
+ SMALLINT: str = "SMALLINT"
30
+
31
+ @classmethod
32
+ @abstractmethod
33
+ def get_type(cls, v: Any) -> str:
34
+ """
35
+ Returns a suitable SQL type string for a Python value/object.
36
+
37
+ This method should handle conversion of Python types to appropriate
38
+ SQL column types for table creation and schema operations.
39
+
40
+ Args:
41
+ v: Python value or type to convert
42
+
43
+ Returns:
44
+ SQL type string appropriate for this database
45
+
46
+ Examples:
47
+ get_type(str) -> "TEXT"
48
+ get_type("hello") -> "TEXT"
49
+ get_type(int) -> "INTEGER"
50
+ get_type(123) -> "INTEGER"
51
+ get_type(datetime.datetime.now()) -> "TIMESTAMP"
52
+ """
53
+ pass
54
+
55
+ @classmethod
56
+ @abstractmethod
57
+ def get_conv(cls, v: Any) -> str:
58
+ """
59
+ Returns a base SQL type for expression usage (e.g. CAST operations).
60
+
61
+ This is typically used for CAST expressions and should return
62
+ the fundamental SQL type without precision/scale specifiers.
63
+
64
+ Args:
65
+ v: Python value or type to convert
66
+
67
+ Returns:
68
+ Base SQL type string for casting
69
+
70
+ Examples:
71
+ get_conv(decimal.Decimal("123.45")) -> "NUMERIC"
72
+ get_conv(datetime.datetime.now()) -> "TIMESTAMP"
73
+ """
74
+ pass
75
+
76
+ @classmethod
77
+ @abstractmethod
78
+ def py_type(cls, sql_type: str) -> Type:
79
+ """
80
+ Returns the Python type that corresponds to an SQL type string.
81
+
82
+ This method handles the reverse mapping from SQL types back to
83
+ Python types, typically used when reading schema information.
84
+
85
+ Args:
86
+ sql_type: SQL type string from database schema
87
+
88
+ Returns:
89
+ Corresponding Python type
90
+
91
+ Examples:
92
+ py_type("INTEGER") -> int
93
+ py_type("TEXT") -> str
94
+ py_type("TIMESTAMP") -> datetime.datetime
95
+
96
+ Raises:
97
+ Exception: If the SQL type is not recognized
98
+ """
99
+ pass
100
+
101
+ @classmethod
102
+ def _handle_special_values(cls, v: Any) -> tuple[bool, str]:
103
+ """
104
+ Helper method to handle special value prefixes like @@CURRENT_TIMESTAMP.
105
+
106
+ Args:
107
+ v: Value to check
108
+
109
+ Returns:
110
+ Tuple of (is_special, processed_value)
111
+ """
112
+ if isinstance(v, str) and v.startswith("@@"):
113
+ return True, v[2:] or cls.TEXT
114
+ return False, ""
115
+
116
+ @classmethod
117
+ def _get_basic_type_mapping(cls) -> dict[Type, str]:
118
+ """
119
+ Returns basic Python type to SQL type mappings.
120
+ Subclasses can override this to customize mappings.
121
+
122
+ Returns:
123
+ Dictionary mapping Python types to SQL types
124
+ """
125
+ return {
126
+ str: cls.TEXT,
127
+ int: cls.INTEGER,
128
+ float: cls.NUMERIC,
129
+ bool: cls.BOOLEAN,
130
+ datetime.datetime: cls.DATETIME,
131
+ datetime.date: cls.DATE,
132
+ datetime.time: cls.TIME,
133
+ decimal.Decimal: cls.NUMERIC,
134
+ bytes: cls.BINARY,
135
+ }
@@ -0,0 +1,73 @@
1
+ import os
2
+ from ..base.initializer import BaseInitializer
3
+ from velocity.db.core import engine
4
+ from .sql import SQL
5
+
6
+
7
+ class MySQLInitializer(BaseInitializer):
8
+ """MySQL database initializer."""
9
+
10
+ @staticmethod
11
+ def initialize(config=None, schema_locked=False, **kwargs):
12
+ """
13
+ Initialize MySQL engine with mysql.connector driver.
14
+
15
+ Args:
16
+ config: Configuration dictionary
17
+ schema_locked: Boolean to lock schema modifications
18
+ **kwargs: Additional configuration parameters
19
+
20
+ Returns:
21
+ Configured Engine instance
22
+ """
23
+ try:
24
+ import mysql.connector
25
+ except ImportError:
26
+ raise ImportError(
27
+ "MySQL connector not available. Install with: pip install mysql-connector-python"
28
+ )
29
+
30
+ # Base configuration from environment (if available)
31
+ base_config = {
32
+ "database": os.environ.get("DBDatabase"),
33
+ "host": os.environ.get("DBHost"),
34
+ "port": os.environ.get("DBPort"),
35
+ "user": os.environ.get("DBUser"),
36
+ "password": os.environ.get("DBPassword"),
37
+ }
38
+
39
+ # Remove None values
40
+ base_config = {k: v for k, v in base_config.items() if v is not None}
41
+
42
+ # Set MySQL-specific defaults
43
+ mysql_defaults = {
44
+ "host": "localhost",
45
+ "port": 3306,
46
+ "charset": "utf8mb4",
47
+ "autocommit": False,
48
+ }
49
+
50
+ # Merge configurations: defaults < env < config < kwargs
51
+ final_config = mysql_defaults.copy()
52
+ final_config.update(base_config)
53
+ final_config = MySQLInitializer._merge_config(final_config, config, **kwargs)
54
+
55
+ # Validate required configuration
56
+ required_keys = ["database", "host", "user"]
57
+ MySQLInitializer._validate_required_config(final_config, required_keys)
58
+
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)
64
+
65
+
66
+ # Maintain backward compatibility
67
+ def initialize(config=None, schema_locked=False, **kwargs):
68
+ """Backward compatible initialization function."""
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)