diff options
author | Federico Caselli <cfederico87@gmail.com> | 2023-03-30 00:25:39 +0200 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-03-31 09:57:09 -0400 |
commit | 96670516b917424e7dd4c6cee5e07147c583f6c9 (patch) | |
tree | 50e53530f33859228b989d03228f6da14449c5b1 | |
parent | db69e680da3aa01572c19cdedb6448e74a01c790 (diff) | |
download | sqlalchemy-96670516b917424e7dd4c6cee5e07147c583f6c9.tar.gz |
Wrap dataclass exceptions clarifying origin
Exceptions such as ``TypeError`` and ``ValueError`` raised by Python
dataclasses when making use of the :class:`_orm.MappedAsDataclass` mixin
class or :meth:`_orm.registry.mapped_as_dataclass` decorator are now
wrapped within an :class:`.InvalidRequestError` wrapper along with
informative context about the error message, referring to the Python
dataclasses documentation as the authoritative source of background
information on the cause of the exception.
Fixes: #9563
Change-Id: I25652485b91c4ee8cf112b91aae8f9041061a8bd
-rw-r--r-- | doc/build/changelog/unreleased_20/9563.rst | 16 | ||||
-rw-r--r-- | doc/build/errors.rst | 33 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/decl_base.py | 8 | ||||
-rw-r--r-- | test/orm/declarative/test_dc_transforms.py | 15 |
4 files changed, 72 insertions, 0 deletions
diff --git a/doc/build/changelog/unreleased_20/9563.rst b/doc/build/changelog/unreleased_20/9563.rst new file mode 100644 index 000000000..501cb5c10 --- /dev/null +++ b/doc/build/changelog/unreleased_20/9563.rst @@ -0,0 +1,16 @@ +.. change:: + :tags: usecase, orm + :tickets: 9563 + + Exceptions such as ``TypeError`` and ``ValueError`` raised by Python + dataclasses when making use of the :class:`_orm.MappedAsDataclass` mixin + class or :meth:`_orm.registry.mapped_as_dataclass` decorator are now + wrapped within an :class:`.InvalidRequestError` wrapper along with + informative context about the error message, referring to the Python + dataclasses documentation as the authoritative source of background + information on the cause of the exception. + + .. seealso:: + + :ref:`error_dcte` + diff --git a/doc/build/errors.rst b/doc/build/errors.rst index 307a27414..4c8cb53d7 100644 --- a/doc/build/errors.rst +++ b/doc/build/errors.rst @@ -1443,6 +1443,39 @@ mixin classes which have SQLAlchemy mapped attributes within a dataclass hierarchy have to themselves be dataclasses. +.. _error_dcte: + +Python dataclasses error encountered when creating dataclass for <classname> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using the :class:`_orm.MappedAsDataclass` mixin class or +:meth:`_orm.registry.mapped_as_dataclass` decorator, SQLAlchemy makes use +of the actual `Python dataclasses <dataclasses>`_ module that's in the Python standard library +in order to apply dataclass behaviors to the target class. This API has +its own error scenarios, most of which involve the construction of an +``__init__()`` method on the user defined class; the order of attributes +declared on the class, as well as `on superclasses <dc_superclass>`_, determines +how the ``__init__()`` method will be constructed and there are specific +rules in how the attributes are organized as well as how they should make +use of parameters such as ``init=False``, ``kw_only=True``, etc. **SQLAlchemy +does not control or implement these rules**. Therefore, for errors of this nature, +consult the `Python dataclasses <dataclasses>`_ documentation, with special +attention to the rules applied to `inheritance <_dc_superclass>`_. + +.. seealso:: + + :ref:`orm_declarative_native_dataclasses` - SQLAlchemy dataclasses documentation + + `Python dataclasses <dataclasses>`_ - on the python.org website + + `inheritance <_dc_superclass>`_ - on the python.org website + +.. _dataclasses: https://docs.python.org/3/library/dataclasses.html + +.. _dc_superclass: https://docs.python.org/3/library/dataclasses.html#inheritance + + + AsyncIO Exceptions diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py index 828501d8c..bd62c3c1b 100644 --- a/lib/sqlalchemy/orm/decl_base.py +++ b/lib/sqlalchemy/orm/decl_base.py @@ -1210,6 +1210,14 @@ class _ClassScanMapperConfig(_MapperConfig): if v is not _NoArg.NO_ARG and k != "dataclass_callable" }, ) + except (TypeError, ValueError) as ex: + raise exc.InvalidRequestError( + f"Python dataclasses error encountered when creating " + f"dataclass for {klass.__name__!r}: " + f"{ex!r}. Please refer to Python dataclasses " + "documentation for additional information.", + code="dcte", + ) from ex finally: # restore original annotations outside of the dataclasses # process; for mixins and __abstract__ superclasses, SQLAlchemy diff --git a/test/orm/declarative/test_dc_transforms.py b/test/orm/declarative/test_dc_transforms.py index a8a5e04bb..031aad5d5 100644 --- a/test/orm/declarative/test_dc_transforms.py +++ b/test/orm/declarative/test_dc_transforms.py @@ -741,6 +741,21 @@ class DCTransformsTest(AssertsCompiledSQL, fixtures.TestBase): class Foo(Mixin): bar_value: Mapped[float] = mapped_column(default=78) + def test_dataclass_exception_wrapped(self, dc_decl_base): + with expect_raises_message( + exc.InvalidRequestError, + r"Python dataclasses error encountered when creating dataclass " + r"for \'Foo\': .*Please refer to Python dataclasses.*", + ) as ec: + + class Foo(dc_decl_base): + id: Mapped[int] = mapped_column(primary_key=True, init=False) + foo_value: Mapped[float] = mapped_column(default=78) + foo_no_value: Mapped[float] = mapped_column() + __tablename__ = "foo" + + is_true(isinstance(ec.error.__cause__, TypeError)) + class RelationshipDefaultFactoryTest(fixtures.TestBase): def test_list(self, dc_decl_base: Type[MappedAsDataclass]): |