Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 53be3fc

Browse files
CaselITGerrit Code Review
authored andcommitted
Merge "establish column_property and query_expression as readonly from a dc perspective" into main
2 parents 107ec58 + 9f43b10 commit 53be3fc

File tree

16 files changed

+407
-97
lines changed

16 files changed

+407
-97
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.. change::
2+
:tags: bug, orm
3+
:tickets: 9628
4+
5+
Fixed bug in ORM Declarative Dataclasses where the
6+
:func:`_orm.queryable_attribute` and :func:`_orm.column_property`
7+
constructs, which are documented as read-only constructs in the context of
8+
a Declarative mapping, could not be used with a
9+
:class:`_orm.MappedAsDataclass` class without adding ``init=False``, which
10+
in the case of :func:`_orm.queryable_attribute` was not possible as no
11+
``init`` parameter was included. These constructs have been modified from a
12+
dataclass perspective to be assumed to be "read only", setting
13+
``init=False`` by default and no longer including them in the pep-681
14+
constructor. The dataclass parameters for :func:`_orm.column_property`
15+
``init``, ``default``, ``default_factory``, ``kw_only`` are now deprecated;
16+
these fields don't apply to :func:`_orm.column_property` as used in a
17+
Declarative dataclasses configuration where the construct would be
18+
read-only. Also added read-specific parameter
19+
:paramref:`_orm.queryable_attribute.compare` to
20+
:func:`_orm.queryable_attribute`; :paramref:`_orm.queryable_attribute.repr`
21+
was already present.
22+
23+
24+
25+
.. change::
26+
:tags: bug, orm
27+
28+
Added missing :paramref:`_orm.mapped_column.active_history` parameter
29+
to :func:`_orm.mapped_column` construct.

doc/build/orm/declarative_tables.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,8 +1336,8 @@ declaration, typing tools will be able to match the attribute to the
13361336

13371337
.. _orm_imperative_table_column_options:
13381338

1339-
Applying Load, Persistence and Mapping Options for Mapped Table Columns
1340-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1339+
Applying Load, Persistence and Mapping Options for Imperative Table Columns
1340+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
13411341

13421342
The section :ref:`orm_declarative_column_options` reviewed how to set load
13431343
and persistence options when using the :func:`_orm.mapped_column` construct

