sqlspec 0.5.0__py3-none-any.whl → 0.7.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 +0 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +55 -8
- sqlspec/_typing.py +69 -20
- sqlspec/adapters/adbc/config.py +8 -13
- sqlspec/adapters/aiosqlite/__init__.py +1 -1
- sqlspec/adapters/aiosqlite/config.py +15 -24
- sqlspec/adapters/asyncmy/__init__.py +1 -1
- sqlspec/adapters/asyncmy/config.py +46 -46
- sqlspec/adapters/asyncpg/config.py +28 -33
- sqlspec/adapters/duckdb/config.py +75 -79
- sqlspec/adapters/oracledb/config/_asyncio.py +23 -18
- sqlspec/adapters/oracledb/config/_common.py +52 -72
- sqlspec/adapters/oracledb/config/_sync.py +21 -16
- sqlspec/adapters/psycopg/config/_async.py +17 -17
- sqlspec/adapters/psycopg/config/_common.py +17 -34
- sqlspec/adapters/psycopg/config/_sync.py +16 -17
- sqlspec/adapters/sqlite/config.py +13 -15
- sqlspec/base.py +148 -8
- sqlspec/exceptions.py +4 -6
- sqlspec/extensions/litestar/config.py +0 -0
- sqlspec/extensions/litestar/plugin.py +21 -12
- sqlspec/filters.py +11 -13
- sqlspec/typing.py +110 -127
- sqlspec/utils/deprecation.py +8 -10
- sqlspec/utils/fixtures.py +3 -5
- sqlspec/utils/module_loader.py +4 -6
- sqlspec/utils/text.py +2 -3
- {sqlspec-0.5.0.dist-info → sqlspec-0.7.0.dist-info}/METADATA +24 -15
- sqlspec-0.7.0.dist-info/RECORD +46 -0
- sqlspec-0.7.0.dist-info/licenses/LICENSE +21 -0
- sqlspec-0.5.0.dist-info/RECORD +0 -44
- {sqlspec-0.5.0.dist-info → sqlspec-0.7.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.5.0.dist-info → sqlspec-0.7.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from contextlib import asynccontextmanager
|
|
4
2
|
from dataclasses import dataclass
|
|
5
|
-
from typing import TYPE_CHECKING, TypeVar, Union
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union
|
|
6
4
|
|
|
7
5
|
from asyncpg import Record
|
|
8
6
|
from asyncpg import create_pool as asyncpg_create_pool
|
|
9
|
-
from asyncpg.connection import Connection
|
|
10
7
|
from asyncpg.pool import Pool, PoolConnectionProxy
|
|
11
8
|
from typing_extensions import TypeAlias
|
|
12
9
|
|
|
13
10
|
from sqlspec._serialization import decode_json, encode_json
|
|
14
|
-
from sqlspec.base import
|
|
11
|
+
from sqlspec.base import AsyncDatabaseConfig, GenericPoolConfig
|
|
15
12
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
16
13
|
from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
|
|
17
14
|
|
|
18
15
|
if TYPE_CHECKING:
|
|
19
|
-
from asyncio import AbstractEventLoop
|
|
16
|
+
from asyncio import AbstractEventLoop # pyright: ignore[reportAttributeAccessIssue]
|
|
20
17
|
from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine
|
|
21
|
-
|
|
18
|
+
|
|
19
|
+
from asyncpg.connection import Connection
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
__all__ = (
|
|
@@ -29,7 +27,7 @@ __all__ = (
|
|
|
29
27
|
|
|
30
28
|
T = TypeVar("T")
|
|
31
29
|
|
|
32
|
-
PgConnection: TypeAlias = Union[Connection, PoolConnectionProxy]
|
|
30
|
+
PgConnection: TypeAlias = "Union[Connection, PoolConnectionProxy]" # pyright: ignore[reportMissingTypeArgument]
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
@dataclass
|
|
@@ -42,60 +40,57 @@ class AsyncPgPoolConfig(GenericPoolConfig):
|
|
|
42
40
|
dsn: str
|
|
43
41
|
"""Connection arguments specified using as a single string in the following format: ``postgres://user:pass@host:port/database?option=value``
|
|
44
42
|
"""
|
|
45
|
-
connect_kwargs: dict[Any, Any]
|
|
43
|
+
connect_kwargs: "Optional[Union[dict[Any, Any], EmptyType]]" = Empty
|
|
46
44
|
"""A dictionary of arguments which will be passed directly to the ``connect()`` method as keyword arguments.
|
|
47
45
|
"""
|
|
48
|
-
connection_class: type[Connection]
|
|
46
|
+
connection_class: "Optional[Union[type[Connection], EmptyType]]" = Empty # pyright: ignore[reportMissingTypeArgument]
|
|
49
47
|
"""The class to use for connections. Must be a subclass of Connection
|
|
50
48
|
"""
|
|
51
|
-
record_class: type[Record]
|
|
49
|
+
record_class: "Union[type[Record], EmptyType]" = Empty
|
|
52
50
|
"""If specified, the class to use for records returned by queries on the connections in this pool. Must be a subclass of Record."""
|
|
53
51
|
|
|
54
|
-
min_size: int
|
|
52
|
+
min_size: "Union[int, EmptyType]" = Empty
|
|
55
53
|
"""The number of connections to keep open inside the connection pool."""
|
|
56
|
-
max_size: int
|
|
54
|
+
max_size: "Union[int, EmptyType]" = Empty
|
|
57
55
|
"""The number of connections to allow in connection pool “overflow”, that is connections that can be opened above
|
|
58
56
|
and beyond the pool_size setting, which defaults to 10."""
|
|
59
57
|
|
|
60
|
-
max_queries: int
|
|
58
|
+
max_queries: "Union[int, EmptyType]" = Empty
|
|
61
59
|
"""Number of queries after a connection is closed and replaced with a new connection.
|
|
62
60
|
"""
|
|
63
|
-
max_inactive_connection_lifetime: float
|
|
61
|
+
max_inactive_connection_lifetime: "Union[float, EmptyType]" = Empty
|
|
64
62
|
"""Number of seconds after which inactive connections in the pool will be closed. Pass 0 to disable this mechanism."""
|
|
65
63
|
|
|
66
|
-
setup: Coroutine[None, type[Connection], Any]
|
|
64
|
+
setup: "Union[Coroutine[None, type[Connection], Any], EmptyType]" = Empty # pyright: ignore[reportMissingTypeArgument]
|
|
67
65
|
"""A coroutine to prepare a connection right before it is returned from Pool.acquire(). An example use case would be to automatically set up notifications listeners for all connections of a pool."""
|
|
68
|
-
init: Coroutine[None, type[Connection], Any]
|
|
66
|
+
init: "Union[Coroutine[None, type[Connection], Any], EmptyType]" = Empty # pyright: ignore[reportMissingTypeArgument]
|
|
69
67
|
"""A coroutine to prepare a connection right before it is returned from Pool.acquire(). An example use case would be to automatically set up notifications listeners for all connections of a pool."""
|
|
70
68
|
|
|
71
|
-
loop: AbstractEventLoop
|
|
69
|
+
loop: "Union[AbstractEventLoop, EmptyType]" = Empty
|
|
72
70
|
"""An asyncio event loop instance. If None, the default event loop will be used."""
|
|
73
71
|
|
|
74
72
|
|
|
75
73
|
@dataclass
|
|
76
|
-
class AsyncPgConfig(
|
|
74
|
+
class AsyncPgConfig(AsyncDatabaseConfig[PgConnection, Pool]): # pyright: ignore[reportMissingTypeArgument]
|
|
77
75
|
"""Asyncpg Configuration."""
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
__supports_connection_pooling__ = True
|
|
81
|
-
|
|
82
|
-
pool_config: AsyncPgPoolConfig | None = None
|
|
77
|
+
pool_config: "Optional[AsyncPgPoolConfig]" = None
|
|
83
78
|
"""Asyncpg Pool configuration"""
|
|
84
|
-
json_deserializer: Callable[[str], Any] = decode_json
|
|
79
|
+
json_deserializer: "Callable[[str], Any]" = decode_json
|
|
85
80
|
"""For dialects that support the :class:`JSON <sqlalchemy.types.JSON>` datatype, this is a Python callable that will
|
|
86
81
|
convert a JSON string to a Python object. By default, this is set to SQLSpec's
|
|
87
82
|
:attr:`decode_json() <sqlspec._serialization.decode_json>` function."""
|
|
88
|
-
json_serializer: Callable[[Any], str] = encode_json
|
|
83
|
+
json_serializer: "Callable[[Any], str]" = encode_json
|
|
89
84
|
"""For dialects that support the JSON datatype, this is a Python callable that will render a given object as JSON.
|
|
90
85
|
By default, SQLSpec's :attr:`encode_json() <sqlspec._serialization.encode_json>` is used."""
|
|
91
|
-
pool_instance: Pool
|
|
86
|
+
pool_instance: "Optional[Pool[Any]]" = None
|
|
92
87
|
"""Optional pool to use.
|
|
93
88
|
|
|
94
89
|
If set, the plugin will use the provided pool rather than instantiate one.
|
|
95
90
|
"""
|
|
96
91
|
|
|
97
92
|
@property
|
|
98
|
-
def pool_config_dict(self) -> dict[str, Any]:
|
|
93
|
+
def pool_config_dict(self) -> "dict[str, Any]":
|
|
99
94
|
"""Return the pool configuration as a dict.
|
|
100
95
|
|
|
101
96
|
Returns:
|
|
@@ -107,7 +102,7 @@ class AsyncPgConfig(DatabaseConfigProtocol[PgConnection, Pool], GenericDatabaseC
|
|
|
107
102
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
108
103
|
raise ImproperConfigurationError(msg)
|
|
109
104
|
|
|
110
|
-
async def create_pool(self) -> Pool:
|
|
105
|
+
async def create_pool(self) -> "Pool": # pyright: ignore[reportMissingTypeArgument,reportUnknownParameterType]
|
|
111
106
|
"""Return a pool. If none exists yet, create one.
|
|
112
107
|
|
|
113
108
|
Returns:
|
|
@@ -129,21 +124,21 @@ class AsyncPgConfig(DatabaseConfigProtocol[PgConnection, Pool], GenericDatabaseC
|
|
|
129
124
|
)
|
|
130
125
|
return self.pool_instance
|
|
131
126
|
|
|
132
|
-
def provide_pool(self, *args: Any, **kwargs: Any) -> Awaitable[Pool]:
|
|
127
|
+
def provide_pool(self, *args: "Any", **kwargs: "Any") -> "Awaitable[Pool]": # pyright: ignore[reportMissingTypeArgument,reportUnknownParameterType]
|
|
133
128
|
"""Create a pool instance.
|
|
134
129
|
|
|
135
130
|
Returns:
|
|
136
131
|
A Pool instance.
|
|
137
132
|
"""
|
|
138
|
-
return self.create_pool()
|
|
133
|
+
return self.create_pool() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
|
|
139
134
|
|
|
140
135
|
@asynccontextmanager
|
|
141
|
-
async def provide_connection(self, *args: Any, **kwargs: Any) -> AsyncGenerator[PoolConnectionProxy, None]:
|
|
136
|
+
async def provide_connection(self, *args: "Any", **kwargs: "Any") -> "AsyncGenerator[PoolConnectionProxy, None]": # pyright: ignore[reportMissingTypeArgument,reportUnknownParameterType]
|
|
142
137
|
"""Create a connection instance.
|
|
143
138
|
|
|
144
139
|
Returns:
|
|
145
140
|
A connection instance.
|
|
146
141
|
"""
|
|
147
|
-
db_pool = await self.provide_pool(*args, **kwargs)
|
|
148
|
-
async with db_pool.acquire() as connection:
|
|
142
|
+
db_pool = await self.provide_pool(*args, **kwargs) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
|
|
143
|
+
async with db_pool.acquire() as connection: # pyright: ignore[reportUnknownVariableType]
|
|
149
144
|
yield connection
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from contextlib import contextmanager
|
|
4
2
|
from dataclasses import dataclass
|
|
5
|
-
from typing import TYPE_CHECKING, Any, cast
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Union, cast
|
|
6
4
|
|
|
7
5
|
from duckdb import DuckDBPyConnection
|
|
6
|
+
from typing_extensions import NotRequired, TypedDict
|
|
8
7
|
|
|
9
|
-
from sqlspec.base import
|
|
8
|
+
from sqlspec.base import NoPoolSyncConfig
|
|
10
9
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
11
10
|
from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
|
|
12
11
|
|
|
@@ -17,8 +16,7 @@ if TYPE_CHECKING:
|
|
|
17
16
|
__all__ = ("DuckDBConfig", "ExtensionConfig")
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
class ExtensionConfig:
|
|
19
|
+
class ExtensionConfig(TypedDict):
|
|
22
20
|
"""Configuration for a DuckDB extension.
|
|
23
21
|
|
|
24
22
|
This class provides configuration options for DuckDB extensions, including installation
|
|
@@ -29,44 +27,20 @@ class ExtensionConfig:
|
|
|
29
27
|
|
|
30
28
|
name: str
|
|
31
29
|
"""The name of the extension to install"""
|
|
32
|
-
config: dict[str, Any]
|
|
30
|
+
config: "NotRequired[dict[str, Any]]"
|
|
33
31
|
"""Optional configuration settings to apply after installation"""
|
|
34
|
-
force_install: bool
|
|
32
|
+
force_install: "NotRequired[bool]"
|
|
35
33
|
"""Whether to force reinstall if already present"""
|
|
36
|
-
repository: str
|
|
34
|
+
repository: "NotRequired[str]"
|
|
37
35
|
"""Optional repository name to install from"""
|
|
38
|
-
repository_url: str
|
|
36
|
+
repository_url: "NotRequired[str]"
|
|
39
37
|
"""Optional repository URL to install from"""
|
|
40
|
-
version: str
|
|
38
|
+
version: "NotRequired[str]"
|
|
41
39
|
"""Optional version of the extension to install"""
|
|
42
40
|
|
|
43
|
-
@classmethod
|
|
44
|
-
def from_dict(cls, name: str, config: dict[str, Any] | bool | None = None) -> ExtensionConfig:
|
|
45
|
-
"""Create an ExtensionConfig from a configuration dictionary.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
name: The name of the extension
|
|
49
|
-
config: Configuration dictionary that may contain settings
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
A new ExtensionConfig instance
|
|
53
|
-
"""
|
|
54
|
-
if config is None:
|
|
55
|
-
return cls(name=name)
|
|
56
|
-
|
|
57
|
-
if not isinstance(config, dict):
|
|
58
|
-
config = {"force_install": bool(config)}
|
|
59
|
-
|
|
60
|
-
install_args = {
|
|
61
|
-
key: config.pop(key)
|
|
62
|
-
for key in ["force_install", "repository", "repository_url", "version", "config", "name"]
|
|
63
|
-
if key in config
|
|
64
|
-
}
|
|
65
|
-
return cls(name=name, **install_args)
|
|
66
|
-
|
|
67
41
|
|
|
68
42
|
@dataclass
|
|
69
|
-
class DuckDBConfig(
|
|
43
|
+
class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
|
|
70
44
|
"""Configuration for DuckDB database connections.
|
|
71
45
|
|
|
72
46
|
This class provides configuration options for DuckDB database connections, wrapping all parameters
|
|
@@ -75,28 +49,24 @@ class DuckDBConfig(NoPoolConfig[DuckDBPyConnection], GenericDatabaseConfig):
|
|
|
75
49
|
For details see: https://duckdb.org/docs/api/python/overview#connection-options
|
|
76
50
|
"""
|
|
77
51
|
|
|
78
|
-
database: str
|
|
52
|
+
database: "Union[str, EmptyType]" = Empty
|
|
79
53
|
"""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."""
|
|
80
54
|
|
|
81
|
-
read_only: bool
|
|
55
|
+
read_only: "Union[bool, EmptyType]" = Empty
|
|
82
56
|
"""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."""
|
|
83
57
|
|
|
84
|
-
config: dict[str, Any]
|
|
58
|
+
config: "Union[dict[str, Any], EmptyType]" = Empty
|
|
85
59
|
"""A dictionary of configuration options to be passed to DuckDB. These can include settings like 'access_mode', 'max_memory', 'threads', etc.
|
|
86
60
|
|
|
87
61
|
For details see: https://duckdb.org/docs/api/python/overview#connection-options
|
|
88
62
|
"""
|
|
89
63
|
|
|
90
|
-
extensions: Sequence[ExtensionConfig]
|
|
64
|
+
extensions: "Union[Sequence[ExtensionConfig], ExtensionConfig, EmptyType]" = Empty
|
|
91
65
|
"""A sequence of extension configurations to install and configure upon connection creation."""
|
|
92
66
|
|
|
93
67
|
def __post_init__(self) -> None:
|
|
94
68
|
"""Post-initialization validation and processing.
|
|
95
69
|
|
|
96
|
-
This method handles merging extension configurations from both the extensions field
|
|
97
|
-
and the config dictionary, if present. The config['extensions'] field can be either:
|
|
98
|
-
- A dictionary mapping extension names to their configurations
|
|
99
|
-
- A list of extension names (which will be installed with force_install=True)
|
|
100
70
|
|
|
101
71
|
Raises:
|
|
102
72
|
ImproperConfigurationError: If there are duplicate extension configurations.
|
|
@@ -106,56 +76,81 @@ class DuckDBConfig(NoPoolConfig[DuckDBPyConnection], GenericDatabaseConfig):
|
|
|
106
76
|
|
|
107
77
|
if self.extensions is Empty:
|
|
108
78
|
self.extensions = []
|
|
79
|
+
if isinstance(self.extensions, dict):
|
|
80
|
+
self.extensions = [self.extensions]
|
|
109
81
|
# this is purely for mypy
|
|
110
82
|
assert isinstance(self.config, dict) # noqa: S101
|
|
111
83
|
assert isinstance(self.extensions, list) # noqa: S101
|
|
84
|
+
config_exts: list[ExtensionConfig] = self.config.pop("extensions", [])
|
|
85
|
+
if not isinstance(config_exts, list): # pyright: ignore[reportUnnecessaryIsInstance]
|
|
86
|
+
config_exts = [config_exts] # type: ignore[unreachable]
|
|
112
87
|
|
|
113
|
-
|
|
114
|
-
|
|
88
|
+
try:
|
|
89
|
+
if (
|
|
90
|
+
len(set({ext["name"] for ext in config_exts}).intersection({ext["name"] for ext in self.extensions}))
|
|
91
|
+
> 0
|
|
92
|
+
): # pyright: ignore[ reportUnknownArgumentType]
|
|
93
|
+
msg = "Configuring the same extension in both 'extensions' and as a key in 'config['extensions']' is not allowed. Please use only one method to configure extensions."
|
|
94
|
+
raise ImproperConfigurationError(msg)
|
|
95
|
+
except (KeyError, TypeError) as e:
|
|
115
96
|
msg = "When configuring extensions in the 'config' dictionary, the value must be a dictionary or sequence of extension names"
|
|
116
|
-
raise ImproperConfigurationError(msg)
|
|
117
|
-
|
|
118
|
-
_e = {str(ext): {"force_install": False} for ext in _e} # pyright: ignore[reportUnknownVariableType,reportUnknownArgumentType]
|
|
97
|
+
raise ImproperConfigurationError(msg) from e
|
|
98
|
+
self.extensions.extend(config_exts)
|
|
119
99
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
raise ImproperConfigurationError(msg)
|
|
100
|
+
def _configure_connection(self, connection: "DuckDBPyConnection") -> None:
|
|
101
|
+
"""Configure the connection.
|
|
123
102
|
|
|
124
|
-
|
|
103
|
+
Args:
|
|
104
|
+
connection: The DuckDB connection to configure.
|
|
105
|
+
"""
|
|
106
|
+
for config in cast("list[str]", self.config):
|
|
107
|
+
connection.execute(config)
|
|
125
108
|
|
|
126
|
-
def _configure_extensions(self, connection: DuckDBPyConnection) -> None:
|
|
109
|
+
def _configure_extensions(self, connection: "DuckDBPyConnection") -> None:
|
|
127
110
|
"""Configure extensions for the connection.
|
|
128
111
|
|
|
129
112
|
Args:
|
|
130
113
|
connection: The DuckDB connection to configure extensions for.
|
|
131
114
|
|
|
132
|
-
|
|
133
|
-
ImproperConfigurationError: If extension installation or configuration fails.
|
|
115
|
+
|
|
134
116
|
"""
|
|
135
117
|
if self.extensions is Empty:
|
|
136
118
|
return
|
|
137
119
|
|
|
138
120
|
for extension in cast("list[ExtensionConfig]", self.extensions):
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
121
|
+
self._configure_extension(connection, extension)
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _configure_extension(connection: "DuckDBPyConnection", extension: ExtensionConfig) -> None:
|
|
125
|
+
"""Configure a single extension for the connection.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
connection: The DuckDB connection to configure extension for.
|
|
129
|
+
extension: The extension configuration to apply.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
ImproperConfigurationError: If extension installation or configuration fails.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
if extension.get("force_install"):
|
|
136
|
+
connection.install_extension(
|
|
137
|
+
extension=extension["name"],
|
|
138
|
+
force_install=extension.get("force_install", False),
|
|
139
|
+
repository=extension.get("repository"),
|
|
140
|
+
repository_url=extension.get("repository_url"),
|
|
141
|
+
version=extension.get("version"),
|
|
142
|
+
)
|
|
143
|
+
connection.load_extension(extension["name"])
|
|
144
|
+
|
|
145
|
+
if extension.get("config"):
|
|
146
|
+
for key, value in extension.get("config", {}).items():
|
|
147
|
+
connection.execute(f"SET {key}={value}")
|
|
148
|
+
except Exception as e:
|
|
149
|
+
msg = f"Failed to configure extension {extension['name']}. Error: {e!s}"
|
|
150
|
+
raise ImproperConfigurationError(msg) from e
|
|
156
151
|
|
|
157
152
|
@property
|
|
158
|
-
def connection_config_dict(self) -> dict[str, Any]:
|
|
153
|
+
def connection_config_dict(self) -> "dict[str, Any]":
|
|
159
154
|
"""Return the connection configuration as a dict.
|
|
160
155
|
|
|
161
156
|
Returns:
|
|
@@ -166,7 +161,7 @@ class DuckDBConfig(NoPoolConfig[DuckDBPyConnection], GenericDatabaseConfig):
|
|
|
166
161
|
config["database"] = ":memory:"
|
|
167
162
|
return config
|
|
168
163
|
|
|
169
|
-
def create_connection(self) -> DuckDBPyConnection:
|
|
164
|
+
def create_connection(self) -> "DuckDBPyConnection":
|
|
170
165
|
"""Create and return a new database connection with configured extensions.
|
|
171
166
|
|
|
172
167
|
Returns:
|
|
@@ -180,20 +175,21 @@ class DuckDBConfig(NoPoolConfig[DuckDBPyConnection], GenericDatabaseConfig):
|
|
|
180
175
|
try:
|
|
181
176
|
connection = duckdb.connect(**self.connection_config_dict) # pyright: ignore[reportUnknownMemberType]
|
|
182
177
|
self._configure_extensions(connection)
|
|
183
|
-
|
|
178
|
+
self._configure_connection(connection)
|
|
184
179
|
except Exception as e:
|
|
185
180
|
msg = f"Could not configure the DuckDB connection. Error: {e!s}"
|
|
186
181
|
raise ImproperConfigurationError(msg) from e
|
|
182
|
+
else:
|
|
183
|
+
return connection
|
|
187
184
|
|
|
188
185
|
@contextmanager
|
|
189
|
-
def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[DuckDBPyConnection, None, None]:
|
|
186
|
+
def provide_connection(self, *args: Any, **kwargs: Any) -> "Generator[DuckDBPyConnection, None, None]":
|
|
190
187
|
"""Create and provide a database connection.
|
|
191
188
|
|
|
192
189
|
Yields:
|
|
193
190
|
A DuckDB connection instance.
|
|
194
191
|
|
|
195
|
-
|
|
196
|
-
ImproperConfigurationError: If the connection could not be established.
|
|
192
|
+
|
|
197
193
|
"""
|
|
198
194
|
connection = self.create_connection()
|
|
199
195
|
try:
|
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from contextlib import asynccontextmanager
|
|
4
2
|
from dataclasses import dataclass
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
6
4
|
|
|
7
|
-
from oracledb import create_pool_async as oracledb_create_pool
|
|
5
|
+
from oracledb import create_pool_async as oracledb_create_pool # pyright: ignore[reportUnknownVariableType]
|
|
8
6
|
from oracledb.connection import AsyncConnection
|
|
9
7
|
from oracledb.pool import AsyncConnectionPool
|
|
10
8
|
|
|
11
9
|
from sqlspec.adapters.oracledb.config._common import (
|
|
12
|
-
OracleGenericDatabaseConfig,
|
|
13
10
|
OracleGenericPoolConfig,
|
|
14
11
|
)
|
|
12
|
+
from sqlspec.base import AsyncDatabaseConfig
|
|
15
13
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
16
14
|
from sqlspec.typing import dataclass_to_dict
|
|
17
15
|
|
|
18
16
|
if TYPE_CHECKING:
|
|
19
17
|
from collections.abc import AsyncGenerator, Awaitable
|
|
20
|
-
|
|
18
|
+
|
|
21
19
|
|
|
22
20
|
__all__ = (
|
|
23
21
|
"OracleAsyncDatabaseConfig",
|
|
@@ -31,22 +29,29 @@ class OracleAsyncPoolConfig(OracleGenericPoolConfig[AsyncConnection, AsyncConnec
|
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
@dataclass
|
|
34
|
-
class OracleAsyncDatabaseConfig(
|
|
35
|
-
"""Async
|
|
32
|
+
class OracleAsyncDatabaseConfig(AsyncDatabaseConfig[AsyncConnection, AsyncConnectionPool]):
|
|
33
|
+
"""Oracle Async database Configuration.
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
This class provides the base configuration for Oracle database connections, extending
|
|
36
|
+
the generic database configuration with Oracle-specific settings. It supports both
|
|
37
|
+
thin and thick modes of the python-oracledb driver.([1](https://python-oracledb.readthedocs.io/en/latest/index.html))
|
|
38
|
+
|
|
39
|
+
The configuration supports all standard Oracle connection parameters and can be used
|
|
40
|
+
with both synchronous and asynchronous connections. It includes support for features
|
|
41
|
+
like Oracle Wallet, external authentication, connection pooling, and advanced security
|
|
42
|
+
options.([2](https://python-oracledb.readthedocs.io/en/latest/user_guide/tuning.html))
|
|
43
|
+
"""
|
|
39
44
|
|
|
40
|
-
pool_config: OracleAsyncPoolConfig
|
|
45
|
+
pool_config: "Optional[OracleAsyncPoolConfig]" = None
|
|
41
46
|
"""Oracle Pool configuration"""
|
|
42
|
-
pool_instance: AsyncConnectionPool
|
|
47
|
+
pool_instance: "Optional[AsyncConnectionPool]" = None
|
|
43
48
|
"""Optional pool to use.
|
|
44
49
|
|
|
45
50
|
If set, the plugin will use the provided pool rather than instantiate one.
|
|
46
51
|
"""
|
|
47
52
|
|
|
48
53
|
@property
|
|
49
|
-
def pool_config_dict(self) -> dict[str, Any]:
|
|
54
|
+
def pool_config_dict(self) -> "dict[str, Any]":
|
|
50
55
|
"""Return the pool configuration as a dict.
|
|
51
56
|
|
|
52
57
|
Returns:
|
|
@@ -58,7 +63,7 @@ class OracleAsyncDatabaseConfig(OracleGenericDatabaseConfig[AsyncConnection, Asy
|
|
|
58
63
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
59
64
|
raise ImproperConfigurationError(msg)
|
|
60
65
|
|
|
61
|
-
async def create_pool(self) -> AsyncConnectionPool:
|
|
66
|
+
async def create_pool(self) -> "AsyncConnectionPool":
|
|
62
67
|
"""Return a pool. If none exists yet, create one.
|
|
63
68
|
|
|
64
69
|
Returns:
|
|
@@ -74,11 +79,11 @@ class OracleAsyncDatabaseConfig(OracleGenericDatabaseConfig[AsyncConnection, Asy
|
|
|
74
79
|
pool_config = self.pool_config_dict
|
|
75
80
|
self.pool_instance = oracledb_create_pool(**pool_config)
|
|
76
81
|
if self.pool_instance is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
77
|
-
msg = "Could not configure the 'pool_instance'. Please check your configuration."
|
|
82
|
+
msg = "Could not configure the 'pool_instance'. Please check your configuration." # type: ignore[unreachable]
|
|
78
83
|
raise ImproperConfigurationError(msg)
|
|
79
84
|
return self.pool_instance
|
|
80
85
|
|
|
81
|
-
def provide_pool(self, *args: Any, **kwargs: Any) -> Awaitable[AsyncConnectionPool]:
|
|
86
|
+
def provide_pool(self, *args: "Any", **kwargs: "Any") -> "Awaitable[AsyncConnectionPool]":
|
|
82
87
|
"""Create a pool instance.
|
|
83
88
|
|
|
84
89
|
Returns:
|
|
@@ -87,12 +92,12 @@ class OracleAsyncDatabaseConfig(OracleGenericDatabaseConfig[AsyncConnection, Asy
|
|
|
87
92
|
return self.create_pool()
|
|
88
93
|
|
|
89
94
|
@asynccontextmanager
|
|
90
|
-
async def provide_connection(self, *args: Any, **kwargs: Any) -> AsyncGenerator[AsyncConnection, None]:
|
|
95
|
+
async def provide_connection(self, *args: "Any", **kwargs: "Any") -> "AsyncGenerator[AsyncConnection, None]":
|
|
91
96
|
"""Create a connection instance.
|
|
92
97
|
|
|
93
98
|
Returns:
|
|
94
99
|
A connection instance.
|
|
95
100
|
"""
|
|
96
101
|
db_pool = await self.provide_pool(*args, **kwargs)
|
|
97
|
-
async with db_pool.acquire() as connection:
|
|
102
|
+
async with db_pool.acquire() as connection: # pyright: ignore[reportUnknownMemberType]
|
|
98
103
|
yield connection
|