sqlspec 0.16.1__cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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 (148) hide show
  1. 51ff5a9eadfdefd49f98__mypyc.cpython-311-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +92 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +77 -0
  6. sqlspec/_sql.py +1780 -0
  7. sqlspec/_typing.py +680 -0
  8. sqlspec/adapters/__init__.py +0 -0
  9. sqlspec/adapters/adbc/__init__.py +5 -0
  10. sqlspec/adapters/adbc/_types.py +12 -0
  11. sqlspec/adapters/adbc/config.py +361 -0
  12. sqlspec/adapters/adbc/driver.py +512 -0
  13. sqlspec/adapters/aiosqlite/__init__.py +19 -0
  14. sqlspec/adapters/aiosqlite/_types.py +13 -0
  15. sqlspec/adapters/aiosqlite/config.py +253 -0
  16. sqlspec/adapters/aiosqlite/driver.py +248 -0
  17. sqlspec/adapters/asyncmy/__init__.py +19 -0
  18. sqlspec/adapters/asyncmy/_types.py +12 -0
  19. sqlspec/adapters/asyncmy/config.py +180 -0
  20. sqlspec/adapters/asyncmy/driver.py +274 -0
  21. sqlspec/adapters/asyncpg/__init__.py +21 -0
  22. sqlspec/adapters/asyncpg/_types.py +17 -0
  23. sqlspec/adapters/asyncpg/config.py +229 -0
  24. sqlspec/adapters/asyncpg/driver.py +344 -0
  25. sqlspec/adapters/bigquery/__init__.py +18 -0
  26. sqlspec/adapters/bigquery/_types.py +12 -0
  27. sqlspec/adapters/bigquery/config.py +298 -0
  28. sqlspec/adapters/bigquery/driver.py +558 -0
  29. sqlspec/adapters/duckdb/__init__.py +22 -0
  30. sqlspec/adapters/duckdb/_types.py +12 -0
  31. sqlspec/adapters/duckdb/config.py +504 -0
  32. sqlspec/adapters/duckdb/driver.py +368 -0
  33. sqlspec/adapters/oracledb/__init__.py +32 -0
  34. sqlspec/adapters/oracledb/_types.py +14 -0
  35. sqlspec/adapters/oracledb/config.py +317 -0
  36. sqlspec/adapters/oracledb/driver.py +538 -0
  37. sqlspec/adapters/psqlpy/__init__.py +16 -0
  38. sqlspec/adapters/psqlpy/_types.py +11 -0
  39. sqlspec/adapters/psqlpy/config.py +214 -0
  40. sqlspec/adapters/psqlpy/driver.py +530 -0
  41. sqlspec/adapters/psycopg/__init__.py +32 -0
  42. sqlspec/adapters/psycopg/_types.py +17 -0
  43. sqlspec/adapters/psycopg/config.py +426 -0
  44. sqlspec/adapters/psycopg/driver.py +796 -0
  45. sqlspec/adapters/sqlite/__init__.py +15 -0
  46. sqlspec/adapters/sqlite/_types.py +11 -0
  47. sqlspec/adapters/sqlite/config.py +240 -0
  48. sqlspec/adapters/sqlite/driver.py +294 -0
  49. sqlspec/base.py +571 -0
  50. sqlspec/builder/__init__.py +62 -0
  51. sqlspec/builder/_base.py +473 -0
  52. sqlspec/builder/_column.py +320 -0
  53. sqlspec/builder/_ddl.py +1346 -0
  54. sqlspec/builder/_ddl_utils.py +103 -0
  55. sqlspec/builder/_delete.py +76 -0
  56. sqlspec/builder/_insert.py +256 -0
  57. sqlspec/builder/_merge.py +71 -0
  58. sqlspec/builder/_parsing_utils.py +140 -0
  59. sqlspec/builder/_select.py +170 -0
  60. sqlspec/builder/_update.py +188 -0
  61. sqlspec/builder/mixins/__init__.py +55 -0
  62. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  63. sqlspec/builder/mixins/_delete_operations.py +41 -0
  64. sqlspec/builder/mixins/_insert_operations.py +244 -0
  65. sqlspec/builder/mixins/_join_operations.py +122 -0
  66. sqlspec/builder/mixins/_merge_operations.py +476 -0
  67. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  68. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  69. sqlspec/builder/mixins/_select_operations.py +603 -0
  70. sqlspec/builder/mixins/_update_operations.py +187 -0
  71. sqlspec/builder/mixins/_where_clause.py +621 -0
  72. sqlspec/cli.py +247 -0
  73. sqlspec/config.py +395 -0
  74. sqlspec/core/__init__.py +63 -0
  75. sqlspec/core/cache.cpython-311-aarch64-linux-gnu.so +0 -0
  76. sqlspec/core/cache.py +871 -0
  77. sqlspec/core/compiler.cpython-311-aarch64-linux-gnu.so +0 -0
  78. sqlspec/core/compiler.py +417 -0
  79. sqlspec/core/filters.cpython-311-aarch64-linux-gnu.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-311-aarch64-linux-gnu.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-311-aarch64-linux-gnu.so +0 -0
  84. sqlspec/core/parameters.py +1237 -0
  85. sqlspec/core/result.cpython-311-aarch64-linux-gnu.so +0 -0
  86. sqlspec/core/result.py +677 -0
  87. sqlspec/core/splitter.cpython-311-aarch64-linux-gnu.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-311-aarch64-linux-gnu.so +0 -0
  90. sqlspec/core/statement.py +676 -0
  91. sqlspec/driver/__init__.py +19 -0
  92. sqlspec/driver/_async.py +502 -0
  93. sqlspec/driver/_common.py +631 -0
  94. sqlspec/driver/_sync.py +503 -0
  95. sqlspec/driver/mixins/__init__.py +6 -0
  96. sqlspec/driver/mixins/_result_tools.py +193 -0
  97. sqlspec/driver/mixins/_sql_translator.py +86 -0
  98. sqlspec/exceptions.py +193 -0
  99. sqlspec/extensions/__init__.py +0 -0
  100. sqlspec/extensions/aiosql/__init__.py +10 -0
  101. sqlspec/extensions/aiosql/adapter.py +461 -0
  102. sqlspec/extensions/litestar/__init__.py +6 -0
  103. sqlspec/extensions/litestar/_utils.py +52 -0
  104. sqlspec/extensions/litestar/cli.py +48 -0
  105. sqlspec/extensions/litestar/config.py +92 -0
  106. sqlspec/extensions/litestar/handlers.py +260 -0
  107. sqlspec/extensions/litestar/plugin.py +145 -0
  108. sqlspec/extensions/litestar/providers.py +454 -0
  109. sqlspec/loader.cpython-311-aarch64-linux-gnu.so +0 -0
  110. sqlspec/loader.py +760 -0
  111. sqlspec/migrations/__init__.py +35 -0
  112. sqlspec/migrations/base.py +414 -0
  113. sqlspec/migrations/commands.py +443 -0
  114. sqlspec/migrations/loaders.py +402 -0
  115. sqlspec/migrations/runner.py +213 -0
  116. sqlspec/migrations/tracker.py +140 -0
  117. sqlspec/migrations/utils.py +129 -0
  118. sqlspec/protocols.py +407 -0
  119. sqlspec/py.typed +0 -0
  120. sqlspec/storage/__init__.py +23 -0
  121. sqlspec/storage/backends/__init__.py +0 -0
  122. sqlspec/storage/backends/base.py +163 -0
  123. sqlspec/storage/backends/fsspec.py +386 -0
  124. sqlspec/storage/backends/obstore.py +459 -0
  125. sqlspec/storage/capabilities.py +102 -0
  126. sqlspec/storage/registry.py +239 -0
  127. sqlspec/typing.py +299 -0
  128. sqlspec/utils/__init__.py +3 -0
  129. sqlspec/utils/correlation.py +150 -0
  130. sqlspec/utils/deprecation.py +106 -0
  131. sqlspec/utils/fixtures.cpython-311-aarch64-linux-gnu.so +0 -0
  132. sqlspec/utils/fixtures.py +58 -0
  133. sqlspec/utils/logging.py +127 -0
  134. sqlspec/utils/module_loader.py +89 -0
  135. sqlspec/utils/serializers.py +4 -0
  136. sqlspec/utils/singleton.py +32 -0
  137. sqlspec/utils/sync_tools.cpython-311-aarch64-linux-gnu.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-311-aarch64-linux-gnu.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-311-aarch64-linux-gnu.so +0 -0
  142. sqlspec/utils/type_guards.py +1139 -0
  143. sqlspec-0.16.1.dist-info/METADATA +365 -0
  144. sqlspec-0.16.1.dist-info/RECORD +148 -0
  145. sqlspec-0.16.1.dist-info/WHEEL +7 -0
  146. sqlspec-0.16.1.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.1.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.1.dist-info/licenses/NOTICE +29 -0
