sqlspec 0.1.1__py3-none-any.whl → 0.4.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 (42) hide show
  1. sqlspec/__metadata__.py +1 -1
  2. sqlspec/_serialization.py +1 -1
  3. sqlspec/_typing.py +138 -0
  4. sqlspec/adapters/adbc/config.py +52 -0
  5. sqlspec/adapters/aiosqlite/__init__.py +3 -0
  6. sqlspec/adapters/aiosqlite/config.py +95 -0
  7. sqlspec/adapters/asyncmy/__init__.py +3 -0
  8. sqlspec/adapters/asyncmy/config.py +176 -0
  9. sqlspec/adapters/asyncpg/__init__.py +0 -0
  10. sqlspec/adapters/asyncpg/config.py +145 -0
  11. sqlspec/adapters/duckdb/__init__.py +0 -0
  12. sqlspec/adapters/duckdb/config.py +201 -0
  13. sqlspec/adapters/oracledb/__init__.py +13 -0
  14. sqlspec/adapters/oracledb/config/__init__.py +9 -0
  15. sqlspec/adapters/oracledb/config/_asyncio.py +95 -0
  16. sqlspec/adapters/oracledb/config/_common.py +151 -0
  17. sqlspec/adapters/oracledb/config/_sync.py +95 -0
  18. sqlspec/adapters/psycopg/__init__.py +0 -0
  19. sqlspec/adapters/psycopg/config/__init__.py +9 -0
  20. sqlspec/adapters/psycopg/config/_async.py +75 -0
  21. sqlspec/adapters/psycopg/config/_common.py +73 -0
  22. sqlspec/adapters/psycopg/config/_sync.py +75 -0
  23. sqlspec/adapters/sqlite/__init__.py +0 -0
  24. sqlspec/adapters/sqlite/config.py +92 -0
  25. sqlspec/config.py +16 -0
  26. sqlspec/exceptions.py +29 -0
  27. sqlspec/extensions/__init__.py +0 -0
  28. sqlspec/extensions/litestar/__init__.py +0 -0
  29. sqlspec/extensions/litestar/plugin.py +34 -0
  30. sqlspec/filters.py +35 -28
  31. sqlspec/typing.py +415 -0
  32. sqlspec-0.4.0.dist-info/METADATA +84 -0
  33. sqlspec-0.4.0.dist-info/RECORD +39 -0
  34. {sqlspec-0.1.1.dist-info → sqlspec-0.4.0.dist-info}/WHEEL +1 -1
  35. sqlspec-0.4.0.dist-info/licenses/NOTICE +29 -0
  36. sqlspec/types/empty.py +0 -18
  37. sqlspec/types/protocols.py +0 -117
  38. sqlspec/utils/dataclass.py +0 -130
  39. sqlspec-0.1.1.dist-info/METADATA +0 -25
  40. sqlspec-0.1.1.dist-info/RECORD +0 -14
  41. /sqlspec/{types → adapters}/__init__.py +0 -0
  42. /sqlspec/{utils → adapters/adbc}/__init__.py +0 -0
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import contextmanager
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, Any, cast
6
+
7
+ from sqlspec.config import GenericDatabaseConfig
8
+ from sqlspec.exceptions import ImproperConfigurationError
9
+ from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Generator, Sequence
13
+
14
+ from duckdb import DuckDBPyConnection
15
+
16
+ __all__ = ("DuckDBConfig", "ExtensionConfig")
17
+
18
+
19
+ @dataclass
20
+ class ExtensionConfig:
21
+ """Configuration for a DuckDB extension.
22
+
23
+ This class provides configuration options for DuckDB extensions, including installation
24
+ and post-install configuration settings.
25
+
26
+ Args:
27
+ name: The name of the extension to install
28
+ config: Optional configuration settings to apply after installation
29
+ force_install: Whether to force reinstall if already present
30
+ repository: Optional repository name to install from
31
+ repository_url: Optional repository URL to install from
32
+ version: Optional version of the extension to install
33
+ """
34
+
35
+ name: str
36
+ config: dict[str, Any] | None = None
37
+ force_install: bool = False
38
+ repository: str | None = None
39
+ repository_url: str | None = None
40
+ version: str | None = None
41
+
42
+ @classmethod
43
+ def from_dict(cls, name: str, config: dict[str, Any] | bool | None = None) -> ExtensionConfig:
44
+ """Create an ExtensionConfig from a configuration dictionary.
45
+
46
+ Args:
47
+ name: The name of the extension
48
+ config: Configuration dictionary that may contain settings
49
+
50
+ Returns:
51
+ A new ExtensionConfig instance
52
+ """
53
+ if config is None:
54
+ return cls(name=name)
55
+
56
+ if not isinstance(config, dict):
57
+ config = {"force_install": bool(config)}
58
+
59
+ install_args = {
60
+ key: config.pop(key)
61
+ for key in ["force_install", "repository", "repository_url", "version", "config", "name"]
62
+ if key in config
63
+ }
64
+ return cls(name=name, **install_args)
65
+
66
+
67
+ @dataclass
68
+ class DuckDBConfig(GenericDatabaseConfig):
69
+ """Configuration for DuckDB database connections.
70
+
71
+ This class provides configuration options for DuckDB database connections, wrapping all parameters
72
+ available to duckdb.connect().
73
+
74
+ For details see: https://duckdb.org/docs/api/python/overview#connection-options
75
+ """
76
+
77
+ database: str | EmptyType = Empty
78
+ """The path to the database file to be opened. Pass ":memory:" to open a connection to a database that resides in RAM instead of on disk. If not specified, an in-memory database will be created."""
79
+
80
+ read_only: bool | EmptyType = Empty
81
+ """If True, the database will be opened in read-only mode. This is required if multiple processes want to access the same database file at the same time."""
82
+
83
+ config: dict[str, Any] | EmptyType = Empty
84
+ """A dictionary of configuration options to be passed to DuckDB. These can include settings like 'access_mode', 'max_memory', 'threads', etc.
85
+
86
+ For details see: https://duckdb.org/docs/api/python/overview#connection-options
87
+ """
88
+
89
+ extensions: Sequence[ExtensionConfig] | EmptyType = Empty
90
+ """A sequence of extension configurations to install and configure upon connection creation."""
91
+
92
+ def __post_init__(self) -> None:
93
+ """Post-initialization validation and processing.
94
+
95
+ This method handles merging extension configurations from both the extensions field
96
+ and the config dictionary, if present. The config['extensions'] field can be either:
97
+ - A dictionary mapping extension names to their configurations
98
+ - A list of extension names (which will be installed with force_install=True)
99
+
100
+ Raises:
101
+ ImproperConfigurationError: If there are duplicate extension configurations.
102
+ """
103
+ if self.config is Empty:
104
+ self.config = {}
105
+
106
+ if self.extensions is Empty:
107
+ self.extensions = []
108
+ # this is purely for mypy
109
+ assert isinstance(self.config, dict) # noqa: S101
110
+ assert isinstance(self.extensions, list) # noqa: S101
111
+
112
+ _e = self.config.pop("extensions", {})
113
+ if not isinstance(_e, (dict, list, tuple)):
114
+ msg = "When configuring extensions in the 'config' dictionary, the value must be a dictionary or sequence of extension names"
115
+ raise ImproperConfigurationError(msg)
116
+ if not isinstance(_e, dict):
117
+ _e = {str(ext): {"force_install": False} for ext in _e}
118
+
119
+ if len(set(_e.keys()).intersection({ext.name for ext in self.extensions})) > 0:
120
+ msg = "Configuring the same extension in both 'extensions' and as a key in 'config['extensions']' is not allowed"
121
+ raise ImproperConfigurationError(msg)
122
+
123
+ self.extensions.extend([ExtensionConfig.from_dict(name, ext_config) for name, ext_config in _e.items()])
124
+
125
+ def _configure_extensions(self, connection: DuckDBPyConnection) -> None:
126
+ """Configure extensions for the connection.
127
+
128
+ Args:
129
+ connection: The DuckDB connection to configure extensions for.
130
+
131
+ Raises:
132
+ ImproperConfigurationError: If extension installation or configuration fails.
133
+ """
134
+ if self.extensions is Empty:
135
+ return
136
+
137
+ for extension in cast("list[ExtensionConfig]", self.extensions):
138
+ try:
139
+ if extension.force_install:
140
+ connection.install_extension(
141
+ extension=extension.name,
142
+ force_install=extension.force_install,
143
+ repository=extension.repository,
144
+ repository_url=extension.repository_url,
145
+ version=extension.version,
146
+ )
147
+ connection.load_extension(extension.name)
148
+
149
+ if extension.config:
150
+ for key, value in extension.config.items():
151
+ connection.execute(f"SET {key}={value}")
152
+ except Exception as e:
153
+ msg = f"Failed to configure extension {extension.name}. Error: {e!s}"
154
+ raise ImproperConfigurationError(msg) from e
155
+
156
+ @property
157
+ def connection_config_dict(self) -> dict[str, Any]:
158
+ """Return the connection configuration as a dict.
159
+
160
+ Returns:
161
+ A string keyed dict of config kwargs for the duckdb.connect() function.
162
+ """
163
+ config = dataclass_to_dict(self, exclude_empty=True, exclude={"extensions"}, convert_nested=False)
164
+ if not config.get("database"):
165
+ config["database"] = ":memory:"
166
+ return config
167
+
168
+ def create_connection(self) -> DuckDBPyConnection:
169
+ """Create and return a new database connection with configured extensions.
170
+
171
+ Returns:
172
+ A new DuckDB connection instance with extensions installed and configured.
173
+
174
+ Raises:
175
+ ImproperConfigurationError: If the connection could not be established or extensions could not be configured.
176
+ """
177
+ import duckdb
178
+
179
+ try:
180
+ connection = duckdb.connect(**self.connection_config_dict)
181
+ self._configure_extensions(connection)
182
+ return connection
183
+ except Exception as e:
184
+ msg = f"Could not configure the DuckDB connection. Error: {e!s}"
185
+ raise ImproperConfigurationError(msg) from e
186
+
187
+ @contextmanager
188
+ def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[DuckDBPyConnection, None, None]:
189
+ """Create and provide a database connection.
190
+
191
+ Yields:
192
+ A DuckDB connection instance.
193
+
194
+ Raises:
195
+ ImproperConfigurationError: If the connection could not be established.
196
+ """
197
+ connection = self.create_connection()
198
+ try:
199
+ yield connection
200
+ finally:
201
+ connection.close()
@@ -0,0 +1,13 @@
1
+ from .config import (
2
+ OracleAsyncDatabaseConfig,
3
+ OracleAsyncPoolConfig,
4
+ OracleSyncDatabaseConfig,
5
+ OracleSyncPoolConfig,
6
+ )
7
+
8
+ __all__ = (
9
+ "OracleAsyncDatabaseConfig",
10
+ "OracleAsyncPoolConfig",
11
+ "OracleSyncDatabaseConfig",
12
+ "OracleSyncPoolConfig",
13
+ )
@@ -0,0 +1,9 @@
1
+ from sqlspec.adapters.oracledb.config._asyncio import OracleAsyncDatabaseConfig, OracleAsyncPoolConfig
2
+ from sqlspec.adapters.oracledb.config._sync import OracleSyncDatabaseConfig, OracleSyncPoolConfig
3
+
4
+ __all__ = (
5
+ "OracleAsyncDatabaseConfig",
6
+ "OracleAsyncPoolConfig",
7
+ "OracleSyncDatabaseConfig",
8
+ "OracleSyncPoolConfig",
9
+ )
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import asynccontextmanager
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
6
+
7
+ from oracledb import create_pool_async as oracledb_create_pool
8
+ from oracledb.connection import AsyncConnection
9
+ from oracledb.pool import AsyncConnectionPool
10
+
11
+ from sqlspec.adapters.oracledb.config._common import (
12
+ OracleGenericDatabaseConfig,
13
+ OracleGenericPoolConfig,
14
+ )
15
+ from sqlspec.exceptions import ImproperConfigurationError
16
+ from sqlspec.typing import dataclass_to_dict
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import AsyncGenerator, Awaitable
20
+ from typing import Any
21
+
22
+ __all__ = (
23
+ "OracleAsyncDatabaseConfig",
24
+ "OracleAsyncPoolConfig",
25
+ )
26
+
27
+
28
+ @dataclass
29
+ class OracleAsyncPoolConfig(OracleGenericPoolConfig[AsyncConnectionPool, AsyncConnection]):
30
+ """Async Oracle Pool Config"""
31
+
32
+
33
+ @dataclass
34
+ class OracleAsyncDatabaseConfig(OracleGenericDatabaseConfig[AsyncConnectionPool, AsyncConnection]):
35
+ """Async Oracle database Configuration."""
36
+
37
+ pool_config: OracleAsyncPoolConfig | None = None
38
+ """Oracle Pool configuration"""
39
+ pool_instance: AsyncConnectionPool | None = None
40
+ """Optional pool to use.
41
+
42
+ If set, the plugin will use the provided pool rather than instantiate one.
43
+ """
44
+
45
+ @property
46
+ def pool_config_dict(self) -> dict[str, Any]:
47
+ """Return the pool configuration as a dict.
48
+
49
+ Returns:
50
+ A string keyed dict of config kwargs for the Asyncpg :func:`create_pool <oracledb.pool.create_pool>`
51
+ function.
52
+ """
53
+ if self.pool_config is not None:
54
+ return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
55
+ msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
56
+ raise ImproperConfigurationError(msg)
57
+
58
+ async def create_pool(self) -> AsyncConnectionPool:
59
+ """Return a pool. If none exists yet, create one.
60
+
61
+ Returns:
62
+ Getter that returns the pool instance used by the plugin.
63
+ """
64
+ if self.pool_instance is not None:
65
+ return self.pool_instance
66
+
67
+ if self.pool_config is None:
68
+ msg = "One of 'pool_config' or 'pool_instance' must be provided."
69
+ raise ImproperConfigurationError(msg)
70
+
71
+ pool_config = self.pool_config_dict
72
+ self.pool_instance = oracledb_create_pool(**pool_config)
73
+ if self.pool_instance is None:
74
+ msg = "Could not configure the 'pool_instance'. Please check your configuration."
75
+ raise ImproperConfigurationError(msg)
76
+ return self.pool_instance
77
+
78
+ def provide_pool(self, *args: Any, **kwargs: Any) -> Awaitable[AsyncConnectionPool]:
79
+ """Create a pool instance.
80
+
81
+ Returns:
82
+ A Pool instance.
83
+ """
84
+ return self.create_pool()
85
+
86
+ @asynccontextmanager
87
+ async def provide_connection(self, *args: Any, **kwargs: Any) -> AsyncGenerator[AsyncConnection, None]:
88
+ """Create a connection instance.
89
+
90
+ Returns:
91
+ A connection instance.
92
+ """
93
+ db_pool = await self.provide_pool(*args, **kwargs)
94
+ async with db_pool.acquire() as connection:
95
+ yield connection
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Generic, TypeVar
5
+
6
+ from oracledb import ConnectionPool
7
+
8
+ from sqlspec.config import GenericDatabaseConfig, GenericPoolConfig
9
+ from sqlspec.typing import Empty
10
+
11
+ if TYPE_CHECKING:
12
+ import ssl
13
+ from collections.abc import Callable
14
+ from typing import Any
15
+
16
+ from oracledb import AuthMode, ConnectParams, Purity
17
+ from oracledb.connection import AsyncConnection, Connection
18
+ from oracledb.pool import AsyncConnectionPool, ConnectionPool
19
+
20
+ from sqlspec.typing import EmptyType
21
+
22
+ __all__ = (
23
+ "OracleGenericDatabaseConfig",
24
+ "OracleGenericPoolConfig",
25
+ )
26
+
27
+
28
+ T = TypeVar("T")
29
+
30
+ ConnectionT = TypeVar("ConnectionT", bound="Connection | AsyncConnection")
31
+ PoolT = TypeVar("PoolT", bound="ConnectionPool | AsyncConnectionPool")
32
+
33
+
34
+ @dataclass
35
+ class OracleGenericPoolConfig(Generic[PoolT, ConnectionT], GenericPoolConfig):
36
+ """Configuration for Oracle database connection pools.
37
+
38
+ This class provides configuration options for both synchronous and asynchronous Oracle
39
+ database connection pools. It supports all standard Oracle connection parameters and pool-specific
40
+ settings.([1](https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html))
41
+ """
42
+
43
+ conn_class: type[ConnectionT] | EmptyType = Empty
44
+ """The connection class to use (Connection or AsyncConnection)"""
45
+ dsn: str | EmptyType = Empty
46
+ """Connection string for the database """
47
+ pool: PoolT | EmptyType = Empty
48
+ """Existing pool instance to use"""
49
+ params: ConnectParams | EmptyType = Empty
50
+ """Connection parameters object"""
51
+ user: str | EmptyType = Empty
52
+ """Username for database authentication"""
53
+ proxy_user: str | EmptyType = Empty
54
+ """Name of the proxy user to connect through"""
55
+ password: str | EmptyType = Empty
56
+ """Password for database authentication"""
57
+ newpassword: str | EmptyType = Empty
58
+ """New password for password change operations"""
59
+ wallet_password: str | EmptyType = Empty
60
+ """Password for accessing Oracle Wallet"""
61
+ access_token: str | tuple | Callable | EmptyType = Empty
62
+ """Token for token-based authentication"""
63
+ host: str | EmptyType = Empty
64
+ """Database server hostname"""
65
+ port: int | EmptyType = Empty
66
+ """Database server port number"""
67
+ protocol: str | EmptyType = Empty
68
+ """Network protocol (TCP or TCPS)"""
69
+ https_proxy: str | EmptyType = Empty
70
+ """HTTPS proxy server address"""
71
+ https_proxy_port: int | EmptyType = Empty
72
+ """HTTPS proxy server port"""
73
+ service_name: str | EmptyType = Empty
74
+ """Oracle service name"""
75
+ sid: str | EmptyType = Empty
76
+ """Oracle System ID (SID)"""
77
+ server_type: str | EmptyType = Empty
78
+ """Server type (dedicated, shared, pooled, or drcp)"""
79
+ cclass: str | EmptyType = Empty
80
+ """Connection class for database resident connection pooling"""
81
+ purity: Purity | EmptyType = Empty
82
+ """Session purity (NEW, SELF, or DEFAULT)"""
83
+ expire_time: int | EmptyType = Empty
84
+ """Time in minutes after which idle connections are closed"""
85
+ retry_count: int | EmptyType = Empty
86
+ """Number of attempts to connect"""
87
+ retry_delay: int | EmptyType = Empty
88
+ """Time in seconds between connection attempts"""
89
+ tcp_connect_timeout: float | EmptyType = Empty
90
+ """Timeout for establishing TCP connections"""
91
+ ssl_server_dn_match: bool | EmptyType = Empty
92
+ """If True, verify server certificate DN"""
93
+ ssl_server_cert_dn: str | EmptyType = Empty
94
+ """Expected server certificate DN"""
95
+ wallet_location: str | EmptyType = Empty
96
+ """Location of Oracle Wallet"""
97
+ events: bool | EmptyType = Empty
98
+ """If True, enables Oracle events for FAN and RLB"""
99
+ externalauth: bool | EmptyType = Empty
100
+ """If True, uses external authentication"""
101
+ mode: AuthMode | EmptyType = Empty
102
+ """Session mode (SYSDBA, SYSOPER, etc.)"""
103
+ disable_oob: bool | EmptyType = Empty
104
+ """If True, disables Oracle out-of-band breaks"""
105
+ stmtcachesize: int | EmptyType = Empty
106
+ """Size of the statement cache"""
107
+ edition: str | EmptyType = Empty
108
+ """Edition name for edition-based redefinition"""
109
+ tag: str | EmptyType = Empty
110
+ """Connection pool tag"""
111
+ matchanytag: bool | EmptyType = Empty
112
+ """If True, allows connections with different tags"""
113
+ config_dir: str | EmptyType = Empty
114
+ """Directory containing Oracle configuration files"""
115
+ appcontext: list | EmptyType = Empty
116
+ """Application context list"""
117
+ shardingkey: list | EmptyType = Empty
118
+ """Sharding key list"""
119
+ supershardingkey: list | EmptyType = Empty
120
+ """Super sharding key list"""
121
+ debug_jdwp: str | EmptyType = Empty
122
+ """JDWP debugging string"""
123
+ connection_id_prefix: str | EmptyType = Empty
124
+ """Prefix for connection identifiers"""
125
+ ssl_context: Any | EmptyType = Empty
126
+ """SSL context for TCPS connections"""
127
+ sdu: int | EmptyType = Empty
128
+ """Session data unit size"""
129
+ pool_boundary: str | EmptyType = Empty
130
+ """Connection pool boundary (statement or transaction)"""
131
+ use_tcp_fast_open: bool | EmptyType = Empty
132
+ """If True, enables TCP Fast Open"""
133
+ ssl_version: ssl.TLSVersion | EmptyType = Empty
134
+ """SSL/TLS protocol version"""
135
+ handle: int | EmptyType = Empty
136
+ """Oracle service context handle"""
137
+
138
+
139
+ @dataclass
140
+ class OracleGenericDatabaseConfig(Generic[PoolT, ConnectionT], GenericDatabaseConfig):
141
+ """Oracle database Configuration.
142
+
143
+ This class provides the base configuration for Oracle database connections, extending
144
+ the generic database configuration with Oracle-specific settings. It supports both
145
+ thin and thick modes of the python-oracledb driver.([1](https://python-oracledb.readthedocs.io/en/latest/index.html))
146
+
147
+ The configuration supports all standard Oracle connection parameters and can be used
148
+ with both synchronous and asynchronous connections. It includes support for features
149
+ like Oracle Wallet, external authentication, connection pooling, and advanced security
150
+ options.([2](https://python-oracledb.readthedocs.io/en/latest/user_guide/tuning.html))
151
+ """
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import contextmanager
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
6
+
7
+ from oracledb import create_pool as oracledb_create_pool
8
+ from oracledb.connection import Connection
9
+ from oracledb.pool import ConnectionPool
10
+
11
+ from sqlspec.adapters.oracledb.config._common import (
12
+ OracleGenericDatabaseConfig,
13
+ OracleGenericPoolConfig,
14
+ )
15
+ from sqlspec.exceptions import ImproperConfigurationError
16
+ from sqlspec.typing import dataclass_to_dict
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Generator
20
+ from typing import Any
21
+
22
+ __all__ = (
23
+ "OracleSyncDatabaseConfig",
24
+ "OracleSyncPoolConfig",
25
+ )
26
+
27
+
28
+ @dataclass
29
+ class OracleSyncPoolConfig(OracleGenericPoolConfig[ConnectionPool, Connection]):
30
+ """Sync Oracle Pool Config"""
31
+
32
+
33
+ @dataclass
34
+ class OracleSyncDatabaseConfig(OracleGenericDatabaseConfig[ConnectionPool, Connection]):
35
+ """Oracle database Configuration."""
36
+
37
+ pool_config: OracleSyncPoolConfig | None = None
38
+ """Oracle Pool configuration"""
39
+ pool_instance: ConnectionPool | None = None
40
+ """Optional pool to use.
41
+
42
+ If set, the plugin will use the provided pool rather than instantiate one.
43
+ """
44
+
45
+ @property
46
+ def pool_config_dict(self) -> dict[str, Any]:
47
+ """Return the pool configuration as a dict.
48
+
49
+ Returns:
50
+ A string keyed dict of config kwargs for the Asyncpg :func:`create_pool <oracledb.pool.create_pool>`
51
+ function.
52
+ """
53
+ if self.pool_config:
54
+ return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
55
+ msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
56
+ raise ImproperConfigurationError(msg)
57
+
58
+ def create_pool(self) -> ConnectionPool:
59
+ """Return a pool. If none exists yet, create one.
60
+
61
+ Returns:
62
+ Getter that returns the pool instance used by the plugin.
63
+ """
64
+ if self.pool_instance is not None:
65
+ return self.pool_instance
66
+
67
+ if self.pool_config is None:
68
+ msg = "One of 'pool_config' or 'pool_instance' must be provided."
69
+ raise ImproperConfigurationError(msg)
70
+
71
+ pool_config = self.pool_config_dict
72
+ self.pool_instance = oracledb_create_pool(**pool_config)
73
+ if self.pool_instance is None:
74
+ msg = "Could not configure the 'pool_instance'. Please check your configuration."
75
+ raise ImproperConfigurationError(msg)
76
+ return self.pool_instance
77
+
78
+ def provide_pool(self, *args: Any, **kwargs: Any) -> ConnectionPool:
79
+ """Create a pool instance.
80
+
81
+ Returns:
82
+ A Pool instance.
83
+ """
84
+ return self.create_pool()
85
+
86
+ @contextmanager
87
+ def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[Connection, None, None]:
88
+ """Create a connection instance.
89
+
90
+ Returns:
91
+ A connection instance.
92
+ """
93
+ db_pool = self.provide_pool(*args, **kwargs)
94
+ with db_pool.acquire() as connection:
95
+ yield connection
File without changes
@@ -0,0 +1,9 @@
1
+ from ._async import PsycoPgAsyncDatabaseConfig, PsycoPgAsyncPoolConfig
2
+ from ._sync import PsycoPgSyncDatabaseConfig, PsycoPgSyncPoolConfig
3
+
4
+ __all__ = (
5
+ "PsycoPgAsyncDatabaseConfig",
6
+ "PsycoPgAsyncPoolConfig",
7
+ "PsycoPgSyncDatabaseConfig",
8
+ "PsycoPgSyncPoolConfig",
9
+ )
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import asynccontextmanager
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
6
+
7
+ from psycopg import AsyncConnection
8
+ from psycopg_pool import AsyncConnectionPool
9
+
10
+ from sqlspec.adapters.psycopg.config._common import (
11
+ PsycoPgGenericDatabaseConfig,
12
+ PsycoPgGenericPoolConfig,
13
+ )
14
+ from sqlspec.exceptions import ImproperConfigurationError
15
+ from sqlspec.typing import dataclass_to_dict
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import AsyncGenerator, Awaitable
19
+ from typing import Any
20
+
21
+
22
+ __all__ = (
23
+ "PsycoPgAsyncDatabaseConfig",
24
+ "PsycoPgAsyncPoolConfig",
25
+ )
26
+
27
+
28
+ @dataclass
29
+ class PsycoPgAsyncPoolConfig(PsycoPgGenericPoolConfig[AsyncConnectionPool, AsyncConnection]):
30
+ """Async Psycopg Pool Config"""
31
+
32
+
33
+ @dataclass
34
+ class PsycoPgAsyncDatabaseConfig(PsycoPgGenericDatabaseConfig[AsyncConnectionPool, AsyncConnection]):
35
+ """Async Psycopg database Configuration."""
36
+
37
+ pool_config: PsycoPgAsyncPoolConfig | None = None
38
+ """Psycopg Pool configuration"""
39
+ pool_instance: AsyncConnectionPool | None = None
40
+ """Optional pool to use"""
41
+
42
+ @property
43
+ def pool_config_dict(self) -> dict[str, Any]:
44
+ """Return the pool configuration as a dict."""
45
+ if self.pool_config:
46
+ return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
47
+ msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
48
+ raise ImproperConfigurationError(msg)
49
+
50
+ async def create_pool(self) -> AsyncConnectionPool:
51
+ """Create and return a connection pool."""
52
+ if self.pool_instance is not None:
53
+ return self.pool_instance
54
+
55
+ if self.pool_config is None:
56
+ msg = "One of 'pool_config' or 'pool_instance' must be provided."
57
+ raise ImproperConfigurationError(msg)
58
+
59
+ pool_config = self.pool_config_dict
60
+ self.pool_instance = AsyncConnectionPool(**pool_config)
61
+ if self.pool_instance is None:
62
+ msg = "Could not configure the 'pool_instance'. Please check your configuration." # type: ignore[unreachable]
63
+ raise ImproperConfigurationError(msg)
64
+ return self.pool_instance
65
+
66
+ def provide_pool(self, *args: Any, **kwargs: Any) -> Awaitable[AsyncConnectionPool]:
67
+ """Create and return a connection pool."""
68
+ return self.create_pool()
69
+
70
+ @asynccontextmanager
71
+ async def provide_connection(self, *args: Any, **kwargs: Any) -> AsyncGenerator[AsyncConnection, None]:
72
+ """Create and provide a database connection."""
73
+ pool = await self.provide_pool(*args, **kwargs)
74
+ async with pool.connection() as connection:
75
+ yield connection