sqlspec 0.25.0__py3-none-any.whl → 0.27.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 (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -58
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +153 -0
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +102 -0
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,253 @@
1
+ """AsyncPG session store for Litestar integration."""
2
+
3
+ from datetime import datetime, timedelta, timezone
4
+ from typing import TYPE_CHECKING
5
+
6
+ from sqlspec.extensions.litestar.store import BaseSQLSpecStore
7
+ from sqlspec.utils.logging import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from sqlspec.adapters.asyncpg.config import AsyncpgConfig
11
+
12
+ logger = get_logger("adapters.asyncpg.litestar.store")
13
+
14
+ __all__ = ("AsyncpgStore",)
15
+
16
+
17
+ class AsyncpgStore(BaseSQLSpecStore["AsyncpgConfig"]):
18
+ """PostgreSQL session store using AsyncPG driver.
19
+
20
+ Implements server-side session storage for Litestar using PostgreSQL
21
+ via the AsyncPG driver. Provides efficient session management with:
22
+ - Native async PostgreSQL operations
23
+ - UPSERT support using ON CONFLICT
24
+ - Automatic expiration handling
25
+ - Efficient cleanup of expired sessions
26
+
27
+ Args:
28
+ config: AsyncpgConfig instance with extension_config["litestar"] settings.
29
+
30
+ Example:
31
+ from sqlspec.adapters.asyncpg import AsyncpgConfig
32
+ from sqlspec.adapters.asyncpg.litestar.store import AsyncpgStore
33
+
34
+ config = AsyncpgConfig(
35
+ pool_config={"dsn": "postgresql://..."},
36
+ extension_config={"litestar": {"session_table": "my_sessions"}}
37
+ )
38
+ store = AsyncpgStore(config)
39
+ await store.create_table()
40
+ """
41
+
42
+ __slots__ = ()
43
+
44
+ def __init__(self, config: "AsyncpgConfig") -> None:
45
+ """Initialize AsyncPG session store.
46
+
47
+ Args:
48
+ config: AsyncpgConfig instance.
49
+
50
+ Notes:
51
+ Table name is read from config.extension_config["litestar"]["session_table"].
52
+ """
53
+ super().__init__(config)
54
+
55
+ def _get_create_table_sql(self) -> str:
56
+ """Get PostgreSQL CREATE TABLE SQL with optimized schema.
57
+
58
+ Returns:
59
+ SQL statement to create the sessions table with proper indexes.
60
+
61
+ Notes:
62
+ - Uses TIMESTAMPTZ for timezone-aware expiration timestamps
63
+ - Partial index WHERE expires_at IS NOT NULL reduces index size/maintenance
64
+ - FILLFACTOR 80 leaves space for HOT updates, reducing table bloat
65
+ - Audit columns (created_at, updated_at) help with debugging
66
+ - Table name is internally controlled, not user input (S608 suppressed)
67
+ """
68
+ return f"""
69
+ CREATE TABLE IF NOT EXISTS {self._table_name} (
70
+ session_id TEXT PRIMARY KEY,
71
+ data BYTEA NOT NULL,
72
+ expires_at TIMESTAMPTZ,
73
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
74
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
75
+ ) WITH (fillfactor = 80);
76
+
77
+ CREATE INDEX IF NOT EXISTS idx_{self._table_name}_expires_at
78
+ ON {self._table_name}(expires_at) WHERE expires_at IS NOT NULL;
79
+
80
+ ALTER TABLE {self._table_name} SET (
81
+ autovacuum_vacuum_scale_factor = 0.05,
82
+ autovacuum_analyze_scale_factor = 0.02
83
+ );
84
+ """
85
+
86
+ def _get_drop_table_sql(self) -> "list[str]":
87
+ """Get PostgreSQL DROP TABLE SQL statements.
88
+
89
+ Returns:
90
+ List of SQL statements to drop indexes and table.
91
+ """
92
+ return [f"DROP INDEX IF EXISTS idx_{self._table_name}_expires_at", f"DROP TABLE IF EXISTS {self._table_name}"]
93
+
94
+ async def create_table(self) -> None:
95
+ """Create the session table if it doesn't exist."""
96
+ sql = self._get_create_table_sql()
97
+ async with self._config.provide_session() as driver:
98
+ await driver.execute_script(sql)
99
+ logger.debug("Created session table: %s", self._table_name)
100
+
101
+ async def get(self, key: str, renew_for: "int | timedelta | None" = None) -> "bytes | None":
102
+ """Get a session value by key.
103
+
104
+ Args:
105
+ key: Session ID to retrieve.
106
+ renew_for: If given, renew the expiry time for this duration.
107
+
108
+ Returns:
109
+ Session data as bytes if found and not expired, None otherwise.
110
+
111
+ Notes:
112
+ Uses CURRENT_TIMESTAMP instead of NOW() for SQL standard compliance.
113
+ The query planner can use the partial index for expires_at > CURRENT_TIMESTAMP.
114
+ """
115
+ sql = f"""
116
+ SELECT data, expires_at FROM {self._table_name}
117
+ WHERE session_id = $1
118
+ AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
119
+ """
120
+
121
+ async with self._config.provide_connection() as conn:
122
+ row = await conn.fetchrow(sql, key)
123
+
124
+ if row is None:
125
+ return None
126
+
127
+ if renew_for is not None and row["expires_at"] is not None:
128
+ new_expires_at = self._calculate_expires_at(renew_for)
129
+ if new_expires_at is not None:
130
+ update_sql = f"""
131
+ UPDATE {self._table_name}
132
+ SET expires_at = $1, updated_at = CURRENT_TIMESTAMP
133
+ WHERE session_id = $2
134
+ """
135
+ await conn.execute(update_sql, new_expires_at, key)
136
+
137
+ return bytes(row["data"])
138
+
139
+ async def set(self, key: str, value: "str | bytes", expires_in: "int | timedelta | None" = None) -> None:
140
+ """Store a session value.
141
+
142
+ Args:
143
+ key: Session ID.
144
+ value: Session data.
145
+ expires_in: Time until expiration.
146
+
147
+ Notes:
148
+ Uses EXCLUDED to reference the proposed insert values in ON CONFLICT.
149
+ Updates updated_at timestamp on every write for audit trail.
150
+ """
151
+ data = self._value_to_bytes(value)
152
+ expires_at = self._calculate_expires_at(expires_in)
153
+
154
+ sql = f"""
155
+ INSERT INTO {self._table_name} (session_id, data, expires_at)
156
+ VALUES ($1, $2, $3)
157
+ ON CONFLICT (session_id)
158
+ DO UPDATE SET
159
+ data = EXCLUDED.data,
160
+ expires_at = EXCLUDED.expires_at,
161
+ updated_at = CURRENT_TIMESTAMP
162
+ """
163
+
164
+ async with self._config.provide_connection() as conn:
165
+ await conn.execute(sql, key, data, expires_at)
166
+
167
+ async def delete(self, key: str) -> None:
168
+ """Delete a session by key.
169
+
170
+ Args:
171
+ key: Session ID to delete.
172
+ """
173
+ sql = f"DELETE FROM {self._table_name} WHERE session_id = $1"
174
+
175
+ async with self._config.provide_connection() as conn:
176
+ await conn.execute(sql, key)
177
+
178
+ async def delete_all(self) -> None:
179
+ """Delete all sessions from the store."""
180
+ sql = f"DELETE FROM {self._table_name}"
181
+
182
+ async with self._config.provide_connection() as conn:
183
+ await conn.execute(sql)
184
+ logger.debug("Deleted all sessions from table: %s", self._table_name)
185
+
186
+ async def exists(self, key: str) -> bool:
187
+ """Check if a session key exists and is not expired.
188
+
189
+ Args:
190
+ key: Session ID to check.
191
+
192
+ Returns:
193
+ True if the session exists and is not expired.
194
+
195
+ Notes:
196
+ Uses CURRENT_TIMESTAMP for consistency with get() method.
197
+ """
198
+ sql = f"""
199
+ SELECT 1 FROM {self._table_name}
200
+ WHERE session_id = $1
201
+ AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
202
+ """
203
+
204
+ async with self._config.provide_connection() as conn:
205
+ result = await conn.fetchval(sql, key)
206
+ return result is not None
207
+
208
+ async def expires_in(self, key: str) -> "int | None":
209
+ """Get the time in seconds until the session expires.
210
+
211
+ Args:
212
+ key: Session ID to check.
213
+
214
+ Returns:
215
+ Seconds until expiration, or None if no expiry or key doesn't exist.
216
+ """
217
+ sql = f"""
218
+ SELECT expires_at FROM {self._table_name}
219
+ WHERE session_id = $1
220
+ """
221
+
222
+ async with self._config.provide_connection() as conn:
223
+ expires_at = await conn.fetchval(sql, key)
224
+
225
+ if expires_at is None:
226
+ return None
227
+
228
+ now = datetime.now(timezone.utc)
229
+ if expires_at <= now:
230
+ return 0
231
+
232
+ delta = expires_at - now
233
+ return int(delta.total_seconds())
234
+
235
+ async def delete_expired(self) -> int:
236
+ """Delete all expired sessions.
237
+
238
+ Returns:
239
+ Number of sessions deleted.
240
+
241
+ Notes:
242
+ Uses CURRENT_TIMESTAMP for consistency.
243
+ For very large tables (10M+ rows), consider batching deletes
244
+ to avoid holding locks too long.
245
+ """
246
+ sql = f"DELETE FROM {self._table_name} WHERE expires_at <= CURRENT_TIMESTAMP"
247
+
248
+ async with self._config.provide_connection() as conn:
249
+ result = await conn.execute(sql)
250
+ count = int(result.split()[-1])
251
+ if count > 0:
252
+ logger.debug("Cleaned up %d expired sessions", count)
253
+ return count
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
3
3
  from google.cloud.bigquery import Client
4
4
 
5
5
  if TYPE_CHECKING:
6
- from typing_extensions import TypeAlias
6
+ from typing import TypeAlias
7
7
 
8
8
  BigQueryConnection: TypeAlias = Client
9
9
  else:
@@ -0,0 +1,5 @@
1
+ """BigQuery ADK store for Google Agent Development Kit session/event storage."""
2
+
3
+ from sqlspec.adapters.bigquery.adk.store import BigQueryADKStore
4
+
5
+ __all__ = ("BigQueryADKStore",)