@@ -0,0 +1,504 @@
1
+ """DuckDB database configuration with connection pooling."""
2
+ # ruff: noqa: D107 W293 RUF100 S110 PLR0913 FA100 BLE001 UP037 COM812 ARG002
3
+
4
+ import logging
5
+ import threading
6
+ import time
7
+ from collections.abc import Sequence
8
+ from contextlib import contextmanager, suppress
9
+ from typing import TYPE_CHECKING, Any, Final, Optional, TypedDict, cast
10
+
11
+ import duckdb
12
+ from typing_extensions import NotRequired
13
+
14
+ from sqlspec.adapters.duckdb._types import DuckDBConnection
15
+ from sqlspec.adapters.duckdb.driver import DuckDBCursor, DuckDBDriver, duckdb_statement_config
16
+ from sqlspec.config import SyncDatabaseConfig
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Generator
20
+ from typing import Callable, ClassVar, Union
21
+
22
+ from sqlspec.core.statement import StatementConfig
23
+
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ DEFAULT_MIN_POOL: Final[int] = 1
28
+ DEFAULT_MAX_POOL: Final[int] = 4
29
+ POOL_TIMEOUT: Final[float] = 30.0
30
+ POOL_RECYCLE: Final[int] = 86400
31
+
32
+ __all__ = (
33
+ "DuckDBConfig",
34
+ "DuckDBConnectionParams",
35
+ "DuckDBConnectionPool",
36
+ "DuckDBDriverFeatures",
37
+ "DuckDBExtensionConfig",
38
+ "DuckDBPoolParams",
39
+ "DuckDBSecretConfig",
40
+ )
41
+
42
+
43
+ class DuckDBConnectionPool:
44
+ """Thread-local connection manager for DuckDB with performance optimizations.
45
+
46
+ Uses thread-local storage to ensure each thread gets its own DuckDB connection,
47
+ preventing the thread-safety issues that cause segmentation faults when
48
+ multiple cursors share the same connection concurrently.
49
+
50
+ This design trades traditional pooling for thread safety, which is essential
51
+ for DuckDB since connections and cursors are not thread-safe.
52
+ """
53
+
54
+ __slots__ = (
55
+ "_connection_config",
56
+ "_connection_times",
57
+ "_created_connections",
58
+ "_extensions",
59
+ "_lock",
60
+ "_on_connection_create",
61
+ "_recycle",
62
+ "_secrets",
63
+ "_thread_local",
64
+ )
65
+
66
+ def __init__( # noqa: PLR0913
67
+ self,
68
+ connection_config: "dict[str, Any]", # noqa: UP037
69
+ pool_min_size: int = DEFAULT_MIN_POOL,
70
+ pool_max_size: int = DEFAULT_MAX_POOL,
71
+ pool_timeout: float = POOL_TIMEOUT,
72
+ pool_recycle_seconds: int = POOL_RECYCLE,
73
+ extensions: "Optional[list[dict[str, Any]]]" = None, # noqa: FA100, UP037
74
+ secrets: "Optional[list[dict[str, Any]]]" = None, # noqa: FA100, UP037
75
+ on_connection_create: "Optional[Callable[[DuckDBConnection], None]]" = None, # noqa: FA100
76
+ ) -> None:
77
+ """Initialize the thread-local connection manager."""
78
+ self._connection_config = connection_config
79
+ self._recycle = pool_recycle_seconds
80
+ self._extensions = extensions or []
81
+ self._secrets = secrets or []
82
+ self._on_connection_create = on_connection_create
83
+ self._thread_local = threading.local()
84
+ self._lock = threading.RLock()
85
+ self._created_connections = 0
86
+ self._connection_times: "dict[int, float]" = {}
87
+
88
+ def _create_connection(self) -> DuckDBConnection:
89
+ """Create a new DuckDB connection with extensions and secrets."""
90
+ connect_parameters = {}
91
+ config_dict = {}
92
+
93
+ for key, value in self._connection_config.items():
94
+ if key in {"database", "read_only"}:
95
+ connect_parameters[key] = value
96
+ else:
97
+ config_dict[key] = value
98
+
99
+ if config_dict:
100
+ connect_parameters["config"] = config_dict
101
+
102
+ connection = duckdb.connect(**connect_parameters)
103
+
104
+ for ext_config in self._extensions:
105
+ ext_name = ext_config.get("name")
106
+ if not ext_name:
107
+ continue
108
+
109
+ install_kwargs = {}
110
+ if "version" in ext_config:
111
+ install_kwargs["version"] = ext_config["version"]
112
+ if "repository" in ext_config:
113
+ install_kwargs["repository"] = ext_config["repository"]
114
+ if ext_config.get("force_install", False):
115
+ install_kwargs["force_install"] = True
116
+
117
+ try:
118
+ if install_kwargs:
119
+ connection.install_extension(ext_name, **install_kwargs)
120
+ connection.load_extension(ext_name)
121
+ except Exception: # noqa: BLE001, S110
122
+ pass
123
+
124
+ for secret_config in self._secrets:
125
+ secret_type = secret_config.get("secret_type")
126
+ secret_name = secret_config.get("name")
127
+ secret_value = secret_config.get("value")
128
+
129
+ if not (secret_type and secret_name and secret_value):
130
+ continue
131
+
132
+ value_pairs = []
133
+ for key, value in secret_value.items():
134
+ escaped_value = str(value).replace("'", "''")
135
+ value_pairs.append(f"'{key}' = '{escaped_value}'")
136
+ value_string = ", ".join(value_pairs)
137
+ scope_clause = ""
138
+ if "scope" in secret_config:
139
+ scope_clause = f" SCOPE '{secret_config['scope']}'"
140
+
141
+ sql = f""" # noqa: S608
142
+ CREATE SECRET {secret_name} (
143
+ TYPE {secret_type},
144
+ {value_string}
145
+ ){scope_clause}
146
+ """
147
+ with suppress(Exception):
148
+ connection.execute(sql)
149
+
150
+ if self._on_connection_create:
151
+ with suppress(Exception):
152
+ self._on_connection_create(connection)
153
+
154
+ conn_id = id(connection)
155
+ with self._lock:
156
+ self._created_connections += 1
157
+ self._connection_times[conn_id] = time.time()
158
+
159
+ return connection
160
+
161
+ def _get_thread_connection(self) -> DuckDBConnection:
162
+ """Get or create a connection for the current thread.
163
+
164
+ Each thread gets its own dedicated DuckDB connection to prevent
165
+ thread-safety issues with concurrent cursor operations.
166
+ """
167
+ if not hasattr(self._thread_local, "connection"):
168
+ self._thread_local.connection = self._create_connection()
169
+ self._thread_local.created_at = time.time()
170
+
171
+ # Check if connection needs recycling
172
+ if self._recycle > 0 and time.time() - self._thread_local.created_at > self._recycle:
173
+ with suppress(Exception):
174
+ self._thread_local.connection.close()
175
+ self._thread_local.connection = self._create_connection()
176
+ self._thread_local.created_at = time.time()
177
+
178
+ return cast("DuckDBConnection", self._thread_local.connection)
179
+
180
+ def _close_thread_connection(self) -> None:
181
+ """Close the connection for the current thread."""
182
+ if hasattr(self._thread_local, "connection"):
183
+ with suppress(Exception):
184
+ self._thread_local.connection.close()
185
+ del self._thread_local.connection
186
+ if hasattr(self._thread_local, "created_at"):
187
+ del self._thread_local.created_at
188
+
189
+ def _is_connection_alive(self, connection: DuckDBConnection) -> bool:
190
+ """Check if a connection is still alive and usable.
191
+
192
+ Args:
193
+ connection: Connection to check
194
+
195
+ Returns:
196
+ True if connection is alive, False otherwise
197
+ """
198
+ try:
199
+ cursor = connection.cursor()
200
+ cursor.close()
201
+ except Exception:
202
+ return False
203
+ return True
204
+
205
+ @contextmanager
206
+ def get_connection(self) -> "Generator[DuckDBConnection, None, None]":
207
+ """Get a thread-local connection.
208
+
209
+ Each thread gets its own dedicated DuckDB connection to prevent
210
+ thread-safety issues with concurrent cursor operations.
211
+
212
+ Yields:
213
+ DuckDBConnection: A thread-local connection.
214
+ """
215
+ connection = self._get_thread_connection()
216
+ try:
217
+ yield connection
218
+ except Exception:
219
+ # On error, close and recreate connection for this thread
220
+ self._close_thread_connection()
221
+ raise
222
+
223
+ def close(self) -> None:
224
+ """Close the thread-local connection if it exists."""
225
+ self._close_thread_connection()
226
+
227
+ def size(self) -> int:
228
+ """Get current pool size (always 1 for thread-local)."""
229
+ return 1 if hasattr(self._thread_local, "connection") else 0
230
+
231
+ def checked_out(self) -> int:
232
+ """Get number of checked out connections (always 0 for thread-local)."""
233
+ return 0
234
+
235
+ def acquire(self) -> DuckDBConnection:
236
+ """Acquire a thread-local connection.
237
+
238
+ Each thread gets its own dedicated DuckDB connection to prevent
239
+ thread-safety issues with concurrent cursor operations.
240
+
241
+ Returns:
242
+ DuckDBConnection: A thread-local connection
243
+ """
244
+ return self._get_thread_connection()
245
+
246
+
247
+ class DuckDBConnectionParams(TypedDict, total=False):
248
+ """DuckDB connection parameters."""
249
+
250
+ database: NotRequired[str]
251
+ read_only: NotRequired[bool]
252
+ config: NotRequired[dict[str, Any]]
253
+ memory_limit: NotRequired[str]
254
+ threads: NotRequired[int]
255
+ temp_directory: NotRequired[str]
256
+ max_temp_directory_size: NotRequired[str]
257
+ autoload_known_extensions: NotRequired[bool]
258
+ autoinstall_known_extensions: NotRequired[bool]
259
+ allow_community_extensions: NotRequired[bool]
260
+ allow_unsigned_extensions: NotRequired[bool]
261
+ extension_directory: NotRequired[str]
262
+ custom_extension_repository: NotRequired[str]
263
+ autoinstall_extension_repository: NotRequired[str]
264
+ allow_persistent_secrets: NotRequired[bool]
265
+ enable_external_access: NotRequired[bool]
266
+ secret_directory: NotRequired[str]
267
+ enable_object_cache: NotRequired[bool]
268
+ parquet_metadata_cache: NotRequired[str]
269
+ enable_external_file_cache: NotRequired[bool]
270
+ checkpoint_threshold: NotRequired[str]
271
+ enable_progress_bar: NotRequired[bool]
272
+ progress_bar_time: NotRequired[float]
273
+ enable_logging: NotRequired[bool]
274
+ log_query_path: NotRequired[str]
275
+ logging_level: NotRequired[str]
276
+ preserve_insertion_order: NotRequired[bool]
277
+ default_null_order: NotRequired[str]
278
+ default_order: NotRequired[str]
279
+ ieee_floating_point_ops: NotRequired[bool]
280
+ binary_as_string: NotRequired[bool]
281
+ arrow_large_buffer_size: NotRequired[bool]
282
+ errors_as_json: NotRequired[bool]
283
+ extra: NotRequired[dict[str, Any]]
284
+
285
+
286
+ class DuckDBPoolParams(DuckDBConnectionParams, total=False):
287
+ """Complete pool configuration for DuckDB adapter.
288
+
289
+ Combines standardized pool parameters with DuckDB-specific connection parameters.
290
+ """
291
+
292
+ # Standardized pool parameters (consistent across ALL adapters)
293
+ pool_min_size: NotRequired[int]
294
+ pool_max_size: NotRequired[int]
295
+ pool_timeout: NotRequired[float]
296
+ pool_recycle_seconds: NotRequired[int]
297
+
298
+
299
+ class DuckDBExtensionConfig(TypedDict, total=False):
300
+ """DuckDB extension configuration for auto-management."""
301
+
302
+ name: str
303
+ """Name of the extension to install/load."""
304
+
305
+ version: NotRequired[str]
306
+ """Specific version of the extension."""
307
+
308
+ repository: NotRequired[str]
309
+ """Repository for the extension (core, community, or custom URL)."""
310
+
311
+ force_install: NotRequired[bool]
312
+ """Force reinstallation of the extension."""
313
+
314
+
315
+ class DuckDBSecretConfig(TypedDict, total=False):
316
+ """DuckDB secret configuration for AI/API integrations."""
317
+
318
+ secret_type: str
319
+ """Type of secret (e.g., 'openai', 'aws', 'azure', 'gcp')."""
320
+
321
+ name: str
322
+ """Name of the secret."""
323
+
324
+ value: dict[str, Any]
325
+ """Secret configuration values."""
326
+
327
+ scope: NotRequired[str]
328
+ """Scope of the secret (LOCAL or PERSISTENT)."""
329
+
330
+
331
+ class DuckDBDriverFeatures(TypedDict, total=False):
332
+ """TypedDict for DuckDB driver features configuration."""
333
+
334
+ extensions: NotRequired[Sequence[DuckDBExtensionConfig]]
335
+ """List of extensions to install/load on connection creation."""
336
+ secrets: NotRequired[Sequence[DuckDBSecretConfig]]
337
+ """List of secrets to create for AI/API integrations."""
338
+ on_connection_create: NotRequired["Callable[[DuckDBConnection], Optional[DuckDBConnection]]"]
339
+ """Callback executed when connection is created."""
340
+
341
+
342
+ class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, DuckDBDriver]):
343
+ """Enhanced DuckDB configuration with connection pooling and intelligent features.
344
+
345
+ This configuration supports all of DuckDB's unique features including:
346
+
347
+ - Connection pooling optimized for DuckDB's architecture
348
+ - Extension auto-management and installation
349
+ - Secret management for API integrations
350
+ - Intelligent auto configuration settings
351
+ - High-performance Arrow integration
352
+ - Direct file querying capabilities
353
+ - Performance optimizations for analytics workloads
354
+
355
+ DuckDB Connection Pool Best Practices:
356
+ - DuckDB performs best with long-lived connections that maintain cache
357
+ - Default pool size is 1-4 connections (DuckDB is optimized for single connection)
358
+ - Connection recycling is set to 24 hours by default (set to 0 to disable)
359
+ - Shared memory databases use `:memory:shared_db` for proper concurrency
360
+ - Health checks are minimized to reduce overhead
361
+ """
362
+
363
+ driver_type: "ClassVar[type[DuckDBDriver]]" = DuckDBDriver
364
+ connection_type: "ClassVar[type[DuckDBConnection]]" = DuckDBConnection
365
+
366
+ def __init__(
367
+ self,
368
+ *,
369
+ pool_config: "Optional[Union[DuckDBPoolParams, dict[str, Any]]]" = None,
370
+ migration_config: Optional[dict[str, Any]] = None,
371
+ pool_instance: "Optional[DuckDBConnectionPool]" = None,
372
+ statement_config: "Optional[StatementConfig]" = None,
373
+ driver_features: "Optional[Union[DuckDBDriverFeatures, dict[str, Any]]]" = None,
374
+ ) -> None:
375
+ """Initialize DuckDB configuration with intelligent features."""
376
+ if pool_config is None:
377
+ pool_config = {}
378
+ if "database" not in pool_config:
379
+ pool_config["database"] = ":memory:shared_db"
380
+
381
+ if pool_config.get("database") in {":memory:", ""}:
382
+ pool_config["database"] = ":memory:shared_db"
383
+
384
+ super().__init__(
385
+ pool_config=dict(pool_config),
386
+ pool_instance=pool_instance,
387
+ migration_config=migration_config,
388
+ statement_config=statement_config or duckdb_statement_config,
389
+ driver_features=cast("dict[str, Any]", driver_features),
390
+ )
391
+
392
+ def _get_connection_config_dict(self) -> "dict[str, Any]":
393
+ """Get connection configuration as plain dict for pool creation."""
394
+ return {
395
+ k: v
396
+ for k, v in self.pool_config.items()
397
+ if v is not None
398
+ and k not in {"pool_min_size", "pool_max_size", "pool_timeout", "pool_recycle_seconds", "extra"}
399
+ }
400
+
401
+ def _get_pool_config_dict(self) -> "dict[str, Any]":
402
+ """Get pool configuration as plain dict for pool creation."""
403
+ return {
404
+ k: v
405
+ for k, v in self.pool_config.items()
406
+ if v is not None and k in {"pool_min_size", "pool_max_size", "pool_timeout", "pool_recycle_seconds"}
407
+ }
408
+
409
+ def _create_pool(self) -> DuckDBConnectionPool:
410
+ """Create the DuckDB connection pool."""
411
+
412
+ extensions = self.driver_features.get("extensions", None)
413
+ secrets = self.driver_features.get("secrets", None)
414
+ on_connection_create = self.driver_features.get("on_connection_create", None)
415
+
416
+ extensions_dicts = [dict(ext) for ext in extensions] if extensions else None
417
+ secrets_dicts = [dict(secret) for secret in secrets] if secrets else None
418
+
419
+ pool_callback = None
420
+ if on_connection_create:
421
+
422
+ def wrapped_callback(conn: DuckDBConnection) -> None:
423
+ on_connection_create(conn)
424
+
425
+ pool_callback = wrapped_callback
426
+ conf = {"extensions": extensions_dicts, "secrets": secrets_dicts, "on_connection_create": pool_callback}
427
+
428
+ return DuckDBConnectionPool(
429
+ connection_config=self._get_connection_config_dict(),
430
+ **conf, # type: ignore[arg-type]
431
+ **self._get_pool_config_dict(),
432
+ )
433
+
434
+ def _close_pool(self) -> None:
435
+ """Close the connection pool."""
436
+ if self.pool_instance:
437
+ self.pool_instance.close()
438
+
439
+ def create_connection(self) -> DuckDBConnection:
440
+ """Get a DuckDB connection from the pool.
441
+
442
+ This method ensures the pool is created and returns a connection
443
+ from the pool. The connection is checked out from the pool and must
444
+ be properly managed by the caller.
445
+
446
+ Returns:
447
+ DuckDBConnection: A connection from the pool
448
+
449
+ Note:
450
+ For automatic connection management, prefer using provide_connection()
451
+ or provide_session() which handle returning connections to the pool.
452
+ The caller is responsible for returning the connection to the pool
453
+ using pool.release(connection) when done.
454
+ """
455
+ pool = self.provide_pool()
456
+
457
+ return pool.acquire()
458
+
459
+ @contextmanager
460
+ def provide_connection(self, *args: Any, **kwargs: Any) -> "Generator[DuckDBConnection, None, None]":
461
+ """Provide a pooled DuckDB connection context manager.
462
+
463
+ Args:
464
+ *args: Additional arguments.
465
+ **kwargs: Additional keyword arguments.
466
+
467
+ Yields:
468
+ A DuckDB connection instance.
469
+ """
470
+ pool = self.provide_pool()
471
+ with pool.get_connection() as connection:
472
+ yield connection
473
+
474
+ @contextmanager
475
+ def provide_session(
476
+ self, *args: Any, statement_config: "Optional[StatementConfig]" = None, **kwargs: Any
477
+ ) -> "Generator[DuckDBDriver, None, None]":
478
+ """Provide a DuckDB driver session context manager.
479
+
480
+ Args:
481
+ *args: Additional arguments.
482
+ statement_config: Optional statement configuration override.
483
+ **kwargs: Additional keyword arguments.
484
+
485
+ Yields:
486
+ A context manager that yields a DuckDBDriver instance.
487
+ """
488
+ with self.provide_connection(*args, **kwargs) as connection:
489
+ driver = self.driver_type(connection=connection, statement_config=statement_config or self.statement_config)
490
+ yield driver
491
+
492
+ def get_signature_namespace(self) -> "dict[str, type[Any]]":
493
+ """Get the signature namespace for DuckDB types.
494
+
495
+ This provides all DuckDB-specific types that Litestar needs to recognize
496
+ to avoid serialization attempts.
497
+
498
+ Returns:
499
+ Dictionary mapping type names to types.
500
+ """
501
+
502
+ namespace = super().get_signature_namespace()
503
+ namespace.update({"DuckDBConnection": DuckDBConnection, "DuckDBCursor": DuckDBCursor})
504
+ return namespace