@@ -734,12 +734,10 @@ async def test_repo_update_many_non_returning_backend_refresh(
734734
735735 # Create multiple authors
736736 authors = await maybe_async (
737- author_repo .create_many (
738- [
739- {"name" : "Author A" , "dob" : datetime .date (1990 , 1 , 1 )},
740- {"name" : "Author B" , "dob" : datetime .date (1991 , 2 , 2 )},
741- ]
742- )
737+ author_repo .create_many ([
738+ {"name" : "Author A" , "dob" : datetime .date (1990 , 1 , 1 )},
739+ {"name" : "Author B" , "dob" : datetime .date (1991 , 2 , 2 )},
740+ ])
743741 )
744742
745743 # Prepare update data with partial changes
@@ -775,13 +773,11 @@ async def test_service_mixed_input_types_update_many(
775773
776774 # Create multiple authors
777775 authors = await maybe_async (
778- author_service .create_many (
779- [
780- {"name" : "Author 1" , "dob" : datetime .date (1990 , 1 , 1 )},
781- {"name" : "Author 2" , "dob" : datetime .date (1991 , 2 , 2 )},
782- {"name" : "Author 3" , "dob" : datetime .date (1992 , 3 , 3 )},
783- ]
784- )
776+ author_service .create_many ([
777+ {"name" : "Author 1" , "dob" : datetime .date (1990 , 1 , 1 )},
778+ {"name" : "Author 2" , "dob" : datetime .date (1991 , 2 , 2 )},
779+ {"name" : "Author 3" , "dob" : datetime .date (1992 , 3 , 3 )},
780+ ])
785781 )
786782
787783 # Get ID type from model for dynamic schema creation
@@ -831,3 +827,73 @@ class AuthorUpdateMsgspec(msgspec.Struct): # type: ignore[name-defined,misc]
831827 assert author .dob is not None
832828 assert author .created_at is not None
833829 assert author .updated_at is not None
830+
831+
832+ async def test_repo_update_with_model_instance_partial_fields_github_560 (
833+ seeded_test_session_async : "tuple[AsyncSession, dict[str, type]]" ,
834+ ) -> None :
835+ """Test repository update with model instances for partial updates (GitHub Issue #560).
836+
837+ This test verifies that when updating with a model instance where only some fields
838+ are explicitly set, the unset fields do not overwrite existing data with None.
839+ """
840+ _session , models = seeded_test_session_async
841+ author_model = models ["author" ]
842+ author_repo = get_repository_from_session (seeded_test_session_async , "author" )
843+
844+ # Create an author with all fields populated
845+ author = await maybe_async (author_repo .create ({"name" : "Original Name" , "dob" : datetime .date (1990 , 1 , 1 )}))
846+ original_dob = author .dob
847+ original_id = author .id
848+
849+ # Create a partial update using a model instance with only id and name set
850+ # This mimics the pattern: Author(id=1, name="Updated Name")
851+ # SQLAlchemy initializes 'dob' to None, but it wasn't explicitly set by the user
852+ partial_update = author_model (id = original_id , name = "Updated Name" )
853+
854+ # Update via repository - should only update name, leave dob unchanged
855+ updated_author = await maybe_async (author_repo .update (partial_update ))
856+
857+ # Verify: name was updated, but dob remains unchanged (not overwritten with None)
858+ assert updated_author .name == "Updated Name"
859+ assert updated_author .dob == original_dob # Should be unchanged
860+ assert updated_author .id == original_id
861+
862+
863+ async def test_repo_update_many_with_model_instances_partial_fields_github_560 (
864+ seeded_test_session_async : "tuple[AsyncSession, dict[str, type]]" ,
865+ ) -> None :
866+ """Test repository update_many with model instances for partial updates (GitHub Issue #560).
867+
868+ This test verifies that update_many correctly handles model instances with partially
869+ set fields, preventing None values from overwriting existing data.
870+ """
871+ _session , models = seeded_test_session_async
872+ author_model = models ["author" ]
873+ author_repo = get_repository_from_session (seeded_test_session_async , "author" )
874+
875+ # Create multiple authors with all fields populated
876+ author1 = await maybe_async (author_repo .create ({"name" : "Author One" , "dob" : datetime .date (1990 , 1 , 1 )}))
877+ author2 = await maybe_async (author_repo .create ({"name" : "Author Two" , "dob" : datetime .date (1991 , 2 , 2 )}))
878+
879+ original_dob1 = author1 .dob
880+ original_dob2 = author2 .dob
881+
882+ # Create partial updates using model instances with only id and name set
883+ partial_updates = [
884+ author_model (id = author1 .id , name = "Updated One" ),
885+ author_model (id = author2 .id , name = "Updated Two" ),
886+ ]
887+
888+ # Update via repository - should only update names, leave dobs unchanged
889+ updated_authors = await maybe_async (author_repo .update_many (partial_updates ))
890+
891+ # Verify: names were updated, but dobs remain unchanged
892+ assert len (updated_authors ) == 2
893+ updated_by_id = {author .id : author for author in updated_authors }
894+
895+ assert updated_by_id [author1 .id ].name == "Updated One"
896+ assert updated_by_id [author1 .id ].dob == original_dob1 # Should be unchanged
897+
898+ assert updated_by_id [author2 .id ].name == "Updated Two"
899+ assert updated_by_id [author2 .id ].dob == original_dob2 # Should be unchanged
0 commit comments