sqlspec 0.16.2__py3-none-any.whl → 0.17.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.

@@ -1,38 +1,24 @@
1
1
  """DuckDB database configuration with connection pooling."""
2
- # ruff: noqa: D107 W293 RUF100 S110 PLR0913 FA100 BLE001 UP037 COM812 ARG002
3
2
 
4
- import logging
5
- import threading
6
- import time
7
3
  from collections.abc import Sequence
8
- from contextlib import contextmanager, suppress
9
- from typing import TYPE_CHECKING, Any, Final, Optional, TypedDict, cast
4
+ from contextlib import contextmanager
5
+ from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypedDict, Union, cast
10
6
 
11
- import duckdb
12
7
  from typing_extensions import NotRequired
13
8
 
14
9
  from sqlspec.adapters.duckdb._types import DuckDBConnection
15
10
  from sqlspec.adapters.duckdb.driver import DuckDBCursor, DuckDBDriver, duckdb_statement_config
11
+ from sqlspec.adapters.duckdb.pool import DuckDBConnectionPool
16
12
  from sqlspec.config import SyncDatabaseConfig
17
13
 
18
14
  if TYPE_CHECKING:
19
- from collections.abc import Generator
20
- from typing import Callable, ClassVar, Union
15
+ from collections.abc import Callable, Generator
21
16
 
22
17
  from sqlspec.core.statement import StatementConfig
23
18
 
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
19
  __all__ = (
33
20
  "DuckDBConfig",
34
21
  "DuckDBConnectionParams",
35
- "DuckDBConnectionPool",
36
22
  "DuckDBDriverFeatures",
37
23
  "DuckDBExtensionConfig",
38
24
  "DuckDBPoolParams",
@@ -40,210 +26,6 @@ __all__ = (
40
26
  )
41
27
 
42
28
 
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
29
  class DuckDBConnectionParams(TypedDict, total=False):
248
30
  """DuckDB connection parameters."""
249
31
 
@@ -398,16 +180,9 @@ class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, Du
398
180
  and k not in {"pool_min_size", "pool_max_size", "pool_timeout", "pool_recycle_seconds", "extra"}
399
181
  }
400
182
 
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
183
  def _create_pool(self) -> DuckDBConnectionPool:
410
- """Create the DuckDB connection pool."""
184
+ """Create connection pool from configuration."""
185
+ connection_config = self._get_connection_config_dict()
411
186
 
412
187
  extensions = self.driver_features.get("extensions", None)
413
188
  secrets = self.driver_features.get("secrets", None)
@@ -423,12 +198,13 @@ class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, Du
423
198
  on_connection_create(conn)
424
199
 
425
200
  pool_callback = wrapped_callback
426
- conf = {"extensions": extensions_dicts, "secrets": secrets_dicts, "on_connection_create": pool_callback}
427
201
 
428
202
  return DuckDBConnectionPool(
429
- connection_config=self._get_connection_config_dict(),
430
- **conf, # type: ignore[arg-type]
431
- **self._get_pool_config_dict(),
203
+ connection_config=connection_config,
204
+ extensions=extensions_dicts,
205
+ secrets=secrets_dicts,
206
+ on_connection_create=pool_callback,
207
+ **self.pool_config, # Pass all pool_config as kwargs to be filtered by the pool
432
208
  )
433
209
 
434
210
  def _close_pool(self) -> None:
