sqlspec 0.4.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/__metadata__.py +1 -1
- sqlspec/_serialization.py +1 -1
- sqlspec/_typing.py +31 -1
- sqlspec/adapters/adbc/config.py +7 -7
- sqlspec/adapters/aiosqlite/config.py +11 -18
- sqlspec/adapters/asyncmy/config.py +9 -4
- sqlspec/adapters/asyncpg/config.py +10 -10
- sqlspec/adapters/duckdb/__init__.py +3 -0
- sqlspec/adapters/duckdb/config.py +15 -14
- sqlspec/adapters/oracledb/__init__.py +1 -1
- sqlspec/adapters/oracledb/config/_asyncio.py +17 -7
- sqlspec/adapters/oracledb/config/_common.py +7 -25
- sqlspec/adapters/oracledb/config/_sync.py +17 -7
- sqlspec/adapters/psycopg/config/__init__.py +2 -2
- sqlspec/adapters/psycopg/config/_async.py +13 -8
- sqlspec/adapters/psycopg/config/_common.py +3 -18
- sqlspec/adapters/psycopg/config/_sync.py +12 -8
- sqlspec/adapters/sqlite/config.py +3 -3
- sqlspec/base.py +221 -0
- sqlspec/extensions/litestar/config.py +0 -0
- sqlspec/extensions/litestar/plugin.py +18 -10
- sqlspec/filters.py +2 -1
- sqlspec/typing.py +119 -31
- sqlspec/utils/__init__.py +0 -0
- sqlspec/utils/deprecation.py +111 -0
- sqlspec/utils/fixtures.py +66 -0
- sqlspec/utils/module_loader.py +94 -0
- sqlspec/utils/text.py +46 -0
- sqlspec-0.6.0.dist-info/METADATA +128 -0
- sqlspec-0.6.0.dist-info/RECORD +46 -0
- sqlspec-0.6.0.dist-info/licenses/LICENSE +21 -0
- sqlspec/config.py +0 -16
- sqlspec-0.4.0.dist-info/METADATA +0 -84
- sqlspec-0.4.0.dist-info/RECORD +0 -39
- {sqlspec-0.4.0.dist-info → sqlspec-0.6.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.4.0.dist-info → sqlspec-0.6.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -7,10 +7,8 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
from psycopg import Connection
|
|
8
8
|
from psycopg_pool import ConnectionPool
|
|
9
9
|
|
|
10
|
-
from sqlspec.adapters.psycopg.config._common import
|
|
11
|
-
|
|
12
|
-
PsycoPgGenericPoolConfig,
|
|
13
|
-
)
|
|
10
|
+
from sqlspec.adapters.psycopg.config._common import PsycoPgGenericPoolConfig
|
|
11
|
+
from sqlspec.base import SyncDatabaseConfig
|
|
14
12
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
15
13
|
from sqlspec.typing import dataclass_to_dict
|
|
16
14
|
|
|
@@ -26,13 +24,19 @@ __all__ = (
|
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
@dataclass
|
|
29
|
-
class PsycoPgSyncPoolConfig(PsycoPgGenericPoolConfig[
|
|
27
|
+
class PsycoPgSyncPoolConfig(PsycoPgGenericPoolConfig[Connection, ConnectionPool]):
|
|
30
28
|
"""Sync Psycopg Pool Config"""
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
@dataclass
|
|
34
|
-
class PsycoPgSyncDatabaseConfig(
|
|
35
|
-
"""Sync Psycopg database Configuration.
|
|
32
|
+
class PsycoPgSyncDatabaseConfig(SyncDatabaseConfig[Connection, ConnectionPool]):
|
|
33
|
+
"""Sync Psycopg database Configuration.
|
|
34
|
+
This class provides the base configuration for Psycopg database connections, extending
|
|
35
|
+
the generic database configuration with Psycopg-specific settings.([1](https://www.psycopg.org/psycopg3/docs/api/connections.html))
|
|
36
|
+
|
|
37
|
+
The configuration supports all standard Psycopg connection parameters and can be used
|
|
38
|
+
with both synchronous and asynchronous connections.([2](https://www.psycopg.org/psycopg3/docs/api/connections.html))
|
|
39
|
+
"""
|
|
36
40
|
|
|
37
41
|
pool_config: PsycoPgSyncPoolConfig | None = None
|
|
38
42
|
"""Psycopg Pool configuration"""
|
|
@@ -58,7 +62,7 @@ class PsycoPgSyncDatabaseConfig(PsycoPgGenericDatabaseConfig[ConnectionPool, Con
|
|
|
58
62
|
|
|
59
63
|
pool_config = self.pool_config_dict
|
|
60
64
|
self.pool_instance = ConnectionPool(**pool_config)
|
|
61
|
-
if self.pool_instance is None:
|
|
65
|
+
if self.pool_instance is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
62
66
|
msg = "Could not configure the 'pool_instance'. Please check your configuration." # type: ignore[unreachable]
|
|
63
67
|
raise ImproperConfigurationError(msg)
|
|
64
68
|
return self.pool_instance
|
|
@@ -4,7 +4,7 @@ from contextlib import contextmanager
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Literal
|
|
6
6
|
|
|
7
|
-
from sqlspec.
|
|
7
|
+
from sqlspec.base import NoPoolSyncConfig
|
|
8
8
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
9
9
|
from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
|
|
10
10
|
|
|
@@ -16,7 +16,7 @@ __all__ = ("SqliteConfig",)
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@dataclass
|
|
19
|
-
class SqliteConfig(
|
|
19
|
+
class SqliteConfig(NoPoolSyncConfig["Connection"]):
|
|
20
20
|
"""Configuration for SQLite database connections.
|
|
21
21
|
|
|
22
22
|
This class provides configuration options for SQLite database connections, wrapping all parameters
|
|
@@ -25,7 +25,7 @@ class SqliteConfig(GenericDatabaseConfig):
|
|
|
25
25
|
For details see: https://docs.python.org/3/library/sqlite3.html#sqlite3.connect
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
database: str
|
|
28
|
+
database: str = ":memory:"
|
|
29
29
|
"""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."""
|
|
30
30
|
|
|
31
31
|
timeout: float | EmptyType = Empty
|
sqlspec/base.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import AsyncGenerator, Awaitable, Generator
|
|
3
|
+
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Annotated, Any, ClassVar, Generic, TypeVar, Union, cast, overload
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
"AsyncDatabaseConfig",
|
|
9
|
+
"DatabaseConfigProtocol",
|
|
10
|
+
"GenericPoolConfig",
|
|
11
|
+
"NoPoolAsyncConfig",
|
|
12
|
+
"NoPoolSyncConfig",
|
|
13
|
+
"SyncDatabaseConfig",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
ConnectionT = TypeVar("ConnectionT")
|
|
17
|
+
PoolT = TypeVar("PoolT")
|
|
18
|
+
AsyncConfigT = TypeVar("AsyncConfigT", bound="Union[AsyncDatabaseConfig[Any, Any], NoPoolAsyncConfig[Any]]")
|
|
19
|
+
SyncConfigT = TypeVar("SyncConfigT", bound="Union[SyncDatabaseConfig[Any, Any], NoPoolSyncConfig[Any]]")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class DatabaseConfigProtocol(Generic[ConnectionT, PoolT], ABC):
|
|
24
|
+
"""Protocol defining the interface for database configurations."""
|
|
25
|
+
|
|
26
|
+
__is_async__: ClassVar[bool] = False
|
|
27
|
+
__supports_connection_pooling__: ClassVar[bool] = False
|
|
28
|
+
|
|
29
|
+
def __hash__(self) -> int:
|
|
30
|
+
return id(self)
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def create_connection(self) -> Union[ConnectionT, Awaitable[ConnectionT]]:
|
|
34
|
+
"""Create and return a new database connection."""
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def provide_connection(
|
|
39
|
+
self, *args: Any, **kwargs: Any
|
|
40
|
+
) -> Union[
|
|
41
|
+
Generator[ConnectionT, None, None],
|
|
42
|
+
AsyncGenerator[ConnectionT, None],
|
|
43
|
+
AbstractContextManager[ConnectionT],
|
|
44
|
+
AbstractAsyncContextManager[ConnectionT],
|
|
45
|
+
]:
|
|
46
|
+
"""Provide a database connection context manager."""
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def connection_config_dict(self) -> dict[str, Any]:
|
|
52
|
+
"""Return the connection configuration as a dict."""
|
|
53
|
+
raise NotImplementedError
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def create_pool(self) -> Union[PoolT, Awaitable[PoolT]]:
|
|
57
|
+
"""Create and return connection pool."""
|
|
58
|
+
raise NotImplementedError
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def provide_pool(
|
|
62
|
+
self, *args: Any, **kwargs: Any
|
|
63
|
+
) -> Union[PoolT, Awaitable[PoolT], AbstractContextManager[PoolT], AbstractAsyncContextManager[PoolT]]:
|
|
64
|
+
"""Provide pool instance."""
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def is_async(self) -> bool:
|
|
69
|
+
"""Return whether the configuration is for an async database."""
|
|
70
|
+
return self.__is_async__
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def support_connection_pooling(self) -> bool:
|
|
74
|
+
"""Return whether the configuration supports connection pooling."""
|
|
75
|
+
return self.__supports_connection_pooling__
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class NoPoolSyncConfig(DatabaseConfigProtocol[ConnectionT, None]):
|
|
79
|
+
"""Base class for a sync database configurations that do not implement a pool."""
|
|
80
|
+
|
|
81
|
+
__is_async__ = False
|
|
82
|
+
__supports_connection_pooling__ = False
|
|
83
|
+
|
|
84
|
+
def create_pool(self) -> None:
|
|
85
|
+
"""This database backend has not implemented the pooling configurations."""
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
def provide_pool(self, *args: Any, **kwargs: Any) -> None:
|
|
89
|
+
"""This database backend has not implemented the pooling configurations."""
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class NoPoolAsyncConfig(DatabaseConfigProtocol[ConnectionT, None]):
|
|
94
|
+
"""Base class for an async database configurations that do not implement a pool."""
|
|
95
|
+
|
|
96
|
+
__is_async__ = True
|
|
97
|
+
__supports_connection_pooling__ = False
|
|
98
|
+
|
|
99
|
+
async def create_pool(self) -> None:
|
|
100
|
+
"""This database backend has not implemented the pooling configurations."""
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
def provide_pool(self, *args: Any, **kwargs: Any) -> None:
|
|
104
|
+
"""This database backend has not implemented the pooling configurations."""
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class GenericPoolConfig:
|
|
110
|
+
"""Generic Database Pool Configuration."""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class SyncDatabaseConfig(DatabaseConfigProtocol[ConnectionT, PoolT]):
|
|
115
|
+
"""Generic Sync Database Configuration."""
|
|
116
|
+
|
|
117
|
+
__is_async__ = False
|
|
118
|
+
__supports_connection_pooling__ = True
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class AsyncDatabaseConfig(DatabaseConfigProtocol[ConnectionT, PoolT]):
|
|
123
|
+
"""Generic Async Database Configuration."""
|
|
124
|
+
|
|
125
|
+
__is_async__ = True
|
|
126
|
+
__supports_connection_pooling__ = True
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ConfigManager:
|
|
130
|
+
"""Type-safe configuration manager with literal inference."""
|
|
131
|
+
|
|
132
|
+
def __init__(self) -> None:
|
|
133
|
+
self._configs: dict[Any, DatabaseConfigProtocol[Any, Any]] = {}
|
|
134
|
+
|
|
135
|
+
@overload
|
|
136
|
+
def add_config(self, config: SyncConfigT) -> type[SyncConfigT]: ...
|
|
137
|
+
|
|
138
|
+
@overload
|
|
139
|
+
def add_config(self, config: AsyncConfigT) -> type[AsyncConfigT]: ...
|
|
140
|
+
|
|
141
|
+
def add_config(
|
|
142
|
+
self,
|
|
143
|
+
config: Union[
|
|
144
|
+
SyncConfigT,
|
|
145
|
+
AsyncConfigT,
|
|
146
|
+
],
|
|
147
|
+
) -> Union[Annotated[type[SyncConfigT], int], Annotated[type[AsyncConfigT], int]]: # pyright: ignore[reportInvalidTypeVarUse]
|
|
148
|
+
"""Add a new configuration to the manager."""
|
|
149
|
+
key = Annotated[type(config), id(config)] # type: ignore[valid-type]
|
|
150
|
+
self._configs[key] = config
|
|
151
|
+
return key # type: ignore[return-value] # pyright: ignore[reportReturnType]
|
|
152
|
+
|
|
153
|
+
@overload
|
|
154
|
+
def get_config(self, name: type[SyncConfigT]) -> SyncConfigT: ...
|
|
155
|
+
|
|
156
|
+
@overload
|
|
157
|
+
def get_config(self, name: type[AsyncConfigT]) -> AsyncConfigT: ...
|
|
158
|
+
|
|
159
|
+
def get_config(
|
|
160
|
+
self, name: Union[type[DatabaseConfigProtocol[ConnectionT, PoolT]], Any]
|
|
161
|
+
) -> DatabaseConfigProtocol[ConnectionT, PoolT]:
|
|
162
|
+
"""Retrieve a configuration by its type."""
|
|
163
|
+
config = self._configs.get(name)
|
|
164
|
+
if not config:
|
|
165
|
+
raise KeyError(f"No configuration found for {name}")
|
|
166
|
+
return config
|
|
167
|
+
|
|
168
|
+
@overload
|
|
169
|
+
def get_connection(
|
|
170
|
+
self,
|
|
171
|
+
name: Union[
|
|
172
|
+
type[NoPoolSyncConfig[ConnectionT]],
|
|
173
|
+
type[SyncDatabaseConfig[ConnectionT, PoolT]],
|
|
174
|
+
],
|
|
175
|
+
) -> ConnectionT: ... # pyright: ignore[reportInvalidTypeVarUse]
|
|
176
|
+
|
|
177
|
+
@overload
|
|
178
|
+
def get_connection(
|
|
179
|
+
self,
|
|
180
|
+
name: Union[
|
|
181
|
+
type[NoPoolAsyncConfig[ConnectionT]],
|
|
182
|
+
type[AsyncDatabaseConfig[ConnectionT, PoolT]],
|
|
183
|
+
],
|
|
184
|
+
) -> Awaitable[ConnectionT]: ... # pyright: ignore[reportInvalidTypeVarUse]
|
|
185
|
+
|
|
186
|
+
def get_connection(
|
|
187
|
+
self,
|
|
188
|
+
name: Union[
|
|
189
|
+
type[NoPoolSyncConfig[ConnectionT]],
|
|
190
|
+
type[NoPoolAsyncConfig[ConnectionT]],
|
|
191
|
+
type[SyncDatabaseConfig[ConnectionT, PoolT]],
|
|
192
|
+
type[AsyncDatabaseConfig[ConnectionT, PoolT]],
|
|
193
|
+
],
|
|
194
|
+
) -> Union[ConnectionT, Awaitable[ConnectionT]]:
|
|
195
|
+
"""Create and return a connection from the specified configuration."""
|
|
196
|
+
config = self.get_config(name)
|
|
197
|
+
return config.create_connection()
|
|
198
|
+
|
|
199
|
+
@overload
|
|
200
|
+
def get_pool(self, name: type[Union[NoPoolSyncConfig[ConnectionT], NoPoolAsyncConfig[ConnectionT]]]) -> None: ... # pyright: ignore[reportInvalidTypeVarUse]
|
|
201
|
+
|
|
202
|
+
@overload
|
|
203
|
+
def get_pool(self, name: type[SyncDatabaseConfig[ConnectionT, PoolT]]) -> type[PoolT]: ... # pyright: ignore[reportInvalidTypeVarUse]
|
|
204
|
+
|
|
205
|
+
@overload
|
|
206
|
+
def get_pool(self, name: type[AsyncDatabaseConfig[ConnectionT, PoolT]]) -> Awaitable[type[PoolT]]: ... # pyright: ignore[reportInvalidTypeVarUse]
|
|
207
|
+
|
|
208
|
+
def get_pool(
|
|
209
|
+
self,
|
|
210
|
+
name: Union[
|
|
211
|
+
type[NoPoolSyncConfig[ConnectionT]],
|
|
212
|
+
type[NoPoolAsyncConfig[ConnectionT]],
|
|
213
|
+
type[SyncDatabaseConfig[ConnectionT, PoolT]],
|
|
214
|
+
type[AsyncDatabaseConfig[ConnectionT, PoolT]],
|
|
215
|
+
],
|
|
216
|
+
) -> Union[type[PoolT], Awaitable[type[PoolT]], None]:
|
|
217
|
+
"""Create and return a connection pool from the specified configuration."""
|
|
218
|
+
config = self.get_config(name)
|
|
219
|
+
if isinstance(config, (NoPoolSyncConfig, NoPoolAsyncConfig)):
|
|
220
|
+
return None
|
|
221
|
+
return cast("Union[type[PoolT], Awaitable[type[PoolT]]]", config.create_pool())
|
|
File without changes
|
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from litestar.plugins import InitPluginProtocol
|
|
6
6
|
|
|
7
|
+
from sqlspec.base import ConfigManager
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from litestar.config.app import AppConfig
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
T = TypeVar("T")
|
|
12
|
-
|
|
13
|
-
|
|
14
13
|
class SQLSpecPlugin(InitPluginProtocol):
|
|
15
|
-
"""
|
|
14
|
+
"""SQLSpec plugin."""
|
|
16
15
|
|
|
17
16
|
__slots__ = ("_config",)
|
|
18
17
|
|
|
19
|
-
def __init__(self, config:
|
|
20
|
-
"""Initialize ``
|
|
18
|
+
def __init__(self, config: ConfigManager) -> None:
|
|
19
|
+
"""Initialize ``SQLSpecPlugin``.
|
|
21
20
|
|
|
22
21
|
Args:
|
|
23
|
-
config: configure
|
|
22
|
+
config: configure SQLSpec plugin for use with Litestar.
|
|
24
23
|
"""
|
|
25
24
|
self._config = config
|
|
26
25
|
|
|
26
|
+
@property
|
|
27
|
+
def config(self) -> ConfigManager:
|
|
28
|
+
"""Return the plugin config.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
ConfigManager.
|
|
32
|
+
"""
|
|
33
|
+
return self._config
|
|
34
|
+
|
|
27
35
|
def on_app_init(self, app_config: AppConfig) -> AppConfig:
|
|
28
|
-
"""Configure application for use with
|
|
36
|
+
"""Configure application for use with SQLSpec.
|
|
29
37
|
|
|
30
38
|
Args:
|
|
31
39
|
app_config: The :class:`AppConfig <.config.app.AppConfig>` instance.
|
|
32
40
|
"""
|
|
33
|
-
app_config.
|
|
41
|
+
app_config.signature_types.append(ConfigManager)
|
|
34
42
|
return app_config
|
sqlspec/filters.py
CHANGED
|
@@ -25,12 +25,13 @@ __all__ = (
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
T = TypeVar("T")
|
|
28
|
+
StatementT = TypeVar("StatementT", bound="str")
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class StatementFilter(Protocol):
|
|
31
32
|
"""Protocol for filters that can be appended to a statement."""
|
|
32
33
|
|
|
33
|
-
def append_to_statement(self, statement:
|
|
34
|
+
def append_to_statement(self, statement: StatementT) -> StatementT:
|
|
34
35
|
"""Append the filter to the statement."""
|
|
35
36
|
return statement
|
|
36
37
|
|
sqlspec/typing.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
1
|
+
from __future__ import annotations # noqa: A005
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
from dataclasses import Field, fields
|
|
@@ -48,11 +48,21 @@ FilterTypeT = TypeVar("FilterTypeT", bound="StatementFilter")
|
|
|
48
48
|
|
|
49
49
|
:class:`~advanced_alchemy.filters.StatementFilter`
|
|
50
50
|
"""
|
|
51
|
+
ModelDTOT = TypeVar("ModelDTOT", bound="Struct | BaseModel")
|
|
52
|
+
"""Type variable for model DTOs.
|
|
53
|
+
|
|
54
|
+
:class:`msgspec.Struct`|:class:`pydantic.BaseModel`
|
|
55
|
+
"""
|
|
56
|
+
PydanticOrMsgspecT: TypeAlias = Union[Struct, BaseModel]
|
|
57
|
+
"""Type alias for pydantic or msgspec models.
|
|
58
|
+
|
|
59
|
+
:class:`msgspec.Struct` or :class:`pydantic.BaseModel`
|
|
60
|
+
"""
|
|
51
61
|
ModelDictT: TypeAlias = Union[dict[str, Any], ModelT, DataclassProtocol, Struct, BaseModel]
|
|
52
62
|
"""Type alias for model dictionaries.
|
|
53
63
|
|
|
54
64
|
Represents:
|
|
55
|
-
- :type:`dict[str, Any]` |
|
|
65
|
+
- :type:`dict[str, Any]` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel`
|
|
56
66
|
"""
|
|
57
67
|
ModelDictListT: TypeAlias = Sequence[Union[dict[str, Any], ModelT, DataclassProtocol, Struct, BaseModel]]
|
|
58
68
|
"""Type alias for model dictionary lists.
|
|
@@ -72,7 +82,7 @@ def is_dataclass_instance(obj: Any) -> TypeGuard[DataclassProtocol]:
|
|
|
72
82
|
Returns:
|
|
73
83
|
True if the object is a dataclass instance.
|
|
74
84
|
"""
|
|
75
|
-
return hasattr(type(obj), "__dataclass_fields__")
|
|
85
|
+
return hasattr(type(obj), "__dataclass_fields__") # pyright: ignore[reportUnknownArgumentType]
|
|
76
86
|
|
|
77
87
|
|
|
78
88
|
@lru_cache(typed=True)
|
|
@@ -104,7 +114,33 @@ def is_pydantic_model(v: Any) -> TypeGuard[BaseModel]:
|
|
|
104
114
|
return PYDANTIC_INSTALLED and isinstance(v, BaseModel)
|
|
105
115
|
|
|
106
116
|
|
|
107
|
-
def
|
|
117
|
+
def is_pydantic_model_with_field(v: Any, field_name: str) -> TypeGuard[BaseModel]:
|
|
118
|
+
"""Check if a pydantic model has a specific field.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
v: Value to check.
|
|
122
|
+
field_name: Field name to check for.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
bool
|
|
126
|
+
"""
|
|
127
|
+
return is_pydantic_model(v) and field_name in v.model_fields
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def is_pydantic_model_without_field(v: Any, field_name: str) -> TypeGuard[BaseModel]:
|
|
131
|
+
"""Check if a pydantic model does not have a specific field.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
v: Value to check.
|
|
135
|
+
field_name: Field name to check for.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
bool
|
|
139
|
+
"""
|
|
140
|
+
return not is_pydantic_model_with_field(v, field_name)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def is_msgspec_struct(v: Any) -> TypeGuard[Struct]:
|
|
108
144
|
"""Check if a value is a msgspec model.
|
|
109
145
|
|
|
110
146
|
Args:
|
|
@@ -116,6 +152,32 @@ def is_msgspec_model(v: Any) -> TypeGuard[Struct]:
|
|
|
116
152
|
return MSGSPEC_INSTALLED and isinstance(v, Struct)
|
|
117
153
|
|
|
118
154
|
|
|
155
|
+
def is_msgspec_struct_with_field(v: Any, field_name: str) -> TypeGuard[Struct]:
|
|
156
|
+
"""Check if a msgspec model has a specific field.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
v: Value to check.
|
|
160
|
+
field_name: Field name to check for.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
bool
|
|
164
|
+
"""
|
|
165
|
+
return is_msgspec_struct(v) and field_name in v.__struct_fields__
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def is_msgspec_struct_without_field(v: Any, field_name: str) -> TypeGuard[Struct]:
|
|
169
|
+
"""Check if a msgspec model does not have a specific field.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
v: Value to check.
|
|
173
|
+
field_name: Field name to check for.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
bool
|
|
177
|
+
"""
|
|
178
|
+
return not is_msgspec_struct_with_field(v, field_name)
|
|
179
|
+
|
|
180
|
+
|
|
119
181
|
def is_dict(v: Any) -> TypeGuard[dict[str, Any]]:
|
|
120
182
|
"""Check if a value is a dictionary.
|
|
121
183
|
|
|
@@ -154,8 +216,8 @@ def is_dict_without_field(v: Any, field_name: str) -> TypeGuard[dict[str, Any]]:
|
|
|
154
216
|
return is_dict(v) and field_name not in v
|
|
155
217
|
|
|
156
218
|
|
|
157
|
-
def
|
|
158
|
-
"""Check if a value is a
|
|
219
|
+
def is_schema(v: Any) -> TypeGuard[Struct | BaseModel]:
|
|
220
|
+
"""Check if a value is a msgspec Struct or Pydantic model.
|
|
159
221
|
|
|
160
222
|
Args:
|
|
161
223
|
v: Value to check.
|
|
@@ -163,11 +225,23 @@ def is_dataclass(v: Any) -> TypeGuard[DataclassProtocol]:
|
|
|
163
225
|
Returns:
|
|
164
226
|
bool
|
|
165
227
|
"""
|
|
166
|
-
return
|
|
228
|
+
return is_msgspec_struct(v) or is_pydantic_model(v)
|
|
167
229
|
|
|
168
230
|
|
|
169
|
-
def
|
|
170
|
-
"""Check if a
|
|
231
|
+
def is_schema_or_dict(v: Any) -> TypeGuard[Struct | BaseModel | dict[str, Any]]:
|
|
232
|
+
"""Check if a value is a msgspec Struct, Pydantic model, or dict.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
v: Value to check.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
bool
|
|
239
|
+
"""
|
|
240
|
+
return is_schema(v) or is_dict(v)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def is_schema_with_field(v: Any, field_name: str) -> TypeGuard[Struct | BaseModel]:
|
|
244
|
+
"""Check if a value is a msgspec Struct or Pydantic model with a specific field.
|
|
171
245
|
|
|
172
246
|
Args:
|
|
173
247
|
v: Value to check.
|
|
@@ -176,11 +250,11 @@ def is_dataclass_with_field(v: Any, field_name: str) -> TypeGuard[DataclassProto
|
|
|
176
250
|
Returns:
|
|
177
251
|
bool
|
|
178
252
|
"""
|
|
179
|
-
return
|
|
253
|
+
return is_msgspec_struct_with_field(v, field_name) or is_pydantic_model_with_field(v, field_name)
|
|
180
254
|
|
|
181
255
|
|
|
182
|
-
def
|
|
183
|
-
"""Check if a
|
|
256
|
+
def is_schema_without_field(v: Any, field_name: str) -> TypeGuard[Struct | BaseModel]:
|
|
257
|
+
"""Check if a value is a msgspec Struct or Pydantic model without a specific field.
|
|
184
258
|
|
|
185
259
|
Args:
|
|
186
260
|
v: Value to check.
|
|
@@ -189,11 +263,11 @@ def is_dataclass_without_field(v: Any, field_name: str) -> TypeGuard[DataclassPr
|
|
|
189
263
|
Returns:
|
|
190
264
|
bool
|
|
191
265
|
"""
|
|
192
|
-
return
|
|
266
|
+
return not is_schema_with_field(v, field_name)
|
|
193
267
|
|
|
194
268
|
|
|
195
|
-
def
|
|
196
|
-
"""Check if a
|
|
269
|
+
def is_schema_or_dict_with_field(v: Any, field_name: str) -> TypeGuard[Struct | BaseModel | dict[str, Any]]:
|
|
270
|
+
"""Check if a value is a msgspec Struct, Pydantic model, or dict with a specific field.
|
|
197
271
|
|
|
198
272
|
Args:
|
|
199
273
|
v: Value to check.
|
|
@@ -202,11 +276,11 @@ def is_pydantic_model_with_field(v: Any, field_name: str) -> TypeGuard[BaseModel
|
|
|
202
276
|
Returns:
|
|
203
277
|
bool
|
|
204
278
|
"""
|
|
205
|
-
return
|
|
279
|
+
return is_schema_with_field(v, field_name) or is_dict_with_field(v, field_name)
|
|
206
280
|
|
|
207
281
|
|
|
208
|
-
def
|
|
209
|
-
"""Check if a
|
|
282
|
+
def is_schema_or_dict_without_field(v: Any, field_name: str) -> TypeGuard[Struct | BaseModel | dict[str, Any]]:
|
|
283
|
+
"""Check if a value is a msgspec Struct, Pydantic model, or dict without a specific field.
|
|
210
284
|
|
|
211
285
|
Args:
|
|
212
286
|
v: Value to check.
|
|
@@ -215,11 +289,23 @@ def is_pydantic_model_without_field(v: Any, field_name: str) -> TypeGuard[BaseMo
|
|
|
215
289
|
Returns:
|
|
216
290
|
bool
|
|
217
291
|
"""
|
|
218
|
-
return not
|
|
292
|
+
return not is_schema_or_dict_with_field(v, field_name)
|
|
219
293
|
|
|
220
294
|
|
|
221
|
-
def
|
|
222
|
-
"""Check if a
|
|
295
|
+
def is_dataclass(v: Any) -> TypeGuard[DataclassProtocol]:
|
|
296
|
+
"""Check if a value is a dataclass.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
v: Value to check.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
bool
|
|
303
|
+
"""
|
|
304
|
+
return is_dataclass_instance(v)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def is_dataclass_with_field(v: Any, field_name: str) -> TypeGuard[DataclassProtocol]:
|
|
308
|
+
"""Check if a dataclass has a specific field.
|
|
223
309
|
|
|
224
310
|
Args:
|
|
225
311
|
v: Value to check.
|
|
@@ -228,11 +314,11 @@ def is_msgspec_model_with_field(v: Any, field_name: str) -> TypeGuard[Struct]:
|
|
|
228
314
|
Returns:
|
|
229
315
|
bool
|
|
230
316
|
"""
|
|
231
|
-
return
|
|
317
|
+
return is_dataclass(v) and field_name in v.__dataclass_fields__
|
|
232
318
|
|
|
233
319
|
|
|
234
|
-
def
|
|
235
|
-
"""Check if a
|
|
320
|
+
def is_dataclass_without_field(v: Any, field_name: str) -> TypeGuard[DataclassProtocol]:
|
|
321
|
+
"""Check if a dataclass does not have a specific field.
|
|
236
322
|
|
|
237
323
|
Args:
|
|
238
324
|
v: Value to check.
|
|
@@ -241,7 +327,7 @@ def is_msgspec_model_without_field(v: Any, field_name: str) -> TypeGuard[Struct]
|
|
|
241
327
|
Returns:
|
|
242
328
|
bool
|
|
243
329
|
"""
|
|
244
|
-
return
|
|
330
|
+
return is_dataclass(v) and field_name not in v.__dataclass_fields__
|
|
245
331
|
|
|
246
332
|
|
|
247
333
|
def extract_dataclass_fields(
|
|
@@ -339,7 +425,7 @@ def dataclass_to_dict(
|
|
|
339
425
|
ret[field.name] = dataclass_to_dict(value, exclude_none, exclude_empty)
|
|
340
426
|
else:
|
|
341
427
|
ret[field.name] = getattr(obj, field.name)
|
|
342
|
-
return ret
|
|
428
|
+
return cast("dict[str, Any]", ret)
|
|
343
429
|
|
|
344
430
|
|
|
345
431
|
def schema_dump(
|
|
@@ -355,13 +441,15 @@ def schema_dump(
|
|
|
355
441
|
Returns:
|
|
356
442
|
:type: dict[str, Any]
|
|
357
443
|
"""
|
|
444
|
+
if is_dict(data):
|
|
445
|
+
return data
|
|
358
446
|
if is_dataclass(data):
|
|
359
447
|
return dataclass_to_dict(data, exclude_empty=exclude_unset)
|
|
360
448
|
if is_pydantic_model(data):
|
|
361
449
|
return data.model_dump(exclude_unset=exclude_unset)
|
|
362
|
-
if
|
|
450
|
+
if is_msgspec_struct(data) and exclude_unset:
|
|
363
451
|
return {f: val for f in data.__struct_fields__ if (val := getattr(data, f, None)) != UNSET}
|
|
364
|
-
if
|
|
452
|
+
if is_msgspec_struct(data) and not exclude_unset:
|
|
365
453
|
return {f: getattr(data, f, None) for f in data.__struct_fields__}
|
|
366
454
|
return cast("dict[str,Any]", data)
|
|
367
455
|
|
|
@@ -394,9 +482,9 @@ __all__ = (
|
|
|
394
482
|
"is_dict",
|
|
395
483
|
"is_dict_with_field",
|
|
396
484
|
"is_dict_without_field",
|
|
397
|
-
"
|
|
398
|
-
"
|
|
399
|
-
"
|
|
485
|
+
"is_msgspec_struct",
|
|
486
|
+
"is_msgspec_struct_with_field",
|
|
487
|
+
"is_msgspec_struct_without_field",
|
|
400
488
|
"is_pydantic_model",
|
|
401
489
|
"is_pydantic_model_with_field",
|
|
402
490
|
"is_pydantic_model_without_field",
|
|
File without changes
|