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,173 @@
1
+ """PostgreSQL-specific data dictionary for metadata queries via asyncpg."""
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING, Any, cast
5
+
6
+ from sqlspec.driver import AsyncDataDictionaryBase, AsyncDriverAdapterBase, VersionInfo
7
+ from sqlspec.utils.logging import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+
12
+ from sqlspec.adapters.asyncpg.driver import AsyncpgDriver
13
+
14
+ logger = get_logger("adapters.asyncpg.data_dictionary")
15
+
16
+ # Compiled regex patterns
17
+ POSTGRES_VERSION_PATTERN = re.compile(r"PostgreSQL (\d+)\.(\d+)(?:\.(\d+))?")
18
+
19
+ __all__ = ("PostgresAsyncDataDictionary",)
20
+
21
+
22
+ class PostgresAsyncDataDictionary(AsyncDataDictionaryBase):
23
+ """PostgreSQL-specific async data dictionary."""
24
+
25
+ async def get_version(self, driver: AsyncDriverAdapterBase) -> "VersionInfo | None":
26
+ """Get PostgreSQL database version information.
27
+
28
+ Args:
29
+ driver: Async database driver instance
30
+
31
+ Returns:
32
+ PostgreSQL version information or None if detection fails
33
+ """
34
+ asyncpg_driver = cast("AsyncpgDriver", driver)
35
+ version_str = await asyncpg_driver.select_value("SELECT version()")
36
+ if not version_str:
37
+ logger.warning("No PostgreSQL version information found")
38
+ return None
39
+
40
+ # Parse version like "PostgreSQL 15.3 on x86_64-pc-linux-gnu..."
41
+ version_match = POSTGRES_VERSION_PATTERN.search(str(version_str))
42
+ if not version_match:
43
+ logger.warning("Could not parse PostgreSQL version: %s", version_str)
44
+ return None
45
+
46
+ major = int(version_match.group(1))
47
+ minor = int(version_match.group(2))
48
+ patch = int(version_match.group(3)) if version_match.group(3) else 0
49
+
50
+ version_info = VersionInfo(major, minor, patch)
51
+ logger.debug("Detected PostgreSQL version: %s", version_info)
52
+ return version_info
53
+
54
+ async def get_feature_flag(self, driver: AsyncDriverAdapterBase, feature: str) -> bool:
55
+ """Check if PostgreSQL database supports a specific feature.
56
+
57
+ Args:
58
+ driver: Async database driver instance
59
+ feature: Feature name to check
60
+
61
+ Returns:
62
+ True if feature is supported, False otherwise
63
+ """
64
+ version_info = await self.get_version(driver)
65
+ if not version_info:
66
+ return False
67
+
68
+ feature_checks: dict[str, Callable[..., bool]] = {
69
+ "supports_json": lambda v: v >= VersionInfo(9, 2, 0),
70
+ "supports_jsonb": lambda v: v >= VersionInfo(9, 4, 0),
71
+ "supports_uuid": lambda _: True, # UUID extension widely available
72
+ "supports_arrays": lambda _: True, # PostgreSQL has excellent array support
73
+ "supports_returning": lambda v: v >= VersionInfo(8, 2, 0),
74
+ "supports_upsert": lambda v: v >= VersionInfo(9, 5, 0), # ON CONFLICT
75
+ "supports_window_functions": lambda v: v >= VersionInfo(8, 4, 0),
76
+ "supports_cte": lambda v: v >= VersionInfo(8, 4, 0),
77
+ "supports_transactions": lambda _: True,
78
+ "supports_prepared_statements": lambda _: True,
79
+ "supports_schemas": lambda _: True,
80
+ "supports_partitioning": lambda v: v >= VersionInfo(10, 0, 0),
81
+ }
82
+
83
+ if feature in feature_checks:
84
+ return bool(feature_checks[feature](version_info))
85
+
86
+ return False
87
+
88
+ async def get_optimal_type(self, driver: AsyncDriverAdapterBase, type_category: str) -> str:
89
+ """Get optimal PostgreSQL type for a category.
90
+
91
+ Args:
92
+ driver: Async database driver instance
93
+ type_category: Type category
94
+
95
+ Returns:
96
+ PostgreSQL-specific type name
97
+ """
98
+ version_info = await self.get_version(driver)
99
+
100
+ if type_category == "json":
101
+ if version_info and version_info >= VersionInfo(9, 4, 0):
102
+ return "JSONB" # Prefer JSONB over JSON
103
+ if version_info and version_info >= VersionInfo(9, 2, 0):
104
+ return "JSON"
105
+ return "TEXT"
106
+
107
+ type_map = {
108
+ "uuid": "UUID",
109
+ "boolean": "BOOLEAN",
110
+ "timestamp": "TIMESTAMP WITH TIME ZONE",
111
+ "text": "TEXT",
112
+ "blob": "BYTEA",
113
+ "array": "ARRAY",
114
+ }
115
+ return type_map.get(type_category, "TEXT")
116
+
117
+ async def get_columns(
118
+ self, driver: AsyncDriverAdapterBase, table: str, schema: "str | None" = None
119
+ ) -> "list[dict[str, Any]]":
120
+ """Get column information for a table using information_schema.
121
+
122
+ Args:
123
+ driver: AsyncPG driver instance
124
+ table: Table name to query columns for
125
+ schema: Schema name (None for default 'public')
126
+
127
+ Returns:
128
+ List of column metadata dictionaries with keys:
129
+ - column_name: Name of the column
130
+ - data_type: PostgreSQL data type
131
+ - is_nullable: Whether column allows NULL (YES/NO)
132
+ - column_default: Default value if any
133
+ """
134
+ asyncpg_driver = cast("AsyncpgDriver", driver)
135
+
136
+ if schema:
137
+ sql = f"""
138
+ SELECT column_name, data_type, is_nullable, column_default
139
+ FROM information_schema.columns
140
+ WHERE table_name = '{table}' AND table_schema = '{schema}'
141
+ ORDER BY ordinal_position
142
+ """
143
+ else:
144
+ sql = f"""
145
+ SELECT column_name, data_type, is_nullable, column_default
146
+ FROM information_schema.columns
147
+ WHERE table_name = '{table}' AND table_schema = 'public'
148
+ ORDER BY ordinal_position
149
+ """
150
+
151
+ result = await asyncpg_driver.execute(sql)
152
+ return result.data or []
153
+
154
+ def list_available_features(self) -> "list[str]":
155
+ """List available PostgreSQL feature flags.
156
+
157
+ Returns:
158
+ List of supported feature names
159
+ """
160
+ return [
161
+ "supports_json",
162
+ "supports_jsonb",
163
+ "supports_uuid",
164
+ "supports_arrays",
165
+ "supports_returning",
166
+ "supports_upsert",
167
+ "supports_window_functions",
168
+ "supports_cte",
169
+ "supports_transactions",
170
+ "supports_prepared_statements",
171
+ "supports_schemas",
172
+ "supports_partitioning",
173
+ ]
@@ -4,8 +4,9 @@ Provides async PostgreSQL connectivity with parameter processing, resource manag
4
4
  PostgreSQL COPY operation support, and transaction management.