@@ -0,0 +1,243 @@
1
+ """DuckDB connection pool with thread-local connections."""
2
+
3
+ import logging
4
+ import threading
5
+ import time
6
+ from contextlib import contextmanager, suppress
7
+ from typing import TYPE_CHECKING, Any, Final, Optional, cast
8
+
9
+ import duckdb
10
+
11
+ from sqlspec.adapters.duckdb._types import DuckDBConnection
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Callable, Generator
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ DEFAULT_MIN_POOL: Final[int] = 1
20
+ DEFAULT_MAX_POOL: Final[int] = 4
21
+ POOL_TIMEOUT: Final[float] = 30.0
22
+ POOL_RECYCLE: Final[int] = 86400
23
+
24
+ __all__ = ("DuckDBConnectionPool",)
25
+
26
+
27
+ class DuckDBConnectionPool:
28
+ """Thread-local connection manager for DuckDB with performance optimizations.
29
+
30
+ Uses thread-local storage to ensure each thread gets its own DuckDB connection,
31
+ preventing the thread-safety issues that cause segmentation faults when
32
+ multiple cursors share the same connection concurrently.
33
+
34
+ This design trades traditional pooling for thread safety, which is essential
35
+ for DuckDB since connections and cursors are not thread-safe.
36
+ """
37
+
38
+ __slots__ = (
39
+ "_connection_config",
40
+ "_connection_times",
41
+ "_created_connections",
42
+ "_extensions",
43
+ "_lock",
44
+ "_on_connection_create",
45
+ "_recycle",
46
+ "_secrets",
47
+ "_thread_local",
48
+ )
49
+
50
+ def __init__(
51
+ self,
52
+ connection_config: "dict[str, Any]",
53
+ pool_recycle_seconds: int = POOL_RECYCLE,
54
+ extensions: "Optional[list[dict[str, Any]]]" = None,
55
+ secrets: "Optional[list[dict[str, Any]]]" = None,
56
+ on_connection_create: "Optional[Callable[[DuckDBConnection], None]]" = None,
57
+ **kwargs: Any, # Accept and ignore additional parameters for compatibility
58
+ ) -> None:
59
+ """Initialize the thread-local connection manager.
60
+
61
+ Args:
62
+ connection_config: DuckDB connection configuration
63
+ pool_recycle_seconds: Connection recycle time in seconds
64
+ extensions: List of extensions to install/load
65
+ secrets: List of secrets to create
66
+ on_connection_create: Callback executed when connection is created
67
+ **kwargs: Additional parameters ignored for compatibility
68
+ """
69
+ self._connection_config = connection_config
70
+ self._recycle = pool_recycle_seconds
71
+ self._extensions = extensions or []
72
+ self._secrets = secrets or []
73
+ self._on_connection_create = on_connection_create
74
+ self._thread_local = threading.local()
75
+ self._lock = threading.RLock()
76
+ self._created_connections = 0
77
+ self._connection_times: dict[int, float] = {}
78
+
79
+ def _create_connection(self) -> DuckDBConnection:
80
+ """Create a new DuckDB connection with extensions and secrets."""
81
+ connect_parameters = {}
82
+ config_dict = {}
83
+
84
+ for key, value in self._connection_config.items():
85
+ if key in {"database", "read_only"}:
86
+ connect_parameters[key] = value
87
+ else:
88
+ config_dict[key] = value
89
+
90
+ if config_dict:
91
+ connect_parameters["config"] = config_dict
92
+
93
+ connection = duckdb.connect(**connect_parameters)
94
+
95
+ for ext_config in self._extensions:
96
+ ext_name = ext_config.get("name")
97
+ if not ext_name:
98
+ continue
99
+
100
+ install_kwargs = {}
101
+ if "version" in ext_config:
102
+ install_kwargs["version"] = ext_config["version"]
103
+ if "repository" in ext_config:
104
+ install_kwargs["repository"] = ext_config["repository"]
105
+ if ext_config.get("force_install", False):
106
+ install_kwargs["force_install"] = True
107
+
108
+ try:
109
+ if install_kwargs:
110
+ connection.install_extension(ext_name, **install_kwargs)
111
+ connection.load_extension(ext_name)
112
+ except Exception as e:
113
+ logger.debug("Failed to load DuckDB extension %s: %s", ext_name, e)
114
+
115
+ for secret_config in self._secrets:
116
+ secret_type = secret_config.get("secret_type")
117
+ secret_name = secret_config.get("name")
118
+ secret_value = secret_config.get("value")
119
+
120
+ if not (secret_type and secret_name and secret_value):
121
+ continue
122
+
123
+ value_pairs = []
124
+ for key, value in secret_value.items():
125
+ escaped_value = str(value).replace("'", "''")
126
+ value_pairs.append(f"'{key}' = '{escaped_value}'")
127
+ value_string = ", ".join(value_pairs)
128
+ scope_clause = ""
129
+ if "scope" in secret_config:
130
+ scope_clause = f" SCOPE '{secret_config['scope']}'"
131
+
132
+ sql = f"""
133
+ CREATE SECRET {secret_name} (
134
+ TYPE {secret_type},
135
+ {value_string}
136
+ ){scope_clause}
137
+ """
138
+ with suppress(Exception):
139
+ connection.execute(sql)
140
+
141
+ if self._on_connection_create:
142
+ with suppress(Exception):
143
+ self._on_connection_create(connection)
144
+
145
+ conn_id = id(connection)
146
+ with self._lock:
147
+ self._created_connections += 1
148
+ self._connection_times[conn_id] = time.time()
149
+
150
+ return connection
151
+
152
+ def _get_thread_connection(self) -> DuckDBConnection:
153
+ """Get or create a connection for the current thread.
154
+
155
+ Each thread gets its own dedicated DuckDB connection to prevent
156
+ thread-safety issues with concurrent cursor operations.
157
+ """
158
+ if not hasattr(self._thread_local, "connection"):
159
+ self._thread_local.connection = self._create_connection()
160
+ self._thread_local.created_at = time.time()
161
+
162
+ # Check if connection needs recycling
163
+ if self._recycle > 0 and time.time() - self._thread_local.created_at > self._recycle:
164
+ with suppress(Exception):
165
+ self._thread_local.connection.close()
166
+ self._thread_local.connection = self._create_connection()
167
+ self._thread_local.created_at = time.time()
168
+
169
+ return cast("DuckDBConnection", self._thread_local.connection)
170
+
171
+ def _close_thread_connection(self) -> None:
172
+ """Close the connection for the current thread."""
173
+ if hasattr(self._thread_local, "connection"):
174
+ with suppress(Exception):
175
+ self._thread_local.connection.close()
176
+ del self._thread_local.connection
177
+ if hasattr(self._thread_local, "created_at"):
178
+ del self._thread_local.created_at
179
+
180
+ def _is_connection_alive(self, connection: DuckDBConnection) -> bool:
181
+ """Check if a connection is still alive and usable.
182
+
183
+ Args:
184
+ connection: Connection to check
185
+
186
+ Returns:
187
+ True if connection is alive, False otherwise
188
+ """
189
+ try:
190
+ cursor = connection.cursor()
191
+ cursor.close()
192
+ except Exception:
193
+ return False
194
+ return True
195
+
196
+ @contextmanager
197
+ def get_connection(self) -> "Generator[DuckDBConnection, None, None]":
198
+ """Get a thread-local connection.
199
+
200
+ Each thread gets its own dedicated DuckDB connection to prevent
201
+ thread-safety issues with concurrent cursor operations.
202
+
203
+ Yields:
204
+ DuckDBConnection: A thread-local connection.
205
+ """
206
+ connection = self._get_thread_connection()
207
+ try:
208
+ yield connection
209
+ except Exception:
210
+ # On error, close and recreate connection for this thread
211
+ self._close_thread_connection()
212
+ raise
213
+
214
+ def close(self) -> None:
215
+ """Close the thread-local connection if it exists."""
216
+ self._close_thread_connection()
217
+
218
+ def size(self) -> int:
219
+ """Get current pool size (always 1 for thread-local)."""
220
+ return 1 if hasattr(self._thread_local, "connection") else 0
221
+
222
+ def checked_out(self) -> int:
223
+ """Get number of checked out connections (always 0 for thread-local)."""
224
+ return 0
225
+
226
+ def acquire(self) -> DuckDBConnection:
227
+ """Acquire a thread-local connection.
228
+
229
+ Each thread gets its own dedicated DuckDB connection to prevent
230
+ thread-safety issues with concurrent cursor operations.
231
+
232
+ Returns:
233
+ DuckDBConnection: A thread-local connection
234
+ """
235
+ return self._get_thread_connection()
236
+
237
+ def release(self, connection: DuckDBConnection) -> None:
238
+ """Release a connection (no-op for thread-local connections).
239
+
240
+ Args:
241
+ connection: The connection to release (ignored)
242
+ """
243
+ # No-op: thread-local connections are managed per-thread
@@ -3,11 +3,13 @@
3
3
  from sqlspec.adapters.sqlite._types import SqliteConnection
