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 CHANGED
@@ -285,7 +285,7 @@ from .types import Uuid as Uuid
285
285
  from .types import VARBINARY as VARBINARY
286
286
  from .types import VARCHAR as VARCHAR
287
287
 
288
- __version__ = "2.1.4"
288
+ __version__ = "2.1.5"
289
289
 
290
290
 
291
291
  def __go(lcls: Any) -> None:
@@ -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
- r"FOREIGN KEY \((.*?)\) "
5074
- rf"REFERENCES (?:({qtoken})\.)?({qtoken})\(((?:{qtoken}(?: *, *)?)+)\)" # noqa: E501
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
- if self._still_open_and_dbapi_connection_is_valid:
762
- pool_proxied_connection = self._dbapi_connection
763
- assert pool_proxied_connection is not None
764
- pool_proxied_connection.invalidate(exception)
765
-
766
- self._dbapi_connection = None
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.
@@ -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
- try:
751
- dialect.initialize(c)
752
- finally:
753
- # note that "invalidated" and "closed" are mutually
754
- # exclusive in 1.4 Connection.
755
- if not c.invalidated and not c.closed:
756
- # transaction is rolled back otherwise, tested by
757
- # test/dialect/postgresql/test_dialect.py
758
- # ::MiscBackendTest::test_initial_transaction_state
759
- dialect.do_rollback(c.connection)
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,
@@ -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
- to_expire = attrib.intersection(dict_).difference(to_evaluate)
1937
- if to_expire:
1938
- state._expire_attributes(dict_, to_expire)
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)
@@ -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` supports decorating functions that are
405
- explicitly decorated with ``@classmethod``. This is never necessary from a
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,