5
5
  """
6
6
 
7
+ import datetime
7
8
  import re
8
- from typing import TYPE_CHECKING, Any, Final, Optional
9
+ from typing import TYPE_CHECKING, Any, Final
9
10
 
10
11
  import asyncpg
11
12
 
@@ -13,7 +14,19 @@ from sqlspec.core.cache import get_cache_config
13
14
  from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
14
15
  from sqlspec.core.statement import StatementConfig
15
16
  from sqlspec.driver import AsyncDriverAdapterBase
16
- from sqlspec.exceptions import SQLParsingError, SQLSpecError
17
+ from sqlspec.exceptions import (
18
+ CheckViolationError,
19
+ DatabaseConnectionError,
20
+ DataError,
21
+ ForeignKeyViolationError,
22
+ IntegrityError,
23
+ NotNullViolationError,
24
+ OperationalError,
25
+ SQLParsingError,
26
+ SQLSpecError,
27
+ TransactionError,
28
+ UniqueViolationError,
29
+ )
17
30
  from sqlspec.utils.logging import get_logger
18
31
 
19
32
  if TYPE_CHECKING:
@@ -23,12 +36,55 @@ if TYPE_CHECKING:
23
36
  from sqlspec.core.result import SQLResult
24
37
  from sqlspec.core.statement import SQL
25
38
  from sqlspec.driver import ExecutionResult
39
+ from sqlspec.driver._async import AsyncDataDictionaryBase
26
40
 
27
41
  __all__ = ("AsyncpgCursor", "AsyncpgDriver", "AsyncpgExceptionHandler", "asyncpg_statement_config")
28
42
 
29
43
  logger = get_logger("adapters.asyncpg")
30
44
 
31
45
 
46
+ def _convert_datetime_param(value: Any) -> Any:
47
+ """Convert datetime parameter, handling ISO strings.
48
+
49
+ Args:
50
+ value: datetime object or ISO format string
51
+
52
+ Returns:
53
+ datetime object for asyncpg
54
+ """
55
+ if isinstance(value, str):
56
+ return datetime.datetime.fromisoformat(value)
57
+ return value
58
+
59
+
60
+ def _convert_date_param(value: Any) -> Any:
61
+ """Convert date parameter, handling ISO strings.
62
+
63
+ Args:
64
+ value: date object or ISO format string
65
+
66
+ Returns:
67
+ date object for asyncpg
68
+ """
69
+ if isinstance(value, str):
70
+ return datetime.date.fromisoformat(value)
71
+ return value
72
+
73
+
74
+ def _convert_time_param(value: Any) -> Any:
75
+ """Convert time parameter, handling ISO strings.
76
+
77
+ Args:
78
+ value: time object or ISO format string
79
+
80
+ Returns:
81
+ time object for asyncpg
82
+ """
83
+ if isinstance(value, str):
84
+ return datetime.time.fromisoformat(value)
85
+ return value
86
+
87
+
32
88
  asyncpg_statement_config = StatementConfig(
33
89
  dialect="postgres",
34
90
  parameter_config=ParameterStyleConfig(
@@ -36,7 +92,11 @@ asyncpg_statement_config = StatementConfig(
36
92
  supported_parameter_styles={ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_PYFORMAT},
37
93
  default_execution_parameter_style=ParameterStyle.NUMERIC,
38
94
  supported_execution_parameter_styles={ParameterStyle.NUMERIC},
39
- type_coercion_map={},
95
+ type_coercion_map={
96
+ datetime.datetime: _convert_datetime_param,
97
+ datetime.date: _convert_date_param,
98
+ datetime.time: _convert_time_param,
99
+ },
40
100
  has_native_list_expansion=True,
41
101
  needs_static_script_compilation=False,
42
102
  preserve_parameter_format=True,
@@ -63,12 +123,15 @@ class AsyncpgCursor:
63
123
  async def __aenter__(self) -> "AsyncpgConnection":
64
124
  return self.connection
65
125
 
66
- async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
67
- _ = (exc_type, exc_val, exc_tb)
126
+ async def __aexit__(self, *_: Any) -> None: ...
68
127
 
69
128
 
70
129
  class AsyncpgExceptionHandler:
71
- """Async context manager for handling AsyncPG database exceptions."""
130
+ """Async context manager for handling AsyncPG database exceptions.
131
+
132
+ Maps PostgreSQL SQLSTATE error codes to specific SQLSpec exceptions
133
+ for better error handling in application code.
134
+ """
72
135
 
73
136
  __slots__ = ()
74
137
 
@@ -79,21 +142,89 @@ class AsyncpgExceptionHandler:
79
142
  if exc_type is None:
80
143
  return
81
144
  if issubclass(exc_type, asyncpg.PostgresError):
82
- e = exc_val
83
- error_code = getattr(e, "sqlstate", None)
84
- if error_code:
85
- if error_code.startswith("23"):
86
- msg = f"PostgreSQL integrity constraint violation [{error_code}]: {e}"
87
- elif error_code.startswith("42"):
88
- msg = f"PostgreSQL SQL syntax error [{error_code}]: {e}"
89
- raise SQLParsingError(msg) from e
90
- elif error_code.startswith("08"):
91
- msg = f"PostgreSQL connection error [{error_code}]: {e}"
92
- else:
93
- msg = f"PostgreSQL database error [{error_code}]: {e}"
94
- else:
95
- msg = f"PostgreSQL database error: {e}"
96
- raise SQLSpecError(msg) from e
145
+ self._map_postgres_exception(exc_val)
146
+
147
+ def _map_postgres_exception(self, e: Any) -> None:
148
+ """Map PostgreSQL exception to SQLSpec exception.
149
+
150
+ Args:
151
+ e: asyncpg.PostgresError instance
152
+
153
+ Raises:
154
+ Specific SQLSpec exception based on SQLSTATE code
155
+ """
156
+ error_code = getattr(e, "sqlstate", None)
157
+
158
+ if not error_code:
159
+ self._raise_generic_error(e, None)
160
+ return
161
+
162
+ if error_code == "23505":
163
+ self._raise_unique_violation(e, error_code)
164
+ elif error_code == "23503":
165
+ self._raise_foreign_key_violation(e, error_code)
166
+ elif error_code == "23502":
167
+ self._raise_not_null_violation(e, error_code)
168
+ elif error_code == "23514":
169
+ self._raise_check_violation(e, error_code)
170
+ elif error_code.startswith("23"):
171
+ self._raise_integrity_error(e, error_code)
172
+ elif error_code.startswith("42"):
173
+ self._raise_parsing_error(e, error_code)
174
+ elif error_code.startswith("08"):
175
+ self._raise_connection_error(e, error_code)
176
+ elif error_code.startswith("40"):
177
+ self._raise_transaction_error(e, error_code)
178
+ elif error_code.startswith("22"):
179
+ self._raise_data_error(e, error_code)
180
+ elif error_code.startswith(("53", "54", "55", "57", "58")):
181
+ self._raise_operational_error(e, error_code)
182
+ else:
183
+ self._raise_generic_error(e, error_code)
184
+
185
+ def _raise_unique_violation(self, e: Any, code: str) -> None:
186
+ msg = f"PostgreSQL unique constraint violation [{code}]: {e}"
187
+ raise UniqueViolationError(msg) from e
188
+
189
+ def _raise_foreign_key_violation(self, e: Any, code: str) -> None:
190
+ msg = f"PostgreSQL foreign key constraint violation [{code}]: {e}"
191
+ raise ForeignKeyViolationError(msg) from e
192
+
193
+ def _raise_not_null_violation(self, e: Any, code: str) -> None:
194
+ msg = f"PostgreSQL not-null constraint violation [{code}]: {e}"
195
+ raise NotNullViolationError(msg) from e
196
+
197
+ def _raise_check_violation(self, e: Any, code: str) -> None:
198
+ msg = f"PostgreSQL check constraint violation [{code}]: {e}"
199
+ raise CheckViolationError(msg) from e
200
+
201
+ def _raise_integrity_error(self, e: Any, code: str) -> None:
202
+ msg = f"PostgreSQL integrity constraint violation [{code}]: {e}"
203
+ raise IntegrityError(msg) from e
204
+
205
+ def _raise_parsing_error(self, e: Any, code: str) -> None:
206
+ msg = f"PostgreSQL SQL syntax error [{code}]: {e}"
207
+ raise SQLParsingError(msg) from e
208
+
209
+ def _raise_connection_error(self, e: Any, code: str) -> None:
210
+ msg = f"PostgreSQL connection error [{code}]: {e}"
211
+ raise DatabaseConnectionError(msg) from e
212
+
213
+ def _raise_transaction_error(self, e: Any, code: str) -> None:
214
+ msg = f"PostgreSQL transaction error [{code}]: {e}"
215
+ raise TransactionError(msg) from e
216
+
217
+ def _raise_data_error(self, e: Any, code: str) -> None:
218
+ msg = f"PostgreSQL data error [{code}]: {e}"
219
+ raise DataError(msg) from e
220
+
221
+ def _raise_operational_error(self, e: Any, code: str) -> None:
222
+ msg = f"PostgreSQL operational error [{code}]: {e}"
223
+ raise OperationalError(msg) from e
224
+
225
+ def _raise_generic_error(self, e: Any, code: "str | None") -> None:
226
+ msg = f"PostgreSQL database error [{code}]: {e}" if code else f"PostgreSQL database error: {e}"
227
+ raise SQLSpecError(msg) from e
97
228
 
98
229
 
99
230
  class AsyncpgDriver(AsyncDriverAdapterBase):
@@ -104,14 +235,14 @@ class AsyncpgDriver(AsyncDriverAdapterBase):
104
235
  and caching, and parameter processing with type coercion.
105
236
  """
