diff options
-rw-r--r-- | doc/build/changelog/unreleased_14/7432.rst | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/row.py | 37 | ||||
-rw-r--r-- | test/sql/test_resultset.py | 74 |
3 files changed, 106 insertions, 13 deletions
diff --git a/doc/build/changelog/unreleased_14/7432.rst b/doc/build/changelog/unreleased_14/7432.rst new file mode 100644 index 000000000..6e3f74c67 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7432.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, engine + :tickets: 7432 + + Corrected the error message for the ``AttributeError`` that's raised when + attempting to write to an attribute on the :class:`_result.Row` class, + which is immutable. The previous message claimed the column didn't exist + which is misleading. diff --git a/lib/sqlalchemy/engine/row.py b/lib/sqlalchemy/engine/row.py index 1a8c4e555..43ef093d6 100644 --- a/lib/sqlalchemy/engine/row.py +++ b/lib/sqlalchemy/engine/row.py @@ -59,21 +59,25 @@ except ImportError: def __init__(self, parent, processors, keymap, key_style, data): """Row objects are constructed by CursorResult objects.""" - self._parent = parent + object.__setattr__(self, "_parent", parent) if processors: - self._data = tuple( - [ - proc(value) if proc else value - for proc, value in zip(processors, data) - ] + object.__setattr__( + self, + "_data", + tuple( + [ + proc(value) if proc else value + for proc, value in zip(processors, data) + ] + ), ) else: - self._data = tuple(data) + object.__setattr__(self, "_data", tuple(data)) - self._keymap = keymap + object.__setattr__(self, "_keymap", keymap) - self._key_style = key_style + object.__setattr__(self, "_key_style", key_style) def __reduce__(self): return ( @@ -178,6 +182,12 @@ class Row(BaseRow, collections_abc.Sequence): _default_key_style = KEY_INTEGER_ONLY + def __setattr__(self, name, value): + raise AttributeError("can't set attribute") + + def __delattr__(self, name): + raise AttributeError("can't delete attribute") + @property def _mapping(self): """Return a :class:`.RowMapping` for this :class:`.Row`. @@ -233,10 +243,11 @@ class Row(BaseRow, collections_abc.Sequence): } def __setstate__(self, state): - self._parent = parent = state["_parent"] - self._data = state["_data"] - self._keymap = parent._keymap - self._key_style = state["_key_style"] + parent = state["_parent"] + object.__setattr__(self, "_parent", parent) + object.__setattr__(self, "_data", state["_data"]) + object.__setattr__(self, "_keymap", parent._keymap) + object.__setattr__(self, "_key_style", state["_key_style"]) def _op(self, other, op): return ( diff --git a/test/sql/test_resultset.py b/test/sql/test_resultset.py index e4f07a758..e5b1a0a26 100644 --- a/test/sql/test_resultset.py +++ b/test/sql/test_resultset.py @@ -746,6 +746,80 @@ class CursorResultTest(fixtures.TablesTest): eq_(r._mapping[users.c.user_name], "john") eq_(r.user_name, "john") + @testing.fixture + def _ab_row_fixture(self, connection): + r = connection.execute( + select(literal(1).label("a"), literal(2).label("b")) + ).first() + return r + + def test_named_tuple_access(self, _ab_row_fixture): + r = _ab_row_fixture + eq_(r.a, 1) + eq_(r.b, 2) + + def test_named_tuple_missing_attr(self, _ab_row_fixture): + r = _ab_row_fixture + with expect_raises_message( + AttributeError, "Could not locate column in row for column 'c'" + ): + r.c + + def test_named_tuple_no_delete_present(self, _ab_row_fixture): + r = _ab_row_fixture + with expect_raises_message(AttributeError, "can't delete attribute"): + del r.a + + def test_named_tuple_no_delete_missing(self, _ab_row_fixture): + r = _ab_row_fixture + # including for non-existent attributes + with expect_raises_message(AttributeError, "can't delete attribute"): + del r.c + + def test_named_tuple_no_assign_present(self, _ab_row_fixture): + r = _ab_row_fixture + with expect_raises_message(AttributeError, "can't set attribute"): + r.a = 5 + + with expect_raises_message(AttributeError, "can't set attribute"): + r.a += 5 + + def test_named_tuple_no_assign_missing(self, _ab_row_fixture): + r = _ab_row_fixture + # including for non-existent attributes + with expect_raises_message(AttributeError, "can't set attribute"): + r.c = 5 + + def test_named_tuple_no_self_assign_missing(self, _ab_row_fixture): + r = _ab_row_fixture + with expect_raises_message( + AttributeError, "Could not locate column in row for column 'c'" + ): + r.c += 5 + + def test_mapping_tuple_readonly_errors(self, connection): + r = connection.execute( + select(literal(1).label("a"), literal(2).label("b")) + ).first() + r = r._mapping + eq_(r["a"], 1) + eq_(r["b"], 2) + + with expect_raises_message( + KeyError, "Could not locate column in row for column 'c'" + ): + r["c"] + + with expect_raises_message( + TypeError, "'RowMapping' object does not support item assignment" + ): + r["a"] = 5 + + with expect_raises_message( + TypeError, "'RowMapping' object does not support item assignment" + ): + r["a"] += 5 + def test_column_accessor_err(self, connection): r = connection.execute(select(1)).first() assert_raises_message( |