|
27 | 27 | from advanced_alchemy.repository.typing import MISSING, ModelT, OrderingPair, SQLAlchemyAsyncRepositoryT
|
28 | 28 | from advanced_alchemy.service._util import ResultConverter
|
29 | 29 | from advanced_alchemy.service.typing import (
|
| 30 | + UNSET, |
30 | 31 | BulkModelDictT,
|
31 | 32 | ModelDictListT,
|
32 | 33 | ModelDictT,
|
33 | 34 | asdict,
|
| 35 | + attrs_nothing, |
34 | 36 | is_attrs_instance,
|
35 | 37 | is_dict,
|
36 | 38 | is_dto_data,
|
37 | 39 | is_msgspec_struct,
|
38 | 40 | is_pydantic_model,
|
| 41 | + schema_dump, |
39 | 42 | )
|
40 | 43 | from advanced_alchemy.utils.dataclass import Empty, EmptyType
|
41 | 44 |
|
@@ -448,20 +451,26 @@ async def to_model(
|
448 | 451 | )
|
449 | 452 |
|
450 | 453 | if is_msgspec_struct(data):
|
451 |
| - from msgspec import UNSET |
452 |
| - |
453 | 454 | return model_from_dict(
|
454 | 455 | model=self.model_type,
|
455 |
| - **{f: val for f in data.__struct_fields__ if (val := getattr(data, f, None)) != UNSET}, |
| 456 | + **{ |
| 457 | + f: getattr(data, f) |
| 458 | + for f in data.__struct_fields__ |
| 459 | + if hasattr(data, f) and getattr(data, f) is not UNSET |
| 460 | + }, |
456 | 461 | )
|
457 | 462 |
|
458 | 463 | if is_dto_data(data):
|
459 | 464 | return cast("ModelT", data.create_instance())
|
460 | 465 |
|
461 | 466 | if is_attrs_instance(data):
|
| 467 | + # Filter out attrs.NOTHING values for partial updates |
| 468 | + def filter_unset(attr: Any, value: Any) -> bool: # noqa: ARG001 |
| 469 | + return value is not attrs_nothing |
| 470 | + |
462 | 471 | return model_from_dict(
|
463 | 472 | model=self.model_type,
|
464 |
| - **asdict(data), |
| 473 | + **asdict(data, filter=filter_unset), |
465 | 474 | )
|
466 | 475 |
|
467 | 476 | # Fallback for objects with __dict__ (e.g., regular classes)
|
@@ -729,11 +738,16 @@ async def update(
|
729 | 738 | Returns:
|
730 | 739 | Updated representation.
|
731 | 740 | """
|
732 |
| - if is_dict(data) and item_id is not None: |
| 741 | + if ( |
| 742 | + is_dict(data) or is_pydantic_model(data) or is_msgspec_struct(data) or is_attrs_instance(data) |
| 743 | + ) and item_id is not None: |
733 | 744 | existing_instance = await self.repository.get(
|
734 | 745 | item_id, id_attribute=id_attribute, load=load, execution_options=execution_options
|
735 | 746 | )
|
736 |
| - update_data = await self.to_model_on_update(data) |
| 747 | + update_data = ( |
| 748 | + await self.to_model_on_update(data) if is_dict(data) else schema_dump(data, exclude_unset=True) |
| 749 | + ) |
| 750 | + |
737 | 751 | if is_dict(update_data):
|
738 | 752 | for key, value in update_data.items():
|
739 | 753 | if getattr(existing_instance, key, MISSING) is not MISSING:
|
|
0 commit comments