sqlspec 0.32.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.
Files changed (262) hide show
  1. sqlspec/__init__.py +104 -0
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +14 -0
  4. sqlspec/_serialization.py +312 -0
  5. sqlspec/_typing.py +784 -0
  6. sqlspec/adapters/__init__.py +0 -0
  7. sqlspec/adapters/adbc/__init__.py +5 -0
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  10. sqlspec/adapters/adbc/adk/store.py +880 -0
  11. sqlspec/adapters/adbc/config.py +436 -0
  12. sqlspec/adapters/adbc/data_dictionary.py +537 -0
  13. sqlspec/adapters/adbc/driver.py +841 -0
  14. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  15. sqlspec/adapters/adbc/litestar/store.py +504 -0
  16. sqlspec/adapters/adbc/type_converter.py +153 -0
  17. sqlspec/adapters/aiosqlite/__init__.py +29 -0
  18. sqlspec/adapters/aiosqlite/_types.py +13 -0
  19. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/adk/store.py +536 -0
  21. sqlspec/adapters/aiosqlite/config.py +310 -0
  22. sqlspec/adapters/aiosqlite/data_dictionary.py +260 -0
  23. sqlspec/adapters/aiosqlite/driver.py +463 -0
  24. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  25. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  26. sqlspec/adapters/aiosqlite/pool.py +500 -0
  27. sqlspec/adapters/asyncmy/__init__.py +25 -0
  28. sqlspec/adapters/asyncmy/_types.py +12 -0
  29. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/adk/store.py +503 -0
  31. sqlspec/adapters/asyncmy/config.py +246 -0
  32. sqlspec/adapters/asyncmy/data_dictionary.py +241 -0
  33. sqlspec/adapters/asyncmy/driver.py +632 -0
  34. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  35. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  36. sqlspec/adapters/asyncpg/__init__.py +23 -0
  37. sqlspec/adapters/asyncpg/_type_handlers.py +76 -0
  38. sqlspec/adapters/asyncpg/_types.py +23 -0
  39. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/adk/store.py +460 -0
  41. sqlspec/adapters/asyncpg/config.py +464 -0
  42. sqlspec/adapters/asyncpg/data_dictionary.py +321 -0
  43. sqlspec/adapters/asyncpg/driver.py +720 -0
  44. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  45. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  46. sqlspec/adapters/bigquery/__init__.py +18 -0
  47. sqlspec/adapters/bigquery/_types.py +12 -0
  48. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  49. sqlspec/adapters/bigquery/adk/store.py +585 -0
  50. sqlspec/adapters/bigquery/config.py +298 -0
  51. sqlspec/adapters/bigquery/data_dictionary.py +256 -0
  52. sqlspec/adapters/bigquery/driver.py +1073 -0
  53. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  54. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  55. sqlspec/adapters/bigquery/type_converter.py +125 -0
  56. sqlspec/adapters/duckdb/__init__.py +24 -0
  57. sqlspec/adapters/duckdb/_types.py +12 -0
  58. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  59. sqlspec/adapters/duckdb/adk/store.py +563 -0
  60. sqlspec/adapters/duckdb/config.py +396 -0
  61. sqlspec/adapters/duckdb/data_dictionary.py +264 -0
  62. sqlspec/adapters/duckdb/driver.py +604 -0
  63. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  64. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  65. sqlspec/adapters/duckdb/pool.py +273 -0
  66. sqlspec/adapters/duckdb/type_converter.py +133 -0
  67. sqlspec/adapters/oracledb/__init__.py +32 -0
  68. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  69. sqlspec/adapters/oracledb/_types.py +39 -0
  70. sqlspec/adapters/oracledb/_uuid_handlers.py +130 -0
  71. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  72. sqlspec/adapters/oracledb/adk/store.py +1632 -0
  73. sqlspec/adapters/oracledb/config.py +469 -0
  74. sqlspec/adapters/oracledb/data_dictionary.py +717 -0
  75. sqlspec/adapters/oracledb/driver.py +1493 -0
  76. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  77. sqlspec/adapters/oracledb/litestar/store.py +765 -0
  78. sqlspec/adapters/oracledb/migrations.py +532 -0
  79. sqlspec/adapters/oracledb/type_converter.py +207 -0
  80. sqlspec/adapters/psqlpy/__init__.py +16 -0
  81. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  82. sqlspec/adapters/psqlpy/_types.py +12 -0
  83. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  84. sqlspec/adapters/psqlpy/adk/store.py +483 -0
  85. sqlspec/adapters/psqlpy/config.py +271 -0
  86. sqlspec/adapters/psqlpy/data_dictionary.py +179 -0
  87. sqlspec/adapters/psqlpy/driver.py +892 -0
  88. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  90. sqlspec/adapters/psqlpy/type_converter.py +102 -0
  91. sqlspec/adapters/psycopg/__init__.py +32 -0
  92. sqlspec/adapters/psycopg/_type_handlers.py +90 -0
  93. sqlspec/adapters/psycopg/_types.py +18 -0
  94. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  95. sqlspec/adapters/psycopg/adk/store.py +962 -0
  96. sqlspec/adapters/psycopg/config.py +487 -0
  97. sqlspec/adapters/psycopg/data_dictionary.py +630 -0
  98. sqlspec/adapters/psycopg/driver.py +1336 -0
  99. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  100. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  101. sqlspec/adapters/spanner/__init__.py +38 -0
  102. sqlspec/adapters/spanner/_type_handlers.py +186 -0
  103. sqlspec/adapters/spanner/_types.py +12 -0
  104. sqlspec/adapters/spanner/adk/__init__.py +5 -0
  105. sqlspec/adapters/spanner/adk/store.py +435 -0
  106. sqlspec/adapters/spanner/config.py +241 -0
  107. sqlspec/adapters/spanner/data_dictionary.py +95 -0
  108. sqlspec/adapters/spanner/dialect/__init__.py +6 -0
  109. sqlspec/adapters/spanner/dialect/_spangres.py +52 -0
  110. sqlspec/adapters/spanner/dialect/_spanner.py +123 -0
  111. sqlspec/adapters/spanner/driver.py +366 -0
  112. sqlspec/adapters/spanner/litestar/__init__.py +5 -0
  113. sqlspec/adapters/spanner/litestar/store.py +266 -0
  114. sqlspec/adapters/spanner/type_converter.py +46 -0
  115. sqlspec/adapters/sqlite/__init__.py +18 -0
  116. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  117. sqlspec/adapters/sqlite/_types.py +11 -0
  118. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  119. sqlspec/adapters/sqlite/adk/store.py +582 -0
  120. sqlspec/adapters/sqlite/config.py +221 -0
  121. sqlspec/adapters/sqlite/data_dictionary.py +256 -0
  122. sqlspec/adapters/sqlite/driver.py +527 -0
  123. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  124. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  125. sqlspec/adapters/sqlite/pool.py +140 -0
  126. sqlspec/base.py +811 -0
  127. sqlspec/builder/__init__.py +146 -0
  128. sqlspec/builder/_base.py +900 -0
  129. sqlspec/builder/_column.py +517 -0
  130. sqlspec/builder/_ddl.py +1642 -0
  131. sqlspec/builder/_delete.py +84 -0
  132. sqlspec/builder/_dml.py +381 -0
  133. sqlspec/builder/_expression_wrappers.py +46 -0
  134. sqlspec/builder/_factory.py +1537 -0
  135. sqlspec/builder/_insert.py +315 -0
  136. sqlspec/builder/_join.py +375 -0
  137. sqlspec/builder/_merge.py +848 -0
  138. sqlspec/builder/_parsing_utils.py +297 -0
  139. sqlspec/builder/_select.py +1615 -0
  140. sqlspec/builder/_update.py +161 -0
  141. sqlspec/builder/_vector_expressions.py +259 -0
  142. sqlspec/cli.py +764 -0
  143. sqlspec/config.py +1540 -0
  144. sqlspec/core/__init__.py +305 -0
  145. sqlspec/core/cache.py +785 -0
  146. sqlspec/core/compiler.py +603 -0
  147. sqlspec/core/filters.py +872 -0
  148. sqlspec/core/hashing.py +274 -0
  149. sqlspec/core/metrics.py +83 -0
  150. sqlspec/core/parameters/__init__.py +64 -0
  151. sqlspec/core/parameters/_alignment.py +266 -0
  152. sqlspec/core/parameters/_converter.py +413 -0
  153. sqlspec/core/parameters/_processor.py +341 -0
  154. sqlspec/core/parameters/_registry.py +201 -0
  155. sqlspec/core/parameters/_transformers.py +226 -0
  156. sqlspec/core/parameters/_types.py +430 -0
  157. sqlspec/core/parameters/_validator.py +123 -0
  158. sqlspec/core/pipeline.py +187 -0
  159. sqlspec/core/result.py +1124 -0
  160. sqlspec/core/splitter.py +940 -0
  161. sqlspec/core/stack.py +163 -0
  162. sqlspec/core/statement.py +835 -0
  163. sqlspec/core/type_conversion.py +235 -0
  164. sqlspec/driver/__init__.py +36 -0
  165. sqlspec/driver/_async.py +1027 -0
  166. sqlspec/driver/_common.py +1236 -0
  167. sqlspec/driver/_sync.py +1025 -0
  168. sqlspec/driver/mixins/__init__.py +7 -0
  169. sqlspec/driver/mixins/_result_tools.py +61 -0
  170. sqlspec/driver/mixins/_sql_translator.py +122 -0
  171. sqlspec/driver/mixins/_storage.py +311 -0
  172. sqlspec/exceptions.py +321 -0
  173. sqlspec/extensions/__init__.py +0 -0
  174. sqlspec/extensions/adk/__init__.py +53 -0
  175. sqlspec/extensions/adk/_types.py +51 -0
  176. sqlspec/extensions/adk/converters.py +172 -0
  177. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  178. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  179. sqlspec/extensions/adk/service.py +181 -0
  180. sqlspec/extensions/adk/store.py +536 -0
  181. sqlspec/extensions/aiosql/__init__.py +10 -0
  182. sqlspec/extensions/aiosql/adapter.py +471 -0
  183. sqlspec/extensions/fastapi/__init__.py +19 -0
  184. sqlspec/extensions/fastapi/extension.py +341 -0
  185. sqlspec/extensions/fastapi/providers.py +543 -0
  186. sqlspec/extensions/flask/__init__.py +36 -0
  187. sqlspec/extensions/flask/_state.py +72 -0
  188. sqlspec/extensions/flask/_utils.py +40 -0
  189. sqlspec/extensions/flask/extension.py +402 -0
  190. sqlspec/extensions/litestar/__init__.py +23 -0
  191. sqlspec/extensions/litestar/_utils.py +52 -0
  192. sqlspec/extensions/litestar/cli.py +92 -0
  193. sqlspec/extensions/litestar/config.py +90 -0
  194. sqlspec/extensions/litestar/handlers.py +316 -0
  195. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  196. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  197. sqlspec/extensions/litestar/plugin.py +638 -0
  198. sqlspec/extensions/litestar/providers.py +454 -0
  199. sqlspec/extensions/litestar/store.py +265 -0
  200. sqlspec/extensions/otel/__init__.py +58 -0
  201. sqlspec/extensions/prometheus/__init__.py +107 -0
  202. sqlspec/extensions/starlette/__init__.py +10 -0
  203. sqlspec/extensions/starlette/_state.py +26 -0
  204. sqlspec/extensions/starlette/_utils.py +52 -0
  205. sqlspec/extensions/starlette/extension.py +257 -0
  206. sqlspec/extensions/starlette/middleware.py +154 -0
  207. sqlspec/loader.py +716 -0
  208. sqlspec/migrations/__init__.py +36 -0
  209. sqlspec/migrations/base.py +728 -0
  210. sqlspec/migrations/commands.py +1140 -0
  211. sqlspec/migrations/context.py +142 -0
  212. sqlspec/migrations/fix.py +203 -0
  213. sqlspec/migrations/loaders.py +450 -0
  214. sqlspec/migrations/runner.py +1024 -0
  215. sqlspec/migrations/templates.py +234 -0
  216. sqlspec/migrations/tracker.py +403 -0
  217. sqlspec/migrations/utils.py +256 -0
  218. sqlspec/migrations/validation.py +203 -0
  219. sqlspec/observability/__init__.py +22 -0
  220. sqlspec/observability/_config.py +228 -0
  221. sqlspec/observability/_diagnostics.py +67 -0
  222. sqlspec/observability/_dispatcher.py +151 -0
  223. sqlspec/observability/_observer.py +180 -0
  224. sqlspec/observability/_runtime.py +381 -0
  225. sqlspec/observability/_spans.py +158 -0
  226. sqlspec/protocols.py +530 -0
  227. sqlspec/py.typed +0 -0
  228. sqlspec/storage/__init__.py +46 -0
  229. sqlspec/storage/_utils.py +104 -0
  230. sqlspec/storage/backends/__init__.py +1 -0
  231. sqlspec/storage/backends/base.py +163 -0
  232. sqlspec/storage/backends/fsspec.py +398 -0
  233. sqlspec/storage/backends/local.py +377 -0
  234. sqlspec/storage/backends/obstore.py +580 -0
  235. sqlspec/storage/errors.py +104 -0
  236. sqlspec/storage/pipeline.py +604 -0
  237. sqlspec/storage/registry.py +289 -0
  238. sqlspec/typing.py +219 -0
  239. sqlspec/utils/__init__.py +31 -0
  240. sqlspec/utils/arrow_helpers.py +95 -0
  241. sqlspec/utils/config_resolver.py +153 -0
  242. sqlspec/utils/correlation.py +132 -0
  243. sqlspec/utils/data_transformation.py +114 -0
  244. sqlspec/utils/dependencies.py +79 -0
  245. sqlspec/utils/deprecation.py +113 -0
  246. sqlspec/utils/fixtures.py +250 -0
  247. sqlspec/utils/logging.py +172 -0
  248. sqlspec/utils/module_loader.py +273 -0
  249. sqlspec/utils/portal.py +325 -0
  250. sqlspec/utils/schema.py +288 -0
  251. sqlspec/utils/serializers.py +396 -0
  252. sqlspec/utils/singleton.py +41 -0
  253. sqlspec/utils/sync_tools.py +277 -0
  254. sqlspec/utils/text.py +108 -0
  255. sqlspec/utils/type_converters.py +99 -0
  256. sqlspec/utils/type_guards.py +1324 -0
  257. sqlspec/utils/version.py +444 -0
  258. sqlspec-0.32.0.dist-info/METADATA +202 -0
  259. sqlspec-0.32.0.dist-info/RECORD +262 -0
  260. sqlspec-0.32.0.dist-info/WHEEL +4 -0
  261. sqlspec-0.32.0.dist-info/entry_points.txt +2 -0
  262. sqlspec-0.32.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,271 @@
