sqlspec 0.17.1__py3-none-any.whl → 0.18.0__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 sqlspec might be problematic. Click here for more details.

Files changed (75) hide show
  1. sqlspec/__init__.py +1 -1
  2. sqlspec/_sql.py +54 -159
  3. sqlspec/adapters/adbc/config.py +24 -30
  4. sqlspec/adapters/adbc/driver.py +42 -61
  5. sqlspec/adapters/aiosqlite/config.py +5 -10
  6. sqlspec/adapters/aiosqlite/driver.py +9 -25
  7. sqlspec/adapters/aiosqlite/pool.py +43 -35
  8. sqlspec/adapters/asyncmy/config.py +10 -7
  9. sqlspec/adapters/asyncmy/driver.py +18 -39
  10. sqlspec/adapters/asyncpg/config.py +4 -0
  11. sqlspec/adapters/asyncpg/driver.py +32 -79
  12. sqlspec/adapters/bigquery/config.py +12 -65
  13. sqlspec/adapters/bigquery/driver.py +39 -133
  14. sqlspec/adapters/duckdb/config.py +11 -15
  15. sqlspec/adapters/duckdb/driver.py +61 -85
  16. sqlspec/adapters/duckdb/pool.py +2 -5
  17. sqlspec/adapters/oracledb/_types.py +8 -1
  18. sqlspec/adapters/oracledb/config.py +55 -38
  19. sqlspec/adapters/oracledb/driver.py +35 -92
  20. sqlspec/adapters/oracledb/migrations.py +257 -0
  21. sqlspec/adapters/psqlpy/config.py +13 -9
  22. sqlspec/adapters/psqlpy/driver.py +28 -103
  23. sqlspec/adapters/psycopg/config.py +9 -5
  24. sqlspec/adapters/psycopg/driver.py +107 -175
  25. sqlspec/adapters/sqlite/config.py +7 -5
  26. sqlspec/adapters/sqlite/driver.py +37 -73
  27. sqlspec/adapters/sqlite/pool.py +3 -12
  28. sqlspec/base.py +1 -8
  29. sqlspec/builder/__init__.py +1 -1
  30. sqlspec/builder/_base.py +34 -20
  31. sqlspec/builder/_ddl.py +407 -183
  32. sqlspec/builder/_insert.py +1 -1
  33. sqlspec/builder/mixins/_insert_operations.py +26 -6
  34. sqlspec/builder/mixins/_merge_operations.py +1 -1
  35. sqlspec/builder/mixins/_select_operations.py +1 -5
  36. sqlspec/config.py +32 -13
  37. sqlspec/core/__init__.py +89 -14
  38. sqlspec/core/cache.py +57 -104
  39. sqlspec/core/compiler.py +57 -112
  40. sqlspec/core/filters.py +1 -21
  41. sqlspec/core/hashing.py +13 -47
  42. sqlspec/core/parameters.py +272 -261
  43. sqlspec/core/result.py +12 -27
  44. sqlspec/core/splitter.py +17 -21
  45. sqlspec/core/statement.py +150 -159
  46. sqlspec/driver/_async.py +2 -15
  47. sqlspec/driver/_common.py +16 -95
  48. sqlspec/driver/_sync.py +2 -15
  49. sqlspec/driver/mixins/_result_tools.py +8 -29
  50. sqlspec/driver/mixins/_sql_translator.py +6 -8
  51. sqlspec/exceptions.py +1 -2
  52. sqlspec/loader.py +43 -115
  53. sqlspec/migrations/__init__.py +1 -1
  54. sqlspec/migrations/base.py +34 -45
  55. sqlspec/migrations/commands.py +34 -15
  56. sqlspec/migrations/loaders.py +1 -1
  57. sqlspec/migrations/runner.py +104 -19
  58. sqlspec/migrations/tracker.py +49 -2
  59. sqlspec/protocols.py +3 -6
  60. sqlspec/storage/__init__.py +4 -4
  61. sqlspec/storage/backends/fsspec.py +5 -6
  62. sqlspec/storage/backends/obstore.py +7 -8
  63. sqlspec/storage/registry.py +3 -3
  64. sqlspec/utils/__init__.py +2 -2
  65. sqlspec/utils/logging.py +6 -10
  66. sqlspec/utils/sync_tools.py +27 -4
  67. sqlspec/utils/text.py +6 -1
  68. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
  69. sqlspec-0.18.0.dist-info/RECORD +138 -0
  70. sqlspec/builder/_ddl_utils.py +0 -103
  71. sqlspec-0.17.1.dist-info/RECORD +0 -138
  72. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
  73. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
  74. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
  75. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/core/statement.py CHANGED
