sqlspec 0.6.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 +0 -2
- sqlspec/_serialization.py +55 -8
- sqlspec/_typing.py +69 -20
- sqlspec/adapters/adbc/config.py +6 -11
- sqlspec/adapters/aiosqlite/__init__.py +1 -1
- sqlspec/adapters/aiosqlite/config.py +12 -14
- sqlspec/adapters/asyncmy/__init__.py +1 -1
- sqlspec/adapters/asyncmy/config.py +44 -44
- sqlspec/adapters/asyncpg/config.py +26 -27
- sqlspec/adapters/duckdb/config.py +73 -77
- sqlspec/adapters/oracledb/config/_asyncio.py +9 -11
- sqlspec/adapters/oracledb/config/_common.py +50 -52
- sqlspec/adapters/oracledb/config/_sync.py +8 -10
- sqlspec/adapters/psycopg/config/_async.py +7 -9
- sqlspec/adapters/psycopg/config/_common.py +15 -17
- sqlspec/adapters/psycopg/config/_sync.py +7 -9
- sqlspec/adapters/sqlite/config.py +11 -13
- sqlspec/base.py +14 -8
- sqlspec/exceptions.py +4 -6
- sqlspec/extensions/litestar/plugin.py +8 -7
- 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.6.0.dist-info → sqlspec-0.7.0.dist-info}/METADATA +8 -1
- sqlspec-0.7.0.dist-info/RECORD +46 -0
- sqlspec-0.6.0.dist-info/RECORD +0 -46
- {sqlspec-0.6.0.dist-info → sqlspec-0.7.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.6.0.dist-info → sqlspec-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.6.0.dist-info → sqlspec-0.7.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from contextlib import asynccontextmanager
|
|
4
2
|
from dataclasses import dataclass
|
|
5
|
-
from typing import TYPE_CHECKING, Any, 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
|
|
|
@@ -19,6 +16,8 @@ if TYPE_CHECKING:
|
|
|
19
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
|
|
20
|
+
|
|
22
21
|
|
|
23
22
|
__all__ = (
|
|
24
23
|
"AsyncPgConfig",
|
|
@@ -28,7 +27,7 @@ __all__ = (
|
|
|
28
27
|
|
|
29
28
|
T = TypeVar("T")
|
|
30
29
|
|
|
31
|
-
PgConnection: TypeAlias = Union[Connection, PoolConnectionProxy]
|
|
30
|
+
PgConnection: TypeAlias = "Union[Connection, PoolConnectionProxy]" # pyright: ignore[reportMissingTypeArgument]
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
@dataclass
|
|
@@ -41,57 +40,57 @@ class AsyncPgPoolConfig(GenericPoolConfig):
|
|
|
41
40
|
dsn: str
|
|
42
41
|
"""Connection arguments specified using as a single string in the following format: ``postgres://user:pass@host:port/database?option=value``
|
|
43
42
|
"""
|
|
44
|
-
connect_kwargs: dict[Any, Any]
|
|
43
|
+
connect_kwargs: "Optional[Union[dict[Any, Any], EmptyType]]" = Empty
|
|
45
44
|
"""A dictionary of arguments which will be passed directly to the ``connect()`` method as keyword arguments.
|
|
46
45
|
"""
|
|
47
|
-
connection_class: type[Connection]
|
|
46
|
+
connection_class: "Optional[Union[type[Connection], EmptyType]]" = Empty # pyright: ignore[reportMissingTypeArgument]
|
|
48
47
|
"""The class to use for connections. Must be a subclass of Connection
|
|
49
48
|
"""
|
|
50
|
-
record_class: type[Record]
|
|
49
|
+
record_class: "Union[type[Record], EmptyType]" = Empty
|
|
51
50
|
"""If specified, the class to use for records returned by queries on the connections in this pool. Must be a subclass of Record."""
|
|
52
51
|
|
|
53
|
-
min_size: int
|
|
52
|
+
min_size: "Union[int, EmptyType]" = Empty
|
|
54
53
|
"""The number of connections to keep open inside the connection pool."""
|
|
55
|
-
max_size: int
|
|
54
|
+
max_size: "Union[int, EmptyType]" = Empty
|
|
56
55
|
"""The number of connections to allow in connection pool “overflow”, that is connections that can be opened above
|
|
57
56
|
and beyond the pool_size setting, which defaults to 10."""
|
|
58
57
|
|
|
59
|
-
max_queries: int
|
|
58
|
+
max_queries: "Union[int, EmptyType]" = Empty
|
|
60
59
|
"""Number of queries after a connection is closed and replaced with a new connection.
|
|
61
60
|
"""
|
|
62
|
-
max_inactive_connection_lifetime: float
|
|
61
|
+
max_inactive_connection_lifetime: "Union[float, EmptyType]" = Empty
|
|
63
62
|
"""Number of seconds after which inactive connections in the pool will be closed. Pass 0 to disable this mechanism."""
|
|
64
63
|
|
|
65
|
-
setup: Coroutine[None, type[Connection], Any]
|
|
64
|
+
setup: "Union[Coroutine[None, type[Connection], Any], EmptyType]" = Empty # pyright: ignore[reportMissingTypeArgument]
|
|
66
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."""
|
|
67
|
-
init: Coroutine[None, type[Connection], Any]
|
|
66
|
+
init: "Union[Coroutine[None, type[Connection], Any], EmptyType]" = Empty # pyright: ignore[reportMissingTypeArgument]
|
|
68
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."""
|
|
69
68
|
|
|
70
|
-
loop: AbstractEventLoop
|
|
69
|
+
loop: "Union[AbstractEventLoop, EmptyType]" = Empty
|
|
71
70
|
"""An asyncio event loop instance. If None, the default event loop will be used."""
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
@dataclass
|
|
75
|
-
class AsyncPgConfig(AsyncDatabaseConfig[PgConnection, Pool]):
|
|
74
|
+
class AsyncPgConfig(AsyncDatabaseConfig[PgConnection, Pool]): # pyright: ignore[reportMissingTypeArgument]
|
|
76
75
|
"""Asyncpg Configuration."""
|
|
77
76
|
|
|
78
|
-
pool_config: AsyncPgPoolConfig
|
|
77
|
+
pool_config: "Optional[AsyncPgPoolConfig]" = None
|
|
79
78
|
"""Asyncpg Pool configuration"""
|
|
80
|
-
json_deserializer: Callable[[str], Any] = decode_json
|
|
79
|
+
json_deserializer: "Callable[[str], Any]" = decode_json
|
|
81
80
|
"""For dialects that support the :class:`JSON <sqlalchemy.types.JSON>` datatype, this is a Python callable that will
|
|
82
81
|
convert a JSON string to a Python object. By default, this is set to SQLSpec's
|
|
83
82
|
:attr:`decode_json() <sqlspec._serialization.decode_json>` function."""
|
|
84
|
-
json_serializer: Callable[[Any], str] = encode_json
|
|
83
|
+
json_serializer: "Callable[[Any], str]" = encode_json
|
|
85
84
|
"""For dialects that support the JSON datatype, this is a Python callable that will render a given object as JSON.
|
|
86
85
|
By default, SQLSpec's :attr:`encode_json() <sqlspec._serialization.encode_json>` is used."""
|
|
87
|
-
pool_instance: Pool
|
|
86
|
+
pool_instance: "Optional[Pool[Any]]" = None
|
|
88
87
|
"""Optional pool to use.
|
|
89
88
|
|
|
90
89
|
If set, the plugin will use the provided pool rather than instantiate one.
|
|
91
90
|
"""
|
|
92
91
|
|
|
93
92
|
@property
|
|
94
|
-
def pool_config_dict(self) -> dict[str, Any]:
|
|
93
|
+
def pool_config_dict(self) -> "dict[str, Any]":
|
|
95
94
|
"""Return the pool configuration as a dict.
|
|
96
95
|
|
|
97
96
|
Returns:
|
|
@@ -103,7 +102,7 @@ class AsyncPgConfig(AsyncDatabaseConfig[PgConnection, Pool]):
|
|
|
103
102
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
104
103
|
raise ImproperConfigurationError(msg)
|
|
105
104
|
|
|
106
|
-
async def create_pool(self) -> Pool:
|
|
105
|
+
async def create_pool(self) -> "Pool": # pyright: ignore[reportMissingTypeArgument,reportUnknownParameterType]
|
|
107
106
|
"""Return a pool. If none exists yet, create one.
|
|
108
107
|
|
|
109
108
|
Returns:
|
|
@@ -125,21 +124,21 @@ class AsyncPgConfig(AsyncDatabaseConfig[PgConnection, Pool]):
|
|
|
125
124
|
)
|
|
126
125
|
return self.pool_instance
|
|
127
126
|
|
|
128
|
-
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]
|
|
129
128
|
"""Create a pool instance.
|
|
130
129
|
|
|
131
130
|
Returns:
|
|
132
131
|
A Pool instance.
|
|
133
132
|
"""
|
|
134
|
-
return self.create_pool()
|
|
133
|
+
return self.create_pool() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
|
|
135
134
|
|
|
136
135
|
@asynccontextmanager
|
|
137
|
-
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]
|
|
138
137
|
"""Create a connection instance.
|
|
139
138
|
|
|
140
139
|
Returns:
|
|
141
140
|
A connection instance.
|
|
142
141
|
"""
|
|
143
|
-
db_pool = await self.provide_pool(*args, **kwargs)
|
|
144
|
-
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]
|
|
145
144
|
yield connection
|
|
@@ -1,10 +1,9 @@
|
|
|
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
8
|
from sqlspec.base import NoPoolSyncConfig
|
|
10
9
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
@@ -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,41 +27,17 @@ 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
43
|
class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
|
|
@@ -75,28 +49,24 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
|
|
|
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(NoPoolSyncConfig[DuckDBPyConnection]):
|
|
|
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(NoPoolSyncConfig[DuckDBPyConnection]):
|
|
|
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(NoPoolSyncConfig[DuckDBPyConnection]):
|
|
|
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,8 +1,6 @@
|
|
|
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
5
|
from oracledb import create_pool_async as oracledb_create_pool # pyright: ignore[reportUnknownVariableType]
|
|
8
6
|
from oracledb.connection import AsyncConnection
|
|
@@ -17,7 +15,7 @@ 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",
|
|
@@ -44,16 +42,16 @@ class OracleAsyncDatabaseConfig(AsyncDatabaseConfig[AsyncConnection, AsyncConnec
|
|
|
44
42
|
options.([2](https://python-oracledb.readthedocs.io/en/latest/user_guide/tuning.html))
|
|
45
43
|
"""
|
|
46
44
|
|
|
47
|
-
pool_config: OracleAsyncPoolConfig
|
|
45
|
+
pool_config: "Optional[OracleAsyncPoolConfig]" = None
|
|
48
46
|
"""Oracle Pool configuration"""
|
|
49
|
-
pool_instance: AsyncConnectionPool
|
|
47
|
+
pool_instance: "Optional[AsyncConnectionPool]" = None
|
|
50
48
|
"""Optional pool to use.
|
|
51
49
|
|
|
52
50
|
If set, the plugin will use the provided pool rather than instantiate one.
|
|
53
51
|
"""
|
|
54
52
|
|
|
55
53
|
@property
|
|
56
|
-
def pool_config_dict(self) -> dict[str, Any]:
|
|
54
|
+
def pool_config_dict(self) -> "dict[str, Any]":
|
|
57
55
|
"""Return the pool configuration as a dict.
|
|
58
56
|
|
|
59
57
|
Returns:
|
|
@@ -65,7 +63,7 @@ class OracleAsyncDatabaseConfig(AsyncDatabaseConfig[AsyncConnection, AsyncConnec
|
|
|
65
63
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
66
64
|
raise ImproperConfigurationError(msg)
|
|
67
65
|
|
|
68
|
-
async def create_pool(self) -> AsyncConnectionPool:
|
|
66
|
+
async def create_pool(self) -> "AsyncConnectionPool":
|
|
69
67
|
"""Return a pool. If none exists yet, create one.
|
|
70
68
|
|
|
71
69
|
Returns:
|
|
@@ -81,11 +79,11 @@ class OracleAsyncDatabaseConfig(AsyncDatabaseConfig[AsyncConnection, AsyncConnec
|
|
|
81
79
|
pool_config = self.pool_config_dict
|
|
82
80
|
self.pool_instance = oracledb_create_pool(**pool_config)
|
|
83
81
|
if self.pool_instance is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
84
|
-
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]
|
|
85
83
|
raise ImproperConfigurationError(msg)
|
|
86
84
|
return self.pool_instance
|
|
87
85
|
|
|
88
|
-
def provide_pool(self, *args: Any, **kwargs: Any) -> Awaitable[AsyncConnectionPool]:
|
|
86
|
+
def provide_pool(self, *args: "Any", **kwargs: "Any") -> "Awaitable[AsyncConnectionPool]":
|
|
89
87
|
"""Create a pool instance.
|
|
90
88
|
|
|
91
89
|
Returns:
|
|
@@ -94,7 +92,7 @@ class OracleAsyncDatabaseConfig(AsyncDatabaseConfig[AsyncConnection, AsyncConnec
|
|
|
94
92
|
return self.create_pool()
|
|
95
93
|
|
|
96
94
|
@asynccontextmanager
|
|
97
|
-
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]":
|
|
98
96
|
"""Create a connection instance.
|
|
99
97
|
|
|
100
98
|
Returns:
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from dataclasses import dataclass
|
|
4
|
-
from typing import TYPE_CHECKING, Generic, TypeVar
|
|
2
|
+
from typing import TYPE_CHECKING, Generic, TypeVar, Union
|
|
5
3
|
|
|
6
4
|
from oracledb import ConnectionPool
|
|
7
5
|
|
|
@@ -24,8 +22,8 @@ __all__ = ("OracleGenericPoolConfig",)
|
|
|
24
22
|
|
|
25
23
|
T = TypeVar("T")
|
|
26
24
|
|
|
27
|
-
ConnectionT = TypeVar("ConnectionT", bound="Connection
|
|
28
|
-
PoolT = TypeVar("PoolT", bound="ConnectionPool
|
|
25
|
+
ConnectionT = TypeVar("ConnectionT", bound="Union[Connection, AsyncConnection]")
|
|
26
|
+
PoolT = TypeVar("PoolT", bound="Union[ConnectionPool, AsyncConnectionPool]")
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
@dataclass
|
|
@@ -37,97 +35,97 @@ class OracleGenericPoolConfig(Generic[ConnectionT, PoolT], GenericPoolConfig):
|
|
|
37
35
|
settings.([1](https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html))
|
|
38
36
|
"""
|
|
39
37
|
|
|
40
|
-
conn_class: type[ConnectionT]
|
|
38
|
+
conn_class: "Union[type[ConnectionT], EmptyType]" = Empty
|
|
41
39
|
"""The connection class to use (Connection or AsyncConnection)"""
|
|
42
|
-
dsn: str
|
|
40
|
+
dsn: "Union[str, EmptyType]" = Empty
|
|
43
41
|
"""Connection string for the database """
|
|
44
|
-
pool: PoolT
|
|
42
|
+
pool: "Union[PoolT, EmptyType]" = Empty
|
|
45
43
|
"""Existing pool instance to use"""
|
|
46
|
-
params: ConnectParams
|
|
44
|
+
params: "Union[ConnectParams, EmptyType]" = Empty
|
|
47
45
|
"""Connection parameters object"""
|
|
48
|
-
user: str
|
|
46
|
+
user: "Union[str, EmptyType]" = Empty
|
|
49
47
|
"""Username for database authentication"""
|
|
50
|
-
proxy_user: str
|
|
48
|
+
proxy_user: "Union[str, EmptyType]" = Empty
|
|
51
49
|
"""Name of the proxy user to connect through"""
|
|
52
|
-
password: str
|
|
50
|
+
password: "Union[str, EmptyType]" = Empty
|
|
53
51
|
"""Password for database authentication"""
|
|
54
|
-
newpassword: str
|
|
52
|
+
newpassword: "Union[str, EmptyType]" = Empty
|
|
55
53
|
"""New password for password change operations"""
|
|
56
|
-
wallet_password: str
|
|
54
|
+
wallet_password: "Union[str, EmptyType]" = Empty
|
|
57
55
|
"""Password for accessing Oracle Wallet"""
|
|
58
|
-
access_token: str
|
|
56
|
+
access_token: "Union[str, tuple[str, ...], Callable[[], str], EmptyType]" = Empty
|
|
59
57
|
"""Token for token-based authentication"""
|
|
60
|
-
host: str
|
|
58
|
+
host: "Union[str, EmptyType]" = Empty
|
|
61
59
|
"""Database server hostname"""
|
|
62
|
-
port: int
|
|
60
|
+
port: "Union[int, EmptyType]" = Empty
|
|
63
61
|
"""Database server port number"""
|
|
64
|
-
protocol: str
|
|
62
|
+
protocol: "Union[str, EmptyType]" = Empty
|
|
65
63
|
"""Network protocol (TCP or TCPS)"""
|
|
66
|
-
https_proxy: str
|
|
64
|
+
https_proxy: "Union[str, EmptyType]" = Empty
|
|
67
65
|
"""HTTPS proxy server address"""
|
|
68
|
-
https_proxy_port: int
|
|
66
|
+
https_proxy_port: "Union[int, EmptyType]" = Empty
|
|
69
67
|
"""HTTPS proxy server port"""
|
|
70
|
-
service_name: str
|
|
68
|
+
service_name: "Union[str, EmptyType]" = Empty
|
|
71
69
|
"""Oracle service name"""
|
|
72
|
-
sid: str
|
|
70
|
+
sid: "Union[str, EmptyType]" = Empty
|
|
73
71
|
"""Oracle System ID (SID)"""
|
|
74
|
-
server_type: str
|
|
72
|
+
server_type: "Union[str, EmptyType]" = Empty
|
|
75
73
|
"""Server type (dedicated, shared, pooled, or drcp)"""
|
|
76
|
-
cclass: str
|
|
74
|
+
cclass: "Union[str, EmptyType]" = Empty
|
|
77
75
|
"""Connection class for database resident connection pooling"""
|
|
78
|
-
purity: Purity
|
|
76
|
+
purity: "Union[Purity, EmptyType]" = Empty
|
|
79
77
|
"""Session purity (NEW, SELF, or DEFAULT)"""
|
|
80
|
-
expire_time: int
|
|
78
|
+
expire_time: "Union[int, EmptyType]" = Empty
|
|
81
79
|
"""Time in minutes after which idle connections are closed"""
|
|
82
|
-
retry_count: int
|
|
80
|
+
retry_count: "Union[int, EmptyType]" = Empty
|
|
83
81
|
"""Number of attempts to connect"""
|
|
84
|
-
retry_delay: int
|
|
82
|
+
retry_delay: "Union[int, EmptyType]" = Empty
|
|
85
83
|
"""Time in seconds between connection attempts"""
|
|
86
|
-
tcp_connect_timeout: float
|
|
84
|
+
tcp_connect_timeout: "Union[float, EmptyType]" = Empty
|
|
87
85
|
"""Timeout for establishing TCP connections"""
|
|
88
|
-
ssl_server_dn_match: bool
|
|
86
|
+
ssl_server_dn_match: "Union[bool, EmptyType]" = Empty
|
|
89
87
|
"""If True, verify server certificate DN"""
|
|
90
|
-
ssl_server_cert_dn: str
|
|
88
|
+
ssl_server_cert_dn: "Union[str, EmptyType]" = Empty
|
|
91
89
|
"""Expected server certificate DN"""
|
|
92
|
-
wallet_location: str
|
|
90
|
+
wallet_location: "Union[str, EmptyType]" = Empty
|
|
93
91
|
"""Location of Oracle Wallet"""
|
|
94
|
-
events: bool
|
|
92
|
+
events: "Union[bool, EmptyType]" = Empty
|
|
95
93
|
"""If True, enables Oracle events for FAN and RLB"""
|
|
96
|
-
externalauth: bool
|
|
94
|
+
externalauth: "Union[bool, EmptyType]" = Empty
|
|
97
95
|
"""If True, uses external authentication"""
|
|
98
|
-
mode: AuthMode
|
|
96
|
+
mode: "Union[AuthMode, EmptyType]" = Empty
|
|
99
97
|
"""Session mode (SYSDBA, SYSOPER, etc.)"""
|
|
100
|
-
disable_oob: bool
|
|
98
|
+
disable_oob: "Union[bool, EmptyType]" = Empty
|
|
101
99
|
"""If True, disables Oracle out-of-band breaks"""
|
|
102
|
-
stmtcachesize: int
|
|
100
|
+
stmtcachesize: "Union[int, EmptyType]" = Empty
|
|
103
101
|
"""Size of the statement cache"""
|
|
104
|
-
edition: str
|
|
102
|
+
edition: "Union[str, EmptyType]" = Empty
|
|
105
103
|
"""Edition name for edition-based redefinition"""
|
|
106
|
-
tag: str
|
|
104
|
+
tag: "Union[str, EmptyType]" = Empty
|
|
107
105
|
"""Connection pool tag"""
|
|
108
|
-
matchanytag: bool
|
|
106
|
+
matchanytag: "Union[bool, EmptyType]" = Empty
|
|
109
107
|
"""If True, allows connections with different tags"""
|
|
110
|
-
config_dir: str
|
|
108
|
+
config_dir: "Union[str, EmptyType]" = Empty
|
|
111
109
|
"""Directory containing Oracle configuration files"""
|
|
112
|
-
appcontext: list[str]
|
|
110
|
+
appcontext: "Union[list[str], EmptyType]" = Empty
|
|
113
111
|
"""Application context list"""
|
|
114
|
-
shardingkey: list[str]
|
|
112
|
+
shardingkey: "Union[list[str], EmptyType]" = Empty
|
|
115
113
|
"""Sharding key list"""
|
|
116
|
-
supershardingkey: list[str]
|
|
114
|
+
supershardingkey: "Union[list[str], EmptyType]" = Empty
|
|
117
115
|
"""Super sharding key list"""
|
|
118
|
-
debug_jdwp: str
|
|
116
|
+
debug_jdwp: "Union[str, EmptyType]" = Empty
|
|
119
117
|
"""JDWP debugging string"""
|
|
120
|
-
connection_id_prefix: str
|
|
118
|
+
connection_id_prefix: "Union[str, EmptyType]" = Empty
|
|
121
119
|
"""Prefix for connection identifiers"""
|
|
122
|
-
ssl_context: Any
|
|
120
|
+
ssl_context: "Union[Any, EmptyType]" = Empty
|
|
123
121
|
"""SSL context for TCPS connections"""
|
|
124
|
-
sdu: int
|
|
122
|
+
sdu: "Union[int, EmptyType]" = Empty
|
|
125
123
|
"""Session data unit size"""
|
|
126
|
-
pool_boundary: str
|
|
124
|
+
pool_boundary: "Union[str, EmptyType]" = Empty
|
|
127
125
|
"""Connection pool boundary (statement or transaction)"""
|
|
128
|
-
use_tcp_fast_open: bool
|
|
126
|
+
use_tcp_fast_open: "Union[bool, EmptyType]" = Empty
|
|
129
127
|
"""If True, enables TCP Fast Open"""
|
|
130
|
-
ssl_version: ssl.TLSVersion
|
|
128
|
+
ssl_version: "Union[ssl.TLSVersion, EmptyType]" = Empty
|
|
131
129
|
"""SSL/TLS protocol version"""
|
|
132
|
-
handle: int
|
|
130
|
+
handle: "Union[int, EmptyType]" = Empty
|
|
133
131
|
"""Oracle service context handle"""
|