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
@@ -14,8 +14,9 @@ PostgreSQL Features:
14
14
  - PostgreSQL-specific error handling
15
15
  """
16
16
 
17
+ import datetime
17
18
  import io
18
- from typing import TYPE_CHECKING, Any, Optional
19
+ from typing import TYPE_CHECKING, Any
19
20
 
20
21
  import psycopg
21
22
 
@@ -25,14 +26,28 @@ from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
25
26
  from sqlspec.core.result import SQLResult
26
27
  from sqlspec.core.statement import SQL, StatementConfig
27
28
  from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
28
- from sqlspec.exceptions import SQLParsingError, SQLSpecError
29
+ from sqlspec.exceptions import (
30
+ CheckViolationError,
31
+ DatabaseConnectionError,
32
+ DataError,
33
+ ForeignKeyViolationError,
34
+ IntegrityError,
35
+ NotNullViolationError,
36
+ OperationalError,
37
+ SQLParsingError,
38
+ SQLSpecError,
39
+ TransactionError,
40
+ UniqueViolationError,
41
+ )
29
42
  from sqlspec.utils.logging import get_logger
30
43
  from sqlspec.utils.serializers import to_json
31
44
 
32
45
  if TYPE_CHECKING:
33
46
  from contextlib import AbstractAsyncContextManager, AbstractContextManager
34
47
 
48
+ from sqlspec.driver._async import AsyncDataDictionaryBase
35
49
  from sqlspec.driver._common import ExecutionResult
50
+ from sqlspec.driver._sync import SyncDataDictionaryBase
36
51
 
37
52
  logger = get_logger("adapters.psycopg")
38
53
 
@@ -94,7 +109,12 @@ psycopg_statement_config = StatementConfig(
94
109
  ParameterStyle.NAMED_PYFORMAT,
95
110
  ParameterStyle.NUMERIC,
96
111
  },
97
- type_coercion_map={dict: to_json},
112
+ type_coercion_map={
113
+ dict: to_json,
114
+ datetime.datetime: lambda x: x,
115
+ datetime.date: lambda x: x,
116
+ datetime.time: lambda x: x,
117
+ },
98
118
  has_native_list_expansion=True,
99
119
  needs_static_script_compilation=False,
100
120
  preserve_parameter_format=True,
@@ -119,20 +139,23 @@ class PsycopgSyncCursor:
119
139
 
120
140
  def __init__(self, connection: PsycopgSyncConnection) -> None:
121
141
  self.connection = connection
122
- self.cursor: Optional[Any] = None
142
+ self.cursor: Any | None = None
123
143
 
124
144
  def __enter__(self) -> Any:
125
145
  self.cursor = self.connection.cursor()
126
146
  return self.cursor
127
147
 
128
- def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
129
- _ = (exc_type, exc_val, exc_tb)
148
+ def __exit__(self, *_: Any) -> None:
130
149
  if self.cursor is not None:
131
150
  self.cursor.close()
132
151
 
133
152
 
134
153
  class PsycopgSyncExceptionHandler:
135
- """Context manager for handling PostgreSQL psycopg database exceptions."""
154
+ """Context manager for handling PostgreSQL psycopg database exceptions.
155
+
156
+ Maps PostgreSQL SQLSTATE error codes to specific SQLSpec exceptions
157
+ for better error handling in application code.
158
+ """
136
159
 
137
160
  __slots__ = ()
138
161
 
@@ -142,39 +165,90 @@ class PsycopgSyncExceptionHandler:
142
165
  def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
143
166
  if exc_type is None:
144
167
  return
145
-
146
- if issubclass(exc_type, psycopg.IntegrityError):
147
- e = exc_val
148
- msg = f"PostgreSQL psycopg integrity constraint violation: {e}"
149
- raise SQLSpecError(msg) from e
150
- if issubclass(exc_type, psycopg.ProgrammingError):
151
- e = exc_val
152
- error_msg = str(e).lower()
153
- if "syntax" in error_msg or "parse" in error_msg:
154
- msg = f"PostgreSQL psycopg SQL syntax error: {e}"
155
- raise SQLParsingError(msg) from e
156
- msg = f"PostgreSQL psycopg programming error: {e}"
157
- raise SQLSpecError(msg) from e
158
- if issubclass(exc_type, psycopg.OperationalError):
159
- e = exc_val
160
- msg = f"PostgreSQL psycopg operational error: {e}"
161
- raise SQLSpecError(msg) from e
162
- if issubclass(exc_type, psycopg.DatabaseError):
163
- e = exc_val
164
- msg = f"PostgreSQL psycopg database error: {e}"
165
- raise SQLSpecError(msg) from e
166
168
  if issubclass(exc_type, psycopg.Error):
167
- e = exc_val
168
- msg = f"PostgreSQL psycopg error: {e}"
169
- raise SQLSpecError(msg) from e
170
- if issubclass(exc_type, Exception):
171
- e = exc_val
172
- error_msg = str(e).lower()
173
- if "parse" in error_msg or "syntax" in error_msg:
174
- msg = f"SQL parsing failed: {e}"
175
- raise SQLParsingError(msg) from e
176
- msg = f"Unexpected database operation error: {e}"
177
- raise SQLSpecError(msg) from e
169
+ self._map_postgres_exception(exc_val)
170
+
171
+ def _map_postgres_exception(self, e: Any) -> None:
172
+ """Map PostgreSQL exception to SQLSpec exception.
173
+
174
+ Args:
175
+ e: psycopg.Error instance
176
+
177
+ Raises:
178
+ Specific SQLSpec exception based on SQLSTATE code
179
+ """
180
+ error_code = getattr(e, "sqlstate", None)
181
+
182
+ if not error_code:
183
+ self._raise_generic_error(e, None)
184
+ return
185
+
186
+ if error_code == "23505":
187
+ self._raise_unique_violation(e, error_code)
188
+ elif error_code == "23503":
189
+ self._raise_foreign_key_violation(e, error_code)
190
+ elif error_code == "23502":
191
+ self._raise_not_null_violation(e, error_code)
192
+ elif error_code == "23514":
193
+ self._raise_check_violation(e, error_code)
194
+ elif error_code.startswith("23"):
195
+ self._raise_integrity_error(e, error_code)
196
+ elif error_code.startswith("42"):
197
+ self._raise_parsing_error(e, error_code)
198
+ elif error_code.startswith("08"):
199
+ self._raise_connection_error(e, error_code)
200
+ elif error_code.startswith("40"):
201
+ self._raise_transaction_error(e, error_code)
202
+ elif error_code.startswith("22"):
203
+ self._raise_data_error(e, error_code)
204
+ elif error_code.startswith(("53", "54", "55", "57", "58")):
205
+ self._raise_operational_error(e, error_code)
206
+ else:
207
+ self._raise_generic_error(e, error_code)
208
+
209
+ def _raise_unique_violation(self, e: Any, code: str) -> None:
210
+ msg = f"PostgreSQL unique constraint violation [{code}]: {e}"
211
+ raise UniqueViolationError(msg) from e
212
+
213
+ def _raise_foreign_key_violation(self, e: Any, code: str) -> None:
214
+ msg = f"PostgreSQL foreign key constraint violation [{code}]: {e}"
215
+ raise ForeignKeyViolationError(msg) from e
216
+
217
+ def _raise_not_null_violation(self, e: Any, code: str) -> None:
218
+ msg = f"PostgreSQL not-null constraint violation [{code}]: {e}"
219
+ raise NotNullViolationError(msg) from e
220
+
221
+ def _raise_check_violation(self, e: Any, code: str) -> None:
222
+ msg = f"PostgreSQL check constraint violation [{code}]: {e}"
223
+ raise CheckViolationError(msg) from e
224
+
225
+ def _raise_integrity_error(self, e: Any, code: str) -> None:
226
+ msg = f"PostgreSQL integrity constraint violation [{code}]: {e}"
227
+ raise IntegrityError(msg) from e
228
+
229
+ def _raise_parsing_error(self, e: Any, code: str) -> None:
230
+ msg = f"PostgreSQL SQL syntax error [{code}]: {e}"
231
+ raise SQLParsingError(msg) from e
232
+
233
+ def _raise_connection_error(self, e: Any, code: str) -> None:
234
+ msg = f"PostgreSQL connection error [{code}]: {e}"
235
+ raise DatabaseConnectionError(msg) from e
236
+
237
+ def _raise_transaction_error(self, e: Any, code: str) -> None:
238
+ msg = f"PostgreSQL transaction error [{code}]: {e}"
239
+ raise TransactionError(msg) from e
240
+
241
+ def _raise_data_error(self, e: Any, code: str) -> None:
242
+ msg = f"PostgreSQL data error [{code}]: {e}"
243
+ raise DataError(msg) from e
244
+
245
+ def _raise_operational_error(self, e: Any, code: str) -> None:
246
+ msg = f"PostgreSQL operational error [{code}]: {e}"
247
+ raise OperationalError(msg) from e
248
+
249
+ def _raise_generic_error(self, e: Any, code: "str | None") -> None:
250
+ msg = f"PostgreSQL database error [{code}]: {e}" if code else f"PostgreSQL database error: {e}"
251
+ raise SQLSpecError(msg) from e
178
252
 
179
253
 
180
254
  class PsycopgSyncDriver(SyncDriverAdapterBase):
@@ -187,14 +261,14 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
187
261
  bulk data transfer, and PostgreSQL-specific error handling.
188
262
  """
