Skip to content

Commit a737b63

Browse files
authored
fix: lazy attributes getting accessed on repository update method (#553)
This fixes #552 by moving the guard condition up preventing the `getattr` from triggering the issue.
1 parent 00815a4 commit a737b63

3 files changed

Lines changed: 23 additions & 22 deletions

File tree

advanced_alchemy/repository/_async.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,17 +1510,17 @@ async def update(
15101510

15111511
# Handle relationships by merging objects into session first
15121512
for relationship in mapper.mapper.relationships:
1513+
if relationship.viewonly or relationship.lazy in { # pragma: no cover
1514+
"write_only",
1515+
"dynamic",
1516+
"raise",
1517+
"raise_on_sql",
1518+
}:
1519+
# Skip relationships with incompatible lazy loading strategies
1520+
continue
1521+
15131522
if (new_value := getattr(data, relationship.key, MISSING)) is not MISSING:
15141523
# Skip relationships that cannot be handled by generic merge operations
1515-
if relationship.viewonly or relationship.lazy in { # pragma: no cover
1516-
"write_only",
1517-
"dynamic",
1518-
"raise",
1519-
"raise_on_sql",
1520-
}:
1521-
# Skip relationships with incompatible lazy loading strategies
1522-
continue
1523-
15241524
if isinstance(new_value, list):
15251525
merged_values = [ # pyright: ignore
15261526
await self.session.merge(item, load=False) # pyright: ignore

advanced_alchemy/repository/_sync.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,17 +1511,17 @@ def update(
15111511

15121512
# Handle relationships by merging objects into session first
15131513
for relationship in mapper.mapper.relationships:
1514+
if relationship.viewonly or relationship.lazy in { # pragma: no cover
1515+
"write_only",
1516+
"dynamic",
1517+
"raise",
1518+
"raise_on_sql",
1519+
}:
1520+
# Skip relationships with incompatible lazy loading strategies
1521+
continue
1522+
15141523
if (new_value := getattr(data, relationship.key, MISSING)) is not MISSING:
15151524
# Skip relationships that cannot be handled by generic merge operations
1516-
if relationship.viewonly or relationship.lazy in { # pragma: no cover
1517-
"write_only",
1518-
"dynamic",
1519-
"raise",
1520-
"raise_on_sql",
1521-
}:
1522-
# Skip relationships with incompatible lazy loading strategies
1523-
continue
1524-
15251525
if isinstance(new_value, list):
15261526
merged_values = [ # pyright: ignore
15271527
self.session.merge(item, load=False) # pyright: ignore

tests/unit/test_repository.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
import decimal
77
from collections.abc import AsyncGenerator, Collection, Generator
88
from typing import TYPE_CHECKING, Any, Union, cast
9-
from unittest.mock import AsyncMock, MagicMock
9+
from unittest.mock import AsyncMock, MagicMock, PropertyMock
1010
from uuid import uuid4
1111

1212
import pytest
1313
from msgspec import Struct
1414
from pydantic import BaseModel
1515
from pytest_lazy_fixtures import lf
1616
from sqlalchemy import Integer, String
17-
from sqlalchemy.exc import SQLAlchemyError
17+
from sqlalchemy.exc import InvalidRequestError, SQLAlchemyError
1818
from sqlalchemy.ext.asyncio import AsyncSession
1919
from sqlalchemy.orm import InstrumentedAttribute, Mapped, Session, mapped_column
2020
from sqlalchemy.types import TypeEngine
@@ -1509,11 +1509,12 @@ async def test_update_skips_raise_lazy_relationships(
15091509
mock_mapper.mapper.columns = []
15101510
mock_mapper.mapper.relationships = [mock_relationship]
15111511

1512-
# Mock the data object to have the raise relationship attribute
1513-
mock_instance.items = MagicMock()
1512+
# Mock the data object to raise an error when accessing the relationship
1513+
type(mock_instance).items = PropertyMock(side_effect=InvalidRequestError)
15141514

15151515
mocker.patch.object(mock_repo, "get_id_attribute_value", return_value=id_)
15161516
mocker.patch.object(mock_repo, "get", return_value=existing_instance)
1517+
mocker.patch("advanced_alchemy.repository._sync.inspect", return_value=mock_mapper)
15171518
mocker.patch("advanced_alchemy.repository._async.inspect", return_value=mock_mapper)
15181519
mock_repo.session.merge.return_value = existing_instance # pyright: ignore[reportFunctionMemberAccess]
15191520

0 commit comments

Comments
 (0)