^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Support for dataclasses features is partial. Currently **supported** are
-the ``init``, ``repr``, ``eq``, ``order`` and ``unsafe_hash`` features.
-Currently **not supported** are the ``frozen``, ``slots``, ``match_args``,
-and ``kw_only`` features.
+the ``init``, ``repr``, ``eq``, ``order`` and ``unsafe_hash`` features,
+``match_args`` and ``kw_only`` are supported on Python 3.10+.
+Currently **not supported** are the ``frozen`` and ``slots`` features.
When using the mixin class form with :class:`_orm.MappedAsDataclass`,
class configuration arguments are passed as class-level parameters::
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
default: Optional[Any] = _NoArg.NO_ARG,
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
nullable: Optional[
Union[bool, Literal[SchemaConst.NULL_UNSPECIFIED]]
] = SchemaConst.NULL_UNSPECIFIED,
specifies a default-value generation function that will take place
as part of the ``__init__()``
method as generated by the dataclass process.
+ :param kw_only: Specific to
+ :ref:`orm_declarative_native_dataclasses`, indicates if this field
+ should be marked as keyword-only when generating the ``__init__()``.
:param \**kw: All remaining keyword argments are passed through to the
constructor for the :class:`_schema.Column`.
autoincrement=autoincrement,
insert_default=insert_default,
attribute_options=_AttributeOptions(
- init,
- repr,
- default,
- default_factory,
+ init, repr, default, default_factory, kw_only
),
doc=doc,
key=key,
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
default: Optional[Any] = _NoArg.NO_ARG,
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
active_history: bool = False,
expire_on_flush: bool = True,
info: Optional[_InfoType] = None,
column,
*additional_columns,
attribute_options=_AttributeOptions(
- init,
- repr,
- default,
- default_factory,
+ init, repr, default, default_factory, kw_only
),
group=group,
deferred=deferred,
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
default: Optional[Any] = _NoArg.NO_ARG,
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
info: Optional[_InfoType] = None,
doc: Optional[str] = None,
**__kw: Any,
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
default: Optional[Any] = _NoArg.NO_ARG,
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
info: Optional[_InfoType] = None,
doc: Optional[str] = None,
**__kw: Any,
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
default: Optional[Any] = _NoArg.NO_ARG,
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
info: Optional[_InfoType] = None,
doc: Optional[str] = None,
**__kw: Any,
specifies a default-value generation function that will take place
as part of the ``__init__()``
method as generated by the dataclass process.
+ :param kw_only: Specific to
+ :ref:`orm_declarative_native_dataclasses`, indicates if this field
+ should be marked as keyword-only when generating the ``__init__()``.
"""
if __kw:
_class_or_attr,
*attrs,
attribute_options=_AttributeOptions(
- init,
- repr,
- default,
- default_factory,
+ init, repr, default, default_factory, kw_only
),
group=group,
deferred=deferred,
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
default: Union[_NoArg, _T] = _NoArg.NO_ARG,
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
lazy: _LazyLoadArgumentType = "select",
passive_deletes: Union[Literal["all"], bool] = False,
passive_updates: bool = True,
specifies a default-value generation function that will take place
as part of the ``__init__()``
method as generated by the dataclass process.
-
+ :param kw_only: Specific to
+ :ref:`orm_declarative_native_dataclasses`, indicates if this field
+ should be marked as keyword-only when generating the ``__init__()``.
"""
cascade=cascade,
viewonly=viewonly,
attribute_options=_AttributeOptions(
- init,
- repr,
- default,
- default_factory,
+ init, repr, default, default_factory, kw_only
),
lazy=lazy,
passive_deletes=passive_deletes,
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
default: Union[_NoArg, _T] = _NoArg.NO_ARG,
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
info: Optional[_InfoType] = None,
doc: Optional[str] = None,
) -> Synonym[Any]:
descriptor=descriptor,
comparator_factory=comparator_factory,
attribute_options=_AttributeOptions(
- init,
- repr,
- default,
- default_factory,
+ init, repr, default, default_factory, kw_only
),
doc=doc,
info=info,
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
default: Optional[Any] = _NoArg.NO_ARG,
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
active_history: bool = False,
expire_on_flush: bool = True,
info: Optional[_InfoType] = None,
column,
*additional_columns,
attribute_options=_AttributeOptions(
- init,
- repr,
- default,
- default_factory,
+ init, repr, default, default_factory, kw_only
),
group=group,
deferred=True,
repr,
_NoArg.NO_ARG,
_NoArg.NO_ARG,
+ _NoArg.NO_ARG,
),
expire_on_flush=expire_on_flush,
info=info,
eq: Union[_NoArg, bool] = _NoArg.NO_ARG,
order: Union[_NoArg, bool] = _NoArg.NO_ARG,
unsafe_hash: Union[_NoArg, bool] = _NoArg.NO_ARG,
+ match_args: Union[_NoArg, bool] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
) -> None:
apply_dc_transforms: _DataclassArguments = {
"eq": eq,
"order": order,
"unsafe_hash": unsafe_hash,
+ "match_args": match_args,
+ "kw_only": kw_only,
}
if hasattr(cls, "_sa_apply_dc_transforms"):
eq: Union[_NoArg, bool] = ...,
order: Union[_NoArg, bool] = ...,
unsafe_hash: Union[_NoArg, bool] = ...,
+ match_args: Union[_NoArg, bool] = ...,
+ kw_only: Union[_NoArg, bool] = ...,
) -> Callable[[Type[_O]], Type[_O]]:
...
eq: Union[_NoArg, bool] = _NoArg.NO_ARG,
order: Union[_NoArg, bool] = _NoArg.NO_ARG,
unsafe_hash: Union[_NoArg, bool] = _NoArg.NO_ARG,
+ match_args: Union[_NoArg, bool] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
) -> Union[Type[_O], Callable[[Type[_O]], Type[_O]]]:
"""Class decorator that will apply the Declarative mapping process
to a given class, and additionally convert the class to be a
"eq": eq,
"order": order,
"unsafe_hash": unsafe_hash,
+ "match_args": match_args,
+ "kw_only": kw_only,
}
_as_declarative(self, cls, cls.__dict__)
return cls
eq: Union[_NoArg, bool]
order: Union[_NoArg, bool]
unsafe_hash: Union[_NoArg, bool]
+ match_args: Union[_NoArg, bool]
+ kw_only: Union[_NoArg, bool]
def _declared_mapping_info(
@classmethod
def _assert_dc_arguments(cls, arguments: _DataclassArguments) -> None:
- disallowed_args = set(arguments).difference(
- {
- "init",
- "repr",
- "order",
- "eq",
- "unsafe_hash",
- }
- )
+ allowed = {
+ "init",
+ "repr",
+ "order",
+ "eq",
+ "unsafe_hash",
+ "kw_only",
+ "match_args",
+ }
+ disallowed_args = set(arguments).difference(allowed)
if disallowed_args:
+ msg = ", ".join(f"{arg!r}" for arg in sorted(disallowed_args))
raise exc.ArgumentError(
- f"Dataclass argument(s) "
- f"""{
- ', '.join(f'{arg!r}'
- for arg in sorted(disallowed_args))
- } are not accepted"""
+ f"Dataclass argument(s) {msg} are not accepted"
)
def _collect_annotation(
dataclasses_repr: Union[_NoArg, bool]
dataclasses_default: Union[_NoArg, Any]
dataclasses_default_factory: Union[_NoArg, Callable[[], Any]]
+ dataclasses_kw_only: Union[_NoArg, bool]
def _as_dataclass_field(self) -> Any:
"""Return a ``dataclasses.Field`` object given these arguments."""
kw["init"] = self.dataclasses_init
if self.dataclasses_repr is not _NoArg.NO_ARG:
kw["repr"] = self.dataclasses_repr
+ if self.dataclasses_kw_only is not _NoArg.NO_ARG:
+ kw["kw_only"] = self.dataclasses_kw_only
return dataclasses.field(**kw)
_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions(
- _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG
+ _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG
)
from sqlalchemy.orm import composite
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import deferred
+from sqlalchemy.orm import interfaces
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass
from sqlalchemy.testing import is_false
from sqlalchemy.testing import is_true
from sqlalchemy.testing import ne_
+from sqlalchemy.util import compat
class DCTransformsTest(AssertsCompiledSQL, fixtures.TestBase):
),
)
+ @testing.only_if(lambda: compat.py310, "python 3.10 is required")
+ def test_kw_only(self, dc_decl_base: Type[MappedAsDataclass]):
+ class A(dc_decl_base):
+ __tablename__ = "a"
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+ data: Mapped[str] = mapped_column(kw_only=True)
+
+ fas = pyinspect.getfullargspec(A.__init__)
+ eq_(fas.args, ["self", "id"])
+ eq_(fas.kwonlyargs, ["data"])
+
class RelationshipDefaultFactoryTest(fixtures.TestBase):
def test_list(self, dc_decl_base: Type[MappedAsDataclass]):
class DataclassArgsTest(fixtures.TestBase):
dc_arg_names = ("init", "repr", "eq", "order", "unsafe_hash")
+ if compat.py310:
+ dc_arg_names += ("match_args", "kw_only")
@testing.fixture(params=product(dc_arg_names, (True, False)))
def dc_argument_fixture(self, request: Any, registry: _RegistryType):
"order": False,
"unsafe_hash": False,
}
+ if compat.py310:
+ default |= {"match_args": True, "kw_only": False}
to_apply = {k: v for k, v in args.items() if v}
effective = {**default, **to_apply}
return to_apply, effective
if dc_arguments["init"]:
def create(data, x):
- return cls(data, x)
+ if dc_arguments.get("kw_only"):
+ return cls(data=data, x=x)
+ else:
+ return cls(data, x)
else:
getattr(self, f"_assert_not_{n}")(cls, create, dc_arguments)
if dc_arguments["init"]:
- a1 = cls("some data")
+ a1 = cls(data="some data")
eq_(a1.x, 7)
a1 = create("some data", 15)
eq_regex(repr(a1), r"<.*A object at 0x.*>")
def _assert_init(self, cls, create, dc_arguments):
- a1 = cls("some data", 5)
+ if not dc_arguments.get("kw_only", False):
+ a1 = cls("some data", 5)
- eq_(a1.data, "some data")
- eq_(a1.x, 5)
+ eq_(a1.data, "some data")
+ eq_(a1.x, 5)
a2 = cls(data="some data", x=5)
eq_(a2.data, "some data")
# no constructor, it sets None for x...ok
eq_(a1.x, None)
+ def _assert_match_args(self, cls, create, dc_arguments):
+ if not dc_arguments["kw_only"]:
+ is_true(len(cls.__match_args__) > 0)
+
+ def _assert_not_match_args(self, cls, create, dc_arguments):
+ is_false(hasattr(cls, "__match_args__"))
+
+ def _assert_kw_only(self, cls, create, dc_arguments):
+ if dc_arguments["init"]:
+ fas = pyinspect.getfullargspec(cls.__init__)
+ eq_(fas.args, ["self"])
+ eq_(
+ len(fas.kwonlyargs),
+ len(pyinspect.signature(cls.__init__).parameters) - 1,
+ )
+
+ def _assert_not_kw_only(self, cls, create, dc_arguments):
+ if dc_arguments["init"]:
+ fas = pyinspect.getfullargspec(cls.__init__)
+ eq_(
+ len(fas.args),
+ len(pyinspect.signature(cls.__init__).parameters),
+ )
+ eq_(fas.kwonlyargs, [])
+
def test_dc_arguments_decorator(
self,
dc_argument_fixture,
"order": True,
"unsafe_hash": False,
}
+ if compat.py310:
+ effective |= {"match_args": True, "kw_only": False}
self._assert_cls(A, effective)
def test_dc_base_unsupported_argument(self, registry: _RegistryType):
id: Mapped[int] = mapped_column(primary_key=True, init=False)
+ @testing.combinations(True, False)
+ def test_attribute_options(self, args):
+ if args:
+ kw = {
+ "init": True,
+ "repr": True,
+ "default": True,
+ "default_factory": list,
+ "kw_only": True,
+ }
+ exp = interfaces._AttributeOptions(True, True, True, list, True)
+ else:
+ kw = {}
+ exp = interfaces._DEFAULT_ATTRIBUTE_OPTIONS
+
+ for prop in [
+ mapped_column(**kw),
+ synonym("some_int", **kw),
+ column_property(Column(Integer), **kw),
+ deferred(Column(Integer), **kw),
+ composite("foo", **kw),
+ relationship("Foo", **kw),
+ ]:
+ eq_(prop._attribute_options, exp)
+
class CompositeTest(fixtures.TestBase, testing.AssertsCompiledSQL):
__dialect__ = "default"