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.
- sqlspec/__init__.py +7 -15
- sqlspec/_serialization.py +256 -24
- sqlspec/_typing.py +71 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +69 -12
- sqlspec/adapters/adbc/data_dictionary.py +340 -0
- sqlspec/adapters/adbc/driver.py +266 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +88 -15
- sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
- sqlspec/adapters/aiosqlite/driver.py +143 -40
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +2 -2
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +68 -23
- sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
- sqlspec/adapters/asyncmy/driver.py +313 -58
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +59 -35
- sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
- sqlspec/adapters/asyncpg/driver.py +170 -25
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +27 -10
- sqlspec/adapters/bigquery/data_dictionary.py +149 -0
- sqlspec/adapters/bigquery/driver.py +368 -142
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +80 -20
- sqlspec/adapters/duckdb/data_dictionary.py +163 -0
- sqlspec/adapters/duckdb/driver.py +167 -45
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +4 -4
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +122 -32
- sqlspec/adapters/oracledb/data_dictionary.py +509 -0
- sqlspec/adapters/oracledb/driver.py +353 -91
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +348 -73
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +46 -17
- sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
- sqlspec/adapters/psqlpy/driver.py +123 -209
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +69 -35
- sqlspec/adapters/psycopg/data_dictionary.py +331 -0
- sqlspec/adapters/psycopg/driver.py +238 -81
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +87 -15
- sqlspec/adapters/sqlite/data_dictionary.py +149 -0
- sqlspec/adapters/sqlite/driver.py +137 -54
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +18 -9
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +162 -89
- sqlspec/builder/_column.py +62 -29
- sqlspec/builder/_ddl.py +180 -121
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +53 -94
- sqlspec/builder/_insert.py +32 -131
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +111 -17
- sqlspec/builder/_select.py +1457 -24
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +307 -194
- sqlspec/config.py +252 -67
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +17 -17
- sqlspec/core/compiler.py +62 -9
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +83 -48
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +36 -30
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +7 -6
- sqlspec/driver/_async.py +188 -151
- sqlspec/driver/_common.py +285 -80
- sqlspec/driver/_sync.py +188 -152
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +75 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +302 -39
- sqlspec/migrations/commands.py +611 -144
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +68 -23
- sqlspec/migrations/runner.py +543 -107
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +16 -84
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +50 -2
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_join_operations.py +0 -389
- sqlspec/builder/mixins/_merge_operations.py +0 -592
- sqlspec/builder/mixins/_order_limit_operations.py +0 -152
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -936
- sqlspec/builder/mixins/_update_operations.py +0 -218
- sqlspec/builder/mixins/_where_clause.py +0 -1304
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
|
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,
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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: "
|
|
114
|
-
driver_features: "
|
|
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") -> "
|
|
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
|