189
263
 
190
- __slots__ = ()
264
+ __slots__ = ("_data_dictionary",)
191
265
  dialect = "postgres"
192
266
 
193
267
  def __init__(
194
268
  self,
195
269
  connection: PsycopgSyncConnection,
196
- statement_config: "Optional[StatementConfig]" = None,
197
- driver_features: "Optional[dict[str, Any]]" = None,
270
+ statement_config: "StatementConfig | None" = None,
271
+ driver_features: "dict[str, Any] | None" = None,
198
272
  ) -> None:
199
273
  if statement_config is None:
200
274
  cache_config = get_cache_config()
@@ -207,6 +281,7 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
207
281
  statement_config = default_config
208
282
 
209
283
  super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
284
+ self._data_dictionary: SyncDataDictionaryBase | None = None
210
285
 
211
286
  def with_cursor(self, connection: PsycopgSyncConnection) -> PsycopgSyncCursor:
212
287
  """Create context manager for PostgreSQL cursor."""
@@ -255,7 +330,7 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
255
330
  except Exception as cleanup_error:
256
331
  logger.warning("Failed to cleanup transaction state: %s", cleanup_error)
257
332
 
258
- def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
333
+ def _try_special_handling(self, cursor: Any, statement: "SQL") -> "SQLResult | None":
259
334
  """Hook for PostgreSQL-specific special operations.
260
335
 
261
336
  Args:
@@ -411,6 +486,19 @@ class PsycopgSyncDriver(SyncDriverAdapterBase):
411
486
  affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
412
487
  return self.create_execution_result(cursor, rowcount_override=affected_rows)
413
488
 
489
+ @property
490
+ def data_dictionary(self) -> "SyncDataDictionaryBase":
491
+ """Get the data dictionary for this driver.
492
+
493
+ Returns:
494
+ Data dictionary instance for metadata queries
495
+ """
496
+ if self._data_dictionary is None:
497
+ from sqlspec.adapters.psycopg.data_dictionary import PostgresSyncDataDictionary
498
+
499
+ self._data_dictionary = PostgresSyncDataDictionary()
500
+ return self._data_dictionary
501
+
414
502
 
415
503
  class PsycopgAsyncCursor:
416
504
  """Async context manager for PostgreSQL psycopg cursor management."""
@@ -419,7 +507,7 @@ class PsycopgAsyncCursor:
419
507
 
420
508
  def __init__(self, connection: "PsycopgAsyncConnection") -> None:
421
509
  self.connection = connection
422
- self.cursor: Optional[Any] = None
510
+ self.cursor: Any | None = None
423
511
 
424
512
  async def __aenter__(self) -> Any:
425
513
  self.cursor = self.connection.cursor()
@@ -432,7 +520,11 @@ class PsycopgAsyncCursor:
432
520
 
433
521
 
434
522
  class PsycopgAsyncExceptionHandler:
435
- """Async context manager for handling PostgreSQL psycopg database exceptions."""
523
+ """Async context manager for handling PostgreSQL psycopg database exceptions.
524
+
525
+ Maps PostgreSQL SQLSTATE error codes to specific SQLSpec exceptions
526
+ for better error handling in application code.
527
+ """
436
528
 
437
529
  __slots__ = ()
438
530
 
@@ -442,39 +534,90 @@ class PsycopgAsyncExceptionHandler:
442
534
  async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
443
535
  if exc_type is None:
444
536
  return
445
-
446
- if issubclass(exc_type, psycopg.IntegrityError):
447
- e = exc_val
448
- msg = f"PostgreSQL psycopg integrity constraint violation: {e}"
449
- raise SQLSpecError(msg) from e
450
- if issubclass(exc_type, psycopg.ProgrammingError):
451
- e = exc_val
452
- error_msg = str(e).lower()
453
- if "syntax" in error_msg or "parse" in error_msg:
454
- msg = f"PostgreSQL psycopg SQL syntax error: {e}"
455
- raise SQLParsingError(msg) from e
456
- msg = f"PostgreSQL psycopg programming error: {e}"
457
- raise SQLSpecError(msg) from e
458
- if issubclass(exc_type, psycopg.OperationalError):
459
- e = exc_val
460
- msg = f"PostgreSQL psycopg operational error: {e}"
461
- raise SQLSpecError(msg) from e
462
- if issubclass(exc_type, psycopg.DatabaseError):
463
- e = exc_val
464
- msg = f"PostgreSQL psycopg database error: {e}"
465
- raise SQLSpecError(msg) from e
466
537
  if issubclass(exc_type, psycopg.Error):
467
- e = exc_val
468
- msg = f"PostgreSQL psycopg error: {e}"
469
- raise SQLSpecError(msg) from e
470
- if issubclass(exc_type, Exception):
471
- e = exc_val
472
- error_msg = str(e).lower()
473
- if "parse" in error_msg or "syntax" in error_msg:
474
- msg = f"SQL parsing failed: {e}"
475
- raise SQLParsingError(msg) from e
476
- msg = f"Unexpected async database operation error: {e}"
477
- raise SQLSpecError(msg) from e
538
+ self._map_postgres_exception(exc_val)
539
+
540
+ def _map_postgres_exception(self, e: Any) -> None:
541
+ """Map PostgreSQL exception to SQLSpec exception.
542
+
543
+ Args:
544
+ e: psycopg.Error instance
545
+
546
+ Raises:
547
+ Specific SQLSpec exception based on SQLSTATE code
548
+ """
549
+ error_code = getattr(e, "sqlstate", None)
550
+
551
+ if not error_code:
552
+ self._raise_generic_error(e, None)
553
+ return
554
+
555
+ if error_code == "23505":
556
+ self._raise_unique_violation(e, error_code)
557
+ elif error_code == "23503":
558
+ self._raise_foreign_key_violation(e, error_code)
559
+ elif error_code == "23502":
560
+ self._raise_not_null_violation(e, error_code)
561
+ elif error_code == "23514":
562
+ self._raise_check_violation(e, error_code)
563
+ elif error_code.startswith("23"):
564
+ self._raise_integrity_error(e, error_code)
565
+ elif error_code.startswith("42"):
566
+ self._raise_parsing_error(e, error_code)
567
+ elif error_code.startswith("08"):
568
+ self._raise_connection_error(e, error_code)
569
+ elif error_code.startswith("40"):
570
+ self._raise_transaction_error(e, error_code)
571
+ elif error_code.startswith("22"):
572
+ self._raise_data_error(e, error_code)
573
+ elif error_code.startswith(("53", "54", "55", "57", "58")):
574
+ self._raise_operational_error(e, error_code)
575
+ else:
576
+ self._raise_generic_error(e, error_code)
577
+
578
+ def _raise_unique_violation(self, e: Any, code: str) -> None:
579
+ msg = f"PostgreSQL unique constraint violation [{code}]: {e}"
580
+ raise UniqueViolationError(msg) from e
581
+
582
+ def _raise_foreign_key_violation(self, e: Any, code: str) -> None:
583
+ msg = f"PostgreSQL foreign key constraint violation [{code}]: {e}"
584
+ raise ForeignKeyViolationError(msg) from e
585
+
586
+ def _raise_not_null_violation(self, e: Any, code: str) -> None:
587
+ msg = f"PostgreSQL not-null constraint violation [{code}]: {e}"
588
+ raise NotNullViolationError(msg) from e
589
+
590
+ def _raise_check_violation(self, e: Any, code: str) -> None:
591
+ msg = f"PostgreSQL check constraint violation [{code}]: {e}"
592
+ raise CheckViolationError(msg) from e
593
+
594
+ def _raise_integrity_error(self, e: Any, code: str) -> None:
595
+ msg = f"PostgreSQL integrity constraint violation [{code}]: {e}"
596
+ raise IntegrityError(msg) from e
597
+
598
+ def _raise_parsing_error(self, e: Any, code: str) -> None:
599
+ msg = f"PostgreSQL SQL syntax error [{code}]: {e}"
600
+ raise SQLParsingError(msg) from e
601
+
602
+ def _raise_connection_error(self, e: Any, code: str) -> None:
603
+ msg = f"PostgreSQL connection error [{code}]: {e}"
604
+ raise DatabaseConnectionError(msg) from e
605
+
606
+ def _raise_transaction_error(self, e: Any, code: str) -> None:
607
+ msg = f"PostgreSQL transaction error [{code}]: {e}"
608
+ raise TransactionError(msg) from e
609
+
610
+ def _raise_data_error(self, e: Any, code: str) -> None:
611
+ msg = f"PostgreSQL data error [{code}]: {e}"
612
+ raise DataError(msg) from e
613
+
614
+ def _raise_operational_error(self, e: Any, code: str) -> None:
615
+ msg = f"PostgreSQL operational error [{code}]: {e}"
616
+ raise OperationalError(msg) from e
617
+
618
+ def _raise_generic_error(self, e: Any, code: "str | None") -> None:
619
+ msg = f"PostgreSQL database error [{code}]: {e}" if code else f"PostgreSQL database error: {e}"
620
+ raise SQLSpecError(msg) from e
478
621
 
479
622
 
480
623
  class PsycopgAsyncDriver(AsyncDriverAdapterBase):
@@ -488,14 +631,14 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
488
631
  and async pub/sub support.
489
632
  """