1
+ """Psqlpy database configuration."""
2
+
3
+ import logging
4
+ from collections.abc import AsyncGenerator
5
+ from contextlib import asynccontextmanager
6
+ from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
7
+
8
+ from psqlpy import ConnectionPool
9
+ from typing_extensions import NotRequired
10
+
11
+ from sqlspec.adapters.psqlpy._types import PsqlpyConnection
12
+ from sqlspec.adapters.psqlpy.driver import (
13
+ PsqlpyCursor,
14
+ PsqlpyDriver,
15
+ PsqlpyExceptionHandler,
16
+ build_psqlpy_statement_config,
17
+ )
18
+ from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
19
+ from sqlspec.core import StatementConfig
20
+ from sqlspec.typing import PGVECTOR_INSTALLED
21
+ from sqlspec.utils.serializers import to_json
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Callable
25
+
26
+
27
+ logger = logging.getLogger("sqlspec.adapters.psqlpy")
28
+
29
+
30
+ class PsqlpyConnectionParams(TypedDict):
31
+ """Psqlpy connection parameters."""
32
+
33
+ dsn: NotRequired[str]
34
+ username: NotRequired[str]
35
+ password: NotRequired[str]
36
+ db_name: NotRequired[str]
37
+ host: NotRequired[str]
38
+ port: NotRequired[int]
39
+ connect_timeout_sec: NotRequired[int]
40
+ connect_timeout_nanosec: NotRequired[int]
41
+ tcp_user_timeout_sec: NotRequired[int]
42
+ tcp_user_timeout_nanosec: NotRequired[int]
43
+ keepalives: NotRequired[bool]
44
+ keepalives_idle_sec: NotRequired[int]
45
+ keepalives_idle_nanosec: NotRequired[int]
46
+ keepalives_interval_sec: NotRequired[int]
47
+ keepalives_interval_nanosec: NotRequired[int]
48
+ keepalives_retries: NotRequired[int]
49
+ ssl_mode: NotRequired[str]
50
+ ca_file: NotRequired[str]
51
+ target_session_attrs: NotRequired[str]
52
+ options: NotRequired[str]
53
+ application_name: NotRequired[str]
54
+ client_encoding: NotRequired[str]
55
+ gssencmode: NotRequired[str]
56
+ sslnegotiation: NotRequired[str]
57
+ sslcompression: NotRequired[str]
58
+ sslcert: NotRequired[str]
59
+ sslkey: NotRequired[str]
60
+ sslpassword: NotRequired[str]
61
+ sslrootcert: NotRequired[str]
62
+ sslcrl: NotRequired[str]
63
+ require_auth: NotRequired[str]
64
+ channel_binding: NotRequired[str]
65
+ krbsrvname: NotRequired[str]
66
+ gsslib: NotRequired[str]
67
+ gssdelegation: NotRequired[str]
68
+ service: NotRequired[str]
69
+ load_balance_hosts: NotRequired[str]
70
+
71
+
72
+ class PsqlpyPoolParams(PsqlpyConnectionParams):
73
+ """Psqlpy pool parameters."""
74
+
75
+ hosts: NotRequired[list[str]]
76
+ ports: NotRequired[list[int]]
77
+ conn_recycling_method: NotRequired[str]
78
+ max_db_pool_size: NotRequired[int]
79
+ configure: NotRequired["Callable[..., Any]"]
80
+ extra: NotRequired[dict[str, Any]]
81
+
82
+
83
+ class PsqlpyDriverFeatures(TypedDict):
84
+ """Psqlpy driver feature flags.
85
+
86
+ enable_pgvector: Enable automatic pgvector extension support for vector similarity search.
87
+ Requires pgvector-python package installed.
88
+ Defaults to True when pgvector is installed.
89
+ Provides automatic conversion between NumPy arrays and PostgreSQL vector types.
90
+ json_serializer: Custom JSON serializer applied to the statement configuration.
91
+ json_deserializer: Custom JSON deserializer retained alongside the serializer for parity with asyncpg.
92
+ """
93
+
94
+ enable_pgvector: NotRequired[bool]
95
+ json_serializer: NotRequired["Callable[[Any], str]"]
96
+ json_deserializer: NotRequired["Callable[[str], Any]"]
97
+
98
+
99
+ __all__ = ("PsqlpyConfig", "PsqlpyConnectionParams", "PsqlpyCursor", "PsqlpyDriverFeatures", "PsqlpyPoolParams")
100
+
101
+
102
+ class PsqlpyConfig(AsyncDatabaseConfig[PsqlpyConnection, ConnectionPool, PsqlpyDriver]):
103
+ """Configuration for Psqlpy asynchronous database connections."""
104
+
105
+ driver_type: ClassVar[type[PsqlpyDriver]] = PsqlpyDriver
106
+ connection_type: "ClassVar[type[PsqlpyConnection]]" = PsqlpyConnection
107
+ supports_transactional_ddl: "ClassVar[bool]" = True
108
+ supports_native_arrow_export: ClassVar[bool] = True
109
+ supports_native_arrow_import: ClassVar[bool] = True
110
+ supports_native_parquet_export: ClassVar[bool] = True
111
+ supports_native_parquet_import: ClassVar[bool] = True
112
+
113
+ def __init__(
114
+ self,
115
+ *,
116
+ pool_config: PsqlpyPoolParams | dict[str, Any] | None = None,
117
+ pool_instance: ConnectionPool | None = None,
118
+ migration_config: dict[str, Any] | None = None,
119
+ statement_config: StatementConfig | None = None,
120
+ driver_features: "PsqlpyDriverFeatures | dict[str, Any] | None" = None,
121
+ bind_key: str | None = None,
122
+ extension_config: "ExtensionConfigs | None" = None,
123
+ ) -> None:
124
+ """Initialize Psqlpy configuration.
125
+
126
+ Args:
127
+ pool_config: Pool configuration parameters.
128
+ pool_instance: Existing connection pool instance to use.
129
+ migration_config: Migration configuration.
130
+ statement_config: SQL statement configuration.
131
+ driver_features: Driver feature configuration (TypedDict or dict).
132
+ bind_key: Optional unique identifier for this configuration.
133
+ extension_config: Extension-specific configuration (e.g., Litestar plugin settings).
134
+ """
135
+ processed_pool_config: dict[str, Any] = dict(pool_config) if pool_config else {}
136
+ if "extra" in processed_pool_config:
137
+ extras = processed_pool_config.pop("extra")
138
+ processed_pool_config.update(extras)
139
+
140
+ processed_driver_features: dict[str, Any] = dict(driver_features) if driver_features else {}
141
+ serializer = processed_driver_features.get("json_serializer")
142
+ serializer_callable = to_json if serializer is None else cast("Callable[[Any], str]", serializer)
143
+ processed_driver_features.setdefault("json_serializer", serializer_callable)
144
+ processed_driver_features.setdefault("enable_pgvector", PGVECTOR_INSTALLED)
145
+
146
+ super().__init__(
147
+ pool_config=processed_pool_config,
148
+ pool_instance=pool_instance,
149
+ migration_config=migration_config,
150
+ statement_config=statement_config or build_psqlpy_statement_config(json_serializer=serializer_callable),
151
+ driver_features=processed_driver_features,
152
+ bind_key=bind_key,
153
+ extension_config=extension_config,
154
+ )
155
+
156
+ def _get_pool_config_dict(self) -> dict[str, Any]:
157
+ """Get pool configuration as plain dict for external library.
158
+
159
+ Returns:
160
+ Dictionary with pool parameters, filtering out None values.
161
+ """
162
+ return {k: v for k, v in self.pool_config.items() if v is not None}
163
+
164
+ async def _create_pool(self) -> "ConnectionPool":
165
+ """Create the actual async connection pool."""
166
+ logger.info("Creating psqlpy connection pool", extra={"adapter": "psqlpy"})
167
+
168
+ try:
169
+ config = self._get_pool_config_dict()
170
+
171
+ pool = ConnectionPool(**config)
172
+ logger.info("Psqlpy connection pool created successfully", extra={"adapter": "psqlpy"})
173
+ except Exception as e:
174
+ logger.exception("Failed to create psqlpy connection pool", extra={"adapter": "psqlpy", "error": str(e)})
175
+ raise
176
+ return pool
177
+
178
+ async def _close_pool(self) -> None:
179
+ """Close the actual async connection pool."""
180
+ if not self.pool_instance:
181
+ return
182
+
183
+ logger.info("Closing psqlpy connection pool", extra={"adapter": "psqlpy"})
184
+
185
+ try:
186
+ self.pool_instance.close()
187
+ logger.info("Psqlpy connection pool closed successfully", extra={"adapter": "psqlpy"})
188
+ except Exception as e:
189
+ logger.exception("Failed to close psqlpy connection pool", extra={"adapter": "psqlpy", "error": str(e)})
190
+ raise
191
+
192
+ async def close_pool(self) -> None:
193
+ """Close the connection pool."""
194
+ await self._close_pool()
195
+
196
+ async def create_connection(self) -> "PsqlpyConnection":
197
+ """Create a single async connection (not from pool).
198
+
199
+ Returns:
200
+ A psqlpy Connection instance.
201
+ """
202
+ if not self.pool_instance:
203
+ self.pool_instance = await self._create_pool()
204
+
205
+ return await self.pool_instance.connection()
206
+
207
+ @asynccontextmanager
208
+ async def provide_connection(self, *args: Any, **kwargs: Any) -> AsyncGenerator[PsqlpyConnection, None]:
209
+ """Provide an async connection context manager.
210
+
211
+ Args:
212
+ *args: Additional arguments.
213
+ **kwargs: Additional keyword arguments.
214
+
215
+ Yields:
216
+ A psqlpy Connection instance.
217
+ """
218
+ if not self.pool_instance:
219
+ self.pool_instance = await self._create_pool()
220
+
221
+ async with self.pool_instance.acquire() as conn:
222
+ yield conn
223
+
224
+ @asynccontextmanager
225
+ async def provide_session(
226
+ self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
227
+ ) -> AsyncGenerator[PsqlpyDriver, None]:
228
+ """Provide an async driver session context manager.
229
+
230
+ Args:
231
+ *args: Additional arguments.
232
+ statement_config: Optional statement configuration override.
233
+ **kwargs: Additional keyword arguments.
234
+
235
+ Yields:
236
+ A PsqlpyDriver instance.
237
+ """
238
+ async with self.provide_connection(*args, **kwargs) as conn:
239
+ driver = self.driver_type(
240
+ connection=conn,
241
+ statement_config=statement_config or self.statement_config,
242
+ driver_features=self.driver_features,
243
+ )
244
+ yield self._prepare_driver(driver)
245
+
246
+ async def provide_pool(self, *args: Any, **kwargs: Any) -> ConnectionPool:
247
+ """Provide async pool instance.
248
+
249
+ Returns:
250
+ The async connection pool.
251
+ """
252
+ if not self.pool_instance:
253
+ self.pool_instance = await self.create_pool()
254
+ return self.pool_instance
255
+
256
+ def get_signature_namespace(self) -> "dict[str, Any]":
257
+ """Get the signature namespace for Psqlpy types.
258
+
259
+ Returns:
260
+ Dictionary mapping type names to types.
261
+ """
262
+ namespace = super().get_signature_namespace()
263
+ namespace.update({
264
+ "PsqlpyConnection": PsqlpyConnection,
265
+ "PsqlpyConnectionParams": PsqlpyConnectionParams,
266
+ "PsqlpyCursor": PsqlpyCursor,
267
+ "PsqlpyDriver": PsqlpyDriver,
268
+ "PsqlpyExceptionHandler": PsqlpyExceptionHandler,
269
+ "PsqlpyPoolParams": PsqlpyPoolParams,
270
+ })
271
+ return namespace
@@ -0,0 +1,179 @@
1
+ """PostgreSQL-specific data dictionary for metadata queries via psqlpy."""
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.psqlpy.driver import PsqlpyDriver
13
+
14
+ logger = get_logger("adapters.psqlpy.data_dictionary")
15
+
16
+ # Compiled regex patterns
17
+ POSTGRES_VERSION_PATTERN = re.compile(r"PostgreSQL (\d+)\.(\d+)(?:\.(\d+))?")
18
+
19
+ __all__ = ("PsqlpyAsyncDataDictionary",)
20
+
21
+
22
+ class PsqlpyAsyncDataDictionary(AsyncDataDictionaryBase):
23
+ """PostgreSQL-specific async data dictionary via psqlpy."""
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
+ version_str = await cast("PsqlpyDriver", driver).select_value("SELECT version()")
35
+ if not version_str:
36
+ logger.warning("No PostgreSQL version information found")
37
+ return None
38
+
39
+ # Parse version like "PostgreSQL 15.3 on x86_64-pc-linux-gnu..."
40
+ version_match = POSTGRES_VERSION_PATTERN.search(str(version_str))
41
+ if not version_match:
42
+ logger.warning("Could not parse PostgreSQL version: %s", version_str)
43
+ return None
44
+
45
+ major = int(version_match.group(1))
46
+ minor = int(version_match.group(2))
47
+ patch = int(version_match.group(3)) if version_match.group(3) else 0
48
+
49
+ version_info = VersionInfo(major, minor, patch)
50
+ logger.debug("Detected PostgreSQL version: %s", version_info)
51
+ return version_info
52
+
53
+ async def get_feature_flag(self, driver: AsyncDriverAdapterBase, feature: str) -> bool:
54
+ """Check if PostgreSQL database supports a specific feature.
55
+
56
+ Args:
57
+ driver: Async database driver instance
58
+ feature: Feature name to check
59
+
60
+ Returns:
61
+ True if feature is supported, False otherwise
62
+ """
63
+ version_info = await self.get_version(driver)
64
+ if not version_info:
65
+ return False
66
+
67
+ feature_checks: dict[str, Callable[[VersionInfo], bool]] = {
68
+ "supports_json": lambda v: v >= VersionInfo(9, 2, 0),
69
+ "supports_jsonb": lambda v: v >= VersionInfo(9, 4, 0),
70
+ "supports_uuid": lambda _: True, # UUID extension widely available
71
+ "supports_arrays": lambda _: True, # PostgreSQL has excellent array support
72
+ "supports_returning": lambda v: v >= VersionInfo(8, 2, 0),
73
+ "supports_upsert": lambda v: v >= VersionInfo(9, 5, 0), # ON CONFLICT
74
+ "supports_window_functions": lambda v: v >= VersionInfo(8, 4, 0),
75
+ "supports_cte": lambda v: v >= VersionInfo(8, 4, 0),
76
+ "supports_transactions": lambda _: True,
77
+ "supports_prepared_statements": lambda _: True,
78
+ "supports_schemas": lambda _: True,
79
+ "supports_partitioning": lambda v: v >= VersionInfo(10, 0, 0),
80
+ }
81
+
82
+ if feature in feature_checks:
83
+ return bool(feature_checks[feature](version_info))
84
+
85
+ return False
86
+
87
+ async def get_optimal_type(self, driver: AsyncDriverAdapterBase, type_category: str) -> str:
88
+ """Get optimal PostgreSQL type for a category.
89
+
90
+ Args:
91
+ driver: Async database driver instance
92
+ type_category: Type category
93
+
94
+ Returns:
95
+ PostgreSQL-specific type name
96
+ """
97
+ version_info = await self.get_version(driver)
98
+
99
+ if type_category == "json":
100
+ if version_info and version_info >= VersionInfo(9, 4, 0):
101
+ return "JSONB" # Prefer JSONB over JSON
102
+ if version_info and version_info >= VersionInfo(9, 2, 0):
103
+ return "JSON"
104
+ return "TEXT"
105
+
106
+ type_map = {
107
+ "uuid": "UUID",
108
+ "boolean": "BOOLEAN",
109
+ "timestamp": "TIMESTAMP WITH TIME ZONE",
110
+ "text": "TEXT",
111
+ "blob": "BYTEA",
112
+ "array": "ARRAY",
113
+ }
114
+ return type_map.get(type_category, "TEXT")
115
+
116
+ async def get_columns(
117
+ self, driver: AsyncDriverAdapterBase, table: str, schema: "str | None" = None
118
+ ) -> "list[dict[str, Any]]":
119
+ """Get column information for a table using pg_catalog.
120
+
121
+ Args:
122
+ driver: Psqlpy async driver instance
123
+ table: Table name to query columns for
124
+ schema: Schema name (None for default 'public')
125
+
126
+ Returns:
127
+ List of column metadata dictionaries with keys:
128
+ - column_name: Name of the column
129
+ - data_type: PostgreSQL data type
130
+ - is_nullable: Whether column allows NULL (YES/NO)
131
+ - column_default: Default value if any
132
+
133
+ Notes:
134
+ Uses pg_catalog instead of information_schema to avoid psqlpy's
135
+ inability to handle the PostgreSQL 'name' type returned by information_schema.
136
+ """
137
+ psqlpy_driver = cast("PsqlpyDriver", driver)
138
+
139
+ schema_name = schema or "public"
140
+ sql = """
141
+ SELECT
142
+ a.attname::text AS column_name,
143
+ pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type,
144
+ CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END AS is_nullable,
145
+ pg_catalog.pg_get_expr(d.adbin, d.adrelid)::text AS column_default
146
+ FROM pg_catalog.pg_attribute a
147
+ JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
148
+ JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
149
+ LEFT JOIN pg_catalog.pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
150
+ WHERE c.relname = $1
151
+ AND n.nspname = $2
152
+ AND a.attnum > 0
153
+ AND NOT a.attisdropped
154
+ ORDER BY a.attnum
155
+ """
156
+
157
+ result = await psqlpy_driver.execute(sql, (table, schema_name))
158
+ return result.data or []
159
+
160
+ def list_available_features(self) -> "list[str]":
161
+ """List available PostgreSQL feature flags.
162
+
163
+ Returns:
164
+ List of supported feature names
165
+ """
166
+ return [
167
+ "supports_json",
168
+ "supports_jsonb",
169
+ "supports_uuid",
170
+ "supports_arrays",
171
+ "supports_returning",
172
+ "supports_upsert",
173
+ "supports_window_functions",
174
+ "supports_cte",
175
+ "supports_transactions",
176
+ "supports_prepared_statements",
177
+ "supports_schemas",
178
+ "supports_partitioning",
179
+ ]