lib/sqlalchemy/ext/mypy/names.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from mypy.nodes import ARG_POS
1818
from mypy.nodes import CallExpr
1919
from mypy.nodes import ClassDef
20+
from mypy.nodes import Decorator
2021
from mypy.nodes import Expression
2122
from mypy.nodes import FuncDef
2223
from mypy.nodes import MemberExpr
@@ -261,7 +262,20 @@ def type_id_for_unbound_type(
261262

262263
def type_id_for_callee(callee: Expression) -> Optional[int]:
263264
if isinstance(callee, (MemberExpr, NameExpr)):
264-
if isinstance(callee.node, OverloadedFuncDef):
265+
if isinstance(callee.node, Decorator) and isinstance(
266+
callee.node.func, FuncDef
267+
):
268+
if callee.node.func.type and isinstance(
269+
callee.node.func.type, CallableType
270+
):
271+
ret_type = get_proper_type(callee.node.func.type.ret_type)
272+
273+
if isinstance(ret_type, Instance):
274+
return type_id_for_fullname(ret_type.type.fullname)
275+
276+
return None
277+
278+
elif isinstance(callee.node, OverloadedFuncDef):
265279
if (
266280
callee.node.impl
267281
and callee.node.impl.type

lib/sqlalchemy/orm/_orm_constructors.py

Lines changed: 81 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ def mapped_column(
126126
insert_default: Optional[Any] = _NoArg.NO_ARG,
127127
server_default: Optional[_ServerDefaultType] = None,
128128
server_onupdate: Optional[FetchedValue] = None,
129+
active_history: bool = False,
129130
quote: Optional[bool] = None,
130131
system: bool = False,
131132
comment: Optional[str] = None,
@@ -258,6 +259,20 @@ def mapped_column(
258259
259260
.. versionadded:: 2.0.4
260261
262+
:param active_history=False:
263+
264+
When ``True``, indicates that the "previous" value for a
265+
scalar attribute should be loaded when replaced, if not
266+
already loaded. Normally, history tracking logic for
267+
simple non-primary-key scalar values only needs to be
268+
aware of the "new" value in order to perform a flush. This
269+
flag is available for applications that make use of
270+
:func:`.attributes.get_history` or :meth:`.Session.is_modified`
271+
which also need to know the "previous" value of the attribute.
272+
273+
.. versionadded:: 2.0.10
274+
275+
261276
:param init: Specific to :ref:`orm_declarative_native_dataclasses`,
262277
specifies if the mapped attribute should be part of the ``__init__()``
263278
method as generated by the dataclass process.
@@ -301,6 +316,7 @@ def mapped_column(
301316
index=index,
302317
unique=unique,
303318
info=info,
319+
active_history=active_history,
304320
nullable=nullable,
305321
onupdate=onupdate,
306322
primary_key=primary_key,
@@ -318,14 +334,27 @@ def mapped_column(
318334
)
319335

320336

337+
@util.deprecated_params(
338+
**{
339+
arg: (
340+
"2.0",
341+
f"The :paramref:`_orm.column_property.{arg}` parameter is "
342+
"deprecated for :func:`_orm.column_property`. This parameter "
343+
"applies to a writeable-attribute in a Declarative Dataclasses "
344+
"configuration only, and :func:`_orm.column_property` is treated "
345+
"as a read-only attribute in this context.",
346+
)
347+
for arg in ("init", "kw_only", "default", "default_factory")
348+
}
349+
)
321350
def column_property(
322351
column: _ORMColumnExprArgument[_T],
323352
*additional_columns: _ORMColumnExprArgument[Any],
324353
group: Optional[str] = None,
325354
deferred: bool = False,
326355
raiseload: bool = False,
327356
comparator_factory: Optional[Type[PropComparator[_T]]] = None,
328-
init: Union[_NoArg, bool] = _NoArg.NO_ARG,
357+
init: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
329358
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
330359
default: Optional[Any] = _NoArg.NO_ARG,
331360
default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
@@ -338,49 +367,58 @@ def column_property(
338367
) -> MappedSQLExpression[_T]:
339368
r"""Provide a column-level property for use with a mapping.
340369
341-
Column-based properties can normally be applied to the mapper's
342-
``properties`` dictionary using the :class:`_schema.Column`
343-
element directly.
344-
Use this function when the given column is not directly present within
345-
the mapper's selectable; examples include SQL expressions, functions,
346-
and scalar SELECT queries.
370+
With Declarative mappings, :func:`_orm.column_property` is used to
371+
map read-only SQL expressions to a mapped class.
372+
373+
When using Imperative mappings, :func:`_orm.column_property` also
374+
takes on the role of mapping table columns with additional features.
375+
When using fully Declarative mappings, the :func:`_orm.mapped_column`
376+
construct should be used for this purpose.
377+
378+
With Declarative Dataclass mappings, :func:`_orm.column_property`
379+
is considered to be **read only**, and will not be included in the
380+
Dataclass ``__init__()`` constructor.
347381
348382
The :func:`_orm.column_property` function returns an instance of
349383
:class:`.ColumnProperty`.
350384
351-
Columns that aren't present in the mapper's selectable won't be
352-
persisted by the mapper and are effectively "read-only" attributes.
385+
.. seealso::
386+
387+
:ref:`mapper_column_property_sql_expressions` - general use of
388+
:func:`_orm.column_property` to map SQL expressions
389+
390+
:ref:`orm_imperative_table_column_options` - usage of
391+
:func:`_orm.column_property` with Imperative Table mappings to apply
392+
additional options to a plain :class:`_schema.Column` object
353393
354394
:param \*cols:
355-
list of Column objects to be mapped.
395+
list of Column objects to be mapped.
356396
357397
:param active_history=False:
358-
When ``True``, indicates that the "previous" value for a
359-
scalar attribute should be loaded when replaced, if not
360-
already loaded. Normally, history tracking logic for
361-
simple non-primary-key scalar values only needs to be
362-
aware of the "new" value in order to perform a flush. This
363-
flag is available for applications that make use of
364-
:func:`.attributes.get_history` or :meth:`.Session.is_modified`
365-
which also need to know
366-
the "previous" value of the attribute.
398+
399+
Used only for Imperative Table mappings, or legacy-style Declarative
400+
mappings (i.e. which have not been upgraded to
401+
:func:`_orm.mapped_column`), for column-based attributes that are
402+
expected to be writeable; use :func:`_orm.mapped_column` with
403+
:paramref:`_orm.mapped_column.active_history` for Declarative mappings.
404+
See that parameter for functional details.
367405
368406
:param comparator_factory: a class which extends
369-
:class:`.ColumnProperty.Comparator` which provides custom SQL
370-
clause generation for comparison operations.
407+
:class:`.ColumnProperty.Comparator` which provides custom SQL
408+
clause generation for comparison operations.
371409
372410
:param group:
373411
a group name for this property when marked as deferred.
374412
375413
:param deferred:
376-
when True, the column property is "deferred", meaning that
377-
it does not load immediately, and is instead loaded when the
378-
attribute is first accessed on an instance. See also
379-
:func:`~sqlalchemy.orm.deferred`.
414+
when True, the column property is "deferred", meaning that
415+
it does not load immediately, and is instead loaded when the
416+
attribute is first accessed on an instance. See also
417+
:func:`~sqlalchemy.orm.deferred`.
380418
381419
:param doc:
382-
optional string that will be applied as the doc on the
383-
class-bound descriptor.
420+
optional string that will be applied as the doc on the
421+
class-bound descriptor.
384422
385423
:param expire_on_flush=True:
386424
Disable expiry on flush. A column_property() which refers
@@ -410,20 +448,25 @@ def column_property(
410448
411449
:ref:`orm_queryguide_deferred_raiseload`
412450
413-
.. seealso::
451+
:param init:
452+
453+
:param default:
414454
415-
:ref:`column_property_options` - to map columns while including
416-
mapping options
455+
:param default_factory:
417456
418-
:ref:`mapper_column_property_sql_expressions` - to map SQL
419-
expressions
457+
:param kw_only:
420458
421459
"""
422460
return MappedSQLExpression(
423461
column,
424462
*additional_columns,
425463
attribute_options=_AttributeOptions(
426-
init, repr, default, default_factory, compare, kw_only
464+
False if init is _NoArg.NO_ARG else init,
465+
repr,
466+
default,
467+
default_factory,
468+
compare,
469+
kw_only,
427470
),
428471
group=group,
429472
deferred=deferred,
@@ -433,6 +476,7 @@ def column_property(
433476
expire_on_flush=expire_on_flush,
434477
info=info,
435478
doc=doc,
479+
_assume_readonly_dc_attributes=True,
436480
)
437481

438482

@@ -2017,6 +2061,7 @@ def query_expression(
20172061
default_expr: _ORMColumnExprArgument[_T] = sql.null(),
20182062
*,
20192063
repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
2064+
compare: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
20202065
expire_on_flush: bool = True,
20212066
info: Optional[_InfoType] = None,
20222067
doc: Optional[str] = None,
@@ -2036,16 +2081,17 @@ def query_expression(
20362081
prop = MappedSQLExpression(
20372082
default_expr,
20382083
attribute_options=_AttributeOptions(
2039-
_NoArg.NO_ARG,
2084+
False,
20402085
repr,
20412086
_NoArg.NO_ARG,
20422087
_NoArg.NO_ARG,
2043-
_NoArg.NO_ARG,
2088+
compare,
20442089
_NoArg.NO_ARG,
20452090
),
20462091
expire_on_flush=expire_on_flush,
20472092
info=info,
20482093
doc=doc,
2094+
_assume_readonly_dc_attributes=True,
20492095
)
20502096

20512097
prop.strategy_key = (("query_expression", True),)

lib/sqlalchemy/orm/decl_api.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,9 @@
3737
from . import instrumentation
3838
from . import interfaces
3939
from . import mapperlib
40-
from ._orm_constructors import column_property
4140
from ._orm_constructors import composite
4241
from ._orm_constructors import deferred
4342
from ._orm_constructors import mapped_column
44-
from ._orm_constructors import query_expression
4543
from ._orm_constructors import relationship
4644
from ._orm_constructors import synonym
4745
from .attributes import InstrumentedAttribute
@@ -59,7 +57,6 @@
5957
from .descriptor_props import Synonym
6058
from .descriptor_props import Synonym as _orm_synonym
6159
from .mapper import Mapper
62-
from .properties import ColumnProperty
6360
from .properties import MappedColumn
6461
from .relationships import RelationshipProperty
6562
from .state import InstanceState
@@ -153,15 +150,12 @@ class DeclarativeAttributeIntercept(
153150
MappedColumn,
154151
RelationshipProperty,
155152
Composite,
156-
ColumnProperty,
157153
Synonym,
158154
mapped_column,
159155
relationship,
160156
composite,
161-
column_property,
162157
synonym,
163158
deferred,
164-
query_expression,
165159
),
166160
)
167161
class DCTransformDeclarative(DeclarativeAttributeIntercept):
@@ -1549,15 +1543,12 @@ def __class_getitem__(cls: Type[_T], key: str) -> Type[_T]:
15491543
MappedColumn,
15501544
RelationshipProperty,
15511545
Composite,
1552-
ColumnProperty,
15531546
Synonym,
15541547
mapped_column,
15551548
relationship,
15561549
composite,
1557-
column_property,
15581550
synonym,
15591551
deferred,
1560-
query_expression,
15611552
),
15621553
)
15631554
@overload

lib/sqlalchemy/orm/interfaces.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,15 @@ def _get_arguments_for_make_dataclass(
269269
_NoArg.NO_ARG,
270270
)
271271

272+
_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions(
273+
False,
274+
_NoArg.NO_ARG,
275+
_NoArg.NO_ARG,
276+
_NoArg.NO_ARG,
277+
_NoArg.NO_ARG,
278+
_NoArg.NO_ARG,
279+
)
280+
272281

273282
class _DCAttributeOptions:
274283
"""mixin for descriptors or configurational objects that include dataclass
@@ -519,19 +528,24 @@ def instrument_class(self, mapper: Mapper[Any]) -> None:
519528
"""
520529

521530
def __init__(
522-
self, attribute_options: Optional[_AttributeOptions] = None
531+
self,
532+
attribute_options: Optional[_AttributeOptions] = None,
533+
_assume_readonly_dc_attributes: bool = False,
523534
) -> None:
524535
self._configure_started = False
525536
self._configure_finished = False
526-
if (
527-
attribute_options
528-
and attribute_options != _DEFAULT_ATTRIBUTE_OPTIONS
529-
):
537+
538+
if _assume_readonly_dc_attributes:
539+
default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS
540+
else:
541+
default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS
542+
543+
if attribute_options and attribute_options != default_attrs:
530544
self._has_dataclass_arguments = True
531545
self._attribute_options = attribute_options
532546
else:
533547
self._has_dataclass_arguments = False
534-
self._attribute_options = _DEFAULT_ATTRIBUTE_OPTIONS
548+
self._attribute_options = default_attrs
535549

536550
def init(self) -> None:
537551
"""Called after all mappers are created to assemble

0 commit comments

Comments
 (0)