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.

Files changed (36) hide show
  1. sqlspec/__metadata__.py +1 -1
  2. sqlspec/_serialization.py +1 -1
  3. sqlspec/_typing.py +31 -1
  4. sqlspec/adapters/adbc/config.py +7 -7
  5. sqlspec/adapters/aiosqlite/config.py +11 -18
  6. sqlspec/adapters/asyncmy/config.py +9 -4
  7. sqlspec/adapters/asyncpg/config.py +10 -10
  8. sqlspec/adapters/duckdb/__init__.py +3 -0
  9. sqlspec/adapters/duckdb/config.py +15 -14
  10. sqlspec/adapters/oracledb/__init__.py +1 -1
  11. sqlspec/adapters/oracledb/config/_asyncio.py +17 -7
  12. sqlspec/adapters/oracledb/config/_common.py +7 -25
  13. sqlspec/adapters/oracledb/config/_sync.py +17 -7
  14. sqlspec/adapters/psycopg/config/__init__.py +2 -2
  15. sqlspec/adapters/psycopg/config/_async.py +13 -8
  16. sqlspec/adapters/psycopg/config/_common.py +3 -18
  17. sqlspec/adapters/psycopg/config/_sync.py +12 -8
  18. sqlspec/adapters/sqlite/config.py +3 -3
  19. sqlspec/base.py +221 -0
  20. sqlspec/extensions/litestar/config.py +0 -0
  21. sqlspec/extensions/litestar/plugin.py +18 -10
  22. sqlspec/filters.py +2 -1
  23. sqlspec/typing.py +119 -31
  24. sqlspec/utils/__init__.py +0 -0
  25. sqlspec/utils/deprecation.py +111 -0
  26. sqlspec/utils/fixtures.py +66 -0
  27. sqlspec/utils/module_loader.py +94 -0
  28. sqlspec/utils/text.py +46 -0
  29. sqlspec-0.6.0.dist-info/METADATA +128 -0
  30. sqlspec-0.6.0.dist-info/RECORD +46 -0
  31. sqlspec-0.6.0.dist-info/licenses/LICENSE +21 -0
  32. sqlspec/config.py +0 -16
  33. sqlspec-0.4.0.dist-info/METADATA +0 -84
  34. sqlspec-0.4.0.dist-info/RECORD +0 -39
  35. {sqlspec-0.4.0.dist-info → sqlspec-0.6.0.dist-info}/WHEEL +0 -0
  36. {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
- PsycoPgGenericDatabaseConfig,
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[ConnectionPool, Connection]):
27
+ class PsycoPgSyncPoolConfig(PsycoPgGenericPoolConfig[Connection, ConnectionPool]):
30
28
  """Sync Psycopg Pool Config"""
31
29
 
32
30
 
33
31
  @dataclass
34
- class PsycoPgSyncDatabaseConfig(PsycoPgGenericDatabaseConfig[ConnectionPool, Connection]):
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.config import GenericDatabaseConfig
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(GenericDatabaseConfig):
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, Any, TypeVar
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
- """Aiosql plugin."""
14
+ """SQLSpec plugin."""
16
15
 
17
16
  __slots__ = ("_config",)
18
17
 
19
- def __init__(self, config: Any) -> None:
20
- """Initialize ``AiosqlPlugin``.
18
+ def __init__(self, config: ConfigManager) -> None:
19
+ """Initialize ``SQLSpecPlugin``.
21
20
 
22
21
  Args:
23
- config: configure and start Aiosql.
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 Aiosql.
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.signature_namespace.update(self._config.signature_namespace)
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: str) -> str:
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]` | :class:`~advanced_alchemy.base.ModelProtocol` | :class:`msgspec.Struct` | :class:`pydantic.BaseModel` | :class:`litestar.dto.data_structures.DTOData` | :class:`~advanced_alchemy.base.ModelProtocol`
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 is_msgspec_model(v: Any) -> TypeGuard[Struct]:
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 is_dataclass(v: Any) -> TypeGuard[DataclassProtocol]:
158
- """Check if a value is a dataclass.
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 is_dataclass_instance(v)
228
+ return is_msgspec_struct(v) or is_pydantic_model(v)
167
229
 
168
230
 
169
- def is_dataclass_with_field(v: Any, field_name: str) -> TypeGuard[DataclassProtocol]:
170
- """Check if a dataclass has a specific field.
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 is_dataclass(v) and field_name in v.__dataclass_fields__
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 is_dataclass_without_field(v: Any, field_name: str) -> TypeGuard[DataclassProtocol]:
183
- """Check if a dataclass does not have a specific field.
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 is_dataclass(v) and field_name not in v.__dataclass_fields__
266
+ return not is_schema_with_field(v, field_name)
193
267
 
194
268
 
195
- def is_pydantic_model_with_field(v: Any, field_name: str) -> TypeGuard[BaseModel]:
196
- """Check if a pydantic model has a specific field.
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 is_pydantic_model(v) and field_name in v.model_fields
279
+ return is_schema_with_field(v, field_name) or is_dict_with_field(v, field_name)
206
280
 
207
281
 
208
- def is_pydantic_model_without_field(v: Any, field_name: str) -> TypeGuard[BaseModel]:
209
- """Check if a pydantic model does not have a specific field.
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 is_pydantic_model_with_field(v, field_name)
292
+ return not is_schema_or_dict_with_field(v, field_name)
219
293
 
220
294
 
221
- def is_msgspec_model_with_field(v: Any, field_name: str) -> TypeGuard[Struct]:
222
- """Check if a msgspec model has a specific field.
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 is_msgspec_model(v) and field_name in v.__struct_fields__
317
+ return is_dataclass(v) and field_name in v.__dataclass_fields__
232
318
 
233
319
 
234
- def is_msgspec_model_without_field(v: Any, field_name: str) -> TypeGuard[Struct]:
235
- """Check if a msgspec model does not have a specific field.
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 not is_msgspec_model_with_field(v, field_name)
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 is_msgspec_model(data) and exclude_unset:
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 is_msgspec_model(data) and not exclude_unset:
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
- "is_msgspec_model",
398
- "is_msgspec_model_with_field",
399
- "is_msgspec_model_without_field",
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