@@ -1,22 +1,6 @@
1
- """SQL statement with complete backward compatibility.
1
+ """SQL statement and configuration management."""
2
2
 
3
- This module implements the core SQL class and StatementConfig with complete
4
- backward compatibility while using an optimized processing pipeline.
5
-
6
- Components:
7
- - SQL class: SQL statement with identical external interface
8
- - StatementConfig: Complete backward compatibility for all driver requirements
9
- - ProcessedState: Cached processing results
10
-
11
- Features:
12
- - Lazy compilation: Only compile when needed
13
- - Cached properties: Avoid redundant computation
14
- - Complete StatementConfig compatibility
15
- - Integrated parameter processing and compilation caching
16
- """
17
-
18
- import contextlib
19
- from typing import TYPE_CHECKING, Any, Callable, Optional, Union
3
+ from typing import TYPE_CHECKING, Any, Callable, Final, Optional, Union
20
4
 
21
5
  import sqlglot
22
6
  from mypy_extensions import mypyc_attr
@@ -24,7 +8,7 @@ from sqlglot import exp
24
8
  from sqlglot.errors import ParseError
25
9
  from typing_extensions import TypeAlias
26
10
 
27
- from sqlspec.core.compiler import SQLProcessor
11
+ from sqlspec.core.compiler import OperationType, SQLProcessor
28
12
  from sqlspec.core.parameters import ParameterConverter, ParameterStyle, ParameterStyleConfig, ParameterValidator
29
13
  from sqlspec.typing import Empty, EmptyEnum
30
14
  from sqlspec.utils.logging import get_logger
@@ -46,7 +30,10 @@ __all__ = (
46
30
  )
47
31
  logger = get_logger("sqlspec.core.statement")
48
32
 
