sqlspec 0.12.2__py3-none-any.whl → 0.13.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 (113) hide show
  1. sqlspec/_sql.py +21 -180
  2. sqlspec/adapters/adbc/config.py +10 -12
  3. sqlspec/adapters/adbc/driver.py +120 -118
  4. sqlspec/adapters/aiosqlite/config.py +3 -3
  5. sqlspec/adapters/aiosqlite/driver.py +100 -130
  6. sqlspec/adapters/asyncmy/config.py +3 -4
  7. sqlspec/adapters/asyncmy/driver.py +123 -135
  8. sqlspec/adapters/asyncpg/config.py +3 -7
  9. sqlspec/adapters/asyncpg/driver.py +98 -140
  10. sqlspec/adapters/bigquery/config.py +4 -5
  11. sqlspec/adapters/bigquery/driver.py +125 -167
  12. sqlspec/adapters/duckdb/config.py +3 -6
  13. sqlspec/adapters/duckdb/driver.py +114 -111
  14. sqlspec/adapters/oracledb/config.py +6 -5
  15. sqlspec/adapters/oracledb/driver.py +242 -259
  16. sqlspec/adapters/psqlpy/config.py +3 -7
  17. sqlspec/adapters/psqlpy/driver.py +118 -93
  18. sqlspec/adapters/psycopg/config.py +18 -31
  19. sqlspec/adapters/psycopg/driver.py +283 -236
  20. sqlspec/adapters/sqlite/config.py +3 -3
  21. sqlspec/adapters/sqlite/driver.py +103 -97
  22. sqlspec/config.py +0 -4
  23. sqlspec/driver/_async.py +89 -98
  24. sqlspec/driver/_common.py +52 -17
  25. sqlspec/driver/_sync.py +81 -105
  26. sqlspec/driver/connection.py +207 -0
  27. sqlspec/driver/mixins/_csv_writer.py +91 -0
  28. sqlspec/driver/mixins/_pipeline.py +38 -49
  29. sqlspec/driver/mixins/_result_utils.py +27 -9
  30. sqlspec/driver/mixins/_storage.py +67 -181
  31. sqlspec/driver/mixins/_type_coercion.py +3 -4
  32. sqlspec/driver/parameters.py +138 -0
  33. sqlspec/exceptions.py +10 -2
  34. sqlspec/extensions/aiosql/adapter.py +0 -10
  35. sqlspec/extensions/litestar/handlers.py +0 -1
  36. sqlspec/extensions/litestar/plugin.py +0 -3
  37. sqlspec/extensions/litestar/providers.py +0 -14
  38. sqlspec/loader.py +25 -90
  39. sqlspec/protocols.py +542 -0
  40. sqlspec/service/__init__.py +3 -2
  41. sqlspec/service/_util.py +147 -0
  42. sqlspec/service/base.py +1116 -9
  43. sqlspec/statement/builder/__init__.py +42 -32
  44. sqlspec/statement/builder/_ddl_utils.py +0 -10
  45. sqlspec/statement/builder/_parsing_utils.py +10 -4
  46. sqlspec/statement/builder/base.py +67 -22
  47. sqlspec/statement/builder/column.py +283 -0
  48. sqlspec/statement/builder/ddl.py +91 -67
  49. sqlspec/statement/builder/delete.py +23 -7
  50. sqlspec/statement/builder/insert.py +29 -15
  51. sqlspec/statement/builder/merge.py +4 -4
  52. sqlspec/statement/builder/mixins/_aggregate_functions.py +113 -14
  53. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -1
  54. sqlspec/statement/builder/mixins/_delete_from.py +1 -1
  55. sqlspec/statement/builder/mixins/_from.py +10 -8
  56. sqlspec/statement/builder/mixins/_group_by.py +0 -1
  57. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -1
  58. sqlspec/statement/builder/mixins/_insert_values.py +0 -2
  59. sqlspec/statement/builder/mixins/_join.py +20 -13
  60. sqlspec/statement/builder/mixins/_limit_offset.py +3 -3
  61. sqlspec/statement/builder/mixins/_merge_clauses.py +3 -4
  62. sqlspec/statement/builder/mixins/_order_by.py +2 -2
  63. sqlspec/statement/builder/mixins/_pivot.py +4 -7
  64. sqlspec/statement/builder/mixins/_select_columns.py +6 -5
  65. sqlspec/statement/builder/mixins/_unpivot.py +6 -9
  66. sqlspec/statement/builder/mixins/_update_from.py +2 -1
  67. sqlspec/statement/builder/mixins/_update_set.py +11 -8
  68. sqlspec/statement/builder/mixins/_where.py +61 -34
  69. sqlspec/statement/builder/select.py +32 -17
  70. sqlspec/statement/builder/update.py +25 -11
  71. sqlspec/statement/filters.py +39 -14
  72. sqlspec/statement/parameter_manager.py +220 -0
  73. sqlspec/statement/parameters.py +210 -79
  74. sqlspec/statement/pipelines/__init__.py +166 -23
  75. sqlspec/statement/pipelines/analyzers/_analyzer.py +21 -20
  76. sqlspec/statement/pipelines/context.py +35 -39
  77. sqlspec/statement/pipelines/transformers/__init__.py +2 -3
  78. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +19 -187
  79. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +628 -58
  80. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +76 -0
  81. sqlspec/statement/pipelines/validators/_dml_safety.py +33 -18
  82. sqlspec/statement/pipelines/validators/_parameter_style.py +87 -14
  83. sqlspec/statement/pipelines/validators/_performance.py +38 -23
  84. sqlspec/statement/pipelines/validators/_security.py +39 -62
  85. sqlspec/statement/result.py +37 -129
  86. sqlspec/statement/splitter.py +0 -12
  87. sqlspec/statement/sql.py +863 -391
  88. sqlspec/statement/sql_compiler.py +140 -0
  89. sqlspec/storage/__init__.py +10 -2
  90. sqlspec/storage/backends/fsspec.py +53 -8
  91. sqlspec/storage/backends/obstore.py +15 -19
  92. sqlspec/storage/capabilities.py +101 -0
  93. sqlspec/storage/registry.py +56 -83
  94. sqlspec/typing.py +6 -434
  95. sqlspec/utils/cached_property.py +25 -0
  96. sqlspec/utils/correlation.py +0 -2
  97. sqlspec/utils/logging.py +0 -6
  98. sqlspec/utils/sync_tools.py +0 -4
  99. sqlspec/utils/text.py +0 -5
  100. sqlspec/utils/type_guards.py +892 -0
  101. {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/METADATA +1 -1
  102. sqlspec-0.13.0.dist-info/RECORD +150 -0
  103. sqlspec/statement/builder/protocols.py +0 -20
  104. sqlspec/statement/pipelines/base.py +0 -315
  105. sqlspec/statement/pipelines/result_types.py +0 -41
  106. sqlspec/statement/pipelines/transformers/_remove_comments.py +0 -66
  107. sqlspec/statement/pipelines/transformers/_remove_hints.py +0 -81
  108. sqlspec/statement/pipelines/validators/base.py +0 -67
  109. sqlspec/storage/protocol.py +0 -173
  110. sqlspec-0.12.2.dist-info/RECORD +0 -145
  111. {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/WHEEL +0 -0
  112. {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/licenses/LICENSE +0 -0
  113. {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,892 @@
1
+ """Type guard functions for runtime type checking in SQLSpec.
2
+
3
+ This module provides type-safe runtime checks that help the type checker
4
+ understand type narrowing, replacing defensive hasattr() and duck typing patterns.
5
+
6
+ NOTE: Some sqlspec imports are nested inside functions to prevent circular
7
+ imports where necessary. This module is imported by core sqlspec modules,
8
+ so imports that would create cycles are deferred.
9
+ """
10
+
11
+ from collections.abc import Iterable, Sequence
12
+ from collections.abc import Set as AbstractSet
13
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
14
+
15
+ from sqlspec.typing import (
16
+ LITESTAR_INSTALLED,
17
+ MSGSPEC_INSTALLED,
18
+ PYDANTIC_INSTALLED,
19
+ BaseModel,
20
+ DataclassProtocol,
21
+ DTOData,
22
+ Struct,
23
+ )
24
+
25
+ if TYPE_CHECKING:
26
+ from dataclasses import Field
27
+
28
+ from sqlglot import exp
29
+ from typing_extensions import TypeGuard
30
+
31
+ from sqlspec.protocols import (
32
+ AsyncCloseableConnectionProtocol,
33
+ AsyncCopyCapableConnectionProtocol,
34
+ AsyncPipelineCapableDriverProtocol,
35
+ AsyncTransactionCapableConnectionProtocol,
36
+ AsyncTransactionStateConnectionProtocol,
37
+ BytesConvertibleProtocol,
38
+ DictProtocol,
39
+ FilterAppenderProtocol,
40
+ FilterParameterProtocol,
41
+ HasExpressionsProtocol,
42
+ HasLimitProtocol,
43
+ HasOffsetProtocol,
44
+ HasOrderByProtocol,
45
+ HasRiskLevelProtocol,
46
+ HasSQLMethodProtocol,
47
+ HasWhereProtocol,
48
+ IndexableRow,
49
+ ObjectStoreItemProtocol,
50
+ ParameterValueProtocol,
51
+ SQLBuilderProtocol,
52
+ SyncCloseableConnectionProtocol,
53
+ SyncCopyCapableConnectionProtocol,
54
+ SyncPipelineCapableDriverProtocol,
55
+ SyncTransactionCapableConnectionProtocol,
56
+ SyncTransactionStateConnectionProtocol,
57
+ WithMethodProtocol,
58
+ )
59
+ from sqlspec.statement.builder import Select
60
+ from sqlspec.statement.filters import LimitOffsetFilter, StatementFilter
61
+ from sqlspec.typing import SupportedSchemaModel
62
+
63
+ __all__ = (
64
+ "can_append_to_statement",
65
+ "can_convert_to_schema",
66
+ "can_extract_parameters",
67
+ "dataclass_to_dict",
68
+ "extract_dataclass_fields",
69
+ "extract_dataclass_items",
70
+ "has_bytes_conversion",
71
+ "has_dict_attribute",
72
+ "has_expressions",
73
+ "has_parameter_value",
74
+ "has_query_builder_parameters",
75
+ "has_risk_level",
76
+ "has_sql_method",
77
+ "has_with_method",
78
+ "is_async_closeable_connection",
79
+ "is_async_copy_capable",
80
+ "is_async_pipeline_capable_driver",
81
+ "is_async_transaction_capable",
82
+ "is_async_transaction_state_capable",
83
+ "is_dataclass",
84
+ "is_dataclass_instance",
85
+ "is_dataclass_with_field",
86
+ "is_dataclass_without_field",
87
+ "is_dict",
88
+ "is_dict_row",
89
+ "is_dict_with_field",
90
+ "is_dict_without_field",
91
+ "is_dto_data",
92
+ "is_expression",
93
+ "is_indexable_row",
94
+ "is_iterable_parameters",
95
+ "is_limit_offset_filter",
96
+ "is_msgspec_struct",
97
+ "is_msgspec_struct_with_field",
98
+ "is_msgspec_struct_without_field",
99
+ "is_object_store_item",
100
+ "is_pydantic_model",
101
+ "is_pydantic_model_with_field",
102
+ "is_pydantic_model_without_field",
103
+ "is_schema",
104
+ "is_schema_or_dict",
105
+ "is_schema_or_dict_with_field",
106
+ "is_schema_or_dict_without_field",
107
+ "is_schema_with_field",
108
+ "is_schema_without_field",
109
+ "is_select_builder",
110
+ "is_statement_filter",
111
+ "is_sync_closeable_connection",
112
+ "is_sync_copy_capable",
113
+ "is_sync_pipeline_capable_driver",
114
+ "is_sync_transaction_capable",
115
+ "is_sync_transaction_state_capable",
116
+ "schema_dump",
117
+ "supports_limit",
118
+ "supports_offset",
119
+ "supports_order_by",
120
+ "supports_where",
121
+ )
122
+
123
+
124
+ def is_statement_filter(obj: Any) -> "TypeGuard[StatementFilter]":
125
+ """Check if an object implements the StatementFilter protocol.
126
+
127
+ Args:
128
+ obj: The object to check
129
+
130
+ Returns:
131
+ True if the object is a StatementFilter, False otherwise
132
+ """
133
+ from sqlspec.statement.filters import StatementFilter as FilterProtocol
134
+
135
+ return isinstance(obj, FilterProtocol)
136
+
137
+
138
+ def is_limit_offset_filter(obj: Any) -> "TypeGuard[LimitOffsetFilter]":
139
+ """Check if an object is a LimitOffsetFilter.
140
+
141
+ Args:
142
+ obj: The object to check
143
+
144
+ Returns:
145
+ True if the object is a LimitOffsetFilter, False otherwise
146
+ """
147
+ from sqlspec.statement.filters import LimitOffsetFilter
148
+
149
+ return isinstance(obj, LimitOffsetFilter)
150
+
151
+
152
+ def is_select_builder(obj: Any) -> "TypeGuard[Select]":
153
+ """Check if an object is a Select.
154
+
155
+ Args:
156
+ obj: The object to check
157
+
158
+ Returns:
159
+ True if the object is a Select, False otherwise
160
+ """
161
+ from sqlspec.statement.builder import Select
162
+
163
+ return isinstance(obj, Select)
164
+
165
+
166
+ def is_dict_row(row: Any) -> "TypeGuard[dict[str, Any]]":
167
+ """Check if a row is a dictionary.
168
+
169
+ Args:
170
+ row: The row to check
171
+
172
+ Returns:
173
+ True if the row is a dictionary, False otherwise
174
+ """
175
+ return isinstance(row, dict)
176
+
177
+
178
+ def is_indexable_row(row: Any) -> "TypeGuard[IndexableRow]":
179
+ """Check if a row supports index access via protocol.
180
+
181
+ Args:
182
+ row: The row to check
183
+
184
+ Returns:
185
+ True if the row is indexable, False otherwise
186
+ """
187
+ from sqlspec.protocols import IndexableRow
188
+
189
+ return isinstance(row, IndexableRow)
190
+
191
+
192
+ def is_iterable_parameters(params: Any) -> "TypeGuard[Sequence[Any]]":
193
+ """Check if parameters are iterable (but not string or dict).
194
+
195
+ Args:
196
+ params: The parameters to check
197
+
198
+ Returns:
199
+ True if the parameters are iterable, False otherwise
200
+ """
201
+ return isinstance(params, Sequence) and not isinstance(params, (str, bytes, dict))
202
+
203
+
204
+ def has_with_method(obj: Any) -> "TypeGuard[WithMethodProtocol]":
205
+ """Check if an object has a callable 'with_' method.
206
+
207
+ This is a more specific check than hasattr for SQLGlot expressions.
208
+
209
+ Args:
210
+ obj: The object to check
211
+
212
+ Returns:
213
+ True if the object has a callable with_ method, False otherwise
214
+ """
215
+ from sqlspec.protocols import WithMethodProtocol
216
+
217
+ return isinstance(obj, WithMethodProtocol)
218
+
219
+
220
+ def can_convert_to_schema(obj: Any) -> "TypeGuard[Any]":
221
+ """Check if an object has the ToSchemaMixin capabilities.
222
+
223
+ This provides better DX than isinstance checks for driver mixins.
224
+
225
+ Args:
226
+ obj: The object to check (typically a driver instance)
227
+
228
+ Returns:
229
+ True if the object has to_schema method, False otherwise
230
+ """
231
+ from sqlspec.driver.mixins import ToSchemaMixin
232
+
233
+ return isinstance(obj, ToSchemaMixin)
234
+
235
+
236
+ # Type guards migrated from typing.py
237
+
238
+
239
+ def is_dataclass_instance(obj: Any) -> "TypeGuard[DataclassProtocol]":
240
+ """Check if an object is a dataclass instance.
241
+
242
+ Args:
243
+ obj: An object to check.
244
+
245
+ Returns:
246
+ True if the object is a dataclass instance.
247
+ """
248
+ # and that its type is a dataclass.
249
+ return not isinstance(obj, type) and hasattr(type(obj), "__dataclass_fields__")
250
+
251
+
252
+ def is_dataclass(obj: Any) -> "TypeGuard[DataclassProtocol]":
253
+ """Check if an object is a dataclass.
254
+
255
+ Args:
256
+ obj: Value to check.
257
+
258
+ Returns:
259
+ bool
260
+ """
261
+ if isinstance(obj, type) and hasattr(obj, "__dataclass_fields__"):
262
+ return True
263
+ return is_dataclass_instance(obj)
264
+
265
+
266
+ def is_dataclass_with_field(obj: Any, field_name: str) -> "TypeGuard[object]":
267
+ """Check if an object is a dataclass and has a specific field.
268
+
269
+ Args:
270
+ obj: Value to check.
271
+ field_name: Field name to check for.
272
+
273
+ Returns:
274
+ bool
275
+ """
276
+ return is_dataclass(obj) and hasattr(obj, field_name)
277
+
278
+
279
+ def is_dataclass_without_field(obj: Any, field_name: str) -> "TypeGuard[object]":
280
+ """Check if an object is a dataclass and does not have a specific field.
281
+
282
+ Args:
283
+ obj: Value to check.
284
+ field_name: Field name to check for.
285
+
286
+ Returns:
287
+ bool
288
+ """
289
+ return is_dataclass(obj) and not hasattr(obj, field_name)
290
+
291
+
292
+ def is_pydantic_model(obj: Any) -> "TypeGuard[BaseModel]":
293
+ """Check if a value is a pydantic model.
294
+
295
+ Args:
296
+ obj: Value to check.
297
+
298
+ Returns:
299
+ bool
300
+ """
301
+ return PYDANTIC_INSTALLED and isinstance(obj, BaseModel)
302
+
303
+
304
+ def is_pydantic_model_with_field(obj: Any, field_name: str) -> "TypeGuard[BaseModel]":
305
+ """Check if a pydantic model has a specific field.
306
+
307
+ Args:
308
+ obj: Value to check.
309
+ field_name: Field name to check for.
310
+
311
+ Returns:
312
+ bool
313
+ """
314
+ return is_pydantic_model(obj) and hasattr(obj, field_name)
315
+
316
+
317
+ def is_pydantic_model_without_field(obj: Any, field_name: str) -> "TypeGuard[BaseModel]":
318
+ """Check if a pydantic model does not have a specific field.
319
+
320
+ Args:
321
+ obj: Value to check.
322
+ field_name: Field name to check for.
323
+
324
+ Returns:
325
+ bool
326
+ """
327
+ return is_pydantic_model(obj) and not hasattr(obj, field_name)
328
+
329
+
330
+ def is_msgspec_struct(obj: Any) -> "TypeGuard[Struct]":
331
+ """Check if a value is a msgspec struct.
332
+
333
+ Args:
334
+ obj: Value to check.
335
+
336
+ Returns:
337
+ bool
338
+ """
339
+ return MSGSPEC_INSTALLED and isinstance(obj, Struct)
340
+
341
+
342
+ def is_msgspec_struct_with_field(obj: Any, field_name: str) -> "TypeGuard[Struct]":
343
+ """Check if a msgspec struct has a specific field.
344
+
345
+ Args:
346
+ obj: Value to check.
347
+ field_name: Field name to check for.
348
+
349
+ Returns:
350
+ bool
351
+ """
352
+ return is_msgspec_struct(obj) and hasattr(obj, field_name)
353
+
354
+
355
+ def is_msgspec_struct_without_field(obj: Any, field_name: str) -> "TypeGuard[Struct]":
356
+ """Check if a msgspec struct does not have a specific field.
357
+
358
+ Args:
359
+ obj: Value to check.
360
+ field_name: Field name to check for.
361
+
362
+ Returns:
363
+ bool
364
+ """
365
+ return is_msgspec_struct(obj) and not hasattr(obj, field_name)
366
+
367
+
368
+ def is_dict(obj: Any) -> "TypeGuard[dict[str, Any]]":
369
+ """Check if a value is a dictionary.
370
+
371
+ Args:
372
+ obj: Value to check.
373
+
374
+ Returns:
375
+ bool
376
+ """
377
+ return isinstance(obj, dict)
378
+
379
+
380
+ def is_dict_with_field(obj: Any, field_name: str) -> "TypeGuard[dict[str, Any]]":
381
+ """Check if a dictionary has a specific field.
382
+
383
+ Args:
384
+ obj: Value to check.
385
+ field_name: Field name to check for.
386
+
387
+ Returns:
388
+ bool
389
+ """
390
+ return is_dict(obj) and field_name in obj
391
+
392
+
393
+ def is_dict_without_field(obj: Any, field_name: str) -> "TypeGuard[dict[str, Any]]":
394
+ """Check if a dictionary does not have a specific field.
395
+
396
+ Args:
397
+ obj: Value to check.
398
+ field_name: Field name to check for.
399
+
400
+ Returns:
401
+ bool
402
+ """
403
+ return is_dict(obj) and field_name not in obj
404
+
405
+
406
+ def is_schema(obj: Any) -> "TypeGuard[SupportedSchemaModel]":
407
+ """Check if a value is a msgspec Struct or Pydantic model.
408
+
409
+ Args:
410
+ obj: Value to check.
411
+
412
+ Returns:
413
+ bool
414
+ """
415
+ return is_msgspec_struct(obj) or is_pydantic_model(obj)
416
+
417
+
418
+ def is_schema_or_dict(obj: Any) -> "TypeGuard[Union[SupportedSchemaModel, dict[str, Any]]]":
419
+ """Check if a value is a msgspec Struct, Pydantic model, or dict.
420
+
421
+ Args:
422
+ obj: Value to check.
423
+
424
+ Returns:
425
+ bool
426
+ """
427
+ return is_schema(obj) or is_dict(obj)
428
+
429
+
430
+ def is_schema_with_field(obj: Any, field_name: str) -> "TypeGuard[SupportedSchemaModel]":
431
+ """Check if a value is a msgspec Struct or Pydantic model with a specific field.
432
+
433
+ Args:
434
+ obj: Value to check.
435
+ field_name: Field name to check for.
436
+
437
+ Returns:
438
+ bool
439
+ """
440
+ return is_msgspec_struct_with_field(obj, field_name) or is_pydantic_model_with_field(obj, field_name)
441
+
442
+
443
+ def is_schema_without_field(obj: Any, field_name: str) -> "TypeGuard[SupportedSchemaModel]":
444
+ """Check if a value is a msgspec Struct or Pydantic model without a specific field.
445
+
446
+ Args:
447
+ obj: Value to check.
448
+ field_name: Field name to check for.
449
+
450
+ Returns:
451
+ bool
452
+ """
453
+ return not is_schema_with_field(obj, field_name)
454
+
455
+
456
+ def is_schema_or_dict_with_field(obj: Any, field_name: str) -> "TypeGuard[Union[SupportedSchemaModel, dict[str, Any]]]":
457
+ """Check if a value is a msgspec Struct, Pydantic model, or dict with a specific field.
458
+
459
+ Args:
460
+ obj: Value to check.
461
+ field_name: Field name to check for.
462
+
463
+ Returns:
464
+ bool
465
+ """
466
+ return is_schema_with_field(obj, field_name) or is_dict_with_field(obj, field_name)
467
+
468
+
469
+ def is_schema_or_dict_without_field(
470
+ obj: Any, field_name: str
471
+ ) -> "TypeGuard[Union[SupportedSchemaModel, dict[str, Any]]]":
472
+ """Check if a value is a msgspec Struct, Pydantic model, or dict without a specific field.
473
+
474
+ Args:
475
+ obj: Value to check.
476
+ field_name: Field name to check for.
477
+
478
+ Returns:
479
+ bool
480
+ """
481
+ return not is_schema_or_dict_with_field(obj, field_name)
482
+
483
+
484
+ def is_dto_data(v: Any) -> "TypeGuard[DTOData[Any]]":
485
+ """Check if a value is a Litestar DTOData object.
486
+
487
+ Args:
488
+ v: Value to check.
489
+
490
+ Returns:
491
+ bool
492
+ """
493
+ return LITESTAR_INSTALLED and isinstance(v, DTOData)
494
+
495
+
496
+ def is_expression(obj: Any) -> "TypeGuard[exp.Expression]":
497
+ """Check if a value is a sqlglot Expression.
498
+
499
+ Args:
500
+ obj: Value to check.
501
+
502
+ Returns:
503
+ bool
504
+ """
505
+ from sqlglot import exp
506
+
507
+ return isinstance(obj, exp.Expression)
508
+
509
+
510
+ def has_dict_attribute(obj: Any) -> "TypeGuard[DictProtocol]":
511
+ """Check if an object has a __dict__ attribute.
512
+
513
+ Args:
514
+ obj: Value to check.
515
+
516
+ Returns:
517
+ bool
518
+ """
519
+ from sqlspec.protocols import DictProtocol
520
+
521
+ return isinstance(obj, DictProtocol)
522
+
523
+
524
+ def is_sync_transaction_capable(obj: Any) -> "TypeGuard[SyncTransactionCapableConnectionProtocol]":
525
+ """Check if a connection supports sync transactions.
526
+
527
+ Args:
528
+ obj: Connection object to check.
529
+
530
+ Returns:
531
+ True if the connection has commit and rollback methods.
532
+ """
533
+ from sqlspec.protocols import SyncTransactionCapableConnectionProtocol
534
+
535
+ return isinstance(obj, SyncTransactionCapableConnectionProtocol)
536
+
537
+
538
+ def is_async_transaction_capable(obj: Any) -> "TypeGuard[AsyncTransactionCapableConnectionProtocol]":
539
+ """Check if a connection supports async transactions.
540
+
541
+ Args:
542
+ obj: Connection object to check.
543
+
544
+ Returns:
545
+ True if the connection has async commit and rollback methods.
546
+ """
547
+ from sqlspec.protocols import AsyncTransactionCapableConnectionProtocol
548
+
549
+ return isinstance(obj, AsyncTransactionCapableConnectionProtocol)
550
+
551
+
552
+ def is_sync_transaction_state_capable(obj: Any) -> "TypeGuard[SyncTransactionStateConnectionProtocol]":
553
+ """Check if a connection can report sync transaction state.
554
+
555
+ Args:
556
+ obj: Connection object to check.
557
+
558
+ Returns:
559
+ True if the connection has in_transaction and begin methods.
560
+ """
561
+ from sqlspec.protocols import SyncTransactionStateConnectionProtocol
562
+
563
+ return isinstance(obj, SyncTransactionStateConnectionProtocol)
564
+
565
+
566
+ def is_async_transaction_state_capable(obj: Any) -> "TypeGuard[AsyncTransactionStateConnectionProtocol]":
567
+ """Check if a connection can report async transaction state.
568
+
569
+ Args:
570
+ obj: Connection object to check.
571
+
572
+ Returns:
573
+ True if the connection has in_transaction and async begin methods.
574
+ """
575
+ from sqlspec.protocols import AsyncTransactionStateConnectionProtocol
576
+
577
+ return isinstance(obj, AsyncTransactionStateConnectionProtocol)
578
+
579
+
580
+ def is_sync_closeable_connection(obj: Any) -> "TypeGuard[SyncCloseableConnectionProtocol]":
581
+ """Check if a connection can be closed synchronously.
582
+
583
+ Args:
584
+ obj: Connection object to check.
585
+
586
+ Returns:
587
+ True if the connection has a close method.
588
+ """
589
+ from sqlspec.protocols import SyncCloseableConnectionProtocol
590
+
591
+ return isinstance(obj, SyncCloseableConnectionProtocol)
592
+
593
+
594
+ def is_async_closeable_connection(obj: Any) -> "TypeGuard[AsyncCloseableConnectionProtocol]":
595
+ """Check if a connection can be closed asynchronously.
596
+
597
+ Args:
598
+ obj: Connection object to check.
599
+
600
+ Returns:
601
+ True if the connection has an async close method.
602
+ """
603
+ from sqlspec.protocols import AsyncCloseableConnectionProtocol
604
+
605
+ return isinstance(obj, AsyncCloseableConnectionProtocol)
606
+
607
+
608
+ def is_sync_copy_capable(obj: Any) -> "TypeGuard[SyncCopyCapableConnectionProtocol]":
609
+ """Check if a connection supports sync COPY operations.
610
+
611
+ Args:
612
+ obj: Connection object to check.
613
+
614
+ Returns:
615
+ True if the connection has copy_from and copy_to methods.
616
+ """
617
+ from sqlspec.protocols import SyncCopyCapableConnectionProtocol
618
+
619
+ return isinstance(obj, SyncCopyCapableConnectionProtocol)
620
+
621
+
622
+ def is_async_copy_capable(obj: Any) -> "TypeGuard[AsyncCopyCapableConnectionProtocol]":
623
+ """Check if a connection supports async COPY operations.
624
+
625
+ Args:
626
+ obj: Connection object to check.
627
+
628
+ Returns:
629
+ True if the connection has async copy_from and copy_to methods.
630
+ """
631
+ from sqlspec.protocols import AsyncCopyCapableConnectionProtocol
632
+
633
+ return isinstance(obj, AsyncCopyCapableConnectionProtocol)
634
+
635
+
636
+ def is_sync_pipeline_capable_driver(obj: Any) -> "TypeGuard[SyncPipelineCapableDriverProtocol]":
637
+ """Check if a driver supports sync native pipeline execution.
638
+
639
+ Args:
640
+ obj: Driver object to check.
641
+
642
+ Returns:
643
+ True if the driver has _execute_pipeline_native method.
644
+ """
645
+ from sqlspec.protocols import SyncPipelineCapableDriverProtocol
646
+
647
+ return isinstance(obj, SyncPipelineCapableDriverProtocol)
648
+
649
+
650
+ def is_async_pipeline_capable_driver(obj: Any) -> "TypeGuard[AsyncPipelineCapableDriverProtocol]":
651
+ """Check if a driver supports async native pipeline execution.
652
+
653
+ Args:
654
+ obj: Driver object to check.
655
+
656
+ Returns:
657
+ True if the driver has async _execute_pipeline_native method.
658
+ """
659
+ from sqlspec.protocols import AsyncPipelineCapableDriverProtocol
660
+
661
+ return isinstance(obj, AsyncPipelineCapableDriverProtocol)
662
+
663
+
664
+ # Dataclass utility functions
665
+
666
+
667
+ def extract_dataclass_fields(
668
+ obj: "DataclassProtocol",
669
+ exclude_none: bool = False,
670
+ exclude_empty: bool = False,
671
+ include: "Optional[AbstractSet[str]]" = None,
672
+ exclude: "Optional[AbstractSet[str]]" = None,
673
+ ) -> "tuple[Field[Any], ...]":
674
+ """Extract dataclass fields.
675
+
676
+ Args:
677
+ obj: A dataclass instance.
678
+ exclude_none: Whether to exclude None values.
679
+ exclude_empty: Whether to exclude Empty values.
680
+ include: An iterable of fields to include.
681
+ exclude: An iterable of fields to exclude.
682
+
683
+ Raises:
684
+ ValueError: If there are fields that are both included and excluded.
685
+
686
+ Returns:
687
+ A tuple of dataclass fields.
688
+ """
689
+ from dataclasses import Field, fields
690
+
691
+ from sqlspec._typing import Empty
692
+
693
+ include = include or set()
694
+ exclude = exclude or set()
695
+
696
+ if common := (include & exclude):
697
+ msg = f"Fields {common} are both included and excluded."
698
+ raise ValueError(msg)
699
+
700
+ dataclass_fields: Iterable[Field[Any]] = fields(obj)
701
+ if exclude_none:
702
+ dataclass_fields = (field for field in dataclass_fields if getattr(obj, field.name) is not None)
703
+ if exclude_empty:
704
+ dataclass_fields = (field for field in dataclass_fields if getattr(obj, field.name) is not Empty)
705
+ if include:
706
+ dataclass_fields = (field for field in dataclass_fields if field.name in include)
707
+ if exclude:
708
+ dataclass_fields = (field for field in dataclass_fields if field.name not in exclude)
709
+
710
+ return tuple(dataclass_fields)
711
+
712
+
713
+ def extract_dataclass_items(
714
+ obj: "DataclassProtocol",
715
+ exclude_none: bool = False,
716
+ exclude_empty: bool = False,
717
+ include: "Optional[AbstractSet[str]]" = None,
718
+ exclude: "Optional[AbstractSet[str]]" = None,
719
+ ) -> "tuple[tuple[str, Any], ...]":
720
+ """Extract dataclass name, value pairs.
721
+
722
+ Unlike the 'asdict' method exports by the stdlib, this function does not pickle values.
723
+
724
+ Args:
725
+ obj: A dataclass instance.
726
+ exclude_none: Whether to exclude None values.
727
+ exclude_empty: Whether to exclude Empty values.
728
+ include: An iterable of fields to include.
729
+ exclude: An iterable of fields to exclude.
730
+
731
+ Returns:
732
+ A tuple of key/value pairs.
733
+ """
734
+ dataclass_fields = extract_dataclass_fields(obj, exclude_none, exclude_empty, include, exclude)
735
+ return tuple((field.name, getattr(obj, field.name)) for field in dataclass_fields)
736
+
737
+
738
+ def dataclass_to_dict(
739
+ obj: "DataclassProtocol",
740
+ exclude_none: bool = False,
741
+ exclude_empty: bool = False,
742
+ convert_nested: bool = True,
743
+ exclude: "Optional[AbstractSet[str]]" = None,
744
+ ) -> "dict[str, Any]":
745
+ """Convert a dataclass to a dictionary.
746
+
747
+ This method has important differences to the standard library version:
748
+ - it does not deepcopy values
749
+ - it does not recurse into collections
750
+
751
+ Args:
752
+ obj: A dataclass instance.
753
+ exclude_none: Whether to exclude None values.
754
+ exclude_empty: Whether to exclude Empty values.
755
+ convert_nested: Whether to recursively convert nested dataclasses.
756
+ exclude: An iterable of fields to exclude.
757
+
758
+ Returns:
759
+ A dictionary of key/value pairs.
760
+ """
761
+ ret = {}
762
+ for field in extract_dataclass_fields(obj, exclude_none, exclude_empty, exclude=exclude):
763
+ value = getattr(obj, field.name)
764
+ if is_dataclass_instance(value) and convert_nested:
765
+ ret[field.name] = dataclass_to_dict(value, exclude_none, exclude_empty)
766
+ else:
767
+ ret[field.name] = getattr(obj, field.name)
768
+ return cast("dict[str, Any]", ret)
769
+
770
+
771
+ def schema_dump(
772
+ data: "Union[dict[str, Any], DataclassProtocol, Struct, BaseModel]", exclude_unset: bool = True
773
+ ) -> "dict[str, Any]":
774
+ """Dump a data object to a dictionary.
775
+
776
+ Args:
777
+ data: :type:`dict[str, Any]` | :class:`DataclassProtocol` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel`
778
+ exclude_unset: :type:`bool` Whether to exclude unset values.
779
+
780
+ Returns:
781
+ :type:`dict[str, Any]`
782
+ """
783
+ from sqlspec._typing import UNSET
784
+
785
+ if is_dict(data):
786
+ return data
787
+ if is_dataclass(data):
788
+ return dataclass_to_dict(data, exclude_empty=exclude_unset)
789
+ if is_pydantic_model(data):
790
+ return data.model_dump(exclude_unset=exclude_unset)
791
+ if is_msgspec_struct(data):
792
+ if exclude_unset:
793
+ return {f: val for f in data.__struct_fields__ if (val := getattr(data, f, None)) != UNSET}
794
+ return {f: getattr(data, f, None) for f in data.__struct_fields__}
795
+
796
+ if has_dict_attribute(data):
797
+ return data.__dict__
798
+ return cast("dict[str, Any]", data)
799
+
800
+
801
+ # New type guards for hasattr() pattern replacement
802
+
803
+
804
+ def can_extract_parameters(obj: Any) -> "TypeGuard[FilterParameterProtocol]":
805
+ """Check if an object can extract parameters."""
806
+ from sqlspec.protocols import FilterParameterProtocol
807
+
808
+ return isinstance(obj, FilterParameterProtocol)
809
+
810
+
811
+ def can_append_to_statement(obj: Any) -> "TypeGuard[FilterAppenderProtocol]":
812
+ """Check if an object can append to SQL statements."""
813
+ from sqlspec.protocols import FilterAppenderProtocol
814
+
815
+ return isinstance(obj, FilterAppenderProtocol)
816
+
817
+
818
+ def has_parameter_value(obj: Any) -> "TypeGuard[ParameterValueProtocol]":
819
+ """Check if an object has a value attribute (parameter wrapper)."""
820
+ from sqlspec.protocols import ParameterValueProtocol
821
+
822
+ return isinstance(obj, ParameterValueProtocol)
823
+
824
+
825
+ def has_risk_level(obj: Any) -> "TypeGuard[HasRiskLevelProtocol]":
826
+ """Check if an object has a risk_level attribute."""
827
+ from sqlspec.protocols import HasRiskLevelProtocol
828
+
829
+ return isinstance(obj, HasRiskLevelProtocol)
830
+
831
+
832
+ def supports_where(obj: Any) -> "TypeGuard[HasWhereProtocol]":
833
+ """Check if an SQL expression supports WHERE clauses."""
834
+ from sqlspec.protocols import HasWhereProtocol
835
+
836
+ return isinstance(obj, HasWhereProtocol)
837
+
838
+
839
+ def supports_limit(obj: Any) -> "TypeGuard[HasLimitProtocol]":
840
+ """Check if an SQL expression supports LIMIT clauses."""
841
+ from sqlspec.protocols import HasLimitProtocol
842
+
843
+ return isinstance(obj, HasLimitProtocol)
844
+
845
+
846
+ def supports_offset(obj: Any) -> "TypeGuard[HasOffsetProtocol]":
847
+ """Check if an SQL expression supports OFFSET clauses."""
848
+ from sqlspec.protocols import HasOffsetProtocol
849
+
850
+ return isinstance(obj, HasOffsetProtocol)
851
+
852
+
853
+ def supports_order_by(obj: Any) -> "TypeGuard[HasOrderByProtocol]":
854
+ """Check if an SQL expression supports ORDER BY clauses."""
855
+ from sqlspec.protocols import HasOrderByProtocol
856
+
857
+ return isinstance(obj, HasOrderByProtocol)
858
+
859
+
860
+ def has_bytes_conversion(obj: Any) -> "TypeGuard[BytesConvertibleProtocol]":
861
+ """Check if an object can be converted to bytes."""
862
+ from sqlspec.protocols import BytesConvertibleProtocol
863
+
864
+ return isinstance(obj, BytesConvertibleProtocol)
865
+
866
+
867
+ def has_expressions(obj: Any) -> "TypeGuard[HasExpressionsProtocol]":
868
+ """Check if an object has an expressions attribute."""
869
+ from sqlspec.protocols import HasExpressionsProtocol
870
+
871
+ return isinstance(obj, HasExpressionsProtocol)
872
+
873
+
874
+ def has_sql_method(obj: Any) -> "TypeGuard[HasSQLMethodProtocol]":
875
+ """Check if an object has a sql() method for rendering SQL."""
876
+ from sqlspec.protocols import HasSQLMethodProtocol
877
+
878
+ return isinstance(obj, HasSQLMethodProtocol)
879
+
880
+
881
+ def has_query_builder_parameters(obj: Any) -> "TypeGuard[SQLBuilderProtocol]":
882
+ """Check if an object is a query builder with parameters property."""
883
+ from sqlspec.protocols import SQLBuilderProtocol
884
+
885
+ return isinstance(obj, SQLBuilderProtocol)
886
+
887
+
888
+ def is_object_store_item(obj: Any) -> "TypeGuard[ObjectStoreItemProtocol]":
889
+ """Check if an object is an object store item with path/key attributes."""
890
+ from sqlspec.protocols import ObjectStoreItemProtocol
891
+
892
+ return isinstance(obj, ObjectStoreItemProtocol)