sqlspec 0.3.0__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/_typing.py +27 -1
- sqlspec/adapters/adbc/config.py +1 -3
- sqlspec/adapters/aiosqlite/config.py +2 -19
- sqlspec/adapters/asyncmy/config.py +3 -21
- sqlspec/adapters/asyncpg/config.py +3 -13
- sqlspec/adapters/duckdb/config.py +126 -26
- sqlspec/adapters/oracledb/config/_asyncio.py +8 -11
- sqlspec/adapters/oracledb/config/_common.py +2 -2
- sqlspec/adapters/oracledb/config/_sync.py +2 -9
- sqlspec/adapters/psycopg/config/_async.py +2 -11
- sqlspec/adapters/psycopg/config/_common.py +3 -2
- sqlspec/adapters/psycopg/config/_sync.py +2 -11
- sqlspec/adapters/sqlite/config.py +3 -20
- sqlspec/filters.py +6 -4
- sqlspec/typing.py +131 -3
- {sqlspec-0.3.0.dist-info → sqlspec-0.4.0.dist-info}/METADATA +1 -1
- sqlspec-0.4.0.dist-info/RECORD +39 -0
- sqlspec/utils/__init__.py +0 -0
- sqlspec/utils/dataclass.py +0 -138
- sqlspec/utils/empty.py +0 -18
- sqlspec-0.3.0.dist-info/RECORD +0 -42
- {sqlspec-0.3.0.dist-info → sqlspec-0.4.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.3.0.dist-info → sqlspec-0.4.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/_typing.py
CHANGED
|
@@ -5,15 +5,26 @@ This is used to ensure compatibility when one or more of the libraries are insta
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from enum import Enum
|
|
8
9
|
from typing import (
|
|
9
10
|
Any,
|
|
10
11
|
ClassVar,
|
|
12
|
+
Final,
|
|
11
13
|
Protocol,
|
|
14
|
+
Union,
|
|
12
15
|
cast,
|
|
13
16
|
runtime_checkable,
|
|
14
17
|
)
|
|
15
18
|
|
|
16
|
-
from typing_extensions import TypeVar, dataclass_transform
|
|
19
|
+
from typing_extensions import Literal, TypeVar, dataclass_transform
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@runtime_checkable
|
|
23
|
+
class DataclassProtocol(Protocol):
|
|
24
|
+
"""Protocol for instance checking dataclasses."""
|
|
25
|
+
|
|
26
|
+
__dataclass_fields__: ClassVar[dict[str, Any]]
|
|
27
|
+
|
|
17
28
|
|
|
18
29
|
T = TypeVar("T")
|
|
19
30
|
T_co = TypeVar("T_co", covariant=True)
|
|
@@ -99,11 +110,26 @@ except ImportError:
|
|
|
99
110
|
UNSET = UnsetType.UNSET # pyright: ignore[reportConstantRedefinition]
|
|
100
111
|
MSGSPEC_INSTALLED = False # pyright: ignore[reportConstantRedefinition]
|
|
101
112
|
|
|
113
|
+
|
|
114
|
+
class EmptyEnum(Enum):
|
|
115
|
+
"""A sentinel enum used as placeholder."""
|
|
116
|
+
|
|
117
|
+
EMPTY = 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
EmptyType = Union[Literal[EmptyEnum.EMPTY], UnsetType]
|
|
121
|
+
Empty: Final = EmptyEnum.EMPTY
|
|
122
|
+
|
|
123
|
+
|
|
102
124
|
__all__ = (
|
|
103
125
|
"MSGSPEC_INSTALLED",
|
|
104
126
|
"PYDANTIC_INSTALLED",
|
|
105
127
|
"UNSET",
|
|
106
128
|
"BaseModel",
|
|
129
|
+
"DataclassProtocol",
|
|
130
|
+
"Empty",
|
|
131
|
+
"EmptyEnum",
|
|
132
|
+
"EmptyType",
|
|
107
133
|
"FailFast",
|
|
108
134
|
"Struct",
|
|
109
135
|
"TypeAdapter",
|
sqlspec/adapters/adbc/config.py
CHANGED
|
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import TYPE_CHECKING, TypeVar
|
|
6
6
|
|
|
7
7
|
from sqlspec.config import GenericDatabaseConfig
|
|
8
|
-
from sqlspec.
|
|
8
|
+
from sqlspec.typing import Empty, EmptyType
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from collections.abc import Generator
|
|
@@ -13,8 +13,6 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
from adbc_driver_manager.dbapi import Connection, Cursor
|
|
15
15
|
|
|
16
|
-
from sqlspec.utils.empty import EmptyType
|
|
17
|
-
|
|
18
16
|
__all__ = ("AdbcDatabaseConfig",)
|
|
19
17
|
|
|
20
18
|
ConnectionT = TypeVar("ConnectionT", bound="Connection")
|
|
@@ -6,8 +6,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
6
6
|
|
|
7
7
|
from sqlspec.config import GenericDatabaseConfig
|
|
8
8
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
9
|
-
from sqlspec.
|
|
10
|
-
from sqlspec.utils.empty import Empty, EmptyType
|
|
9
|
+
from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
12
|
from collections.abc import AsyncGenerator
|
|
@@ -60,7 +59,7 @@ class AiosqliteConfig(GenericDatabaseConfig):
|
|
|
60
59
|
Returns:
|
|
61
60
|
A string keyed dict of config kwargs for the aiosqlite.connect() function.
|
|
62
61
|
"""
|
|
63
|
-
return
|
|
62
|
+
return dataclass_to_dict(self, exclude_empty=True, convert_nested=False)
|
|
64
63
|
|
|
65
64
|
async def create_connection(self) -> Connection:
|
|
66
65
|
"""Create and return a new database connection.
|
|
@@ -79,22 +78,6 @@ class AiosqliteConfig(GenericDatabaseConfig):
|
|
|
79
78
|
msg = f"Could not configure the Aiosqlite connection. Error: {e!s}"
|
|
80
79
|
raise ImproperConfigurationError(msg) from e
|
|
81
80
|
|
|
82
|
-
@asynccontextmanager
|
|
83
|
-
async def lifespan(self, *args: Any, **kwargs: Any) -> AsyncGenerator[None, None]:
|
|
84
|
-
"""Manage the lifecycle of a database connection.
|
|
85
|
-
|
|
86
|
-
Yields:
|
|
87
|
-
None
|
|
88
|
-
|
|
89
|
-
Raises:
|
|
90
|
-
ImproperConfigurationError: If the connection could not be established.
|
|
91
|
-
"""
|
|
92
|
-
connection = await self.create_connection()
|
|
93
|
-
try:
|
|
94
|
-
yield
|
|
95
|
-
finally:
|
|
96
|
-
await connection.close()
|
|
97
|
-
|
|
98
81
|
@asynccontextmanager
|
|
99
82
|
async def provide_connection(self, *args: Any, **kwargs: Any) -> AsyncGenerator[Connection, None]:
|
|
100
83
|
"""Create and provide a database connection.
|
|
@@ -5,8 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import TYPE_CHECKING, TypeVar
|
|
6
6
|
|
|
7
7
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
8
|
-
from sqlspec.
|
|
9
|
-
from sqlspec.utils.empty import Empty, EmptyType
|
|
8
|
+
from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
|
|
10
9
|
|
|
11
10
|
if TYPE_CHECKING:
|
|
12
11
|
from collections.abc import AsyncGenerator
|
|
@@ -101,7 +100,7 @@ class AsyncmyPoolConfig:
|
|
|
101
100
|
Returns:
|
|
102
101
|
A string keyed dict of config kwargs for the Asyncmy create_pool function.
|
|
103
102
|
"""
|
|
104
|
-
return
|
|
103
|
+
return dataclass_to_dict(self, exclude_empty=True, convert_nested=False)
|
|
105
104
|
|
|
106
105
|
|
|
107
106
|
@dataclass
|
|
@@ -125,7 +124,7 @@ class AsyncMyConfig:
|
|
|
125
124
|
A string keyed dict of config kwargs for the Asyncmy create_pool function.
|
|
126
125
|
"""
|
|
127
126
|
if self.pool_config:
|
|
128
|
-
return
|
|
127
|
+
return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
|
|
129
128
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
130
129
|
raise ImproperConfigurationError(msg)
|
|
131
130
|
|
|
@@ -162,23 +161,6 @@ class AsyncMyConfig:
|
|
|
162
161
|
"""
|
|
163
162
|
return await self.create_pool()
|
|
164
163
|
|
|
165
|
-
@asynccontextmanager
|
|
166
|
-
async def lifespan(self, *args: Any, **kwargs: Any) -> AsyncGenerator[None, None]:
|
|
167
|
-
"""Manage the lifecycle of a database connection pool.
|
|
168
|
-
|
|
169
|
-
Yields:
|
|
170
|
-
None
|
|
171
|
-
|
|
172
|
-
Raises:
|
|
173
|
-
ImproperConfigurationError: If the pool could not be established.
|
|
174
|
-
"""
|
|
175
|
-
pool = await self.create_pool()
|
|
176
|
-
try:
|
|
177
|
-
yield
|
|
178
|
-
finally:
|
|
179
|
-
pool.close()
|
|
180
|
-
await pool.wait_closed()
|
|
181
|
-
|
|
182
164
|
@asynccontextmanager
|
|
183
165
|
async def provide_connection(self, *args: Any, **kwargs: Any) -> AsyncGenerator[Connection, None]:
|
|
184
166
|
"""Create and provide a database connection.
|
|
@@ -10,8 +10,7 @@ from asyncpg import create_pool as asyncpg_create_pool
|
|
|
10
10
|
from sqlspec._serialization import decode_json, encode_json
|
|
11
11
|
from sqlspec.config import GenericDatabaseConfig, GenericPoolConfig
|
|
12
12
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
13
|
-
from sqlspec.
|
|
14
|
-
from sqlspec.utils.empty import Empty, EmptyType
|
|
13
|
+
from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
|
|
15
14
|
|
|
16
15
|
if TYPE_CHECKING:
|
|
17
16
|
from asyncio import AbstractEventLoop
|
|
@@ -98,7 +97,7 @@ class AsyncPgConfig(GenericDatabaseConfig):
|
|
|
98
97
|
function.
|
|
99
98
|
"""
|
|
100
99
|
if self.pool_config:
|
|
101
|
-
return
|
|
100
|
+
return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
|
|
102
101
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
103
102
|
raise ImproperConfigurationError(msg)
|
|
104
103
|
|
|
@@ -124,16 +123,7 @@ class AsyncPgConfig(GenericDatabaseConfig):
|
|
|
124
123
|
)
|
|
125
124
|
return self.pool_instance
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
async def lifespan(self, *args: Any, **kwargs) -> AsyncGenerator[None, None]:
|
|
129
|
-
db_pool = await self.create_pool()
|
|
130
|
-
try:
|
|
131
|
-
yield
|
|
132
|
-
finally:
|
|
133
|
-
db_pool.terminate()
|
|
134
|
-
await db_pool.close()
|
|
135
|
-
|
|
136
|
-
def provide_pool(self, *args: Any, **kwargs) -> Awaitable[Pool]:
|
|
126
|
+
def provide_pool(self, *args: Any, **kwargs: Any) -> Awaitable[Pool]:
|
|
137
127
|
"""Create a pool instance.
|
|
138
128
|
|
|
139
129
|
Returns:
|
|
@@ -2,19 +2,66 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
6
6
|
|
|
7
7
|
from sqlspec.config import GenericDatabaseConfig
|
|
8
8
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
9
|
-
from sqlspec.
|
|
10
|
-
from sqlspec.utils.empty import Empty, EmptyType
|
|
9
|
+
from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
|
-
from collections.abc import Generator
|
|
12
|
+
from collections.abc import Generator, Sequence
|
|
14
13
|
|
|
15
14
|
from duckdb import DuckDBPyConnection
|
|
16
15
|
|
|
17
|
-
__all__ = ("DuckDBConfig",)
|
|
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)
|
|
18
65
|
|
|
19
66
|
|
|
20
67
|
@dataclass
|
|
@@ -39,6 +86,73 @@ class DuckDBConfig(GenericDatabaseConfig):
|
|
|
39
86
|
For details see: https://duckdb.org/docs/api/python/overview#connection-options
|
|
40
87
|
"""
|
|
41
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
|
+
|
|
42
156
|
@property
|
|
43
157
|
def connection_config_dict(self) -> dict[str, Any]:
|
|
44
158
|
"""Return the connection configuration as a dict.
|
|
@@ -46,44 +160,30 @@ class DuckDBConfig(GenericDatabaseConfig):
|
|
|
46
160
|
Returns:
|
|
47
161
|
A string keyed dict of config kwargs for the duckdb.connect() function.
|
|
48
162
|
"""
|
|
49
|
-
config =
|
|
163
|
+
config = dataclass_to_dict(self, exclude_empty=True, exclude={"extensions"}, convert_nested=False)
|
|
50
164
|
if not config.get("database"):
|
|
51
165
|
config["database"] = ":memory:"
|
|
52
166
|
return config
|
|
53
167
|
|
|
54
168
|
def create_connection(self) -> DuckDBPyConnection:
|
|
55
|
-
"""Create and return a new database connection.
|
|
169
|
+
"""Create and return a new database connection with configured extensions.
|
|
56
170
|
|
|
57
171
|
Returns:
|
|
58
|
-
A new DuckDB connection instance.
|
|
172
|
+
A new DuckDB connection instance with extensions installed and configured.
|
|
59
173
|
|
|
60
174
|
Raises:
|
|
61
|
-
ImproperConfigurationError: If the connection could not be established.
|
|
175
|
+
ImproperConfigurationError: If the connection could not be established or extensions could not be configured.
|
|
62
176
|
"""
|
|
63
177
|
import duckdb
|
|
64
178
|
|
|
65
179
|
try:
|
|
66
|
-
|
|
180
|
+
connection = duckdb.connect(**self.connection_config_dict)
|
|
181
|
+
self._configure_extensions(connection)
|
|
182
|
+
return connection
|
|
67
183
|
except Exception as e:
|
|
68
184
|
msg = f"Could not configure the DuckDB connection. Error: {e!s}"
|
|
69
185
|
raise ImproperConfigurationError(msg) from e
|
|
70
186
|
|
|
71
|
-
@contextmanager
|
|
72
|
-
def lifespan(self, *args: Any, **kwargs: Any) -> Generator[None, None, None]:
|
|
73
|
-
"""Manage the lifecycle of a database connection.
|
|
74
|
-
|
|
75
|
-
Yields:
|
|
76
|
-
None
|
|
77
|
-
|
|
78
|
-
Raises:
|
|
79
|
-
ImproperConfigurationError: If the connection could not be established.
|
|
80
|
-
"""
|
|
81
|
-
connection = self.create_connection()
|
|
82
|
-
try:
|
|
83
|
-
yield
|
|
84
|
-
finally:
|
|
85
|
-
connection.close()
|
|
86
|
-
|
|
87
187
|
@contextmanager
|
|
88
188
|
def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[DuckDBPyConnection, None, None]:
|
|
89
189
|
"""Create and provide a database connection.
|
|
@@ -13,7 +13,7 @@ from sqlspec.adapters.oracledb.config._common import (
|
|
|
13
13
|
OracleGenericPoolConfig,
|
|
14
14
|
)
|
|
15
15
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
16
|
-
from sqlspec.
|
|
16
|
+
from sqlspec.typing import dataclass_to_dict
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
19
|
from collections.abc import AsyncGenerator, Awaitable
|
|
@@ -36,6 +36,11 @@ class OracleAsyncDatabaseConfig(OracleGenericDatabaseConfig[AsyncConnectionPool,
|
|
|
36
36
|
|
|
37
37
|
pool_config: OracleAsyncPoolConfig | None = None
|
|
38
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
|
+
"""
|
|
39
44
|
|
|
40
45
|
@property
|
|
41
46
|
def pool_config_dict(self) -> dict[str, Any]:
|
|
@@ -46,7 +51,7 @@ class OracleAsyncDatabaseConfig(OracleGenericDatabaseConfig[AsyncConnectionPool,
|
|
|
46
51
|
function.
|
|
47
52
|
"""
|
|
48
53
|
if self.pool_config is not None:
|
|
49
|
-
return
|
|
54
|
+
return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
|
|
50
55
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
51
56
|
raise ImproperConfigurationError(msg)
|
|
52
57
|
|
|
@@ -70,15 +75,7 @@ class OracleAsyncDatabaseConfig(OracleGenericDatabaseConfig[AsyncConnectionPool,
|
|
|
70
75
|
raise ImproperConfigurationError(msg)
|
|
71
76
|
return self.pool_instance
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
async def lifespan(self, *args: Any, **kwargs) -> AsyncGenerator[None, None]:
|
|
75
|
-
db_pool = await self.create_pool()
|
|
76
|
-
try:
|
|
77
|
-
yield
|
|
78
|
-
finally:
|
|
79
|
-
await db_pool.close(force=True)
|
|
80
|
-
|
|
81
|
-
def provide_pool(self, *args: Any, **kwargs) -> Awaitable[AsyncConnectionPool]:
|
|
78
|
+
def provide_pool(self, *args: Any, **kwargs: Any) -> Awaitable[AsyncConnectionPool]:
|
|
82
79
|
"""Create a pool instance.
|
|
83
80
|
|
|
84
81
|
Returns:
|
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Generic, TypeVar
|
|
|
6
6
|
from oracledb import ConnectionPool
|
|
7
7
|
|
|
8
8
|
from sqlspec.config import GenericDatabaseConfig, GenericPoolConfig
|
|
9
|
-
from sqlspec.
|
|
9
|
+
from sqlspec.typing import Empty
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
import ssl
|
|
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
|
|
17
17
|
from oracledb.connection import AsyncConnection, Connection
|
|
18
18
|
from oracledb.pool import AsyncConnectionPool, ConnectionPool
|
|
19
19
|
|
|
20
|
-
from sqlspec.
|
|
20
|
+
from sqlspec.typing import EmptyType
|
|
21
21
|
|
|
22
22
|
__all__ = (
|
|
23
23
|
"OracleGenericDatabaseConfig",
|
|
@@ -13,7 +13,7 @@ from sqlspec.adapters.oracledb.config._common import (
|
|
|
13
13
|
OracleGenericPoolConfig,
|
|
14
14
|
)
|
|
15
15
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
16
|
-
from sqlspec.
|
|
16
|
+
from sqlspec.typing import dataclass_to_dict
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
19
|
from collections.abc import Generator
|
|
@@ -51,7 +51,7 @@ class OracleSyncDatabaseConfig(OracleGenericDatabaseConfig[ConnectionPool, Conne
|
|
|
51
51
|
function.
|
|
52
52
|
"""
|
|
53
53
|
if self.pool_config:
|
|
54
|
-
return
|
|
54
|
+
return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
|
|
55
55
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
56
56
|
raise ImproperConfigurationError(msg)
|
|
57
57
|
|
|
@@ -75,13 +75,6 @@ class OracleSyncDatabaseConfig(OracleGenericDatabaseConfig[ConnectionPool, Conne
|
|
|
75
75
|
raise ImproperConfigurationError(msg)
|
|
76
76
|
return self.pool_instance
|
|
77
77
|
|
|
78
|
-
def lifespan(self, *args: Any, **kwargs: Any) -> Generator[None, None, None]:
|
|
79
|
-
db_pool = self.create_pool()
|
|
80
|
-
try:
|
|
81
|
-
yield
|
|
82
|
-
finally:
|
|
83
|
-
db_pool.close()
|
|
84
|
-
|
|
85
78
|
def provide_pool(self, *args: Any, **kwargs: Any) -> ConnectionPool:
|
|
86
79
|
"""Create a pool instance.
|
|
87
80
|
|
|
@@ -12,7 +12,7 @@ from sqlspec.adapters.psycopg.config._common import (
|
|
|
12
12
|
PsycoPgGenericPoolConfig,
|
|
13
13
|
)
|
|
14
14
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
15
|
-
from sqlspec.
|
|
15
|
+
from sqlspec.typing import dataclass_to_dict
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from collections.abc import AsyncGenerator, Awaitable
|
|
@@ -43,7 +43,7 @@ class PsycoPgAsyncDatabaseConfig(PsycoPgGenericDatabaseConfig[AsyncConnectionPoo
|
|
|
43
43
|
def pool_config_dict(self) -> dict[str, Any]:
|
|
44
44
|
"""Return the pool configuration as a dict."""
|
|
45
45
|
if self.pool_config:
|
|
46
|
-
return
|
|
46
|
+
return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
|
|
47
47
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
48
48
|
raise ImproperConfigurationError(msg)
|
|
49
49
|
|
|
@@ -63,15 +63,6 @@ class PsycoPgAsyncDatabaseConfig(PsycoPgGenericDatabaseConfig[AsyncConnectionPoo
|
|
|
63
63
|
raise ImproperConfigurationError(msg)
|
|
64
64
|
return self.pool_instance
|
|
65
65
|
|
|
66
|
-
@asynccontextmanager
|
|
67
|
-
async def lifespan(self, *args: Any, **kwargs: Any) -> AsyncGenerator[None, None]:
|
|
68
|
-
"""Manage the lifecycle of the connection pool."""
|
|
69
|
-
pool = await self.create_pool()
|
|
70
|
-
try:
|
|
71
|
-
yield
|
|
72
|
-
finally:
|
|
73
|
-
await pool.close()
|
|
74
|
-
|
|
75
66
|
def provide_pool(self, *args: Any, **kwargs: Any) -> Awaitable[AsyncConnectionPool]:
|
|
76
67
|
"""Create and return a connection pool."""
|
|
77
68
|
return self.create_pool()
|
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from typing import TYPE_CHECKING, Generic, TypeVar
|
|
5
5
|
|
|
6
6
|
from sqlspec.config import GenericDatabaseConfig, GenericPoolConfig
|
|
7
|
-
from sqlspec.
|
|
7
|
+
from sqlspec.typing import Empty
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from collections.abc import Callable
|
|
@@ -13,7 +13,8 @@ if TYPE_CHECKING:
|
|
|
13
13
|
from psycopg import AsyncConnection, Connection
|
|
14
14
|
from psycopg_pool import AsyncConnectionPool, ConnectionPool
|
|
15
15
|
|
|
16
|
-
from sqlspec.
|
|
16
|
+
from sqlspec.typing import EmptyType
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
__all__ = (
|
|
19
20
|
"PsycoPgGenericDatabaseConfig",
|
|
@@ -12,7 +12,7 @@ from sqlspec.adapters.psycopg.config._common import (
|
|
|
12
12
|
PsycoPgGenericPoolConfig,
|
|
13
13
|
)
|
|
14
14
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
15
|
-
from sqlspec.
|
|
15
|
+
from sqlspec.typing import dataclass_to_dict
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from collections.abc import Generator
|
|
@@ -43,7 +43,7 @@ class PsycoPgSyncDatabaseConfig(PsycoPgGenericDatabaseConfig[ConnectionPool, Con
|
|
|
43
43
|
def pool_config_dict(self) -> dict[str, Any]:
|
|
44
44
|
"""Return the pool configuration as a dict."""
|
|
45
45
|
if self.pool_config:
|
|
46
|
-
return
|
|
46
|
+
return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False)
|
|
47
47
|
msg = "'pool_config' methods can not be used when a 'pool_instance' is provided."
|
|
48
48
|
raise ImproperConfigurationError(msg)
|
|
49
49
|
|
|
@@ -63,15 +63,6 @@ class PsycoPgSyncDatabaseConfig(PsycoPgGenericDatabaseConfig[ConnectionPool, Con
|
|
|
63
63
|
raise ImproperConfigurationError(msg)
|
|
64
64
|
return self.pool_instance
|
|
65
65
|
|
|
66
|
-
@contextmanager
|
|
67
|
-
def lifespan(self, *args: Any, **kwargs: Any) -> Generator[None, None, None]:
|
|
68
|
-
"""Manage the lifecycle of the connection pool."""
|
|
69
|
-
pool = self.create_pool()
|
|
70
|
-
try:
|
|
71
|
-
yield
|
|
72
|
-
finally:
|
|
73
|
-
pool.close()
|
|
74
|
-
|
|
75
66
|
def provide_pool(self, *args: Any, **kwargs: Any) -> ConnectionPool:
|
|
76
67
|
"""Create and return a connection pool."""
|
|
77
68
|
return self.create_pool()
|
|
@@ -6,8 +6,7 @@ from typing import TYPE_CHECKING, Any, Literal
|
|
|
6
6
|
|
|
7
7
|
from sqlspec.config import GenericDatabaseConfig
|
|
8
8
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
9
|
-
from sqlspec.
|
|
10
|
-
from sqlspec.utils.empty import Empty, EmptyType
|
|
9
|
+
from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
12
|
from collections.abc import Generator
|
|
@@ -57,7 +56,7 @@ class SqliteConfig(GenericDatabaseConfig):
|
|
|
57
56
|
Returns:
|
|
58
57
|
A string keyed dict of config kwargs for the sqlite3.connect() function.
|
|
59
58
|
"""
|
|
60
|
-
return
|
|
59
|
+
return dataclass_to_dict(self, exclude_empty=True, convert_nested=False)
|
|
61
60
|
|
|
62
61
|
def create_connection(self) -> Connection:
|
|
63
62
|
"""Create and return a new database connection.
|
|
@@ -71,27 +70,11 @@ class SqliteConfig(GenericDatabaseConfig):
|
|
|
71
70
|
import sqlite3
|
|
72
71
|
|
|
73
72
|
try:
|
|
74
|
-
return sqlite3.connect(**self.connection_config_dict)
|
|
73
|
+
return sqlite3.connect(**self.connection_config_dict) # type: ignore[no-any-return,unused-ignore]
|
|
75
74
|
except Exception as e:
|
|
76
75
|
msg = f"Could not configure the SQLite connection. Error: {e!s}"
|
|
77
76
|
raise ImproperConfigurationError(msg) from e
|
|
78
77
|
|
|
79
|
-
@contextmanager
|
|
80
|
-
def lifespan(self, *args: Any, **kwargs: Any) -> Generator[None, None, None]:
|
|
81
|
-
"""Manage the lifecycle of a database connection.
|
|
82
|
-
|
|
83
|
-
Yields:
|
|
84
|
-
None
|
|
85
|
-
|
|
86
|
-
Raises:
|
|
87
|
-
ImproperConfigurationError: If the connection could not be established.
|
|
88
|
-
"""
|
|
89
|
-
connection = self.create_connection()
|
|
90
|
-
try:
|
|
91
|
-
yield
|
|
92
|
-
finally:
|
|
93
|
-
connection.close()
|
|
94
|
-
|
|
95
78
|
@contextmanager
|
|
96
79
|
def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[Connection, None, None]:
|
|
97
80
|
"""Create and provide a database connection.
|
sqlspec/filters.py
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from abc import ABC
|
|
5
|
+
from abc import ABC
|
|
6
6
|
from collections import abc # noqa: TC003
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from datetime import datetime # noqa: TC003
|
|
9
|
-
from typing import Generic, Literal
|
|
9
|
+
from typing import Generic, Literal, Protocol
|
|
10
10
|
|
|
11
11
|
from typing_extensions import TypeVar
|
|
12
12
|
|
|
@@ -27,9 +27,11 @@ __all__ = (
|
|
|
27
27
|
T = TypeVar("T")
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class StatementFilter(
|
|
31
|
-
|
|
30
|
+
class StatementFilter(Protocol):
|
|
31
|
+
"""Protocol for filters that can be appended to a statement."""
|
|
32
|
+
|
|
32
33
|
def append_to_statement(self, statement: str) -> str:
|
|
34
|
+
"""Append the filter to the statement."""
|
|
33
35
|
return statement
|
|
34
36
|
|
|
35
37
|
|
sqlspec/typing.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
|
+
from dataclasses import Field, fields
|
|
4
5
|
from functools import lru_cache
|
|
5
6
|
from typing import (
|
|
6
7
|
TYPE_CHECKING,
|
|
@@ -18,15 +19,22 @@ from sqlspec._typing import (
|
|
|
18
19
|
PYDANTIC_INSTALLED,
|
|
19
20
|
UNSET,
|
|
20
21
|
BaseModel,
|
|
22
|
+
DataclassProtocol,
|
|
23
|
+
Empty,
|
|
24
|
+
EmptyType,
|
|
21
25
|
FailFast,
|
|
22
26
|
Struct,
|
|
23
27
|
TypeAdapter,
|
|
28
|
+
UnsetType,
|
|
24
29
|
convert,
|
|
25
30
|
)
|
|
26
|
-
from sqlspec.utils.dataclass import DataclassProtocol, is_dataclass_instance, simple_asdict
|
|
27
31
|
|
|
28
32
|
if TYPE_CHECKING:
|
|
29
|
-
from .
|
|
33
|
+
from collections.abc import Iterable
|
|
34
|
+
from collections.abc import Set as AbstractSet
|
|
35
|
+
|
|
36
|
+
from sqlspec.filters import StatementFilter
|
|
37
|
+
|
|
30
38
|
|
|
31
39
|
PYDANTIC_USE_FAILFAST = False # leave permanently disabled for now
|
|
32
40
|
|
|
@@ -55,6 +63,18 @@ A list or sequence of any of the following:
|
|
|
55
63
|
"""
|
|
56
64
|
|
|
57
65
|
|
|
66
|
+
def is_dataclass_instance(obj: Any) -> TypeGuard[DataclassProtocol]:
|
|
67
|
+
"""Check if an object is a dataclass instance.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
obj: An object to check.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if the object is a dataclass instance.
|
|
74
|
+
"""
|
|
75
|
+
return hasattr(type(obj), "__dataclass_fields__")
|
|
76
|
+
|
|
77
|
+
|
|
58
78
|
@lru_cache(typed=True)
|
|
59
79
|
def get_type_adapter(f: type[T]) -> TypeAdapter[T]:
|
|
60
80
|
"""Caches and returns a pydantic type adapter.
|
|
@@ -224,6 +244,104 @@ def is_msgspec_model_without_field(v: Any, field_name: str) -> TypeGuard[Struct]
|
|
|
224
244
|
return not is_msgspec_model_with_field(v, field_name)
|
|
225
245
|
|
|
226
246
|
|
|
247
|
+
def extract_dataclass_fields(
|
|
248
|
+
dt: DataclassProtocol,
|
|
249
|
+
exclude_none: bool = False,
|
|
250
|
+
exclude_empty: bool = False,
|
|
251
|
+
include: AbstractSet[str] | None = None,
|
|
252
|
+
exclude: AbstractSet[str] | None = None,
|
|
253
|
+
) -> tuple[Field[Any], ...]:
|
|
254
|
+
"""Extract dataclass fields.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
dt: A dataclass instance.
|
|
258
|
+
exclude_none: Whether to exclude None values.
|
|
259
|
+
exclude_empty: Whether to exclude Empty values.
|
|
260
|
+
include: An iterable of fields to include.
|
|
261
|
+
exclude: An iterable of fields to exclude.
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
A tuple of dataclass fields.
|
|
266
|
+
"""
|
|
267
|
+
include = include or set()
|
|
268
|
+
exclude = exclude or set()
|
|
269
|
+
|
|
270
|
+
if common := (include & exclude):
|
|
271
|
+
msg = f"Fields {common} are both included and excluded."
|
|
272
|
+
raise ValueError(msg)
|
|
273
|
+
|
|
274
|
+
dataclass_fields: Iterable[Field[Any]] = fields(dt)
|
|
275
|
+
if exclude_none:
|
|
276
|
+
dataclass_fields = (field for field in dataclass_fields if getattr(dt, field.name) is not None)
|
|
277
|
+
if exclude_empty:
|
|
278
|
+
dataclass_fields = (field for field in dataclass_fields if getattr(dt, field.name) is not Empty)
|
|
279
|
+
if include:
|
|
280
|
+
dataclass_fields = (field for field in dataclass_fields if field.name in include)
|
|
281
|
+
if exclude:
|
|
282
|
+
dataclass_fields = (field for field in dataclass_fields if field.name not in exclude)
|
|
283
|
+
|
|
284
|
+
return tuple(dataclass_fields)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def extract_dataclass_items(
|
|
288
|
+
dt: DataclassProtocol,
|
|
289
|
+
exclude_none: bool = False,
|
|
290
|
+
exclude_empty: bool = False,
|
|
291
|
+
include: AbstractSet[str] | None = None,
|
|
292
|
+
exclude: AbstractSet[str] | None = None,
|
|
293
|
+
) -> tuple[tuple[str, Any], ...]:
|
|
294
|
+
"""Extract dataclass name, value pairs.
|
|
295
|
+
|
|
296
|
+
Unlike the 'asdict' method exports by the stdlib, this function does not pickle values.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
dt: A dataclass instance.
|
|
300
|
+
exclude_none: Whether to exclude None values.
|
|
301
|
+
exclude_empty: Whether to exclude Empty values.
|
|
302
|
+
include: An iterable of fields to include.
|
|
303
|
+
exclude: An iterable of fields to exclude.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
A tuple of key/value pairs.
|
|
307
|
+
"""
|
|
308
|
+
dataclass_fields = extract_dataclass_fields(dt, exclude_none, exclude_empty, include, exclude)
|
|
309
|
+
return tuple((field.name, getattr(dt, field.name)) for field in dataclass_fields)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def dataclass_to_dict(
|
|
313
|
+
obj: DataclassProtocol,
|
|
314
|
+
exclude_none: bool = False,
|
|
315
|
+
exclude_empty: bool = False,
|
|
316
|
+
convert_nested: bool = True,
|
|
317
|
+
exclude: set[str] | None = None,
|
|
318
|
+
) -> dict[str, Any]:
|
|
319
|
+
"""Convert a dataclass to a dictionary.
|
|
320
|
+
|
|
321
|
+
This method has important differences to the standard library version:
|
|
322
|
+
- it does not deepcopy values
|
|
323
|
+
- it does not recurse into collections
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
obj: A dataclass instance.
|
|
327
|
+
exclude_none: Whether to exclude None values.
|
|
328
|
+
exclude_empty: Whether to exclude Empty values.
|
|
329
|
+
convert_nested: Whether to recursively convert nested dataclasses.
|
|
330
|
+
exclude: An iterable of fields to exclude.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
A dictionary of key/value pairs.
|
|
334
|
+
"""
|
|
335
|
+
ret = {}
|
|
336
|
+
for field in extract_dataclass_fields(obj, exclude_none, exclude_empty, exclude=exclude):
|
|
337
|
+
value = getattr(obj, field.name)
|
|
338
|
+
if is_dataclass_instance(value) and convert_nested:
|
|
339
|
+
ret[field.name] = dataclass_to_dict(value, exclude_none, exclude_empty)
|
|
340
|
+
else:
|
|
341
|
+
ret[field.name] = getattr(obj, field.name)
|
|
342
|
+
return ret
|
|
343
|
+
|
|
344
|
+
|
|
227
345
|
def schema_dump(
|
|
228
346
|
data: dict[str, Any] | Struct | BaseModel | DataclassProtocol,
|
|
229
347
|
exclude_unset: bool = True,
|
|
@@ -238,7 +356,7 @@ def schema_dump(
|
|
|
238
356
|
:type: dict[str, Any]
|
|
239
357
|
"""
|
|
240
358
|
if is_dataclass(data):
|
|
241
|
-
return
|
|
359
|
+
return dataclass_to_dict(data, exclude_empty=exclude_unset)
|
|
242
360
|
if is_pydantic_model(data):
|
|
243
361
|
return data.model_dump(exclude_unset=exclude_unset)
|
|
244
362
|
if is_msgspec_model(data) and exclude_unset:
|
|
@@ -254,6 +372,9 @@ __all__ = (
|
|
|
254
372
|
"PYDANTIC_USE_FAILFAST",
|
|
255
373
|
"UNSET",
|
|
256
374
|
"BaseModel",
|
|
375
|
+
"DataclassProtocol",
|
|
376
|
+
"Empty",
|
|
377
|
+
"EmptyType",
|
|
257
378
|
"FailFast",
|
|
258
379
|
"FilterTypeT",
|
|
259
380
|
"ModelDictListT",
|
|
@@ -262,7 +383,14 @@ __all__ = (
|
|
|
262
383
|
"TypeAdapter",
|
|
263
384
|
"UnsetType",
|
|
264
385
|
"convert",
|
|
386
|
+
"dataclass_to_dict",
|
|
387
|
+
"extract_dataclass_fields",
|
|
388
|
+
"extract_dataclass_items",
|
|
265
389
|
"get_type_adapter",
|
|
390
|
+
"is_dataclass",
|
|
391
|
+
"is_dataclass_instance",
|
|
392
|
+
"is_dataclass_with_field",
|
|
393
|
+
"is_dataclass_without_field",
|
|
266
394
|
"is_dict",
|
|
267
395
|
"is_dict_with_field",
|
|
268
396
|
"is_dict_without_field",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
sqlspec/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
2
|
+
sqlspec/__metadata__.py,sha256=Vw99abV_UQNVH2jB0IBa9-8emyZQcXm1J9eMtLxFX2Y,496
|
|
3
|
+
sqlspec/_serialization.py,sha256=OL4x0Rz5UjF7RgAKqhYChi5qSS_ImtaVrIlA4DhIKUE,824
|
|
4
|
+
sqlspec/_typing.py,sha256=Za41GlNdEKAGTy66GTofOiGYROE7ccFMvxZdh_vxIFk,3278
|
|
5
|
+
sqlspec/config.py,sha256=BOX_V_q2MOP33tK0ISpYaiQJt3zrvK4D_JIBD9FOixY,272
|
|
6
|
+
sqlspec/exceptions.py,sha256=fhCOILBj0J7HJP67BNSC0d9YUbW8QpZPXM55xJJzE8A,3039
|
|
7
|
+
sqlspec/filters.py,sha256=H5UAn1HKm598QqDujQHwvytKHjw4QoQ2zPpgXMcYpSU,3552
|
|
8
|
+
sqlspec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
sqlspec/typing.py,sha256=t5fpzxUCWkKJIpFDKmUeq77RkdmNNFqMj4azgJpRS7k,11316
|
|
10
|
+
sqlspec/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
sqlspec/adapters/adbc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
sqlspec/adapters/adbc/config.py,sha256=BeiZdqoBQXuht0GXWleqVKL_LOFI3Ygna1XWDmb-dBw,1704
|
|
13
|
+
sqlspec/adapters/aiosqlite/__init__.py,sha256=PLqWg24l3TooJvqA0Xf1WErrxtqwo8DEoL_Zp2iSCzs,68
|
|
14
|
+
sqlspec/adapters/aiosqlite/config.py,sha256=5DOKaYT_lBFTimrITJwIqTHoweZ2aZlQ07kti1VJtxk,3821
|
|
15
|
+
sqlspec/adapters/asyncmy/__init__.py,sha256=o0R_Azae3FHiSZ1TQ5ZjyCneDOuvnEeMjmSkhuiKoWo,103
|
|
16
|
+
sqlspec/adapters/asyncmy/config.py,sha256=7VsNEokhBtBgXDj2gakdwplOYPaV3ADIwWX-o22vwNk,5461
|
|
17
|
+
sqlspec/adapters/asyncpg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
sqlspec/adapters/asyncpg/config.py,sha256=NyVvsT2x3IIZBZF6dTaSv1w6lCPEvmUprYXyTSJ9Ixk,6014
|
|
19
|
+
sqlspec/adapters/duckdb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
sqlspec/adapters/duckdb/config.py,sha256=BgGGkk0Y3CQboQeI1aB81_ABVGmgOL1cbk_cZKzeggQ,7927
|
|
21
|
+
sqlspec/adapters/oracledb/__init__.py,sha256=fFQ2xOxFcgpr-ug4AVv430irnJgBRUINvt4sL3qzyBw,275
|
|
22
|
+
sqlspec/adapters/oracledb/config/__init__.py,sha256=XoHgInT4IbXjDg5ax3ncuUoVvnYB5qQjI-Ib7gwSycU,338
|
|
23
|
+
sqlspec/adapters/oracledb/config/_asyncio.py,sha256=Z87V7t-5blS6NkMhvFjoBgUrJtKgOrTyKSl8xy2uD_M,3211
|
|
24
|
+
sqlspec/adapters/oracledb/config/_common.py,sha256=k8ou3aWR1En1jl5uo4fORMG3CoF-XQ96qdwULtteHLo,6173
|
|
25
|
+
sqlspec/adapters/oracledb/config/_sync.py,sha256=guCrotTdNJajcbG5xwVCLoza7wLPQxZkQaw99s4ibNE,3071
|
|
26
|
+
sqlspec/adapters/psycopg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
sqlspec/adapters/psycopg/config/__init__.py,sha256=pXI9Pa2VYESTchPgM3tt5kFF8tsmgq-ksZRGR6pgiUQ,280
|
|
28
|
+
sqlspec/adapters/psycopg/config/_async.py,sha256=sNQU3dzencGgOToHZuMfxoqhR9EvRyrxdWqtPDC2gUY,2712
|
|
29
|
+
sqlspec/adapters/psycopg/config/_common.py,sha256=gC-QQDy9DNjp0DGZAT3wu7MKW8ejHe5fuHU-318Vgr0,2757
|
|
30
|
+
sqlspec/adapters/psycopg/config/_sync.py,sha256=lwzaeO6I-nYuC7vif-bjn2jLugPwbPjTW226_hcweqo,2590
|
|
31
|
+
sqlspec/adapters/sqlite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
+
sqlspec/adapters/sqlite/config.py,sha256=rCv6XWWWi1dzpmAVnwHxg-ahmAif78yfFhA548fNaT8,3697
|
|
33
|
+
sqlspec/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
sqlspec/extensions/litestar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
+
sqlspec/extensions/litestar/plugin.py,sha256=oiBFfRffNvy_vnGptREd6JYZGB6Yd98KbtVct_VcW0A,837
|
|
36
|
+
sqlspec-0.4.0.dist-info/METADATA,sha256=OnTkY_OG3IeqaJQBkzHjsKuQwkfmhQO4WnhXKbEoDLc,3222
|
|
37
|
+
sqlspec-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
38
|
+
sqlspec-0.4.0.dist-info/licenses/NOTICE,sha256=Lyir8ozXWov7CyYS4huVaOCNrtgL17P-bNV-5daLntQ,1634
|
|
39
|
+
sqlspec-0.4.0.dist-info/RECORD,,
|
sqlspec/utils/__init__.py
DELETED
|
File without changes
|
sqlspec/utils/dataclass.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import Field, fields
|
|
4
|
-
from typing import TYPE_CHECKING, ClassVar, Protocol, runtime_checkable
|
|
5
|
-
|
|
6
|
-
from typing_extensions import TypeGuard
|
|
7
|
-
|
|
8
|
-
from sqlspec.utils.empty import Empty
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from collections.abc import Iterable
|
|
12
|
-
from collections.abc import Set as AbstractSet
|
|
13
|
-
from typing import Any
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
__all__ = (
|
|
17
|
-
"extract_dataclass_fields",
|
|
18
|
-
"extract_dataclass_items",
|
|
19
|
-
"is_dataclass_instance",
|
|
20
|
-
"simple_asdict",
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@runtime_checkable
|
|
25
|
-
class DataclassProtocol(Protocol):
|
|
26
|
-
"""Protocol for instance checking dataclasses"""
|
|
27
|
-
|
|
28
|
-
__dataclass_fields__: ClassVar[dict[str, Any]]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def is_dataclass_instance(obj: Any) -> TypeGuard[DataclassProtocol]:
|
|
32
|
-
"""Check if an object is a dataclass instance.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
obj: An object to check.
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
True if the object is a dataclass instance.
|
|
39
|
-
"""
|
|
40
|
-
return hasattr(type(obj), "__dataclass_fields__")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def extract_dataclass_fields(
|
|
44
|
-
dt: DataclassProtocol,
|
|
45
|
-
exclude_none: bool = False,
|
|
46
|
-
exclude_empty: bool = False,
|
|
47
|
-
include: AbstractSet[str] | None = None,
|
|
48
|
-
exclude: AbstractSet[str] | None = None,
|
|
49
|
-
) -> tuple[Field[Any], ...]:
|
|
50
|
-
"""Extract dataclass fields.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
dt: A dataclass instance.
|
|
54
|
-
exclude_none: Whether to exclude None values.
|
|
55
|
-
exclude_empty: Whether to exclude Empty values.
|
|
56
|
-
include: An iterable of fields to include.
|
|
57
|
-
exclude: An iterable of fields to exclude.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
A tuple of dataclass fields.
|
|
62
|
-
"""
|
|
63
|
-
include = include or set()
|
|
64
|
-
exclude = exclude or set()
|
|
65
|
-
|
|
66
|
-
if common := (include & exclude):
|
|
67
|
-
msg = f"Fields {common} are both included and excluded."
|
|
68
|
-
raise ValueError(msg)
|
|
69
|
-
|
|
70
|
-
dataclass_fields: Iterable[Field[Any]] = fields(dt)
|
|
71
|
-
if exclude_none:
|
|
72
|
-
dataclass_fields = (field for field in dataclass_fields if getattr(dt, field.name) is not None)
|
|
73
|
-
if exclude_empty:
|
|
74
|
-
dataclass_fields = (field for field in dataclass_fields if getattr(dt, field.name) is not Empty)
|
|
75
|
-
if include:
|
|
76
|
-
dataclass_fields = (field for field in dataclass_fields if field.name in include)
|
|
77
|
-
if exclude:
|
|
78
|
-
dataclass_fields = (field for field in dataclass_fields if field.name not in exclude)
|
|
79
|
-
|
|
80
|
-
return tuple(dataclass_fields)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def extract_dataclass_items(
|
|
84
|
-
dt: DataclassProtocol,
|
|
85
|
-
exclude_none: bool = False,
|
|
86
|
-
exclude_empty: bool = False,
|
|
87
|
-
include: AbstractSet[str] | None = None,
|
|
88
|
-
exclude: AbstractSet[str] | None = None,
|
|
89
|
-
) -> tuple[tuple[str, Any], ...]:
|
|
90
|
-
"""Extract dataclass name, value pairs.
|
|
91
|
-
|
|
92
|
-
Unlike the 'asdict' method exports by the stdlib, this function does not pickle values.
|
|
93
|
-
|
|
94
|
-
Args:
|
|
95
|
-
dt: A dataclass instance.
|
|
96
|
-
exclude_none: Whether to exclude None values.
|
|
97
|
-
exclude_empty: Whether to exclude Empty values.
|
|
98
|
-
include: An iterable of fields to include.
|
|
99
|
-
exclude: An iterable of fields to exclude.
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
A tuple of key/value pairs.
|
|
103
|
-
"""
|
|
104
|
-
dataclass_fields = extract_dataclass_fields(dt, exclude_none, exclude_empty, include, exclude)
|
|
105
|
-
return tuple((field.name, getattr(dt, field.name)) for field in dataclass_fields)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def simple_asdict(
|
|
109
|
-
obj: DataclassProtocol,
|
|
110
|
-
exclude_none: bool = False,
|
|
111
|
-
exclude_empty: bool = False,
|
|
112
|
-
convert_nested: bool = True,
|
|
113
|
-
exclude: set[str] | None = None,
|
|
114
|
-
) -> dict[str, Any]:
|
|
115
|
-
"""Convert a dataclass to a dictionary.
|
|
116
|
-
|
|
117
|
-
This method has important differences to the standard library version:
|
|
118
|
-
- it does not deepcopy values
|
|
119
|
-
- it does not recurse into collections
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
obj: A dataclass instance.
|
|
123
|
-
exclude_none: Whether to exclude None values.
|
|
124
|
-
exclude_empty: Whether to exclude Empty values.
|
|
125
|
-
convert_nested: Whether to recursively convert nested dataclasses.
|
|
126
|
-
exclude: An iterable of fields to exclude.
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
A dictionary of key/value pairs.
|
|
130
|
-
"""
|
|
131
|
-
ret = {}
|
|
132
|
-
for field in extract_dataclass_fields(obj, exclude_none, exclude_empty, exclude=exclude):
|
|
133
|
-
value = getattr(obj, field.name)
|
|
134
|
-
if is_dataclass_instance(value) and convert_nested:
|
|
135
|
-
ret[field.name] = simple_asdict(value, exclude_none, exclude_empty)
|
|
136
|
-
else:
|
|
137
|
-
ret[field.name] = getattr(obj, field.name)
|
|
138
|
-
return ret
|
sqlspec/utils/empty.py
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from typing import Final, Literal, Union
|
|
5
|
-
|
|
6
|
-
from sqlspec.typing import UnsetType
|
|
7
|
-
|
|
8
|
-
__all__ = ("Empty", "EmptyType")
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class _EmptyEnum(Enum):
|
|
12
|
-
"""A sentinel enum used as placeholder."""
|
|
13
|
-
|
|
14
|
-
EMPTY = 0
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
EmptyType = Union[Literal[_EmptyEnum.EMPTY], UnsetType]
|
|
18
|
-
Empty: Final = _EmptyEnum.EMPTY
|
sqlspec-0.3.0.dist-info/RECORD
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
sqlspec/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
2
|
-
sqlspec/__metadata__.py,sha256=Vw99abV_UQNVH2jB0IBa9-8emyZQcXm1J9eMtLxFX2Y,496
|
|
3
|
-
sqlspec/_serialization.py,sha256=OL4x0Rz5UjF7RgAKqhYChi5qSS_ImtaVrIlA4DhIKUE,824
|
|
4
|
-
sqlspec/_typing.py,sha256=8tk4KqqO8OLDe-PIow2oaAnzgHOiFb74Mx7yuoAZUgI,2814
|
|
5
|
-
sqlspec/config.py,sha256=BOX_V_q2MOP33tK0ISpYaiQJt3zrvK4D_JIBD9FOixY,272
|
|
6
|
-
sqlspec/exceptions.py,sha256=fhCOILBj0J7HJP67BNSC0d9YUbW8QpZPXM55xJJzE8A,3039
|
|
7
|
-
sqlspec/filters.py,sha256=UtDJVpABSxHLkadiswMcneBsvawmWsz3Bq7wLxLJn74,3454
|
|
8
|
-
sqlspec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
sqlspec/typing.py,sha256=9gXd4qAxZxejXo1NpGDKflsmWu5kQomB3fps3l4EbZs,7289
|
|
10
|
-
sqlspec/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
sqlspec/adapters/adbc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
sqlspec/adapters/adbc/config.py,sha256=lCOoewYM0u9GHHGkux0FbSRAri4oIi91_Zrs_x3FkdQ,1745
|
|
13
|
-
sqlspec/adapters/aiosqlite/__init__.py,sha256=PLqWg24l3TooJvqA0Xf1WErrxtqwo8DEoL_Zp2iSCzs,68
|
|
14
|
-
sqlspec/adapters/aiosqlite/config.py,sha256=SM1nkRnrIzeUUMtL1cmMEh75epkCKtII1D-Wjgnc7ZY,4308
|
|
15
|
-
sqlspec/adapters/asyncmy/__init__.py,sha256=o0R_Azae3FHiSZ1TQ5ZjyCneDOuvnEeMjmSkhuiKoWo,103
|
|
16
|
-
sqlspec/adapters/asyncmy/config.py,sha256=0_0tdx7JvTIR1DZuaHeLg8IaK5Rv3li6dVKVOf8k_AI,5956
|
|
17
|
-
sqlspec/adapters/asyncpg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
sqlspec/adapters/asyncpg/config.py,sha256=Q1TgWtI2lrn0SDtP8KTVfJ2f1I6XhOUPSujKngWIf5A,6306
|
|
19
|
-
sqlspec/adapters/duckdb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
sqlspec/adapters/duckdb/config.py,sha256=2LfJnZYMcbXAR9BQ66Y9rhh7R_u6PI2GHApGo1Oy04k,3461
|
|
21
|
-
sqlspec/adapters/oracledb/__init__.py,sha256=fFQ2xOxFcgpr-ug4AVv430irnJgBRUINvt4sL3qzyBw,275
|
|
22
|
-
sqlspec/adapters/oracledb/config/__init__.py,sha256=XoHgInT4IbXjDg5ax3ncuUoVvnYB5qQjI-Ib7gwSycU,338
|
|
23
|
-
sqlspec/adapters/oracledb/config/_asyncio.py,sha256=qB-1jPwjNdHYrDYjjQqR-q1KqMKFXESk-T9aZtdrwDI,3280
|
|
24
|
-
sqlspec/adapters/oracledb/config/_common.py,sha256=VwMbZAX-jJ2kyAbtgRUOWDvO12nXsb68wO-3d3L9Wz4,6183
|
|
25
|
-
sqlspec/adapters/oracledb/config/_sync.py,sha256=m_OkErwBKvQrFU6Q9PuduSJu_vHbmZl1gWYbpw6b22I,3268
|
|
26
|
-
sqlspec/adapters/psycopg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
-
sqlspec/adapters/psycopg/config/__init__.py,sha256=pXI9Pa2VYESTchPgM3tt5kFF8tsmgq-ksZRGR6pgiUQ,280
|
|
28
|
-
sqlspec/adapters/psycopg/config/_async.py,sha256=mdqFanrnodTq8u3THim3KkiNmEoIQpsKmBpJSaRiGTc,3004
|
|
29
|
-
sqlspec/adapters/psycopg/config/_common.py,sha256=F4rwAlAcVM2HgDVnD87Pda0DjVV-oqzDUd0TofqnUL0,2766
|
|
30
|
-
sqlspec/adapters/psycopg/config/_sync.py,sha256=YILMCmOpGX4E62hepuM0WgT7aPQpwbZpUwjAonmjUYc,2860
|
|
31
|
-
sqlspec/adapters/sqlite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
-
sqlspec/adapters/sqlite/config.py,sha256=0c_eN01mEcM-OIiaDgs-4fM20z03QivANGxAuxJOisA,4117
|
|
33
|
-
sqlspec/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
sqlspec/extensions/litestar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
-
sqlspec/extensions/litestar/plugin.py,sha256=oiBFfRffNvy_vnGptREd6JYZGB6Yd98KbtVct_VcW0A,837
|
|
36
|
-
sqlspec/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
sqlspec/utils/dataclass.py,sha256=g88yP0Pi3EiF8N-Npuo-D6QpPExCLuUvJCO0U_3lHkQ,4301
|
|
38
|
-
sqlspec/utils/empty.py,sha256=u5KC3HUF7MolHYs9kSt7Uq-jTIl0x9gJqf-jX0y5_BY,349
|
|
39
|
-
sqlspec-0.3.0.dist-info/METADATA,sha256=DCx_OsG0w07rjQx01g2av9VxMgQZsmDWcZl0nsqJU8A,3222
|
|
40
|
-
sqlspec-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
41
|
-
sqlspec-0.3.0.dist-info/licenses/NOTICE,sha256=Lyir8ozXWov7CyYS4huVaOCNrtgL17P-bNV-5daLntQ,1634
|
|
42
|
-
sqlspec-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|