python-attrs / cattrs

Composable custom class converters for attrs, dataclasses and friends.
https://catt.rs
MIT License
791 stars 110 forks source link

structure doesn't recognise init=False on SQLAlchemy's declarative base integration #428

Closed tjader closed 11 months ago

tjader commented 11 months ago

Description

SQLAlchemy 2.0 has a new integration with dataclasses (and to some extent attrs). It somewhat works with cattrs as well. However, cattrs doesn't detect the init=False flag on the mapped columns (i.e dataclass fields).

What I Did

Using the example in sqlalchemy's documentation (https://docs.sqlalchemy.org/en/20/orm/dataclasses.html#declarative-dataclass-mapping), you can try running structure on some data. This works if there is no init=False column

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass
from cattrs import structure, unstructure

class Base(MappedAsDataclass, DeclarativeBase):
    """subclasses will be converted to dataclasses"""

class User1(Base):
    __tablename__ = "user1"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]

class User2(Base):
    __tablename__ = "user2"

    id: Mapped[int] = mapped_column(init=False, primary_key=True)
    name: Mapped[str]

a = structure({'id':1, 'name': 'abc'}, User1)

But fails, if we try to structure a class with a init=False field

structure({'name': 'abc'}, User2)
  + Exception Group Traceback (most recent call last):
  |   File "/home/mats/miniconda3/envs/bfbet/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3508, in run_code
  |     exec(code_obj, self.user_global_ns, self.user_ns)
  |   File "<ipython-input-2-27bab276a8a2>", line 1, in <module>
  |     structure({'name': 'abc'}, User2)
  |   File "/home/mats/miniconda3/envs/bfbet/lib/python3.11/site-packages/cattrs/converters.py", line 334, in structure
  |     return self._structure_func.dispatch(cl)(obj, cl)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "<cattrs generated structure __main__.User2>", line 14, in structure_User2
  |     if errors: raise __c_cve('While structuring ' + 'User2', errors, __cl)
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  | cattrs.errors.ClassValidationError: While structuring User2 (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "<cattrs generated structure __main__.User2>", line 5, in structure_User2
    |     res['id'] = __c_structure_id(o['id'])
    |                                   ~^^^^^^
    | KeyError: 'id'
    | Structuring class User2 @ attribute id
    +------------------------------------
tjader commented 11 months ago

I just found this closed (but as of now unreleased) issue. https://github.com/python-attrs/cattrs/issues/40

Tinche commented 11 months ago

Yeah, can you give it a test with main? The next release is very close.

tjader commented 11 months ago

Thanks - The issue has been solved in main already. Looking forward to the next release.