SQLAlchemy 2.1.4__py3-none-any.whl → 2.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +1 -1
- sqlalchemy/dialects/oracle/base.py +4 -1
- sqlalchemy/dialects/postgresql/base.py +12 -2
- sqlalchemy/engine/base.py +13 -6
- sqlalchemy/engine/create.py +20 -10
- sqlalchemy/orm/__init__.py +2 -0
- sqlalchemy/orm/_orm_constructors.py +134 -0
- sqlalchemy/orm/base.py +54 -0
- sqlalchemy/orm/bulk_persistence.py +5 -3
- sqlalchemy/orm/decl_api.py +22 -5
- sqlalchemy/orm/decl_base.py +106 -22
- sqlalchemy/orm/mapper.py +56 -2
- sqlalchemy/orm/properties.py +75 -1
- sqlalchemy/orm/util.py +9 -2
- sqlalchemy/pool/base.py +47 -29
- sqlalchemy/sql/schema.py +2 -0
- sqlalchemy/testing/cancellation.py +77 -6
- sqlalchemy/util/queue.py +11 -0
- {sqlalchemy-2.1.4.dist-info → sqlalchemy-2.1.5.dist-info}/METADATA +1 -1
- {sqlalchemy-2.1.4.dist-info → sqlalchemy-2.1.5.dist-info}/RECORD +24 -24
- {sqlalchemy-2.1.4.dist-info → sqlalchemy-2.1.5.dist-info}/WHEEL +0 -0
- {sqlalchemy-2.1.4.dist-info → sqlalchemy-2.1.5.dist-info}/licenses/AUTHORS +0 -0
- {sqlalchemy-2.1.4.dist-info → sqlalchemy-2.1.5.dist-info}/licenses/LICENSE +0 -0
- {sqlalchemy-2.1.4.dist-info → sqlalchemy-2.1.5.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
|
@@ -2279,7 +2279,10 @@ class OracleDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
2279
2279
|
return self.get_isolation_level(dbapi_conn)
|
|
2280
2280
|
except NotImplementedError:
|
|
2281
2281
|
raise
|
|
2282
|
-
except:
|
|
2282
|
+
except Exception:
|
|
2283
|
+
# Exception only; a bare except here also caught
|
|
2284
|
+
# asyncio.CancelledError and KeyboardInterrupt, returning the
|
|
2285
|
+
# default in place of propagating them
|
|
2283
2286
|
return "READ COMMITTED"
|
|
2284
2287
|
|
|
2285
2288
|
def _execute_reflection(
|
|
@@ -5070,8 +5070,9 @@ class PGDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
5070
5070
|
|
|
5071
5071
|
# https://www.postgresql.org/docs/current/static/sql-createtable.html
|
|
5072
5072
|
return re.compile(
|
|
5073
|
-
|
|
5074
|
-
rf"REFERENCES (?:({qtoken})\.)?({qtoken})
|
|
5073
|
+
rf"FOREIGN KEY \((.*?)(?:, PERIOD ({qtoken}))?\) "
|
|
5074
|
+
rf"REFERENCES (?:({qtoken})\.)?({qtoken})"
|
|
5075
|
+
rf"\(((?:{qtoken}(?: *, *)?)+)(?:, PERIOD ({qtoken}))?\)"
|
|
5075
5076
|
r"[\s]?(MATCH (FULL|PARTIAL|SIMPLE)+)?"
|
|
5076
5077
|
r"[\s]?(?:ON (UPDATE|DELETE) "
|
|
5077
5078
|
r"(CASCADE|RESTRICT|NO ACTION|"
|
|
@@ -5089,9 +5090,11 @@ class PGDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
5089
5090
|
|
|
5090
5091
|
(
|
|
5091
5092
|
constrained_columns,
|
|
5093
|
+
constrained_period_column,
|
|
5092
5094
|
referred_schema,
|
|
5093
5095
|
referred_table,
|
|
5094
5096
|
referred_columns,
|
|
5097
|
+
referred_period_column,
|
|
5095
5098
|
_,
|
|
5096
5099
|
match,
|
|
5097
5100
|
upddelkey1,
|
|
@@ -5103,6 +5106,13 @@ class PGDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
5103
5106
|
initially,
|
|
5104
5107
|
) = m
|
|
5105
5108
|
|
|
5109
|
+
# TODO: the PERIOD designation of a temporal foreign key
|
|
5110
|
+
# (PostgreSQL 18+) is not yet reflected
|
|
5111
|
+
if constrained_period_column:
|
|
5112
|
+
constrained_columns += f", {constrained_period_column}"
|
|
5113
|
+
if referred_period_column:
|
|
5114
|
+
referred_columns += f", {referred_period_column}"
|
|
5115
|
+
|
|
5106
5116
|
onupdate = (
|
|
5107
5117
|
upddelval1
|
|
5108
5118
|
if upddelkey1 == "UPDATE"
|
sqlalchemy/engine/base.py
CHANGED
|
@@ -758,12 +758,19 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
|
|
|
758
758
|
if self.closed:
|
|
759
759
|
raise exc.ResourceClosedError("This Connection is closed")
|
|
760
760
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
761
|
+
try:
|
|
762
|
+
if self._still_open_and_dbapi_connection_is_valid:
|
|
763
|
+
pool_proxied_connection = self._dbapi_connection
|
|
764
|
+
assert pool_proxied_connection is not None
|
|
765
|
+
pool_proxied_connection.invalidate(exception)
|
|
766
|
+
finally:
|
|
767
|
+
# this is always a hard invalidation, which closes the DBAPI
|
|
768
|
+
# connection; for an asyncio driver that close is an awaited
|
|
769
|
+
# terminate() which a cancellation can interrupt. this
|
|
770
|
+
# Connection is invalidated regardless, so that nothing
|
|
771
|
+
# further, such as a rollback, is emitted on the DBAPI
|
|
772
|
+
# connection
|
|
773
|
+
self._dbapi_connection = None
|
|
767
774
|
|
|
768
775
|
def detach(self) -> None:
|
|
769
776
|
"""Detach the underlying DB-API connection from its connection pool.
|
sqlalchemy/engine/create.py
CHANGED
|
@@ -747,16 +747,26 @@ def create_engine(url: Union[str, _url.URL], **kwargs: Any) -> Engine:
|
|
|
747
747
|
)
|
|
748
748
|
c._execution_options = util.EMPTY_DICT
|
|
749
749
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
750
|
+
dialect.initialize(c)
|
|
751
|
+
|
|
752
|
+
# roll back only once initialize() has succeeded, rather than
|
|
753
|
+
# in a finally: block. when initialize() raises, the DBAPI
|
|
754
|
+
# connection may be partway through an operation that c
|
|
755
|
+
# doesn't know about, such as psycopg's TypeInfo.fetch()
|
|
756
|
+
# interrupted by a cancellation; the rollback then fails
|
|
757
|
+
# and its error replaces the original one. the pool closes
|
|
758
|
+
# the connection in that case, so there's nothing to roll
|
|
759
|
+
# back.
|
|
760
|
+
#
|
|
761
|
+
# note that "invalidated" and "closed" are mutually
|
|
762
|
+
# exclusive in 1.4 Connection. c may be invalidated here if
|
|
763
|
+
# initialize() caught an error on a statement that was a
|
|
764
|
+
# disconnect.
|
|
765
|
+
if not c.invalidated and not c.closed:
|
|
766
|
+
# transaction is rolled back otherwise, tested by
|
|
767
|
+
# test/dialect/postgresql/test_dialect.py
|
|
768
|
+
# ::MiscBackendTest::test_initial_transaction_state
|
|
769
|
+
dialect.do_rollback(c.connection)
|
|
760
770
|
|
|
761
771
|
# previously, the "first_connect" event was used here, which was then
|
|
762
772
|
# scaled back if the "on_connect" handler were present. now,
|
sqlalchemy/orm/__init__.py
CHANGED
|
@@ -37,6 +37,7 @@ from ._orm_constructors import outerjoin as outerjoin
|
|
|
37
37
|
from ._orm_constructors import query_expression as query_expression
|
|
38
38
|
from ._orm_constructors import relationship as relationship
|
|
39
39
|
from ._orm_constructors import synonym as synonym
|
|
40
|
+
from ._orm_constructors import unmapped_column as unmapped_column
|
|
40
41
|
from ._orm_constructors import with_loader_criteria as with_loader_criteria
|
|
41
42
|
from ._orm_constructors import with_polymorphic as with_polymorphic
|
|
42
43
|
from .attributes import AttributeEventToken as AttributeEventToken
|
|
@@ -51,6 +52,7 @@ from .base import NotExtension as NotExtension
|
|
|
51
52
|
from .base import ORMDescriptor as ORMDescriptor
|
|
52
53
|
from .base import PassiveFlag as PassiveFlag
|
|
53
54
|
from .base import SQLORMExpression as SQLORMExpression
|
|
55
|
+
from .base import Unmapped as Unmapped
|
|
54
56
|
from .base import WriteOnlyMapped as WriteOnlyMapped
|
|
55
57
|
from .context import FromStatement as FromStatement
|
|
56
58
|
from .context import QueryContext as QueryContext
|
|
@@ -29,6 +29,7 @@ from .descriptor_props import Synonym
|
|
|
29
29
|
from .interfaces import _AttributeOptions
|
|
30
30
|
from .properties import MappedColumn
|
|
31
31
|
from .properties import MappedSQLExpression
|
|
32
|
+
from .properties import UnmappedColumn
|
|
32
33
|
from .query import AliasOption
|
|
33
34
|
from .relationships import _RelationshipArgumentType
|
|
34
35
|
from .relationships import _RelationshipBackPopulatesArgument
|
|
@@ -395,6 +396,139 @@ def mapped_column(
|
|
|
395
396
|
)
|
|
396
397
|
|
|
397
398
|
|
|
399
|
+
def unmapped_column(
|
|
400
|
+
__name_pos: Optional[
|
|
401
|
+
Union[str, _TypeEngineArgument[Any], SchemaEventTarget]
|
|
402
|
+
] = None,
|
|
403
|
+
__type_pos: Optional[
|
|
404
|
+
Union[_TypeEngineArgument[Any], SchemaEventTarget]
|
|
405
|
+
] = None,
|
|
406
|
+
/,
|
|
407
|
+
*args: SchemaEventTarget,
|
|
408
|
+
init: Literal[False] = False,
|
|
409
|
+
nullable: Optional[
|
|
410
|
+
Union[bool, Literal[SchemaConst.NULL_UNSPECIFIED]]
|
|
411
|
+
] = SchemaConst.NULL_UNSPECIFIED,
|
|
412
|
+
primary_key: Optional[bool] = False,
|
|
413
|
+
autoincrement: _AutoIncrementType = "auto",
|
|
414
|
+
use_existing_column: bool = False,
|
|
415
|
+
name: Optional[str] = None,
|
|
416
|
+
type_: Optional[_TypeEngineArgument[Any]] = None,
|
|
417
|
+
index: Optional[bool] = None,
|
|
418
|
+
unique: Optional[bool] = None,
|
|
419
|
+
info: Optional[_InfoType] = None,
|
|
420
|
+
default: Optional[Any] = _NoArg.NO_ARG,
|
|
421
|
+
insert_default: Optional[Any] = _NoArg.NO_ARG,
|
|
422
|
+
onupdate: Optional[Any] = None,
|
|
423
|
+
server_default: Optional[_ServerDefaultArgument] = None,
|
|
424
|
+
server_onupdate: Optional[_ServerOnUpdateArgument] = None,
|
|
425
|
+
quote: Optional[bool] = None,
|
|
426
|
+
system: bool = False,
|
|
427
|
+
comment: Optional[str] = None,
|
|
428
|
+
sort_order: Union[_NoArg, int] = _NoArg.NO_ARG,
|
|
429
|
+
**kw: Any,
|
|
430
|
+
) -> UnmappedColumn[Any]:
|
|
431
|
+
r"""declare a :class:`_schema.Column` that is part of the
|
|
432
|
+
:class:`_schema.Table` for a :ref:`Declarative Table
|
|
433
|
+
<orm_declarative_table>` mapping, but which is not mapped by the ORM.
|
|
434
|
+
|
|
435
|
+
:func:`_orm.unmapped_column` is used with the :class:`_orm.Unmapped`
|
|
436
|
+
annotation::
|
|
437
|
+
|
|
438
|
+
class User(Base):
|
|
439
|
+
__tablename__ = "user"
|
|
440
|
+
|
|
441
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
442
|
+
name: Mapped[str]
|
|
443
|
+
|
|
444
|
+
legacy_name: Unmapped[str | None] = unmapped_column()
|
|
445
|
+
|
|
446
|
+
The resulting :class:`_schema.Column` is added to the
|
|
447
|
+
:class:`_schema.Table` in the same way as one produced by
|
|
448
|
+
:func:`_orm.mapped_column`, deriving its type and nullability from the
|
|
449
|
+
annotation, so that the column is present in DDL as well as for schema
|
|
450
|
+
comparison tools such as Alembic autogenerate. However, the column is
|
|
451
|
+
excluded from the mapping, the attribute is removed from the class, and
|
|
452
|
+
when using :ref:`orm_declarative_native_dataclasses` it is not part of
|
|
453
|
+
the dataclass.
|
|
454
|
+
|
|
455
|
+
A bare ``Unmapped[]`` annotation with no right-hand side is equivalent
|
|
456
|
+
to ``unmapped_column()`` with no arguments; however, see the note at
|
|
457
|
+
:class:`_orm.Unmapped` regarding typing tools and dataclasses.
|
|
458
|
+
|
|
459
|
+
As the attribute is not part of the Python model, ORM and dataclass
|
|
460
|
+
oriented arguments of :func:`_orm.mapped_column`, such as
|
|
461
|
+
``default_factory`` and ``deferred``, are not part of
|
|
462
|
+
:func:`_orm.unmapped_column`.
|
|
463
|
+
|
|
464
|
+
.. versionadded:: 2.1.5
|
|
465
|
+
|
|
466
|
+
.. seealso::
|
|
467
|
+
|
|
468
|
+
:ref:`orm_declarative_unmapped_column`
|
|
469
|
+
|
|
470
|
+
:param __name: String name to give to the :class:`_schema.Column`;
|
|
471
|
+
defaults to the attribute name.
|
|
472
|
+
:param __type: :class:`_types.TypeEngine` type or instance for the
|
|
473
|
+
:class:`_schema.Column`; if omitted, it is derived from the
|
|
474
|
+
annotation, or from a :class:`_schema.ForeignKey` if present.
|
|
475
|
+
:param \*args: Additional positional arguments such as
|
|
476
|
+
:class:`_schema.ForeignKey` and :class:`_schema.CheckConstraint`,
|
|
477
|
+
passed through to the :class:`_schema.Column`.
|
|
478
|
+
:param init: Always ``False``; this parameter is present so that
|
|
479
|
+
:pep:`681` typing tools omit the attribute from the ``__init__()``
|
|
480
|
+
method of :ref:`ORM mapped dataclasses
|
|
481
|
+
<orm_declarative_native_dataclasses>`.
|
|
482
|
+
:param nullable: whether the column is "NULL" or "NOT NULL"; if omitted,
|
|
483
|
+
derived from the annotation as with :func:`_orm.mapped_column`.
|
|
484
|
+
:param primary_key: indicates the :class:`_schema.Column` is part of the
|
|
485
|
+
table's primary key. As the mapper requires that its primary key
|
|
486
|
+
columns be mapped, the columns that the mapper should use for the
|
|
487
|
+
identity of the class must then be indicated using the
|
|
488
|
+
:paramref:`_orm.Mapper.primary_key` parameter; see
|
|
489
|
+
:ref:`orm_declarative_unmapped_column` for an example.
|
|
490
|
+
:param autoincrement: Passed directly to the
|
|
491
|
+
:paramref:`_schema.Column.autoincrement` parameter.
|
|
492
|
+
:param use_existing_column: see
|
|
493
|
+
:paramref:`_orm.mapped_column.use_existing_column`.
|
|
494
|
+
:param default: Passed directly to the :paramref:`_schema.Column.default`
|
|
495
|
+
parameter. As the attribute is not part of a dataclass, this is
|
|
496
|
+
strictly a Core column default.
|
|
497
|
+
:param insert_default: synonym for
|
|
498
|
+
:paramref:`_orm.unmapped_column.default`.
|
|
499
|
+
:param sort_order: see :paramref:`_orm.mapped_column.sort_order`.
|
|
500
|
+
:param \**kw: All remaining keyword arguments are passed through to the
|
|
501
|
+
constructor for the :class:`_schema.Column`, including dialect-specific
|
|
502
|
+
arguments.
|
|
503
|
+
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
return UnmappedColumn(
|
|
507
|
+
__name_pos,
|
|
508
|
+
__type_pos,
|
|
509
|
+
*args,
|
|
510
|
+
name=name,
|
|
511
|
+
type_=type_,
|
|
512
|
+
default=default,
|
|
513
|
+
insert_default=insert_default,
|
|
514
|
+
index=index,
|
|
515
|
+
unique=unique,
|
|
516
|
+
info=info,
|
|
517
|
+
nullable=nullable,
|
|
518
|
+
primary_key=primary_key,
|
|
519
|
+
autoincrement=autoincrement,
|
|
520
|
+
onupdate=onupdate,
|
|
521
|
+
server_default=server_default,
|
|
522
|
+
server_onupdate=server_onupdate,
|
|
523
|
+
use_existing_column=use_existing_column,
|
|
524
|
+
quote=quote,
|
|
525
|
+
comment=comment,
|
|
526
|
+
system=system,
|
|
527
|
+
sort_order=sort_order,
|
|
528
|
+
**kw,
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
|
|
398
532
|
def orm_insert_sentinel(
|
|
399
533
|
name: Optional[str] = None,
|
|
400
534
|
type_: Optional[_TypeEngineArgument[Any]] = None,
|
sqlalchemy/orm/base.py
CHANGED
|
@@ -881,6 +881,60 @@ class _DeclarativeMapped(Mapped[_T_co], _MappedAttribute[_T_co]):
|
|
|
881
881
|
return NotImplemented
|
|
882
882
|
|
|
883
883
|
|
|
884
|
+
class Unmapped(_MappedAnnotationBase[_T_co]):
|
|
885
|
+
"""Represent a database column that is part of the mapped
|
|
886
|
+
:class:`_schema.Table`, but is not mapped by the ORM.
|
|
887
|
+
|
|
888
|
+
The :class:`_orm.Unmapped` type annotation is used in an
|
|
889
|
+
:ref:`Annotated Declarative Table <orm_declarative_mapped_column>`
|
|
890
|
+
mapping, in conjunction with the :func:`_orm.unmapped_column`
|
|
891
|
+
construct, to indicate a column that should be present in the
|
|
892
|
+
:class:`_schema.Table` generated for the class, while not being part
|
|
893
|
+
of the Python model at all. The type and nullability of the
|
|
894
|
+
:class:`_schema.Column` are derived from the annotation in the same
|
|
895
|
+
way as for :class:`_orm.Mapped`.
|
|
896
|
+
|
|
897
|
+
E.g.::
|
|
898
|
+
|
|
899
|
+
class User(Base):
|
|
900
|
+
__tablename__ = "user"
|
|
901
|
+
|
|
902
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
903
|
+
name: Mapped[str]
|
|
904
|
+
|
|
905
|
+
legacy_name: Unmapped[str | None] = unmapped_column()
|
|
906
|
+
|
|
907
|
+
The ``legacy_name`` column is part of ``User.__table__``, but there is
|
|
908
|
+
no ``User.legacy_name`` attribute, and when using
|
|
909
|
+
:ref:`orm_declarative_native_dataclasses`, it is not part of the
|
|
910
|
+
dataclass.
|
|
911
|
+
|
|
912
|
+
A bare ``Unmapped[]`` annotation with no right-hand side is equivalent to
|
|
913
|
+
``unmapped_column()`` with no arguments. When using
|
|
914
|
+
:ref:`orm_declarative_native_dataclasses`, the attribute is omitted from
|
|
915
|
+
the dataclass and its ``__init__()`` method in both cases; however,
|
|
916
|
+
:pep:`681` typing tools are only able to determine this when
|
|
917
|
+
:func:`_orm.unmapped_column` is present, and otherwise see the attribute
|
|
918
|
+
as a required ``__init__()`` argument, so it should always be included
|
|
919
|
+
in that case.
|
|
920
|
+
|
|
921
|
+
.. versionadded:: 2.1.5
|
|
922
|
+
|
|
923
|
+
.. seealso::
|
|
924
|
+
|
|
925
|
+
:ref:`orm_declarative_unmapped_column`
|
|
926
|
+
|
|
927
|
+
:func:`_orm.unmapped_column`
|
|
928
|
+
|
|
929
|
+
"""
|
|
930
|
+
|
|
931
|
+
__slots__ = ()
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
def _is_unmapped_container(mapped_container: Optional[type]) -> bool:
|
|
935
|
+
return mapped_container is Unmapped
|
|
936
|
+
|
|
937
|
+
|
|
884
938
|
class DynamicMapped(_MappedAnnotationBase[_T_co]):
|
|
885
939
|
"""Represent the ORM mapped attribute type for a "dynamic" relationship.
|
|
886
940
|
|
|
@@ -1933,9 +1933,11 @@ class _BulkORMUpdate(_BulkUDCompileState, UpdateDMLState):
|
|
|
1933
1933
|
# attributes that were formerly modified instead get expired.
|
|
1934
1934
|
# this only gets hit if the session had pending changes
|
|
1935
1935
|
# and autoflush were set to False.
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1936
|
+
formerly_modified = attrib.intersection(dict_).difference(
|
|
1937
|
+
to_evaluate
|
|
1938
|
+
)
|
|
1939
|
+
if formerly_modified:
|
|
1940
|
+
state._expire_attributes(dict_, formerly_modified)
|
|
1939
1941
|
|
|
1940
1942
|
states.add(state)
|
|
1941
1943
|
session._register_altered(states)
|
sqlalchemy/orm/decl_api.py
CHANGED
|
@@ -43,6 +43,7 @@ from ._orm_constructors import deferred
|
|
|
43
43
|
from ._orm_constructors import mapped_column
|
|
44
44
|
from ._orm_constructors import relationship
|
|
45
45
|
from ._orm_constructors import synonym
|
|
46
|
+
from ._orm_constructors import unmapped_column
|
|
46
47
|
from .attributes import InstrumentedAttribute
|
|
47
48
|
from .base import _inspect_mapped_class
|
|
48
49
|
from .base import _is_mapped_class
|
|
@@ -166,6 +167,7 @@ class DeclarativeAttributeIntercept(
|
|
|
166
167
|
Composite,
|
|
167
168
|
Synonym,
|
|
168
169
|
mapped_column,
|
|
170
|
+
unmapped_column,
|
|
169
171
|
relationship,
|
|
170
172
|
composite,
|
|
171
173
|
synonym,
|
|
@@ -361,6 +363,7 @@ class declared_attr(interfaces._MappedAttribute[_T_co], _declared_attr_common):
|
|
|
361
363
|
user_id: Mapped[int] = mapped_column(ForeignKey("user_table.id"))
|
|
362
364
|
|
|
363
365
|
@declared_attr
|
|
366
|
+
@classmethod
|
|
364
367
|
def user(cls) -> Mapped["User"]:
|
|
365
368
|
return relationship("User")
|
|
366
369
|
|
|
@@ -371,6 +374,7 @@ class declared_attr(interfaces._MappedAttribute[_T_co], _declared_attr_common):
|
|
|
371
374
|
|
|
372
375
|
class CreateTableName:
|
|
373
376
|
@declared_attr.directive
|
|
377
|
+
@classmethod
|
|
374
378
|
def __tablename__(cls) -> str:
|
|
375
379
|
return cls.__name__.lower()
|
|
376
380
|
|
|
@@ -388,6 +392,7 @@ class declared_attr(interfaces._MappedAttribute[_T_co], _declared_attr_common):
|
|
|
388
392
|
type: Mapped[str] = mapped_column(String(50))
|
|
389
393
|
|
|
390
394
|
@declared_attr.directive
|
|
395
|
+
@classmethod
|
|
391
396
|
def __mapper_args__(cls) -> Dict[str, Any]:
|
|
392
397
|
if cls.__name__ == "Employee":
|
|
393
398
|
return {
|
|
@@ -401,11 +406,8 @@ class declared_attr(interfaces._MappedAttribute[_T_co], _declared_attr_common):
|
|
|
401
406
|
class Engineer(Employee):
|
|
402
407
|
pass
|
|
403
408
|
|
|
404
|
-
:class:`_orm.declared_attr`
|
|
405
|
-
|
|
406
|
-
runtime perspective, however may be needed in order to support :pep:`484`
|
|
407
|
-
typing tools that don't otherwise recognize the decorated function as
|
|
408
|
-
having class-level behaviors for the ``cls`` parameter::
|
|
409
|
+
:class:`_orm.declared_attr` can also be applied to non-directive
|
|
410
|
+
attributes such as :func:`_orm.column_property`::
|
|
409
411
|
|
|
410
412
|
class SomethingMixin:
|
|
411
413
|
x: Mapped[int]
|
|
@@ -416,6 +418,18 @@ class declared_attr(interfaces._MappedAttribute[_T_co], _declared_attr_common):
|
|
|
416
418
|
def x_plus_y(cls) -> Mapped[int]:
|
|
417
419
|
return column_property(cls.x + cls.y)
|
|
418
420
|
|
|
421
|
+
.. tip::
|
|
422
|
+
|
|
423
|
+
The examples above also apply the ``@classmethod`` decorator. This is
|
|
424
|
+
not required at runtime, however :pep:`484` typing tools have no way to
|
|
425
|
+
recognize ``def foo(cls)`` as a classmethod unless ``@classmethod`` is
|
|
426
|
+
present. Adding ``@classmethod`` allows typing tools to correctly
|
|
427
|
+
infer the ``cls`` parameter as the class, which is important when
|
|
428
|
+
accessing mapped attributes such as ``cls.ph1`` inside the body of
|
|
429
|
+
the method. :class:`_orm.declared_attr` supports both
|
|
430
|
+
``@declared_attr`` and ``@declared_attr.directive`` being combined
|
|
431
|
+
with ``@classmethod``.
|
|
432
|
+
|
|
419
433
|
.. versionadded:: 2.0 - :class:`_orm.declared_attr` can accommodate a
|
|
420
434
|
function decorated with ``@classmethod`` to help with :pep:`484`
|
|
421
435
|
integration where needed.
|
|
@@ -1701,6 +1715,7 @@ class registry(EventTarget):
|
|
|
1701
1715
|
Composite,
|
|
1702
1716
|
Synonym,
|
|
1703
1717
|
mapped_column,
|
|
1718
|
+
unmapped_column,
|
|
1704
1719
|
relationship,
|
|
1705
1720
|
composite,
|
|
1706
1721
|
synonym,
|
|
@@ -2165,6 +2180,7 @@ def as_declarative(**kw: Any) -> Callable[[Type[_T]], Type[_T]]:
|
|
|
2165
2180
|
Composite,
|
|
2166
2181
|
Synonym,
|
|
2167
2182
|
mapped_column,
|
|
2183
|
+
unmapped_column,
|
|
2168
2184
|
relationship,
|
|
2169
2185
|
composite,
|
|
2170
2186
|
synonym,
|
|
@@ -2250,6 +2266,7 @@ def _inspect_decl_meta(cls: Type[Any]) -> Optional[Mapper[Any]]:
|
|
|
2250
2266
|
Composite,
|
|
2251
2267
|
Synonym,
|
|
2252
2268
|
mapped_column,
|
|
2269
|
+
unmapped_column,
|
|
2253
2270
|
relationship,
|
|
2254
2271
|
composite,
|
|
2255
2272
|
synonym,
|