49
- SQL_CONFIG_SLOTS = (
33
+ RETURNS_ROWS_OPERATIONS: Final = {"SELECT", "WITH", "VALUES", "TABLE", "SHOW", "DESCRIBE", "PRAGMA"}
34
+ MODIFYING_OPERATIONS: Final = {"INSERT", "UPDATE", "DELETE", "MERGE", "UPSERT"}
35
+
36
+ SQL_CONFIG_SLOTS: Final = (
50
37
  "pre_process_steps",
51
38
  "post_process_steps",
52
39
  "dialect",
@@ -65,7 +52,7 @@ SQL_CONFIG_SLOTS = (
65
52
  "parameter_validator",
66
53
  )
67
54
 
68
- PROCESSED_STATE_SLOTS = (
55
+ PROCESSED_STATE_SLOTS: Final = (
69
56
  "compiled_sql",
70
57
  "execution_parameters",
71
58
  "parsed_expression",
@@ -77,20 +64,17 @@ PROCESSED_STATE_SLOTS = (
77
64
 
78
65
  @mypyc_attr(allow_interpreted_subclasses=False)
79
66
  class ProcessedState:
80
- """Cached processing results for SQL statements.
81
-
82
- Stores the results of processing to avoid redundant compilation,
83
- parsing, and parameter processing.
84
- """
67
+ """Processing results for SQL statements."""
85
68
 
86
69
  __slots__ = PROCESSED_STATE_SLOTS
70
+ operation_type: "OperationType"
87
71
 
88
72
  def __init__(
89
73
  self,
90
74
  compiled_sql: str,
91
75
  execution_parameters: Any,
92
76
  parsed_expression: "Optional[exp.Expression]" = None,
93
- operation_type: str = "UNKNOWN",
77
+ operation_type: "OperationType" = "UNKNOWN",
94
78
  validation_errors: "Optional[list[str]]" = None,
95
79
  is_many: bool = False,
96
80
  ) -> None:
@@ -105,20 +89,9 @@ class ProcessedState:
105
89
  return hash((self.compiled_sql, str(self.execution_parameters), self.operation_type))
106
90
 
107
91
 
108
- @mypyc_attr(allow_interpreted_subclasses=True) # Enable when MyPyC ready
92
+ @mypyc_attr(allow_interpreted_subclasses=False)
109
93
  class SQL:
110
- """SQL statement with complete backward compatibility.
111
-
112
- Provides 100% backward compatibility while using an optimized
113
- core processing pipeline.
114
-
115
- Features:
116
- - Lazy evaluation with cached properties
117
- - Integrated parameter processing pipeline
118
- - Complete StatementFilter and execution mode support
119
- - Same parameter processing behavior
120
- - Same result types and interfaces
121
- """
94
+ """SQL statement with parameter and filter support."""
122
95
 
123
96
  __slots__ = (
124
97
  "_dialect",
@@ -151,9 +124,9 @@ class SQL:
151
124
  is_many: Mark as execute_many operation
152
125
  **kwargs: Additional parameters
153
126
  """
154
- self._statement_config = statement_config or self._create_auto_config(statement, parameters, kwargs)
155
-
156
- self._dialect = self._normalize_dialect(self._statement_config.dialect)
127
+ config = statement_config or self._create_auto_config(statement, parameters, kwargs)
128
+ self._statement_config = config
129
+ self._dialect = self._normalize_dialect(config.dialect)
157
130
  self._processed_state: Union[EmptyEnum, ProcessedState] = Empty
158
131
  self._hash: Optional[int] = None
159
132
  self._filters: list[StatementFilter] = []
@@ -169,7 +142,8 @@ class SQL:
169
142
  if isinstance(statement, str):
170
143
  self._raw_sql = statement
171
144
  else:
172
- self._raw_sql = statement.sql(dialect=str(self._dialect) if self._dialect else None)
145
+ dialect = self._dialect
146
+ self._raw_sql = statement.sql(dialect=str(dialect) if dialect else None)
173
147
 
174
148
  self._is_many = is_many if is_many is not None else self._should_auto_detect_many(parameters)
175
149
 
@@ -188,10 +162,7 @@ class SQL:
188
162
  return None
189
163
  if isinstance(dialect, str):
190
164
  return dialect
191
- try:
192
- return dialect.__class__.__name__.lower()
193
- except AttributeError:
194
- return str(dialect)
165
+ return dialect.__class__.__name__.lower()
195
166
 
196
167
  def _init_from_sql_object(self, sql_obj: "SQL") -> None:
197
168
  """Initialize from existing SQL object."""
@@ -208,19 +179,18 @@ class SQL:
208
179
  """Auto-detect execute_many from parameter structure."""
209
180
  if len(parameters) == 1 and isinstance(parameters[0], list):
210
181
  param_list = parameters[0]
211
- if len(param_list) > 1 and all(isinstance(item, (tuple, list)) for item in param_list):
212
- return True
182
+ if param_list and all(isinstance(item, (tuple, list)) for item in param_list):
183
+ return len(param_list) > 1
213
184
  return False
214
185
 
215
186
  def _process_parameters(self, *parameters: Any, dialect: Optional[str] = None, **kwargs: Any) -> None:
216
- """Process parameters using parameter system."""
187
+ """Process parameters and filters."""
217
188
  if dialect is not None:
218
189
  self._dialect = self._normalize_dialect(dialect)
219
190
 
220
191
  if "is_script" in kwargs:
221
192
  self._is_script = bool(kwargs.pop("is_script"))
222
193
 
223
- # Optimize parameter filtering with direct iteration
224
194
  filters: list[StatementFilter] = []
225
195
  actual_params: list[Any] = []
226
196
  for p in parameters:
@@ -249,36 +219,33 @@ class SQL:
249
219
 
250
220
  self._named_parameters.update(kwargs)
251
221
 
252
- # PRESERVED PROPERTIES - Exact same interface as existing SQL class
253
222
  @property
254
223
  def sql(self) -> str:
255
- """Get the raw SQL string - no compilation triggered."""
224
+ """Get the raw SQL string."""
256
225
  return self._raw_sql
257
226
 
258
227
  @property
259
228
  def parameters(self) -> Any:
260
- """Get the original parameters without triggering compilation."""
229
+ """Get the original parameters."""
261
230
  if self._named_parameters:
262
231
  return self._named_parameters
263
232
  return self._positional_parameters or []
264
233
 
265
234
  @property
266
- def operation_type(self) -> str:
267
- """SQL operation type - requires explicit compilation."""
235
+ def operation_type(self) -> "OperationType":
236
+ """SQL operation type."""
268
237
  if self._processed_state is Empty:
269
238
  return "UNKNOWN"
270
239
  return self._processed_state.operation_type
271
240
 
272
241
  @property
273
242
  def statement_config(self) -> "StatementConfig":
274
- """Statement configuration - preserved interface."""
243
+ """Statement configuration."""
275
244
  return self._statement_config
276
245
 
277
246
  @property
278
247
  def expression(self) -> "Optional[exp.Expression]":
279
- """SQLGlot expression - only available after explicit compilation."""
280
- # This property should only be accessed after compilation
281
- # If not compiled yet, return None
248
+ """SQLGlot expression."""
282
249
  if self._processed_state is not Empty:
283
250
  return self._processed_state.parsed_expression
284
251
  return None
@@ -310,7 +277,7 @@ class SQL:
310
277
 
311
278
  @property
312
279
  def validation_errors(self) -> "list[str]":
313
- """Validation errors - requires explicit compilation."""
280
+ """Validation errors."""
314
281
  if self._processed_state is Empty:
315
282
  return []
316
283
  return self._processed_state.validation_errors.copy()
@@ -322,11 +289,21 @@ class SQL:
322
289
 
323
290
  def returns_rows(self) -> bool:
324
291
  """Check if statement returns rows."""
325
- sql_upper = self._raw_sql.strip().upper()
326
- if any(sql_upper.startswith(op) for op in ("SELECT", "WITH", "VALUES", "TABLE", "SHOW", "DESCRIBE", "PRAGMA")):
292
+ if self._processed_state is Empty:
293
+ self.compile()
294
+ if self._processed_state is Empty:
295
+ return False
296
+
297
+ op_type = self._processed_state.operation_type
298
+ if op_type in RETURNS_ROWS_OPERATIONS:
327
299
  return True
328
300
 
329
- return "RETURNING" in sql_upper
301
+ if self._processed_state.parsed_expression:
302
+ expr = self._processed_state.parsed_expression
303
+ if isinstance(expr, (exp.Insert, exp.Update, exp.Delete)) and expr.args.get("returning"):
304
+ return True
305
+
306
+ return False
330
307
 
331
308
  def is_modifying_operation(self) -> bool:
332
309
  """Check if the SQL statement is a modifying operation.
@@ -334,23 +311,27 @@ class SQL:
334
311
  Returns:
335
312
  True if the operation modifies data (INSERT/UPDATE/DELETE)
336
313
  """
337
- expression = self.expression
338
- if expression and isinstance(expression, (exp.Insert, exp.Update, exp.Delete)):
314
+ if self._processed_state is Empty:
315
+ return False
316
+
317
+ op_type = self._processed_state.operation_type
318
+ if op_type in MODIFYING_OPERATIONS:
339
319
  return True
340
320
 
341
- sql_upper = self.sql.strip().upper()
342
- modifying_operations = ("INSERT", "UPDATE", "DELETE")
343
- return any(sql_upper.startswith(op) for op in modifying_operations)
321
+ if self._processed_state.parsed_expression:
322
+ return isinstance(self._processed_state.parsed_expression, (exp.Insert, exp.Update, exp.Delete, exp.Merge))
323
+
324
+ return False
344
325
 
345
326
  def compile(self) -> tuple[str, Any]:
346
- """Explicitly compile the SQL statement."""
327
+ """Compile the SQL statement."""
347
328
  if self._processed_state is Empty:
348
329
  try:
349
- # Avoid unnecessary variable assignment
350
- processor = SQLProcessor(self._statement_config)
351
- compiled_result = processor.compile(
352
- self._raw_sql, self._named_parameters or self._positional_parameters, is_many=self._is_many
353
- )
330
+ config = self._statement_config
331
+ raw_sql = self._raw_sql
332
+ params = self._named_parameters or self._positional_parameters
333
+ is_many = self._is_many
334
+ compiled_result = SQLProcessor(config).compile(raw_sql, params, is_many=is_many)
354
335
 
355
336
  self._processed_state = ProcessedState(
356
337
  compiled_sql=compiled_result.compiled_sql,
@@ -373,10 +354,10 @@ class SQL:
373
354
 
374
355
  def as_script(self) -> "SQL":
375
356
  """Mark as script execution."""
376
- new_sql = SQL(
377
- self._raw_sql, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
378
- )
379
- # Preserve accumulated parameters when marking as script
357
+ original_params = self._original_parameters
358
+ config = self._statement_config
359
+ is_many = self._is_many
360
+ new_sql = SQL(self._raw_sql, *original_params, statement_config=config, is_many=is_many)
380
361
  new_sql._named_parameters.update(self._named_parameters)
381
362
  new_sql._positional_parameters = self._positional_parameters.copy()
382
363
  new_sql._filters = self._filters.copy()
@@ -394,7 +375,6 @@ class SQL:
394
375
  is_many=self._is_many,
395
376
  **kwargs,
396
377
  )
397
- # Only preserve accumulated parameters when no explicit parameters are provided
398
378
  if parameters is None:
399
379
  new_sql._named_parameters.update(self._named_parameters)
400
380
  new_sql._positional_parameters = self._positional_parameters.copy()
@@ -411,9 +391,10 @@ class SQL:
411
391
  Returns:
412
392
  New SQL instance with the added parameter
413
393
  """
414
- new_sql = SQL(
415
- self._raw_sql, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
416
- )
394
+ original_params = self._original_parameters
395
+ config = self._statement_config
396
+ is_many = self._is_many
397
+ new_sql = SQL(self._raw_sql, *original_params, statement_config=config, is_many=is_many)
417
398
  new_sql._named_parameters.update(self._named_parameters)
418
399
  new_sql._named_parameters[name] = value
419
400
  new_sql._positional_parameters = self._positional_parameters.copy()
@@ -429,20 +410,12 @@ class SQL:
429
410
  Returns:
430
411
  New SQL instance with the WHERE condition applied
431
412
  """
432
- # Parse current SQL with copy=False optimization
433
- current_expr = None
434
- with contextlib.suppress(ParseError):
413
+ try:
435
414
  current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
415
+ except ParseError:
416
+ subquery_sql = f"SELECT * FROM ({self._raw_sql}) AS subquery"
417
+ current_expr = sqlglot.parse_one(subquery_sql, dialect=self._dialect)
436
418
 
437
- if current_expr is None:
438
- try:
439
- current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
440
- except ParseError:
441
- # Use f-string optimization and copy=False
442
- subquery_sql = f"SELECT * FROM ({self._raw_sql}) AS subquery"
443
- current_expr = sqlglot.parse_one(subquery_sql, dialect=self._dialect)
444
-
445
- # Parse condition with copy=False optimization
446
419
  condition_expr: exp.Expression
447
420
  if isinstance(condition, str):
448
421
  try:
@@ -452,32 +425,30 @@ class SQL:
452
425
  else:
453
426
  condition_expr = condition
454
427
 
455
- # Apply WHERE clause
456
428
  if isinstance(current_expr, exp.Select) or supports_where(current_expr):
457
429
  new_expr = current_expr.where(condition_expr, copy=False)
458
430
  else:
459
431
  new_expr = exp.Select().from_(current_expr).where(condition_expr, copy=False)
460
432
 
461
- # Generate SQL and create new instance
462
- new_sql_text = new_expr.sql(dialect=self._dialect)
463
- new_sql = SQL(
464
- new_sql_text, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
465
- )
433
+ original_params = self._original_parameters
434
+ config = self._statement_config
435
+ is_many = self._is_many
436
+ new_sql = SQL(new_expr, *original_params, statement_config=config, is_many=is_many)
466
437
 
467
- # Preserve state efficiently
468
438
  new_sql._named_parameters.update(self._named_parameters)
469
439
  new_sql._positional_parameters = self._positional_parameters.copy()
470
440
  new_sql._filters = self._filters.copy()
471
441
  return new_sql
472
442
 
473
443
  def __hash__(self) -> int:
474
- """Hash value with optimized computation."""
444
+ """Hash value computation."""
475
445
  if self._hash is None:
476
- # Pre-compute tuple components to avoid multiple tuple() calls
477
446
  positional_tuple = tuple(self._positional_parameters)
478
447
  named_tuple = tuple(sorted(self._named_parameters.items())) if self._named_parameters else ()
479
-
480
- self._hash = hash((self._raw_sql, positional_tuple, named_tuple, self._is_many, self._is_script))
448
+ raw_sql = self._raw_sql
449
+ is_many = self._is_many
450
+ is_script = self._is_script
451
+ self._hash = hash((raw_sql, positional_tuple, named_tuple, is_many, is_script))
481
452
  return self._hash
482
453
 
483
454
  def __eq__(self, other: object) -> bool:
@@ -494,11 +465,12 @@ class SQL:
494
465
 
495
466
  def __repr__(self) -> str:
496
467
  """String representation."""
497
- params_str = ""
468
+ params_parts = []
469
+ if self._positional_parameters:
470
+ params_parts.append(f"params={self._positional_parameters}")
498
471
  if self._named_parameters:
499
- params_str = f", named_params={self._named_parameters}"
500
- elif self._positional_parameters:
501
- params_str = f", params={self._positional_parameters}"
472
+ params_parts.append(f"named_params={self._named_parameters}")
473
+ params_str = f", {', '.join(params_parts)}" if params_parts else ""
502
474
 
503
475
  flags = []
504
476
  if self._is_many:
@@ -510,18 +482,9 @@ class SQL:
510
482
  return f"SQL({self._raw_sql!r}{params_str}{flags_str})"
511
483
 
512
484
 
513
- @mypyc_attr(allow_interpreted_subclasses=True)
485
+ @mypyc_attr(allow_interpreted_subclasses=False)
514
486
  class StatementConfig:
515
- """Configuration for SQL statement processing.
516
-
517
- Provides all attributes that drivers expect for SQL processing.
518
-
519
- Features:
520
- - Complete parameter processing configuration
521
- - Caching and execution mode interfaces
522
- - Support for various database-specific operations
523
- - Immutable updates via replace() method
524
- """
487
+ """Configuration for SQL statement processing."""
525
488
 
526
489
  __slots__ = SQL_CONFIG_SLOTS
527
490
 
@@ -548,16 +511,16 @@ class StatementConfig:
548
511
 
549
512
  Args:
550
513
  parameter_config: Parameter style configuration
551
- enable_parsing: Enable SQL parsing using sqlglot
552
- enable_validation: Run SQL validators to check for safety issues
514
+ enable_parsing: Enable SQL parsing
515
+ enable_validation: Run SQL validators
553
516
  enable_transformations: Apply SQL transformers
554
- enable_analysis: Run SQL analyzers for metadata extraction
517
+ enable_analysis: Run SQL analyzers
555
518
  enable_expression_simplification: Apply expression simplification
556
519
  enable_parameter_type_wrapping: Wrap parameters with type information
557
520
  enable_caching: Cache processed SQL statements
558
521
  parameter_converter: Handles parameter style conversions
559
522
  parameter_validator: Validates parameter usage and styles
560
- dialect: SQL dialect for parsing and generation
523
+ dialect: SQL dialect
561
524
  pre_process_steps: Optional list of preprocessing steps
562
525
  post_process_steps: Optional list of postprocessing steps
563
526
  execution_mode: Special execution mode
@@ -598,12 +561,29 @@ class StatementConfig:
598
561
  msg = f"{key!r} is not a field in {type(self).__name__}"
599
562
  raise TypeError(msg)
600
563
 
601
- current_kwargs = {slot: getattr(self, slot) for slot in SQL_CONFIG_SLOTS}
564
+ current_kwargs: dict[str, Any] = {
565
+ "parameter_config": self.parameter_config,
566
+ "enable_parsing": self.enable_parsing,
567
+ "enable_validation": self.enable_validation,
568
+ "enable_transformations": self.enable_transformations,
569
+ "enable_analysis": self.enable_analysis,
570
+ "enable_expression_simplification": self.enable_expression_simplification,
571
+ "enable_parameter_type_wrapping": self.enable_parameter_type_wrapping,
572
+ "enable_caching": self.enable_caching,
573
+ "parameter_converter": self.parameter_converter,
574
+ "parameter_validator": self.parameter_validator,
575
+ "dialect": self.dialect,
576
+ "pre_process_steps": self.pre_process_steps,
577
+ "post_process_steps": self.post_process_steps,
578
+ "execution_mode": self.execution_mode,
579
+ "execution_args": self.execution_args,
580
+ "output_transformer": self.output_transformer,
581
+ }
602
582
  current_kwargs.update(kwargs)
603
583
  return type(self)(**current_kwargs)
604
584
 
605
585
  def __hash__(self) -> int:
606
- """Hash based on key configuration settings."""
586
+ """Hash based on configuration settings."""
607
587
  return hash(
608
588
  (
609
589
  self.enable_parsing,
@@ -619,10 +599,24 @@ class StatementConfig:
619
599
 
620
600
  def __repr__(self) -> str:
621
601
  """String representation of the StatementConfig instance."""
622
- field_strs = []
623
- for slot in SQL_CONFIG_SLOTS:
624
- value = getattr(self, slot)
625
- field_strs.append(f"{slot}={value!r}")
602
+ field_strs = [
603
+ f"parameter_config={self.parameter_config!r}",
604
+ f"enable_parsing={self.enable_parsing!r}",
605
+ f"enable_validation={self.enable_validation!r}",
606
+ f"enable_transformations={self.enable_transformations!r}",
607
+ f"enable_analysis={self.enable_analysis!r}",
608
+ f"enable_expression_simplification={self.enable_expression_simplification!r}",
609
+ f"enable_parameter_type_wrapping={self.enable_parameter_type_wrapping!r}",
610
+ f"enable_caching={self.enable_caching!r}",
611
+ f"parameter_converter={self.parameter_converter!r}",
612
+ f"parameter_validator={self.parameter_validator!r}",
613
+ f"dialect={self.dialect!r}",
614
+ f"pre_process_steps={self.pre_process_steps!r}",
615
+ f"post_process_steps={self.post_process_steps!r}",
616
+ f"execution_mode={self.execution_mode!r}",
617
+ f"execution_args={self.execution_args!r}",
618
+ f"output_transformer={self.output_transformer!r}",
619
+ ]
626
620
  return f"{self.__class__.__name__}({', '.join(field_strs)})"
627
621
 
628
622
  def __eq__(self, other: object) -> bool:
@@ -630,35 +624,32 @@ class StatementConfig:
630
624
  if not isinstance(other, type(self)):
631
625
  return False
632
626
 
633
- for slot in SQL_CONFIG_SLOTS:
634
- self_val = getattr(self, slot)
635
- other_val = getattr(other, slot)
636
-
637
- if hasattr(self_val, "__class__") and hasattr(other_val, "__class__"):
638
- if self_val.__class__ != other_val.__class__:
639
- return False
640
- if slot == "parameter_config":
641
- if not self._compare_parameter_configs(self_val, other_val):
642
- return False
643
- elif slot in {"parameter_converter", "parameter_validator"}:
644
- continue
645
- elif self_val != other_val:
646
- return False
647
- elif self_val != other_val:
648
- return False
649
- return True
627
+ if not self._compare_parameter_configs(self.parameter_config, other.parameter_config):
628
+ return False
629
+
630
+ return (
631
+ self.enable_parsing == other.enable_parsing
632
+ and self.enable_validation == other.enable_validation
633
+ and self.enable_transformations == other.enable_transformations
634
+ and self.enable_analysis == other.enable_analysis
635
+ and self.enable_expression_simplification == other.enable_expression_simplification
636
+ and self.enable_parameter_type_wrapping == other.enable_parameter_type_wrapping
637
+ and self.enable_caching == other.enable_caching
638
+ and self.dialect == other.dialect
639
+ and self.pre_process_steps == other.pre_process_steps
640
+ and self.post_process_steps == other.post_process_steps
641
+ and self.execution_mode == other.execution_mode
642
+ and self.execution_args == other.execution_args
643
+ and self.output_transformer == other.output_transformer
644
+ )
650
645
 
651
646
  def _compare_parameter_configs(self, config1: Any, config2: Any) -> bool:
652
- """Compare parameter configs by key attributes."""
653
- try:
654
- return (
655
- config1.default_parameter_style == config2.default_parameter_style
656
- and config1.supported_parameter_styles == config2.supported_parameter_styles
657
- and getattr(config1, "supported_execution_parameter_styles", None)
658
- == getattr(config2, "supported_execution_parameter_styles", None)
659
- )
660
- except AttributeError:
661
- return False
647
+ """Compare parameter configs."""
648
+ return bool(
649
+ config1.default_parameter_style == config2.default_parameter_style
650
+ and config1.supported_parameter_styles == config2.supported_parameter_styles
651
+ and config1.supported_execution_parameter_styles == config2.supported_execution_parameter_styles
652
+ )
662
653
 
663
654
 
664
655
  def get_default_config() -> StatementConfig:
sqlspec/driver/_async.py CHANGED
@@ -1,8 +1,4 @@
1
- """Asynchronous driver protocol implementation.
2
-
3
- This module provides the async driver infrastructure for database adapters,
4
- including connection management, transaction support, and result processing.
5
- """
1
+ """Asynchronous driver protocol implementation."""
6
2
 
7
3
  from abc import abstractmethod
8
4
  from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, Union, cast, overload
@@ -32,22 +28,13 @@ EMPTY_FILTERS: Final["list[StatementFilter]"] = []
32
28
 
33
29
 
34
30
  class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
35
- """Base class for asynchronous database drivers.
36
-
37
- Provides the foundation for async database adapters, including connection management,
38
- transaction support, and SQL execution methods. All database operations are performed
39
- asynchronously and support context manager patterns for proper resource cleanup.
40
- """
31
+ """Base class for asynchronous database drivers."""
41
32
 
42
33
  __slots__ = ()
43
34
 
44
35
  async def dispatch_statement_execution(self, statement: "SQL", connection: "Any") -> "SQLResult":
45
36
  """Central execution dispatcher using the Template Method Pattern.
46
37
 
47
- Orchestrates the common execution flow, delegating database-specific steps
48
- to abstract methods that concrete adapters must implement.
49
- All database operations are wrapped in exception handling.
50
-
51
38
  Args:
52
39
  statement: The SQL statement to execute
53
40
  connection: The database connection to use