|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import enum |
| 4 | +import inspect |
4 | 5 | from collections.abc import Collection, Mapping |
5 | 6 | from dataclasses import is_dataclass |
6 | 7 | from datetime import date, datetime |
|
22 | 23 | from polyfactory.exceptions import ConfigurationException, MissingDependencyException, ParameterException |
23 | 24 | from polyfactory.factories.base import BaseFactory |
24 | 25 | from polyfactory.factories.base import BuildContext as BaseBuildContext |
25 | | -from polyfactory.field_meta import Constraints, FieldMeta |
| 26 | +from polyfactory.field_meta import Constraints, FieldMeta, Null |
26 | 27 | from polyfactory.persistence import AsyncPersistenceProtocol, SyncPersistenceProtocol |
27 | 28 | from polyfactory.utils.types import Frozendict |
28 | 29 |
|
29 | 30 | try: |
30 | | - from sqlalchemy import ARRAY, Column, Numeric, String, inspect, types |
| 31 | + from sqlalchemy import ARRAY, Column, Numeric, String, types |
| 32 | + from sqlalchemy import inspect as sqla_inspect |
31 | 33 | from sqlalchemy.dialects import mssql, mysql, postgresql, sqlite |
32 | 34 | from sqlalchemy.exc import NoInspectionAvailable |
33 | 35 | from sqlalchemy.ext.associationproxy import AssociationProxy |
34 | 36 | from sqlalchemy.orm import InstanceState, Mapper, RelationshipProperty |
| 37 | + from sqlalchemy.sql.elements import TextClause |
35 | 38 | except ImportError as e: |
36 | 39 | msg = "sqlalchemy is not installed" |
37 | 40 | raise MissingDependencyException(msg) from e |
@@ -178,6 +181,21 @@ async def create_async(cls, **kwargs: Any) -> T: |
178 | 181 | kwargs["_build_context"] = build_context |
179 | 182 | return await super().create_async(**kwargs) |
180 | 183 |
|
| 184 | + @classmethod |
| 185 | + def build(cls, *_: Any, **kwargs: Any) -> T: |
| 186 | + """Build an instance of the factory's __model__ and apply field defaults.""" |
| 187 | + data = super().process_kwargs(**kwargs) |
| 188 | + |
| 189 | + if cls.__use_defaults__: |
| 190 | + for field_meta in cls.get_model_fields(): |
| 191 | + if field_meta.name in data: |
| 192 | + continue |
| 193 | + |
| 194 | + if cls.should_use_default_value(field_meta): |
| 195 | + data[field_meta.name] = field_meta.default |
| 196 | + |
| 197 | + return cls.__model__(**data) |
| 198 | + |
181 | 199 | @classmethod |
182 | 200 | def get_sqlalchemy_types(cls) -> dict[Any, Callable[[], Any]]: |
183 | 201 | """Get mapping of types where column type should be used directly. |
@@ -227,7 +245,7 @@ def get_provider_map(cls) -> dict[Any, Callable[[], Any]]: |
227 | 245 | @classmethod |
228 | 246 | def is_supported_type(cls, value: Any) -> TypeGuard[type[T]]: |
229 | 247 | try: |
230 | | - inspected = inspect(value) |
| 248 | + inspected = sqla_inspect(value) |
231 | 249 | except NoInspectionAvailable: |
232 | 250 | return False |
233 | 251 | return isinstance(inspected, (Mapper, InstanceState)) |
@@ -365,15 +383,39 @@ def _get_association_proxy_type(cls, table: Mapper, proxy: AssociationProxy) -> |
365 | 383 | class_ = target_attr.entity.class_ |
366 | 384 | return class_ if not target_collection.uselist else list[class_] # type: ignore[valid-type] |
367 | 385 |
|
| 386 | + @classmethod |
| 387 | + def get_column_default(cls, col: Column) -> Any: |
| 388 | + default = col.default |
| 389 | + if default is None or not default.has_arg: |
| 390 | + return Null |
| 391 | + |
| 392 | + arg: Any = default.arg # type: ignore[attr-defined] |
| 393 | + |
| 394 | + if default.is_scalar: |
| 395 | + return arg |
| 396 | + |
| 397 | + if default.is_clause_element and isinstance(arg, TextClause): |
| 398 | + return arg.text |
| 399 | + |
| 400 | + if default.is_callable: |
| 401 | + sig = inspect.signature(arg) |
| 402 | + # Skip context-sensitive default functions as there is no |
| 403 | + # obvious and correct way to construct the SQLAlchemy context manually. |
| 404 | + if not sig.parameters: |
| 405 | + return arg(None) |
| 406 | + |
| 407 | + return Null |
| 408 | + |
368 | 409 | @classmethod |
369 | 410 | def get_model_fields(cls) -> list[FieldMeta]: |
370 | 411 | fields_meta: list[FieldMeta] = [] |
371 | 412 |
|
372 | | - table: Mapper = inspect(cls.__model__) # type: ignore[assignment] |
| 413 | + table: Mapper = sqla_inspect(cls.__model__) # type: ignore[assignment] |
373 | 414 | fields_meta.extend( |
374 | 415 | FieldMeta.from_type( |
375 | 416 | annotation=cls.get_type_from_column(column), |
376 | 417 | name=name, |
| 418 | + default=cls.get_column_default(column), |
377 | 419 | ) |
378 | 420 | for name, column in table.columns.items() |
379 | 421 | if cls.should_column_be_set(column) |
|
0 commit comments