4
4
  from sqlspec.adapters.sqlite.config import SqliteConfig, SqliteConnectionParams
5
5
  from sqlspec.adapters.sqlite.driver import SqliteCursor, SqliteDriver, SqliteExceptionHandler, sqlite_statement_config
6
+ from sqlspec.adapters.sqlite.pool import SqliteConnectionPool
6
7
 
7
8
  __all__ = (
8
9
  "SqliteConfig",
9
10
  "SqliteConnection",
10
11
  "SqliteConnectionParams",
12
+ "SqliteConnectionPool",
11
13
  "SqliteCursor",
12
14
  "SqliteDriver",
13
15
  "SqliteExceptionHandler",
@@ -1,7 +1,6 @@
1
1
  """SQLite database configuration with thread-local connections."""
2
2
 
3
- import sqlite3
4
- import threading
3
+ import uuid
5
4
  from contextlib import contextmanager
6
5
  from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypedDict, Union, cast
7
6
 
@@ -9,6 +8,7 @@ from typing_extensions import NotRequired
9
8
 
10
9
  from sqlspec.adapters.sqlite._types import SqliteConnection
11
10
  from sqlspec.adapters.sqlite.driver import SqliteCursor, SqliteDriver, sqlite_statement_config
11
+ from sqlspec.adapters.sqlite.pool import SqliteConnectionPool
12
12
  from sqlspec.config import SyncDatabaseConfig
13
13
 
14
14
  if TYPE_CHECKING:
@@ -30,118 +30,7 @@ class SqliteConnectionParams(TypedDict, total=False):
30
30
  uri: NotRequired[bool]
31
31
 
32
32
 
33
- __all__ = ("SqliteConfig", "SqliteConnectionParams", "SqliteConnectionPool")
34
-
35
-
36
- class SqliteConnectionPool:
37
- """Thread-local connection manager for SQLite.
38
-
39
- SQLite connections aren't thread-safe, so we use thread-local storage
40
- to ensure each thread has its own connection. This is simpler and more
41
- efficient than a traditional pool for SQLite's constraints.
42
- """
43
-
44
- __slots__ = ("_connection_parameters", "_enable_optimizations", "_thread_local")
45
-
46
- def __init__(
47
- self,
48
- connection_parameters: "dict[str, Any]",
49
- enable_optimizations: bool = True,
50
- **kwargs: Any, # Accept and ignore pool parameters for compatibility
51
- ) -> None:
52
- """Initialize the thread-local connection manager.
53
-
54
- Args:
55
- connection_parameters: SQLite connection parameters
56
- enable_optimizations: Whether to apply performance PRAGMAs
57
- **kwargs: Ignored pool parameters for compatibility
58
- """
59
- self._connection_parameters = connection_parameters
60
- self._thread_local = threading.local()
61
- self._enable_optimizations = enable_optimizations
62
-
63
- def _create_connection(self) -> SqliteConnection:
64
- """Create a new SQLite connection with optimizations."""
65
- connection = sqlite3.connect(**self._connection_parameters)
66
-
67
- # Only apply optimizations if requested and not in-memory
68
- if self._enable_optimizations:
69
- database = self._connection_parameters.get("database", ":memory:")
70
- is_memory = database == ":memory:" or database.startswith("file::memory:")
71
-
72
- if not is_memory:
73
- # WAL mode doesn't work with in-memory databases
74
- connection.execute("PRAGMA journal_mode = WAL")
75
- # Set busy timeout for better concurrent access
76
- connection.execute("PRAGMA busy_timeout = 5000")
77
- connection.execute("PRAGMA optimize")
78
- # These work for all database types
79
- connection.execute("PRAGMA foreign_keys = ON")
80
- connection.execute("PRAGMA synchronous = NORMAL")
81
-
82
- return connection # type: ignore[no-any-return]
83
-
84
- def _get_thread_connection(self) -> SqliteConnection:
85
- """Get or create a connection for the current thread."""
86
- try:
87
- return cast("SqliteConnection", self._thread_local.connection)
88
- except AttributeError:
89
- # Connection doesn't exist for this thread yet
90
- connection = self._create_connection()
91
- self._thread_local.connection = connection
92
- return connection
93
-
94
- def _close_thread_connection(self) -> None:
95
- """Close the connection for the current thread."""
96
- try:
97
- connection = self._thread_local.connection
98
- connection.close()
99
- del self._thread_local.connection
100
- except AttributeError:
101
- # No connection for this thread
102
- pass
103
-
104
- @contextmanager
105
- def get_connection(self) -> "Generator[SqliteConnection, None, None]":
106
- """Get a thread-local connection.
107
-
108
- Yields:
109
- SqliteConnection: A thread-local connection.
110
- """
111
- yield self._get_thread_connection()
112
-
113
- def close(self) -> None:
114
- """Close the thread-local connection if it exists."""
115
- self._close_thread_connection()
116
-
117
- def acquire(self) -> SqliteConnection:
118
- """Acquire a thread-local connection.
119
-
120
- Returns:
121
- SqliteConnection: A thread-local connection
122
- """
123
- return self._get_thread_connection()
124
-
125
- def release(self, connection: SqliteConnection) -> None:
126
- """Release a connection (no-op for thread-local connections).
127
-
128
- Args:
129
- connection: The connection to release (ignored)
130
- """
131
- # No-op: thread-local connections are managed per-thread
132
-
133
- # Compatibility methods that return dummy values
134
- def size(self) -> int:
135
- """Get pool size (always 1 for thread-local)."""
136
- try:
137
- _ = self._thread_local.connection
138
- except AttributeError:
139
- return 0
140
- return 1
141
-
142
- def checked_out(self) -> int:
143
- """Get number of checked out connections (always 0)."""
144
- return 0
33
+ __all__ = ("SqliteConfig", "SqliteConnectionParams")
145
34
 
146
35
 
147
36
  class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, SqliteDriver]):
@@ -169,7 +58,7 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
169
58
  if pool_config is None:
170
59
  pool_config = {}
171
60
  if "database" not in pool_config or pool_config["database"] == ":memory:":
172
- pool_config["database"] = "file::memory:?cache=shared"
61
+ pool_config["database"] = f"file:memory_{uuid.uuid4().hex}?mode=memory&cache=private"
173
62
  pool_config["uri"] = True
174
63
 
175
64
  super().__init__(