490
633
 
491
- __slots__ = ()
634
+ __slots__ = ("_data_dictionary",)
492
635
  dialect = "postgres"
493
636
 
494
637
  def __init__(
495
638
  self,
496
639
  connection: "PsycopgAsyncConnection",
497
- statement_config: "Optional[StatementConfig]" = None,
498
- driver_features: "Optional[dict[str, Any]]" = None,
640
+ statement_config: "StatementConfig | None" = None,
641
+ driver_features: "dict[str, Any] | None" = None,
499
642
  ) -> None:
500
643
  if statement_config is None:
501
644
  cache_config = get_cache_config()
@@ -508,6 +651,7 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
508
651
  statement_config = default_config
509
652
 
510
653
  super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
654
+ self._data_dictionary: AsyncDataDictionaryBase | None = None
511
655
 
512
656
  def with_cursor(self, connection: "PsycopgAsyncConnection") -> "PsycopgAsyncCursor":
513
657
  """Create async context manager for PostgreSQL cursor."""
@@ -556,7 +700,7 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
556
700
  except Exception as cleanup_error:
557
701
  logger.warning("Failed to cleanup transaction state: %s", cleanup_error)
558
702
 
559
- async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
703
+ async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "SQLResult | None":
560
704
  """Hook for PostgreSQL-specific special operations.
561
705
 
562
706
  Args:
@@ -714,3 +858,16 @@ class PsycopgAsyncDriver(AsyncDriverAdapterBase):
714
858
 
715
859
  affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
716
860
  return self.create_execution_result(cursor, rowcount_override=affected_rows)
861
+
862
+ @property
863
+ def data_dictionary(self) -> "AsyncDataDictionaryBase":
864
+ """Get the data dictionary for this driver.
865
+
866
+ Returns:
867
+ Data dictionary instance for metadata queries
868
+ """
869
+ if self._data_dictionary is None:
870
+ from sqlspec.adapters.psycopg.data_dictionary import PostgresAsyncDataDictionary
871
+
872
+ self._data_dictionary = PostgresAsyncDataDictionary()
873
+ return self._data_dictionary
@@ -0,0 +1,5 @@
1
+ """Litestar integration for Psycopg adapter."""
2
+
3
+ from sqlspec.adapters.psycopg.litestar.store import PsycopgAsyncStore, PsycopgSyncStore
4
+
5
+ __all__ = ("PsycopgAsyncStore", "PsycopgSyncStore")