106
237
 
107
- __slots__ = ()
238
+ __slots__ = ("_data_dictionary",)
108
239
  dialect = "postgres"
109
240
 
110
241
  def __init__(
111
242
  self,
112
243
  connection: "AsyncpgConnection",
113
- statement_config: "Optional[StatementConfig]" = None,
114
- driver_features: "Optional[dict[str, Any]]" = None,
244
+ statement_config: "StatementConfig | None" = None,
245
+ driver_features: "dict[str, Any] | None" = None,
115
246
  ) -> None:
116
247
  if statement_config is None:
117
248
  cache_config = get_cache_config()
@@ -123,6 +254,7 @@ class AsyncpgDriver(AsyncDriverAdapterBase):
123
254
  )
124
255
 
125
256
  super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
257
+ self._data_dictionary: AsyncDataDictionaryBase | None = None
126
258
 
127
259
  def with_cursor(self, connection: "AsyncpgConnection") -> "AsyncpgCursor":
128
260
  """Create context manager for AsyncPG cursor."""
@@ -132,7 +264,7 @@ class AsyncpgDriver(AsyncDriverAdapterBase):
132
264
  """Handle database exceptions with PostgreSQL error codes."""
133
265
  return AsyncpgExceptionHandler()
134
266
 
135
- async def _try_special_handling(self, cursor: "AsyncpgConnection", statement: "SQL") -> "Optional[SQLResult]":
267
+ async def _try_special_handling(self, cursor: "AsyncpgConnection", statement: "SQL") -> "SQLResult | None":
136
268
  """Handle PostgreSQL COPY operations and other special cases.
137
269
 
138
270
  Args:
@@ -311,3 +443,16 @@ class AsyncpgDriver(AsyncDriverAdapterBase):
311
443
  except asyncpg.PostgresError as e:
312
444
  msg = f"Failed to commit async transaction: {e}"
313
445
  raise SQLSpecError(msg) from e
446
+
447
+ @property
448
+ def data_dictionary(self) -> "AsyncDataDictionaryBase":
449
+ """Get the data dictionary for this driver.
450
+
451
+ Returns:
452
+ Data dictionary instance for metadata queries
453
+ """
454
+ if self._data_dictionary is None:
455
+ from sqlspec.adapters.asyncpg.data_dictionary import PostgresAsyncDataDictionary
456
+
457
+ self._data_dictionary = PostgresAsyncDataDictionary()
458
+ return self._data_dictionary
@@ -0,0 +1,5 @@
1
+ """Litestar integration for AsyncPG adapter."""
2
+
3
+ from sqlspec.adapters.asyncpg.litestar.store import AsyncpgStore
4
+
5
+ __all__ = ("AsyncpgStore",)