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.
- sqlspec/__metadata__.py +1 -1
- sqlspec/_serialization.py +1 -1
- sqlspec/_typing.py +138 -0
- sqlspec/adapters/adbc/config.py +52 -0
- sqlspec/adapters/aiosqlite/__init__.py +3 -0
- sqlspec/adapters/aiosqlite/config.py +95 -0
- sqlspec/adapters/asyncmy/__init__.py +3 -0
- sqlspec/adapters/asyncmy/config.py +176 -0
- sqlspec/adapters/asyncpg/__init__.py +0 -0
- sqlspec/adapters/asyncpg/config.py +145 -0
- sqlspec/adapters/duckdb/__init__.py +0 -0
- sqlspec/adapters/duckdb/config.py +201 -0
- sqlspec/adapters/oracledb/__init__.py +13 -0
- sqlspec/adapters/oracledb/config/__init__.py +9 -0
- sqlspec/adapters/oracledb/config/_asyncio.py +95 -0
- sqlspec/adapters/oracledb/config/_common.py +151 -0
- sqlspec/adapters/oracledb/config/_sync.py +95 -0
- sqlspec/adapters/psycopg/__init__.py +0 -0
- sqlspec/adapters/psycopg/config/__init__.py +9 -0
- sqlspec/adapters/psycopg/config/_async.py +75 -0
- sqlspec/adapters/psycopg/config/_common.py +73 -0
- sqlspec/adapters/psycopg/config/_sync.py +75 -0
- sqlspec/adapters/sqlite/__init__.py +0 -0
- sqlspec/adapters/sqlite/config.py +92 -0
- sqlspec/config.py +16 -0
- sqlspec/exceptions.py +29 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/litestar/__init__.py +0 -0
- sqlspec/extensions/litestar/plugin.py +34 -0
- sqlspec/filters.py +35 -28
- sqlspec/typing.py +415 -0
- sqlspec-0.4.0.dist-info/METADATA +84 -0
- sqlspec-0.4.0.dist-info/RECORD +39 -0
- {sqlspec-0.1.1.dist-info → sqlspec-0.4.0.dist-info}/WHEEL +1 -1
- sqlspec-0.4.0.dist-info/licenses/NOTICE +29 -0
- sqlspec/types/empty.py +0 -18
- sqlspec/types/protocols.py +0 -117
- sqlspec/utils/dataclass.py +0 -130
- sqlspec-0.1.1.dist-info/METADATA +0 -25
- sqlspec-0.1.1.dist-info/RECORD +0 -14
- /sqlspec/{types → adapters}/__init__.py +0 -0
- /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
|