Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py: 28%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1445 statements  

1# orm/session.py 

2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7 

8"""Provides the Session class and related utilities.""" 

9 

10from __future__ import annotations 

11 

12import contextlib 

13from enum import Enum 

14import itertools 

15import sys 

16import typing 

17from typing import Any 

18from typing import Callable 

19from typing import cast 

20from typing import Dict 

21from typing import Generic 

22from typing import Iterable 

23from typing import Iterator 

24from typing import List 

25from typing import NoReturn 

26from typing import Optional 

27from typing import overload 

28from typing import Protocol 

29from typing import Sequence 

30from typing import Set 

31from typing import Tuple 

32from typing import Type 

33from typing import TYPE_CHECKING 

34from typing import TypeVar 

35from typing import Union 

36import weakref 

37 

38from . import attributes 

39from . import bulk_persistence 

40from . import context 

41from . import descriptor_props 

42from . import exc 

43from . import identity 

44from . import loading 

45from . import query 

46from . import state as statelib 

47from ._typing import _O 

48from ._typing import insp_is_mapper 

49from ._typing import is_composite_class 

50from ._typing import is_orm_option 

51from ._typing import is_user_defined_option 

52from .base import _class_to_mapper 

53from .base import _none_set 

54from .base import _state_mapper 

55from .base import instance_str 

56from .base import LoaderCallableStatus 

57from .base import object_mapper 

58from .base import object_state 

59from .base import PassiveFlag 

60from .base import state_str 

61from .context import _ORMCompileState 

62from .context import FromStatement 

63from .identity import IdentityMap 

64from .query import Query 

65from .state import InstanceState 

66from .state_changes import _StateChange 

67from .state_changes import _StateChangeState 

68from .state_changes import _StateChangeStates 

69from .unitofwork import UOWTransaction 

70from .. import engine 

71from .. import exc as sa_exc 

72from .. import sql 

73from .. import util 

74from ..engine import Connection 

75from ..engine import Engine 

76from ..engine.util import TransactionalContext 

77from ..event import dispatcher 

78from ..event import EventTarget 

79from ..inspection import inspect 

80from ..inspection import Inspectable 

81from ..sql import coercions 

82from ..sql import dml 

83from ..sql import roles 

84from ..sql import Select 

85from ..sql import TableClause 

86from ..sql import visitors 

87from ..sql.base import _NoArg 

88from ..sql.base import CompileState 

89from ..sql.schema import Table 

90from ..sql.selectable import ForUpdateArg 

91from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

92from ..util import deprecated_params 

93from ..util import IdentitySet 

94from ..util.typing import Literal 

95from ..util.typing import TupleAny 

96from ..util.typing import TypeVarTuple 

97from ..util.typing import Unpack 

98 

99 

100if typing.TYPE_CHECKING: 

101 from ._typing import _EntityType 

102 from ._typing import _IdentityKeyType 

103 from ._typing import _InstanceDict 

104 from ._typing import OrmExecuteOptionsParameter 

105 from .interfaces import ORMOption 

106 from .interfaces import UserDefinedOption 

107 from .mapper import Mapper 

108 from .path_registry import PathRegistry 

109 from .query import RowReturningQuery 

110 from ..engine import CursorResult 

111 from ..engine import Result 

112 from ..engine import Row 

113 from ..engine import RowMapping 

114 from ..engine.base import Transaction 

115 from ..engine.base import TwoPhaseTransaction 

116 from ..engine.interfaces import _CoreAnyExecuteParams 

117 from ..engine.interfaces import _CoreSingleExecuteParams 

118 from ..engine.interfaces import _ExecuteOptions 

119 from ..engine.interfaces import CoreExecuteOptionsParameter 

120 from ..engine.result import ScalarResult 

121 from ..event import _InstanceLevelDispatch 

122 from ..sql._typing import _ColumnsClauseArgument 

123 from ..sql._typing import _InfoType 

124 from ..sql._typing import _T0 

125 from ..sql._typing import _T1 

126 from ..sql._typing import _T2 

127 from ..sql._typing import _T3 

128 from ..sql._typing import _T4 

129 from ..sql._typing import _T5 

130 from ..sql._typing import _T6 

131 from ..sql._typing import _T7 

132 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

133 from ..sql.base import Executable 

134 from ..sql.base import ExecutableOption 

135 from ..sql.dml import UpdateBase 

136 from ..sql.elements import ClauseElement 

137 from ..sql.roles import TypedColumnsClauseRole 

138 from ..sql.selectable import ForUpdateParameter 

139 from ..sql.selectable import TypedReturnsRows 

140 

141_T = TypeVar("_T", bound=Any) 

142_Ts = TypeVarTuple("_Ts") 

143 

144__all__ = [ 

145 "Session", 

146 "SessionTransaction", 

147 "sessionmaker", 

148 "ORMExecuteState", 

149 "close_all_sessions", 

150 "make_transient", 

151 "make_transient_to_detached", 

152 "object_session", 

153] 

154 

155_sessions: weakref.WeakValueDictionary[int, Session] = ( 

156 weakref.WeakValueDictionary() 

157) 

158"""Weak-referencing dictionary of :class:`.Session` objects. 

159""" 

160 

161statelib._sessions = _sessions 

162 

163_PKIdentityArgument = Union[Any, Tuple[Any, ...]] 

164 

165_BindArguments = Dict[str, Any] 

166 

167_EntityBindKey = Union[Type[_O], "Mapper[_O]"] 

168_SessionBindKey = Union[Type[Any], "Mapper[Any]", "TableClause", str] 

169_SessionBind = Union["Engine", "Connection"] 

170 

171JoinTransactionMode = Literal[ 

172 "conditional_savepoint", 

173 "rollback_only", 

174 "control_fully", 

175 "create_savepoint", 

176] 

177 

178 

179class _ConnectionCallableProto(Protocol): 

180 """a callable that returns a :class:`.Connection` given an instance. 

181 

182 This callable, when present on a :class:`.Session`, is called only from the 

183 ORM's persistence mechanism (i.e. the unit of work flush process) to allow 

184 for connection-per-instance schemes (i.e. horizontal sharding) to be used 

185 as persistence time. 

186 

187 This callable is not present on a plain :class:`.Session`, however 

188 is established when using the horizontal sharding extension. 

189 

190 """ 

191 

192 def __call__( 

193 self, 

194 mapper: Optional[Mapper[Any]] = None, 

195 instance: Optional[object] = None, 

196 **kw: Any, 

197 ) -> Connection: ... 

198 

199 

200def _state_session(state: InstanceState[Any]) -> Optional[Session]: 

201 """Given an :class:`.InstanceState`, return the :class:`.Session` 

202 associated, if any. 

203 """ 

204 return state.session 

205 

206 

207class _SessionClassMethods: 

208 """Class-level methods for :class:`.Session`, :class:`.sessionmaker`.""" 

209 

210 @classmethod 

211 @util.preload_module("sqlalchemy.orm.util") 

212 def identity_key( 

213 cls, 

214 class_: Optional[Type[Any]] = None, 

215 ident: Union[Any, Tuple[Any, ...]] = None, 

216 *, 

217 instance: Optional[Any] = None, 

218 row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None, 

219 identity_token: Optional[Any] = None, 

220 ) -> _IdentityKeyType[Any]: 

221 """Return an identity key. 

222 

223 This is an alias of :func:`.util.identity_key`. 

224 

225 """ 

226 return util.preloaded.orm_util.identity_key( 

227 class_, 

228 ident, 

229 instance=instance, 

230 row=row, 

231 identity_token=identity_token, 

232 ) 

233 

234 @classmethod 

235 def object_session(cls, instance: object) -> Optional[Session]: 

236 """Return the :class:`.Session` to which an object belongs. 

237 

238 This is an alias of :func:`.object_session`. 

239 

240 """ 

241 

242 return object_session(instance) 

243 

244 

245class SessionTransactionState(_StateChangeState): 

246 ACTIVE = 1 

247 PREPARED = 2 

248 COMMITTED = 3 

249 DEACTIVE = 4 

250 CLOSED = 5 

251 PROVISIONING_CONNECTION = 6 

252 

253 

254# backwards compatibility 

255ACTIVE, PREPARED, COMMITTED, DEACTIVE, CLOSED, PROVISIONING_CONNECTION = tuple( 

256 SessionTransactionState 

257) 

258 

259 

260class ORMExecuteState(util.MemoizedSlots): 

261 """Represents a call to the :meth:`_orm.Session.execute` method, as passed 

262 to the :meth:`.SessionEvents.do_orm_execute` event hook. 

263 

264 .. versionadded:: 1.4 

265 

266 .. seealso:: 

267 

268 :ref:`session_execute_events` - top level documentation on how 

269 to use :meth:`_orm.SessionEvents.do_orm_execute` 

270 

271 """ 

272 

273 __slots__ = ( 

274 "session", 

275 "statement", 

276 "parameters", 

277 "execution_options", 

278 "local_execution_options", 

279 "bind_arguments", 

280 "identity_token", 

281 "_compile_state_cls", 

282 "_starting_event_idx", 

283 "_events_todo", 

284 "_update_execution_options", 

285 ) 

286 

287 session: Session 

288 """The :class:`_orm.Session` in use.""" 

289 

290 statement: Executable 

291 """The SQL statement being invoked. 

292 

293 For an ORM selection as would 

294 be retrieved from :class:`_orm.Query`, this is an instance of 

295 :class:`_sql.select` that was generated from the ORM query. 

296 """ 

297 

298 parameters: Optional[_CoreAnyExecuteParams] 

299 """Dictionary of parameters that was passed to 

300 :meth:`_orm.Session.execute`.""" 

301 

302 execution_options: _ExecuteOptions 

303 """The complete dictionary of current execution options. 

304 

305 This is a merge of the statement level options with the 

306 locally passed execution options. 

307 

308 .. seealso:: 

309 

310 :attr:`_orm.ORMExecuteState.local_execution_options` 

311 

312 :meth:`_sql.Executable.execution_options` 

313 

314 :ref:`orm_queryguide_execution_options` 

315 

316 """ 

317 

318 local_execution_options: _ExecuteOptions 

319 """Dictionary view of the execution options passed to the 

320 :meth:`.Session.execute` method. 

321 

322 This does not include options that may be associated with the statement 

323 being invoked. 

324 

325 .. seealso:: 

326 

327 :attr:`_orm.ORMExecuteState.execution_options` 

328 

329 """ 

330 

331 bind_arguments: _BindArguments 

332 """The dictionary passed as the 

333 :paramref:`_orm.Session.execute.bind_arguments` dictionary. 

334 

335 This dictionary may be used by extensions to :class:`_orm.Session` to pass 

336 arguments that will assist in determining amongst a set of database 

337 connections which one should be used to invoke this statement. 

338 

339 """ 

340 

341 _compile_state_cls: Optional[Type[_ORMCompileState]] 

342 _starting_event_idx: int 

343 _events_todo: List[Any] 

344 _update_execution_options: Optional[_ExecuteOptions] 

345 

346 def __init__( 

347 self, 

348 session: Session, 

349 statement: Executable, 

350 parameters: Optional[_CoreAnyExecuteParams], 

351 execution_options: _ExecuteOptions, 

352 bind_arguments: _BindArguments, 

353 compile_state_cls: Optional[Type[_ORMCompileState]], 

354 events_todo: List[_InstanceLevelDispatch[Session]], 

355 ): 

356 """Construct a new :class:`_orm.ORMExecuteState`. 

357 

358 this object is constructed internally. 

359 

360 """ 

361 self.session = session 

362 self.statement = statement 

363 self.parameters = parameters 

364 self.local_execution_options = execution_options 

365 self.execution_options = statement._execution_options.union( 

366 execution_options 

367 ) 

368 self.bind_arguments = bind_arguments 

369 self._compile_state_cls = compile_state_cls 

370 self._events_todo = list(events_todo) 

371 

372 def _remaining_events(self) -> List[_InstanceLevelDispatch[Session]]: 

373 return self._events_todo[self._starting_event_idx + 1 :] 

374 

375 def invoke_statement( 

376 self, 

377 statement: Optional[Executable] = None, 

378 params: Optional[_CoreAnyExecuteParams] = None, 

379 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

380 bind_arguments: Optional[_BindArguments] = None, 

381 ) -> Result[Unpack[TupleAny]]: 

382 """Execute the statement represented by this 

383 :class:`.ORMExecuteState`, without re-invoking events that have 

384 already proceeded. 

385 

386 This method essentially performs a re-entrant execution of the current 

387 statement for which the :meth:`.SessionEvents.do_orm_execute` event is 

388 being currently invoked. The use case for this is for event handlers 

389 that want to override how the ultimate 

390 :class:`_engine.Result` object is returned, such as for schemes that 

391 retrieve results from an offline cache or which concatenate results 

392 from multiple executions. 

393 

394 When the :class:`_engine.Result` object is returned by the actual 

395 handler function within :meth:`_orm.SessionEvents.do_orm_execute` and 

396 is propagated to the calling 

397 :meth:`_orm.Session.execute` method, the remainder of the 

398 :meth:`_orm.Session.execute` method is preempted and the 

399 :class:`_engine.Result` object is returned to the caller of 

400 :meth:`_orm.Session.execute` immediately. 

401 

402 :param statement: optional statement to be invoked, in place of the 

403 statement currently represented by :attr:`.ORMExecuteState.statement`. 

404 

405 :param params: optional dictionary of parameters or list of parameters 

406 which will be merged into the existing 

407 :attr:`.ORMExecuteState.parameters` of this :class:`.ORMExecuteState`. 

408 

409 .. versionchanged:: 2.0 a list of parameter dictionaries is accepted 

410 for executemany executions. 

411 

412 :param execution_options: optional dictionary of execution options 

413 will be merged into the existing 

414 :attr:`.ORMExecuteState.execution_options` of this 

415 :class:`.ORMExecuteState`. 

416 

417 :param bind_arguments: optional dictionary of bind_arguments 

418 which will be merged amongst the current 

419 :attr:`.ORMExecuteState.bind_arguments` 

420 of this :class:`.ORMExecuteState`. 

421 

422 :return: a :class:`_engine.Result` object with ORM-level results. 

423 

424 .. seealso:: 

425 

426 :ref:`do_orm_execute_re_executing` - background and examples on the 

427 appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`. 

428 

429 

430 """ 

431 

432 if statement is None: 

433 statement = self.statement 

434 

435 _bind_arguments = dict(self.bind_arguments) 

436 if bind_arguments: 

437 _bind_arguments.update(bind_arguments) 

438 _bind_arguments["_sa_skip_events"] = True 

439 

440 _params: Optional[_CoreAnyExecuteParams] 

441 if params: 

442 if self.is_executemany: 

443 _params = [] 

444 exec_many_parameters = cast( 

445 "List[Dict[str, Any]]", self.parameters 

446 ) 

447 for _existing_params, _new_params in itertools.zip_longest( 

448 exec_many_parameters, 

449 cast("List[Dict[str, Any]]", params), 

450 ): 

451 if _existing_params is None or _new_params is None: 

452 raise sa_exc.InvalidRequestError( 

453 f"Can't apply executemany parameters to " 

454 f"statement; number of parameter sets passed to " 

455 f"Session.execute() ({len(exec_many_parameters)}) " 

456 f"does not match number of parameter sets given " 

457 f"to ORMExecuteState.invoke_statement() " 

458 f"({len(params)})" 

459 ) 

460 _existing_params = dict(_existing_params) 

461 _existing_params.update(_new_params) 

462 _params.append(_existing_params) 

463 else: 

464 _params = dict(cast("Dict[str, Any]", self.parameters)) 

465 _params.update(cast("Dict[str, Any]", params)) 

466 else: 

467 _params = self.parameters 

468 

469 _execution_options = self.local_execution_options 

470 if execution_options: 

471 _execution_options = _execution_options.union(execution_options) 

472 

473 return self.session._execute_internal( 

474 statement, 

475 _params, 

476 execution_options=_execution_options, 

477 bind_arguments=_bind_arguments, 

478 _parent_execute_state=self, 

479 ) 

480 

481 @property 

482 def bind_mapper(self) -> Optional[Mapper[Any]]: 

483 """Return the :class:`_orm.Mapper` that is the primary "bind" mapper. 

484 

485 For an :class:`_orm.ORMExecuteState` object invoking an ORM 

486 statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement` 

487 attribute is ``True``, this attribute will return the 

488 :class:`_orm.Mapper` that is considered to be the "primary" mapper 

489 of the statement. The term "bind mapper" refers to the fact that 

490 a :class:`_orm.Session` object may be "bound" to multiple 

491 :class:`_engine.Engine` objects keyed to mapped classes, and the 

492 "bind mapper" determines which of those :class:`_engine.Engine` objects 

493 would be selected. 

494 

495 For a statement that is invoked against a single mapped class, 

496 :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable 

497 way of getting this mapper. 

498 

499 .. versionadded:: 1.4.0b2 

500 

501 .. seealso:: 

502 

503 :attr:`_orm.ORMExecuteState.all_mappers` 

504 

505 

506 """ 

507 mp: Optional[Mapper[Any]] = self.bind_arguments.get("mapper", None) 

508 return mp 

509 

510 @property 

511 def all_mappers(self) -> Sequence[Mapper[Any]]: 

512 """Return a sequence of all :class:`_orm.Mapper` objects that are 

513 involved at the top level of this statement. 

514 

515 By "top level" we mean those :class:`_orm.Mapper` objects that would 

516 be represented in the result set rows for a :func:`_sql.select` 

517 query, or for a :func:`_dml.update` or :func:`_dml.delete` query, 

518 the mapper that is the main subject of the UPDATE or DELETE. 

519 

520 .. versionadded:: 1.4.0b2 

521 

522 .. seealso:: 

523 

524 :attr:`_orm.ORMExecuteState.bind_mapper` 

525 

526 

527 

528 """ 

529 if not self.is_orm_statement: 

530 return [] 

531 elif isinstance(self.statement, (Select, FromStatement)): 

532 result = [] 

533 seen = set() 

534 for d in self.statement.column_descriptions: 

535 ent = d["entity"] 

536 if ent: 

537 insp = inspect(ent, raiseerr=False) 

538 if insp and insp.mapper and insp.mapper not in seen: 

539 seen.add(insp.mapper) 

540 result.append(insp.mapper) 

541 return result 

542 elif self.statement.is_dml and self.bind_mapper: 

543 return [self.bind_mapper] 

544 else: 

545 return [] 

546 

547 @property 

548 def is_orm_statement(self) -> bool: 

549 """return True if the operation is an ORM statement. 

550 

551 This indicates that the select(), insert(), update(), or delete() 

552 being invoked contains ORM entities as subjects. For a statement 

553 that does not have ORM entities and instead refers only to 

554 :class:`.Table` metadata, it is invoked as a Core SQL statement 

555 and no ORM-level automation takes place. 

556 

557 """ 

558 return self._compile_state_cls is not None 

559 

560 @property 

561 def is_executemany(self) -> bool: 

562 """return True if the parameters are a multi-element list of 

563 dictionaries with more than one dictionary. 

564 

565 .. versionadded:: 2.0 

566 

567 """ 

568 return isinstance(self.parameters, list) 

569 

570 @property 

571 def is_select(self) -> bool: 

572 """return True if this is a SELECT operation. 

573 

574 .. versionchanged:: 2.0.30 - the attribute is also True for a 

575 :meth:`_sql.Select.from_statement` construct that is itself against 

576 a :class:`_sql.Select` construct, such as 

577 ``select(Entity).from_statement(select(..))`` 

578 

579 """ 

580 return self.statement.is_select 

581 

582 @property 

583 def is_from_statement(self) -> bool: 

584 """return True if this operation is a 

585 :meth:`_sql.Select.from_statement` operation. 

586 

587 This is independent from :attr:`_orm.ORMExecuteState.is_select`, as a 

588 ``select().from_statement()`` construct can be used with 

589 INSERT/UPDATE/DELETE RETURNING types of statements as well. 

590 :attr:`_orm.ORMExecuteState.is_select` will only be set if the 

591 :meth:`_sql.Select.from_statement` is itself against a 

592 :class:`_sql.Select` construct. 

593 

594 .. versionadded:: 2.0.30 

595 

596 """ 

597 return self.statement.is_from_statement 

598 

599 @property 

600 def is_insert(self) -> bool: 

601 """return True if this is an INSERT operation. 

602 

603 .. versionchanged:: 2.0.30 - the attribute is also True for a 

604 :meth:`_sql.Select.from_statement` construct that is itself against 

605 a :class:`_sql.Insert` construct, such as 

606 ``select(Entity).from_statement(insert(..))`` 

607 

608 """ 

609 return self.statement.is_dml and self.statement.is_insert 

610 

611 @property 

612 def is_update(self) -> bool: 

613 """return True if this is an UPDATE operation. 

614 

615 .. versionchanged:: 2.0.30 - the attribute is also True for a 

616 :meth:`_sql.Select.from_statement` construct that is itself against 

617 a :class:`_sql.Update` construct, such as 

618 ``select(Entity).from_statement(update(..))`` 

619 

620 """ 

621 return self.statement.is_dml and self.statement.is_update 

622 

623 @property 

624 def is_delete(self) -> bool: 

625 """return True if this is a DELETE operation. 

626 

627 .. versionchanged:: 2.0.30 - the attribute is also True for a 

628 :meth:`_sql.Select.from_statement` construct that is itself against 

629 a :class:`_sql.Delete` construct, such as 

630 ``select(Entity).from_statement(delete(..))`` 

631 

632 """ 

633 return self.statement.is_dml and self.statement.is_delete 

634 

635 @property 

636 def _is_crud(self) -> bool: 

637 return isinstance(self.statement, (dml.Update, dml.Delete)) 

638 

639 def update_execution_options(self, **opts: Any) -> None: 

640 """Update the local execution options with new values.""" 

641 self.local_execution_options = self.local_execution_options.union(opts) 

642 

643 def _orm_compile_options( 

644 self, 

645 ) -> Optional[ 

646 Union[ 

647 context._ORMCompileState.default_compile_options, 

648 Type[context._ORMCompileState.default_compile_options], 

649 ] 

650 ]: 

651 if not self.is_select: 

652 return None 

653 try: 

654 opts = self.statement._compile_options 

655 except AttributeError: 

656 return None 

657 

658 if opts is not None and opts.isinstance( 

659 context._ORMCompileState.default_compile_options 

660 ): 

661 return opts # type: ignore 

662 else: 

663 return None 

664 

665 @property 

666 def lazy_loaded_from(self) -> Optional[InstanceState[Any]]: 

667 """An :class:`.InstanceState` that is using this statement execution 

668 for a lazy load operation. 

669 

670 The primary rationale for this attribute is to support the horizontal 

671 sharding extension, where it is available within specific query 

672 execution time hooks created by this extension. To that end, the 

673 attribute is only intended to be meaningful at **query execution 

674 time**, and importantly not any time prior to that, including query 

675 compilation time. 

676 

677 """ 

678 return self.load_options._lazy_loaded_from 

679 

680 @property 

681 def loader_strategy_path(self) -> Optional[PathRegistry]: 

682 """Return the :class:`.PathRegistry` for the current load path. 

683 

684 This object represents the "path" in a query along relationships 

685 when a particular object or collection is being loaded. 

686 

687 """ 

688 opts = self._orm_compile_options() 

689 if opts is not None: 

690 return opts._current_path 

691 else: 

692 return None 

693 

694 @property 

695 def is_column_load(self) -> bool: 

696 """Return True if the operation is refreshing column-oriented 

697 attributes on an existing ORM object. 

698 

699 This occurs during operations such as :meth:`_orm.Session.refresh`, 

700 as well as when an attribute deferred by :func:`_orm.defer` is 

701 being loaded, or an attribute that was expired either directly 

702 by :meth:`_orm.Session.expire` or via a commit operation is being 

703 loaded. 

704 

705 Handlers will very likely not want to add any options to queries 

706 when such an operation is occurring as the query should be a straight 

707 primary key fetch which should not have any additional WHERE criteria, 

708 and loader options travelling with the instance 

709 will have already been added to the query. 

710 

711 .. versionadded:: 1.4.0b2 

712 

713 .. seealso:: 

714 

715 :attr:`_orm.ORMExecuteState.is_relationship_load` 

716 

717 """ 

718 opts = self._orm_compile_options() 

719 return opts is not None and opts._for_refresh_state 

720 

721 @property 

722 def is_relationship_load(self) -> bool: 

723 """Return True if this load is loading objects on behalf of a 

724 relationship. 

725 

726 This means, the loader in effect is either a LazyLoader, 

727 SelectInLoader, SubqueryLoader, or similar, and the entire 

728 SELECT statement being emitted is on behalf of a relationship 

729 load. 

730 

731 Handlers will very likely not want to add any options to queries 

732 when such an operation is occurring, as loader options are already 

733 capable of being propagated to relationship loaders and should 

734 be already present. 

735 

736 .. seealso:: 

737 

738 :attr:`_orm.ORMExecuteState.is_column_load` 

739 

740 """ 

741 opts = self._orm_compile_options() 

742 if opts is None: 

743 return False 

744 path = self.loader_strategy_path 

745 return path is not None and not path.is_root 

746 

747 @property 

748 def load_options( 

749 self, 

750 ) -> Union[ 

751 context.QueryContext.default_load_options, 

752 Type[context.QueryContext.default_load_options], 

753 ]: 

754 """Return the load_options that will be used for this execution.""" 

755 

756 if not self.is_select: 

757 raise sa_exc.InvalidRequestError( 

758 "This ORM execution is not against a SELECT statement " 

759 "so there are no load options." 

760 ) 

761 

762 lo: Union[ 

763 context.QueryContext.default_load_options, 

764 Type[context.QueryContext.default_load_options], 

765 ] = self.execution_options.get( 

766 "_sa_orm_load_options", context.QueryContext.default_load_options 

767 ) 

768 return lo 

769 

770 @property 

771 def update_delete_options( 

772 self, 

773 ) -> Union[ 

774 bulk_persistence._BulkUDCompileState.default_update_options, 

775 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

776 ]: 

777 """Return the update_delete_options that will be used for this 

778 execution.""" 

779 

780 if not self._is_crud: 

781 raise sa_exc.InvalidRequestError( 

782 "This ORM execution is not against an UPDATE or DELETE " 

783 "statement so there are no update options." 

784 ) 

785 uo: Union[ 

786 bulk_persistence._BulkUDCompileState.default_update_options, 

787 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

788 ] = self.execution_options.get( 

789 "_sa_orm_update_options", 

790 bulk_persistence._BulkUDCompileState.default_update_options, 

791 ) 

792 return uo 

793 

794 @property 

795 def _non_compile_orm_options(self) -> Sequence[ORMOption]: 

796 return [ 

797 opt 

798 for opt in self.statement._with_options 

799 if is_orm_option(opt) and not opt._is_compile_state 

800 ] 

801 

802 @property 

803 def user_defined_options(self) -> Sequence[UserDefinedOption]: 

804 """The sequence of :class:`.UserDefinedOptions` that have been 

805 associated with the statement being invoked. 

806 

807 """ 

808 return [ 

809 opt 

810 for opt in self.statement._with_options 

811 if is_user_defined_option(opt) 

812 ] 

813 

814 

815class SessionTransactionOrigin(Enum): 

816 """indicates the origin of a :class:`.SessionTransaction`. 

817 

818 This enumeration is present on the 

819 :attr:`.SessionTransaction.origin` attribute of any 

820 :class:`.SessionTransaction` object. 

821 

822 .. versionadded:: 2.0 

823 

824 """ 

825 

826 AUTOBEGIN = 0 

827 """transaction were started by autobegin""" 

828 

829 BEGIN = 1 

830 """transaction were started by calling :meth:`_orm.Session.begin`""" 

831 

832 BEGIN_NESTED = 2 

833 """tranaction were started by :meth:`_orm.Session.begin_nested`""" 

834 

835 SUBTRANSACTION = 3 

836 """transaction is an internal "subtransaction" """ 

837 

838 

839class SessionTransaction(_StateChange, TransactionalContext): 

840 """A :class:`.Session`-level transaction. 

841 

842 :class:`.SessionTransaction` is produced from the 

843 :meth:`_orm.Session.begin` 

844 and :meth:`_orm.Session.begin_nested` methods. It's largely an internal 

845 object that in modern use provides a context manager for session 

846 transactions. 

847 

848 Documentation on interacting with :class:`_orm.SessionTransaction` is 

849 at: :ref:`unitofwork_transaction`. 

850 

851 

852 .. versionchanged:: 1.4 The scoping and API methods to work with the 

853 :class:`_orm.SessionTransaction` object directly have been simplified. 

854 

855 .. seealso:: 

856 

857 :ref:`unitofwork_transaction` 

858 

859 :meth:`.Session.begin` 

860 

861 :meth:`.Session.begin_nested` 

862 

863 :meth:`.Session.rollback` 

864 

865 :meth:`.Session.commit` 

866 

867 :meth:`.Session.in_transaction` 

868 

869 :meth:`.Session.in_nested_transaction` 

870 

871 :meth:`.Session.get_transaction` 

872 

873 :meth:`.Session.get_nested_transaction` 

874 

875 

876 """ 

877 

878 _rollback_exception: Optional[BaseException] = None 

879 

880 _connections: Dict[ 

881 Union[Engine, Connection], Tuple[Connection, Transaction, bool, bool] 

882 ] 

883 session: Session 

884 _parent: Optional[SessionTransaction] 

885 

886 _state: SessionTransactionState 

887 

888 _new: weakref.WeakKeyDictionary[InstanceState[Any], object] 

889 _deleted: weakref.WeakKeyDictionary[InstanceState[Any], object] 

890 _dirty: weakref.WeakKeyDictionary[InstanceState[Any], object] 

891 _key_switches: weakref.WeakKeyDictionary[ 

892 InstanceState[Any], Tuple[Any, Any] 

893 ] 

894 

895 origin: SessionTransactionOrigin 

896 """Origin of this :class:`_orm.SessionTransaction`. 

897 

898 Refers to a :class:`.SessionTransactionOrigin` instance which is an 

899 enumeration indicating the source event that led to constructing 

900 this :class:`_orm.SessionTransaction`. 

901 

902 .. versionadded:: 2.0 

903 

904 """ 

905 

906 nested: bool = False 

907 """Indicates if this is a nested, or SAVEPOINT, transaction. 

908 

909 When :attr:`.SessionTransaction.nested` is True, it is expected 

910 that :attr:`.SessionTransaction.parent` will be present as well, 

911 linking to the enclosing :class:`.SessionTransaction`. 

912 

913 .. seealso:: 

914 

915 :attr:`.SessionTransaction.origin` 

916 

917 """ 

918 

919 def __init__( 

920 self, 

921 session: Session, 

922 origin: SessionTransactionOrigin, 

923 parent: Optional[SessionTransaction] = None, 

924 ): 

925 TransactionalContext._trans_ctx_check(session) 

926 

927 self.session = session 

928 self._connections = {} 

929 self._parent = parent 

930 self.nested = nested = origin is SessionTransactionOrigin.BEGIN_NESTED 

931 self.origin = origin 

932 

933 if session._close_state is _SessionCloseState.CLOSED: 

934 raise sa_exc.InvalidRequestError( 

935 "This Session has been permanently closed and is unable " 

936 "to handle any more transaction requests." 

937 ) 

938 

939 if nested: 

940 if not parent: 

941 raise sa_exc.InvalidRequestError( 

942 "Can't start a SAVEPOINT transaction when no existing " 

943 "transaction is in progress" 

944 ) 

945 

946 self._previous_nested_transaction = session._nested_transaction 

947 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

948 assert parent is not None 

949 else: 

950 assert parent is None 

951 

952 self._state = SessionTransactionState.ACTIVE 

953 

954 self._take_snapshot() 

955 

956 # make sure transaction is assigned before we call the 

957 # dispatch 

958 self.session._transaction = self 

959 

960 self.session.dispatch.after_transaction_create(self.session, self) 

961 

962 def _raise_for_prerequisite_state( 

963 self, operation_name: str, state: _StateChangeState 

964 ) -> NoReturn: 

965 if state is SessionTransactionState.DEACTIVE: 

966 if self._rollback_exception: 

967 raise sa_exc.PendingRollbackError( 

968 "This Session's transaction has been rolled back " 

969 "due to a previous exception during flush." 

970 " To begin a new transaction with this Session, " 

971 "first issue Session.rollback()." 

972 f" Original exception was: {self._rollback_exception}", 

973 code="7s2a", 

974 ) 

975 else: 

976 raise sa_exc.InvalidRequestError( 

977 "This session is in 'inactive' state, due to the " 

978 "SQL transaction being rolled back; no further SQL " 

979 "can be emitted within this transaction." 

980 ) 

981 elif state is SessionTransactionState.CLOSED: 

982 raise sa_exc.ResourceClosedError("This transaction is closed") 

983 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

984 raise sa_exc.InvalidRequestError( 

985 "This session is provisioning a new connection; concurrent " 

986 "operations are not permitted", 

987 code="isce", 

988 ) 

989 else: 

990 raise sa_exc.InvalidRequestError( 

991 f"This session is in '{state.name.lower()}' state; no " 

992 "further SQL can be emitted within this transaction." 

993 ) 

994 

995 @property 

996 def parent(self) -> Optional[SessionTransaction]: 

997 """The parent :class:`.SessionTransaction` of this 

998 :class:`.SessionTransaction`. 

999 

1000 If this attribute is ``None``, indicates this 

1001 :class:`.SessionTransaction` is at the top of the stack, and 

1002 corresponds to a real "COMMIT"/"ROLLBACK" 

1003 block. If non-``None``, then this is either a "subtransaction" 

1004 (an internal marker object used by the flush process) or a 

1005 "nested" / SAVEPOINT transaction. If the 

1006 :attr:`.SessionTransaction.nested` attribute is ``True``, then 

1007 this is a SAVEPOINT, and if ``False``, indicates this a subtransaction. 

1008 

1009 """ 

1010 return self._parent 

1011 

1012 @property 

1013 def is_active(self) -> bool: 

1014 return ( 

1015 self.session is not None 

1016 and self._state is SessionTransactionState.ACTIVE 

1017 ) 

1018 

1019 @property 

1020 def _is_transaction_boundary(self) -> bool: 

1021 return self.nested or not self._parent 

1022 

1023 @_StateChange.declare_states( 

1024 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1025 ) 

1026 def connection( 

1027 self, 

1028 bindkey: Optional[Mapper[Any]], 

1029 execution_options: Optional[_ExecuteOptions] = None, 

1030 **kwargs: Any, 

1031 ) -> Connection: 

1032 bind = self.session.get_bind(bindkey, **kwargs) 

1033 return self._connection_for_bind(bind, execution_options) 

1034 

1035 @_StateChange.declare_states( 

1036 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1037 ) 

1038 def _begin(self, nested: bool = False) -> SessionTransaction: 

1039 return SessionTransaction( 

1040 self.session, 

1041 ( 

1042 SessionTransactionOrigin.BEGIN_NESTED 

1043 if nested 

1044 else SessionTransactionOrigin.SUBTRANSACTION 

1045 ), 

1046 self, 

1047 ) 

1048 

1049 def _iterate_self_and_parents( 

1050 self, upto: Optional[SessionTransaction] = None 

1051 ) -> Iterable[SessionTransaction]: 

1052 current = self 

1053 result: Tuple[SessionTransaction, ...] = () 

1054 while current: 

1055 result += (current,) 

1056 if current._parent is upto: 

1057 break 

1058 elif current._parent is None: 

1059 raise sa_exc.InvalidRequestError( 

1060 "Transaction %s is not on the active transaction list" 

1061 % (upto) 

1062 ) 

1063 else: 

1064 current = current._parent 

1065 

1066 return result 

1067 

1068 def _take_snapshot(self) -> None: 

1069 if not self._is_transaction_boundary: 

1070 parent = self._parent 

1071 assert parent is not None 

1072 self._new = parent._new 

1073 self._deleted = parent._deleted 

1074 self._dirty = parent._dirty 

1075 self._key_switches = parent._key_switches 

1076 return 

1077 

1078 is_begin = self.origin in ( 

1079 SessionTransactionOrigin.BEGIN, 

1080 SessionTransactionOrigin.AUTOBEGIN, 

1081 ) 

1082 if not is_begin and not self.session._flushing: 

1083 self.session.flush() 

1084 

1085 self._new = weakref.WeakKeyDictionary() 

1086 self._deleted = weakref.WeakKeyDictionary() 

1087 self._dirty = weakref.WeakKeyDictionary() 

1088 self._key_switches = weakref.WeakKeyDictionary() 

1089 

1090 def _restore_snapshot(self, dirty_only: bool = False) -> None: 

1091 """Restore the restoration state taken before a transaction began. 

1092 

1093 Corresponds to a rollback. 

1094 

1095 """ 

1096 assert self._is_transaction_boundary 

1097 

1098 to_expunge = set(self._new).union(self.session._new) 

1099 self.session._expunge_states(to_expunge, to_transient=True) 

1100 

1101 for s, (oldkey, newkey) in self._key_switches.items(): 

1102 # we probably can do this conditionally based on 

1103 # if we expunged or not, but safe_discard does that anyway 

1104 self.session.identity_map.safe_discard(s) 

1105 

1106 # restore the old key 

1107 s.key = oldkey 

1108 

1109 # now restore the object, but only if we didn't expunge 

1110 if s not in to_expunge: 

1111 self.session.identity_map.replace(s) 

1112 

1113 for s in set(self._deleted).union(self.session._deleted): 

1114 self.session._update_impl(s, revert_deletion=True) 

1115 

1116 assert not self.session._deleted 

1117 

1118 for s in self.session.identity_map.all_states(): 

1119 if not dirty_only or s.modified or s in self._dirty: 

1120 s._expire(s.dict, self.session.identity_map._modified) 

1121 

1122 def _remove_snapshot(self) -> None: 

1123 """Remove the restoration state taken before a transaction began. 

1124 

1125 Corresponds to a commit. 

1126 

1127 """ 

1128 assert self._is_transaction_boundary 

1129 

1130 if not self.nested and self.session.expire_on_commit: 

1131 for s in self.session.identity_map.all_states(): 

1132 s._expire(s.dict, self.session.identity_map._modified) 

1133 

1134 statelib.InstanceState._detach_states( 

1135 list(self._deleted), self.session 

1136 ) 

1137 self._deleted.clear() 

1138 elif self.nested: 

1139 parent = self._parent 

1140 assert parent is not None 

1141 parent._new.update(self._new) 

1142 parent._dirty.update(self._dirty) 

1143 parent._deleted.update(self._deleted) 

1144 parent._key_switches.update(self._key_switches) 

1145 

1146 @_StateChange.declare_states( 

1147 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1148 ) 

1149 def _connection_for_bind( 

1150 self, 

1151 bind: _SessionBind, 

1152 execution_options: Optional[CoreExecuteOptionsParameter], 

1153 ) -> Connection: 

1154 if bind in self._connections: 

1155 if execution_options: 

1156 util.warn( 

1157 "Connection is already established for the " 

1158 "given bind; execution_options ignored" 

1159 ) 

1160 return self._connections[bind][0] 

1161 

1162 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1163 

1164 local_connect = False 

1165 should_commit = True 

1166 

1167 try: 

1168 if self._parent: 

1169 conn = self._parent._connection_for_bind( 

1170 bind, execution_options 

1171 ) 

1172 if not self.nested: 

1173 return conn 

1174 else: 

1175 if isinstance(bind, engine.Connection): 

1176 conn = bind 

1177 if conn.engine in self._connections: 

1178 raise sa_exc.InvalidRequestError( 

1179 "Session already has a Connection associated " 

1180 "for the given Connection's Engine" 

1181 ) 

1182 else: 

1183 conn = bind.connect() 

1184 local_connect = True 

1185 

1186 try: 

1187 if execution_options: 

1188 conn = conn.execution_options(**execution_options) 

1189 

1190 transaction: Transaction 

1191 if self.session.twophase and self._parent is None: 

1192 # TODO: shouldn't we only be here if not 

1193 # conn.in_transaction() ? 

1194 # if twophase is set and conn.in_transaction(), validate 

1195 # that it is in fact twophase. 

1196 transaction = conn.begin_twophase() 

1197 elif self.nested: 

1198 transaction = conn.begin_nested() 

1199 elif conn.in_transaction(): 

1200 

1201 if local_connect: 

1202 _trans = conn.get_transaction() 

1203 assert _trans is not None 

1204 transaction = _trans 

1205 else: 

1206 join_transaction_mode = ( 

1207 self.session.join_transaction_mode 

1208 ) 

1209 

1210 if join_transaction_mode == "conditional_savepoint": 

1211 if conn.in_nested_transaction(): 

1212 join_transaction_mode = "create_savepoint" 

1213 else: 

1214 join_transaction_mode = "rollback_only" 

1215 

1216 if join_transaction_mode in ( 

1217 "control_fully", 

1218 "rollback_only", 

1219 ): 

1220 if conn.in_nested_transaction(): 

1221 transaction = ( 

1222 conn._get_required_nested_transaction() 

1223 ) 

1224 else: 

1225 transaction = conn._get_required_transaction() 

1226 if join_transaction_mode == "rollback_only": 

1227 should_commit = False 

1228 elif join_transaction_mode == "create_savepoint": 

1229 transaction = conn.begin_nested() 

1230 else: 

1231 assert False, join_transaction_mode 

1232 else: 

1233 transaction = conn.begin() 

1234 except: 

1235 # connection will not not be associated with this Session; 

1236 # close it immediately so that it isn't closed under GC 

1237 if local_connect: 

1238 conn.close() 

1239 raise 

1240 else: 

1241 bind_is_connection = isinstance(bind, engine.Connection) 

1242 

1243 self._connections[conn] = self._connections[conn.engine] = ( 

1244 conn, 

1245 transaction, 

1246 should_commit, 

1247 not bind_is_connection, 

1248 ) 

1249 self.session.dispatch.after_begin(self.session, self, conn) 

1250 return conn 

1251 finally: 

1252 self._state = SessionTransactionState.ACTIVE 

1253 

1254 def prepare(self) -> None: 

1255 if self._parent is not None or not self.session.twophase: 

1256 raise sa_exc.InvalidRequestError( 

1257 "'twophase' mode not enabled, or not root transaction; " 

1258 "can't prepare." 

1259 ) 

1260 self._prepare_impl() 

1261 

1262 @_StateChange.declare_states( 

1263 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1264 ) 

1265 def _prepare_impl(self) -> None: 

1266 if self._parent is None or self.nested: 

1267 self.session.dispatch.before_commit(self.session) 

1268 

1269 stx = self.session._transaction 

1270 assert stx is not None 

1271 if stx is not self: 

1272 for subtransaction in stx._iterate_self_and_parents(upto=self): 

1273 subtransaction.commit() 

1274 

1275 if not self.session._flushing: 

1276 for _flush_guard in range(100): 

1277 if self.session._is_clean(): 

1278 break 

1279 self.session.flush() 

1280 else: 

1281 raise exc.FlushError( 

1282 "Over 100 subsequent flushes have occurred within " 

1283 "session.commit() - is an after_flush() hook " 

1284 "creating new objects?" 

1285 ) 

1286 

1287 if self._parent is None and self.session.twophase: 

1288 try: 

1289 for t in set(self._connections.values()): 

1290 cast("TwoPhaseTransaction", t[1]).prepare() 

1291 except: 

1292 with util.safe_reraise(): 

1293 self.rollback() 

1294 

1295 self._state = SessionTransactionState.PREPARED 

1296 

1297 @_StateChange.declare_states( 

1298 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1299 SessionTransactionState.CLOSED, 

1300 ) 

1301 def commit(self, _to_root: bool = False) -> None: 

1302 if self._state is not SessionTransactionState.PREPARED: 

1303 with self._expect_state(SessionTransactionState.PREPARED): 

1304 self._prepare_impl() 

1305 

1306 if self._parent is None or self.nested: 

1307 for conn, trans, should_commit, autoclose in set( 

1308 self._connections.values() 

1309 ): 

1310 if should_commit: 

1311 trans.commit() 

1312 

1313 self._state = SessionTransactionState.COMMITTED 

1314 self.session.dispatch.after_commit(self.session) 

1315 

1316 self._remove_snapshot() 

1317 

1318 with self._expect_state(SessionTransactionState.CLOSED): 

1319 self.close() 

1320 

1321 if _to_root and self._parent: 

1322 self._parent.commit(_to_root=True) 

1323 

1324 @_StateChange.declare_states( 

1325 ( 

1326 SessionTransactionState.ACTIVE, 

1327 SessionTransactionState.DEACTIVE, 

1328 SessionTransactionState.PREPARED, 

1329 ), 

1330 SessionTransactionState.CLOSED, 

1331 ) 

1332 def rollback( 

1333 self, _capture_exception: bool = False, _to_root: bool = False 

1334 ) -> None: 

1335 stx = self.session._transaction 

1336 assert stx is not None 

1337 if stx is not self: 

1338 for subtransaction in stx._iterate_self_and_parents(upto=self): 

1339 subtransaction.close() 

1340 

1341 boundary = self 

1342 rollback_err = None 

1343 if self._state in ( 

1344 SessionTransactionState.ACTIVE, 

1345 SessionTransactionState.PREPARED, 

1346 ): 

1347 for transaction in self._iterate_self_and_parents(): 

1348 if transaction._parent is None or transaction.nested: 

1349 try: 

1350 for t in set(transaction._connections.values()): 

1351 t[1].rollback() 

1352 

1353 transaction._state = SessionTransactionState.DEACTIVE 

1354 self.session.dispatch.after_rollback(self.session) 

1355 except: 

1356 rollback_err = sys.exc_info() 

1357 finally: 

1358 transaction._state = SessionTransactionState.DEACTIVE 

1359 transaction._restore_snapshot( 

1360 dirty_only=transaction.nested 

1361 ) 

1362 boundary = transaction 

1363 break 

1364 else: 

1365 transaction._state = SessionTransactionState.DEACTIVE 

1366 

1367 sess = self.session 

1368 

1369 if not rollback_err and not sess._is_clean(): 

1370 # if items were added, deleted, or mutated 

1371 # here, we need to re-restore the snapshot 

1372 util.warn( 

1373 "Session's state has been changed on " 

1374 "a non-active transaction - this state " 

1375 "will be discarded." 

1376 ) 

1377 boundary._restore_snapshot(dirty_only=boundary.nested) 

1378 

1379 with self._expect_state(SessionTransactionState.CLOSED): 

1380 self.close() 

1381 

1382 if self._parent and _capture_exception: 

1383 self._parent._rollback_exception = sys.exc_info()[1] 

1384 

1385 if rollback_err and rollback_err[1]: 

1386 raise rollback_err[1].with_traceback(rollback_err[2]) 

1387 

1388 sess.dispatch.after_soft_rollback(sess, self) 

1389 

1390 if _to_root and self._parent: 

1391 self._parent.rollback(_to_root=True) 

1392 

1393 @_StateChange.declare_states( 

1394 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1395 ) 

1396 def close(self, invalidate: bool = False) -> None: 

1397 if self.nested: 

1398 self.session._nested_transaction = ( 

1399 self._previous_nested_transaction 

1400 ) 

1401 

1402 self.session._transaction = self._parent 

1403 

1404 for connection, transaction, should_commit, autoclose in set( 

1405 self._connections.values() 

1406 ): 

1407 if invalidate and self._parent is None: 

1408 connection.invalidate() 

1409 if should_commit and transaction.is_active: 

1410 transaction.close() 

1411 if autoclose and self._parent is None: 

1412 connection.close() 

1413 

1414 self._state = SessionTransactionState.CLOSED 

1415 sess = self.session 

1416 

1417 # TODO: these two None sets were historically after the 

1418 # event hook below, and in 2.0 I changed it this way for some reason, 

1419 # and I remember there being a reason, but not what it was. 

1420 # Why do we need to get rid of them at all? test_memusage::CycleTest 

1421 # passes with these commented out. 

1422 # self.session = None # type: ignore 

1423 # self._connections = None # type: ignore 

1424 

1425 sess.dispatch.after_transaction_end(sess, self) 

1426 

1427 def _get_subject(self) -> Session: 

1428 return self.session 

1429 

1430 def _transaction_is_active(self) -> bool: 

1431 return self._state is SessionTransactionState.ACTIVE 

1432 

1433 def _transaction_is_closed(self) -> bool: 

1434 return self._state is SessionTransactionState.CLOSED 

1435 

1436 def _rollback_can_be_called(self) -> bool: 

1437 return self._state not in (COMMITTED, CLOSED) 

1438 

1439 

1440class _SessionCloseState(Enum): 

1441 ACTIVE = 1 

1442 CLOSED = 2 

1443 CLOSE_IS_RESET = 3 

1444 

1445 

1446class Session(_SessionClassMethods, EventTarget): 

1447 """Manages persistence operations for ORM-mapped objects. 

1448 

1449 The :class:`_orm.Session` is **not safe for use in concurrent threads.**. 

1450 See :ref:`session_faq_threadsafe` for background. 

1451 

1452 The Session's usage paradigm is described at :doc:`/orm/session`. 

1453 

1454 

1455 """ 

1456 

1457 _is_asyncio = False 

1458 

1459 dispatch: dispatcher[Session] 

1460 

1461 identity_map: IdentityMap 

1462 """A mapping of object identities to objects themselves. 

1463 

1464 Iterating through ``Session.identity_map.values()`` provides 

1465 access to the full set of persistent objects (i.e., those 

1466 that have row identity) currently in the session. 

1467 

1468 .. seealso:: 

1469 

1470 :func:`.identity_key` - helper function to produce the keys used 

1471 in this dictionary. 

1472 

1473 """ 

1474 

1475 _new: Dict[InstanceState[Any], Any] 

1476 _deleted: Dict[InstanceState[Any], Any] 

1477 bind: Optional[Union[Engine, Connection]] 

1478 __binds: Dict[_SessionBindKey, _SessionBind] 

1479 _flushing: bool 

1480 _warn_on_events: bool 

1481 _transaction: Optional[SessionTransaction] 

1482 _nested_transaction: Optional[SessionTransaction] 

1483 hash_key: int 

1484 autoflush: bool 

1485 expire_on_commit: bool 

1486 enable_baked_queries: bool 

1487 twophase: bool 

1488 join_transaction_mode: JoinTransactionMode 

1489 _query_cls: Type[Query[Any]] 

1490 _close_state: _SessionCloseState 

1491 

1492 def __init__( 

1493 self, 

1494 bind: Optional[_SessionBind] = None, 

1495 *, 

1496 autoflush: bool = True, 

1497 future: Literal[True] = True, 

1498 expire_on_commit: bool = True, 

1499 autobegin: bool = True, 

1500 twophase: bool = False, 

1501 binds: Optional[Dict[_SessionBindKey, _SessionBind]] = None, 

1502 enable_baked_queries: bool = True, 

1503 info: Optional[_InfoType] = None, 

1504 query_cls: Optional[Type[Query[Any]]] = None, 

1505 autocommit: Literal[False] = False, 

1506 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

1507 close_resets_only: Union[bool, _NoArg] = _NoArg.NO_ARG, 

1508 ): 

1509 r"""Construct a new :class:`_orm.Session`. 

1510 

1511 See also the :class:`.sessionmaker` function which is used to 

1512 generate a :class:`.Session`-producing callable with a given 

1513 set of arguments. 

1514 

1515 :param autoflush: When ``True``, all query operations will issue a 

1516 :meth:`~.Session.flush` call to this ``Session`` before proceeding. 

1517 This is a convenience feature so that :meth:`~.Session.flush` need 

1518 not be called repeatedly in order for database queries to retrieve 

1519 results. 

1520 

1521 .. seealso:: 

1522 

1523 :ref:`session_flushing` - additional background on autoflush 

1524 

1525 :param autobegin: Automatically start transactions (i.e. equivalent to 

1526 invoking :meth:`_orm.Session.begin`) when database access is 

1527 requested by an operation. Defaults to ``True``. Set to 

1528 ``False`` to prevent a :class:`_orm.Session` from implicitly 

1529 beginning transactions after construction, as well as after any of 

1530 the :meth:`_orm.Session.rollback`, :meth:`_orm.Session.commit`, 

1531 or :meth:`_orm.Session.close` methods are called. 

1532 

1533 .. versionadded:: 2.0 

1534 

1535 .. seealso:: 

1536 

1537 :ref:`session_autobegin_disable` 

1538 

1539 :param bind: An optional :class:`_engine.Engine` or 

1540 :class:`_engine.Connection` to 

1541 which this ``Session`` should be bound. When specified, all SQL 

1542 operations performed by this session will execute via this 

1543 connectable. 

1544 

1545 :param binds: A dictionary which may specify any number of 

1546 :class:`_engine.Engine` or :class:`_engine.Connection` 

1547 objects as the source of 

1548 connectivity for SQL operations on a per-entity basis. The keys 

1549 of the dictionary consist of any series of mapped classes, 

1550 arbitrary Python classes that are bases for mapped classes, 

1551 :class:`_schema.Table` objects and :class:`_orm.Mapper` objects. 

1552 The 

1553 values of the dictionary are then instances of 

1554 :class:`_engine.Engine` 

1555 or less commonly :class:`_engine.Connection` objects. 

1556 Operations which 

1557 proceed relative to a particular mapped class will consult this 

1558 dictionary for the closest matching entity in order to determine 

1559 which :class:`_engine.Engine` should be used for a particular SQL 

1560 operation. The complete heuristics for resolution are 

1561 described at :meth:`.Session.get_bind`. Usage looks like:: 

1562 

1563 Session = sessionmaker( 

1564 binds={ 

1565 SomeMappedClass: create_engine("postgresql+psycopg2://engine1"), 

1566 SomeDeclarativeBase: create_engine( 

1567 "postgresql+psycopg2://engine2" 

1568 ), 

1569 some_mapper: create_engine("postgresql+psycopg2://engine3"), 

1570 some_table: create_engine("postgresql+psycopg2://engine4"), 

1571 } 

1572 ) 

1573 

1574 .. seealso:: 

1575 

1576 :ref:`session_partitioning` 

1577 

1578 :meth:`.Session.bind_mapper` 

1579 

1580 :meth:`.Session.bind_table` 

1581 

1582 :meth:`.Session.get_bind` 

1583 

1584 

1585 :param \class_: Specify an alternate class other than 

1586 ``sqlalchemy.orm.session.Session`` which should be used by the 

1587 returned class. This is the only argument that is local to the 

1588 :class:`.sessionmaker` function, and is not sent directly to the 

1589 constructor for ``Session``. 

1590 

1591 :param enable_baked_queries: legacy; defaults to ``True``. 

1592 A parameter consumed 

1593 by the :mod:`sqlalchemy.ext.baked` extension to determine if 

1594 "baked queries" should be cached, as is the normal operation 

1595 of this extension. When set to ``False``, caching as used by 

1596 this particular extension is disabled. 

1597 

1598 .. versionchanged:: 1.4 The ``sqlalchemy.ext.baked`` extension is 

1599 legacy and is not used by any of SQLAlchemy's internals. This 

1600 flag therefore only affects applications that are making explicit 

1601 use of this extension within their own code. 

1602 

1603 :param expire_on_commit: Defaults to ``True``. When ``True``, all 

1604 instances will be fully expired after each :meth:`~.commit`, 

1605 so that all attribute/object access subsequent to a completed 

1606 transaction will load from the most recent database state. 

1607 

1608 .. seealso:: 

1609 

1610 :ref:`session_committing` 

1611 

1612 :param future: Deprecated; this flag is always True. 

1613 

1614 .. seealso:: 

1615 

1616 :ref:`migration_20_toplevel` 

1617 

1618 :param info: optional dictionary of arbitrary data to be associated 

1619 with this :class:`.Session`. Is available via the 

1620 :attr:`.Session.info` attribute. Note the dictionary is copied at 

1621 construction time so that modifications to the per- 

1622 :class:`.Session` dictionary will be local to that 

1623 :class:`.Session`. 

1624 

1625 :param query_cls: Class which should be used to create new Query 

1626 objects, as returned by the :meth:`~.Session.query` method. 

1627 Defaults to :class:`_query.Query`. 

1628 

1629 :param twophase: When ``True``, all transactions will be started as 

1630 a "two phase" transaction, i.e. using the "two phase" semantics 

1631 of the database in use along with an XID. During a 

1632 :meth:`~.commit`, after :meth:`~.flush` has been issued for all 

1633 attached databases, the :meth:`~.TwoPhaseTransaction.prepare` 

1634 method on each database's :class:`.TwoPhaseTransaction` will be 

1635 called. This allows each database to roll back the entire 

1636 transaction, before each transaction is committed. 

1637 

1638 :param autocommit: the "autocommit" keyword is present for backwards 

1639 compatibility but must remain at its default value of ``False``. 

1640 

1641 :param join_transaction_mode: Describes the transactional behavior to 

1642 take when a given bind is a :class:`_engine.Connection` that 

1643 has already begun a transaction outside the scope of this 

1644 :class:`_orm.Session`; in other words the 

1645 :meth:`_engine.Connection.in_transaction()` method returns True. 

1646 

1647 The following behaviors only take effect when the :class:`_orm.Session` 

1648 **actually makes use of the connection given**; that is, a method 

1649 such as :meth:`_orm.Session.execute`, :meth:`_orm.Session.connection`, 

1650 etc. are actually invoked: 

1651 

1652 * ``"conditional_savepoint"`` - this is the default. if the given 

1653 :class:`_engine.Connection` is begun within a transaction but 

1654 does not have a SAVEPOINT, then ``"rollback_only"`` is used. 

1655 If the :class:`_engine.Connection` is additionally within 

1656 a SAVEPOINT, in other words 

1657 :meth:`_engine.Connection.in_nested_transaction()` method returns 

1658 True, then ``"create_savepoint"`` is used. 

1659 

1660 ``"conditional_savepoint"`` behavior attempts to make use of 

1661 savepoints in order to keep the state of the existing transaction 

1662 unchanged, but only if there is already a savepoint in progress; 

1663 otherwise, it is not assumed that the backend in use has adequate 

1664 support for SAVEPOINT, as availability of this feature varies. 

1665 ``"conditional_savepoint"`` also seeks to establish approximate 

1666 backwards compatibility with previous :class:`_orm.Session` 

1667 behavior, for applications that are not setting a specific mode. It 

1668 is recommended that one of the explicit settings be used. 

1669 

1670 * ``"create_savepoint"`` - the :class:`_orm.Session` will use 

1671 :meth:`_engine.Connection.begin_nested()` in all cases to create 

1672 its own transaction. This transaction by its nature rides 

1673 "on top" of any existing transaction that's opened on the given 

1674 :class:`_engine.Connection`; if the underlying database and 

1675 the driver in use has full, non-broken support for SAVEPOINT, the 

1676 external transaction will remain unaffected throughout the 

1677 lifespan of the :class:`_orm.Session`. 

1678 

1679 The ``"create_savepoint"`` mode is the most useful for integrating 

1680 a :class:`_orm.Session` into a test suite where an externally 

1681 initiated transaction should remain unaffected; however, it relies 

1682 on proper SAVEPOINT support from the underlying driver and 

1683 database. 

1684 

1685 .. tip:: When using SQLite, the SQLite driver included through 

1686 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1687 without workarounds. See the sections 

1688 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1689 for details on current workarounds. 

1690 

1691 * ``"control_fully"`` - the :class:`_orm.Session` will take 

1692 control of the given transaction as its own; 

1693 :meth:`_orm.Session.commit` will call ``.commit()`` on the 

1694 transaction, :meth:`_orm.Session.rollback` will call 

1695 ``.rollback()`` on the transaction, :meth:`_orm.Session.close` will 

1696 call ``.rollback`` on the transaction. 

1697 

1698 .. tip:: This mode of use is equivalent to how SQLAlchemy 1.4 would 

1699 handle a :class:`_engine.Connection` given with an existing 

1700 SAVEPOINT (i.e. :meth:`_engine.Connection.begin_nested`); the 

1701 :class:`_orm.Session` would take full control of the existing 

1702 SAVEPOINT. 

1703 

1704 * ``"rollback_only"`` - the :class:`_orm.Session` will take control 

1705 of the given transaction for ``.rollback()`` calls only; 

1706 ``.commit()`` calls will not be propagated to the given 

1707 transaction. ``.close()`` calls will have no effect on the 

1708 given transaction. 

1709 

1710 .. tip:: This mode of use is equivalent to how SQLAlchemy 1.4 would 

1711 handle a :class:`_engine.Connection` given with an existing 

1712 regular database transaction (i.e. 

1713 :meth:`_engine.Connection.begin`); the :class:`_orm.Session` 

1714 would propagate :meth:`_orm.Session.rollback` calls to the 

1715 underlying transaction, but not :meth:`_orm.Session.commit` or 

1716 :meth:`_orm.Session.close` calls. 

1717 

1718 .. versionadded:: 2.0.0rc1 

1719 

1720 :param close_resets_only: Defaults to ``True``. Determines if 

1721 the session should reset itself after calling ``.close()`` 

1722 or should pass in a no longer usable state, disabling re-use. 

1723 

1724 .. versionadded:: 2.0.22 added flag ``close_resets_only``. 

1725 A future SQLAlchemy version may change the default value of 

1726 this flag to ``False``. 

1727 

1728 .. seealso:: 

1729 

1730 :ref:`session_closing` - Detail on the semantics of 

1731 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`. 

1732 

1733 """ # noqa 

1734 

1735 # considering allowing the "autocommit" keyword to still be accepted 

1736 # as long as it's False, so that external test suites, oslo.db etc 

1737 # continue to function as the argument appears to be passed in lots 

1738 # of cases including in our own test suite 

1739 if autocommit: 

1740 raise sa_exc.ArgumentError( 

1741 "autocommit=True is no longer supported" 

1742 ) 

1743 self.identity_map = identity._WeakInstanceDict() 

1744 

1745 if not future: 

1746 raise sa_exc.ArgumentError( 

1747 "The 'future' parameter passed to " 

1748 "Session() may only be set to True." 

1749 ) 

1750 

1751 self._new = {} # InstanceState->object, strong refs object 

1752 self._deleted = {} # same 

1753 self.bind = bind 

1754 self.__binds = {} 

1755 self._flushing = False 

1756 self._warn_on_events = False 

1757 self._transaction = None 

1758 self._nested_transaction = None 

1759 self.hash_key = _new_sessionid() 

1760 self.autobegin = autobegin 

1761 self.autoflush = autoflush 

1762 self.expire_on_commit = expire_on_commit 

1763 self.enable_baked_queries = enable_baked_queries 

1764 

1765 # the idea is that at some point NO_ARG will warn that in the future 

1766 # the default will switch to close_resets_only=False. 

1767 if close_resets_only in (True, _NoArg.NO_ARG): 

1768 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1769 else: 

1770 self._close_state = _SessionCloseState.ACTIVE 

1771 if ( 

1772 join_transaction_mode 

1773 and join_transaction_mode 

1774 not in JoinTransactionMode.__args__ # type: ignore 

1775 ): 

1776 raise sa_exc.ArgumentError( 

1777 f"invalid selection for join_transaction_mode: " 

1778 f'"{join_transaction_mode}"' 

1779 ) 

1780 self.join_transaction_mode = join_transaction_mode 

1781 

1782 self.twophase = twophase 

1783 self._query_cls = query_cls if query_cls else query.Query 

1784 if info: 

1785 self.info.update(info) 

1786 

1787 if binds is not None: 

1788 for key, bind in binds.items(): 

1789 self._add_bind(key, bind) 

1790 

1791 _sessions[self.hash_key] = self 

1792 

1793 # used by sqlalchemy.engine.util.TransactionalContext 

1794 _trans_context_manager: Optional[TransactionalContext] = None 

1795 

1796 connection_callable: Optional[_ConnectionCallableProto] = None 

1797 

1798 def __enter__(self: _S) -> _S: 

1799 return self 

1800 

1801 def __exit__(self, type_: Any, value: Any, traceback: Any) -> None: 

1802 self.close() 

1803 

1804 @contextlib.contextmanager 

1805 def _maker_context_manager(self: _S) -> Iterator[_S]: 

1806 with self: 

1807 with self.begin(): 

1808 yield self 

1809 

1810 def in_transaction(self) -> bool: 

1811 """Return True if this :class:`_orm.Session` has begun a transaction. 

1812 

1813 .. versionadded:: 1.4 

1814 

1815 .. seealso:: 

1816 

1817 :attr:`_orm.Session.is_active` 

1818 

1819 

1820 """ 

1821 return self._transaction is not None 

1822 

1823 def in_nested_transaction(self) -> bool: 

1824 """Return True if this :class:`_orm.Session` has begun a nested 

1825 transaction, e.g. SAVEPOINT. 

1826 

1827 .. versionadded:: 1.4 

1828 

1829 """ 

1830 return self._nested_transaction is not None 

1831 

1832 def get_transaction(self) -> Optional[SessionTransaction]: 

1833 """Return the current root transaction in progress, if any. 

1834 

1835 .. versionadded:: 1.4 

1836 

1837 """ 

1838 trans = self._transaction 

1839 while trans is not None and trans._parent is not None: 

1840 trans = trans._parent 

1841 return trans 

1842 

1843 def get_nested_transaction(self) -> Optional[SessionTransaction]: 

1844 """Return the current nested transaction in progress, if any. 

1845 

1846 .. versionadded:: 1.4 

1847 

1848 """ 

1849 

1850 return self._nested_transaction 

1851 

1852 @util.memoized_property 

1853 def info(self) -> _InfoType: 

1854 """A user-modifiable dictionary. 

1855 

1856 The initial value of this dictionary can be populated using the 

1857 ``info`` argument to the :class:`.Session` constructor or 

1858 :class:`.sessionmaker` constructor or factory methods. The dictionary 

1859 here is always local to this :class:`.Session` and can be modified 

1860 independently of all other :class:`.Session` objects. 

1861 

1862 """ 

1863 return {} 

1864 

1865 def _autobegin_t(self, begin: bool = False) -> SessionTransaction: 

1866 if self._transaction is None: 

1867 if not begin and not self.autobegin: 

1868 raise sa_exc.InvalidRequestError( 

1869 "Autobegin is disabled on this Session; please call " 

1870 "session.begin() to start a new transaction" 

1871 ) 

1872 trans = SessionTransaction( 

1873 self, 

1874 ( 

1875 SessionTransactionOrigin.BEGIN 

1876 if begin 

1877 else SessionTransactionOrigin.AUTOBEGIN 

1878 ), 

1879 ) 

1880 assert self._transaction is trans 

1881 return trans 

1882 

1883 return self._transaction 

1884 

1885 def begin(self, nested: bool = False) -> SessionTransaction: 

1886 """Begin a transaction, or nested transaction, 

1887 on this :class:`.Session`, if one is not already begun. 

1888 

1889 The :class:`_orm.Session` object features **autobegin** behavior, 

1890 so that normally it is not necessary to call the 

1891 :meth:`_orm.Session.begin` 

1892 method explicitly. However, it may be used in order to control 

1893 the scope of when the transactional state is begun. 

1894 

1895 When used to begin the outermost transaction, an error is raised 

1896 if this :class:`.Session` is already inside of a transaction. 

1897 

1898 :param nested: if True, begins a SAVEPOINT transaction and is 

1899 equivalent to calling :meth:`~.Session.begin_nested`. For 

1900 documentation on SAVEPOINT transactions, please see 

1901 :ref:`session_begin_nested`. 

1902 

1903 :return: the :class:`.SessionTransaction` object. Note that 

1904 :class:`.SessionTransaction` 

1905 acts as a Python context manager, allowing :meth:`.Session.begin` 

1906 to be used in a "with" block. See :ref:`session_explicit_begin` for 

1907 an example. 

1908 

1909 .. seealso:: 

1910 

1911 :ref:`session_autobegin` 

1912 

1913 :ref:`unitofwork_transaction` 

1914 

1915 :meth:`.Session.begin_nested` 

1916 

1917 

1918 """ 

1919 

1920 trans = self._transaction 

1921 if trans is None: 

1922 trans = self._autobegin_t(begin=True) 

1923 

1924 if not nested: 

1925 return trans 

1926 

1927 assert trans is not None 

1928 

1929 if nested: 

1930 trans = trans._begin(nested=nested) 

1931 assert self._transaction is trans 

1932 self._nested_transaction = trans 

1933 else: 

1934 raise sa_exc.InvalidRequestError( 

1935 "A transaction is already begun on this Session." 

1936 ) 

1937 

1938 return trans # needed for __enter__/__exit__ hook 

1939 

1940 def begin_nested(self) -> SessionTransaction: 

1941 """Begin a "nested" transaction on this Session, e.g. SAVEPOINT. 

1942 

1943 The target database(s) and associated drivers must support SQL 

1944 SAVEPOINT for this method to function correctly. 

1945 

1946 For documentation on SAVEPOINT 

1947 transactions, please see :ref:`session_begin_nested`. 

1948 

1949 :return: the :class:`.SessionTransaction` object. Note that 

1950 :class:`.SessionTransaction` acts as a context manager, allowing 

1951 :meth:`.Session.begin_nested` to be used in a "with" block. 

1952 See :ref:`session_begin_nested` for a usage example. 

1953 

1954 .. seealso:: 

1955 

1956 :ref:`session_begin_nested` 

1957 

1958 :ref:`pysqlite_serializable` - special workarounds required 

1959 with the SQLite driver in order for SAVEPOINT to work 

1960 correctly. For asyncio use cases, see the section 

1961 :ref:`aiosqlite_serializable`. 

1962 

1963 """ 

1964 return self.begin(nested=True) 

1965 

1966 def rollback(self) -> None: 

1967 """Rollback the current transaction in progress. 

1968 

1969 If no transaction is in progress, this method is a pass-through. 

1970 

1971 The method always rolls back 

1972 the topmost database transaction, discarding any nested 

1973 transactions that may be in progress. 

1974 

1975 .. seealso:: 

1976 

1977 :ref:`session_rollback` 

1978 

1979 :ref:`unitofwork_transaction` 

1980 

1981 """ 

1982 if self._transaction is None: 

1983 pass 

1984 else: 

1985 self._transaction.rollback(_to_root=True) 

1986 

1987 def commit(self) -> None: 

1988 """Flush pending changes and commit the current transaction. 

1989 

1990 When the COMMIT operation is complete, all objects are fully 

1991 :term:`expired`, erasing their internal contents, which will be 

1992 automatically re-loaded when the objects are next accessed. In the 

1993 interim, these objects are in an expired state and will not function if 

1994 they are :term:`detached` from the :class:`.Session`. Additionally, 

1995 this re-load operation is not supported when using asyncio-oriented 

1996 APIs. The :paramref:`.Session.expire_on_commit` parameter may be used 

1997 to disable this behavior. 

1998 

1999 When there is no transaction in place for the :class:`.Session`, 

2000 indicating that no operations were invoked on this :class:`.Session` 

2001 since the previous call to :meth:`.Session.commit`, the method will 

2002 begin and commit an internal-only "logical" transaction, that does not 

2003 normally affect the database unless pending flush changes were 

2004 detected, but will still invoke event handlers and object expiration 

2005 rules. 

2006 

2007 The outermost database transaction is committed unconditionally, 

2008 automatically releasing any SAVEPOINTs in effect. 

2009 

2010 .. seealso:: 

2011 

2012 :ref:`session_committing` 

2013 

2014 :ref:`unitofwork_transaction` 

2015 

2016 :ref:`asyncio_orm_avoid_lazyloads` 

2017 

2018 """ 

2019 trans = self._transaction 

2020 if trans is None: 

2021 trans = self._autobegin_t() 

2022 

2023 trans.commit(_to_root=True) 

2024 

2025 def prepare(self) -> None: 

2026 """Prepare the current transaction in progress for two phase commit. 

2027 

2028 If no transaction is in progress, this method raises an 

2029 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2030 

2031 Only root transactions of two phase sessions can be prepared. If the 

2032 current transaction is not such, an 

2033 :exc:`~sqlalchemy.exc.InvalidRequestError` is raised. 

2034 

2035 """ 

2036 trans = self._transaction 

2037 if trans is None: 

2038 trans = self._autobegin_t() 

2039 

2040 trans.prepare() 

2041 

2042 def connection( 

2043 self, 

2044 bind_arguments: Optional[_BindArguments] = None, 

2045 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2046 ) -> Connection: 

2047 r"""Return a :class:`_engine.Connection` object corresponding to this 

2048 :class:`.Session` object's transactional state. 

2049 

2050 Either the :class:`_engine.Connection` corresponding to the current 

2051 transaction is returned, or if no transaction is in progress, a new 

2052 one is begun and the :class:`_engine.Connection` 

2053 returned (note that no 

2054 transactional state is established with the DBAPI until the first 

2055 SQL statement is emitted). 

2056 

2057 Ambiguity in multi-bind or unbound :class:`.Session` objects can be 

2058 resolved through any of the optional keyword arguments. This 

2059 ultimately makes usage of the :meth:`.get_bind` method for resolution. 

2060 

2061 :param bind_arguments: dictionary of bind arguments. May include 

2062 "mapper", "bind", "clause", other custom arguments that are passed 

2063 to :meth:`.Session.get_bind`. 

2064 

2065 :param execution_options: a dictionary of execution options that will 

2066 be passed to :meth:`_engine.Connection.execution_options`, **when the 

2067 connection is first procured only**. If the connection is already 

2068 present within the :class:`.Session`, a warning is emitted and 

2069 the arguments are ignored. 

2070 

2071 .. seealso:: 

2072 

2073 :ref:`session_transaction_isolation` 

2074 

2075 """ 

2076 

2077 if bind_arguments: 

2078 bind = bind_arguments.pop("bind", None) 

2079 

2080 if bind is None: 

2081 bind = self.get_bind(**bind_arguments) 

2082 else: 

2083 bind = self.get_bind() 

2084 

2085 return self._connection_for_bind( 

2086 bind, 

2087 execution_options=execution_options, 

2088 ) 

2089 

2090 def _connection_for_bind( 

2091 self, 

2092 engine: _SessionBind, 

2093 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2094 **kw: Any, 

2095 ) -> Connection: 

2096 TransactionalContext._trans_ctx_check(self) 

2097 

2098 trans = self._transaction 

2099 if trans is None: 

2100 trans = self._autobegin_t() 

2101 return trans._connection_for_bind(engine, execution_options) 

2102 

2103 @overload 

2104 def _execute_internal( 

2105 self, 

2106 statement: Executable, 

2107 params: Optional[_CoreSingleExecuteParams] = None, 

2108 *, 

2109 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2110 bind_arguments: Optional[_BindArguments] = None, 

2111 _parent_execute_state: Optional[Any] = None, 

2112 _add_event: Optional[Any] = None, 

2113 _scalar_result: Literal[True] = ..., 

2114 ) -> Any: ... 

2115 

2116 @overload 

2117 def _execute_internal( 

2118 self, 

2119 statement: Executable, 

2120 params: Optional[_CoreAnyExecuteParams] = None, 

2121 *, 

2122 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2123 bind_arguments: Optional[_BindArguments] = None, 

2124 _parent_execute_state: Optional[Any] = None, 

2125 _add_event: Optional[Any] = None, 

2126 _scalar_result: bool = ..., 

2127 ) -> Result[Unpack[TupleAny]]: ... 

2128 

2129 def _execute_internal( 

2130 self, 

2131 statement: Executable, 

2132 params: Optional[_CoreAnyExecuteParams] = None, 

2133 *, 

2134 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2135 bind_arguments: Optional[_BindArguments] = None, 

2136 _parent_execute_state: Optional[Any] = None, 

2137 _add_event: Optional[Any] = None, 

2138 _scalar_result: bool = False, 

2139 ) -> Any: 

2140 statement = coercions.expect(roles.StatementRole, statement) 

2141 

2142 if not bind_arguments: 

2143 bind_arguments = {} 

2144 else: 

2145 bind_arguments = dict(bind_arguments) 

2146 

2147 if ( 

2148 statement._propagate_attrs.get("compile_state_plugin", None) 

2149 == "orm" 

2150 ): 

2151 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2152 statement, "orm" 

2153 ) 

2154 if TYPE_CHECKING: 

2155 assert isinstance( 

2156 compile_state_cls, context._AbstractORMCompileState 

2157 ) 

2158 else: 

2159 compile_state_cls = None 

2160 bind_arguments.setdefault("clause", statement) 

2161 

2162 execution_options = util.coerce_to_immutabledict(execution_options) 

2163 

2164 if _parent_execute_state: 

2165 events_todo = _parent_execute_state._remaining_events() 

2166 else: 

2167 events_todo = self.dispatch.do_orm_execute 

2168 if _add_event: 

2169 events_todo = list(events_todo) + [_add_event] 

2170 

2171 if events_todo: 

2172 if compile_state_cls is not None: 

2173 # for event handlers, do the orm_pre_session_exec 

2174 # pass ahead of the event handlers, so that things like 

2175 # .load_options, .update_delete_options etc. are populated. 

2176 # is_pre_event=True allows the hook to hold off on things 

2177 # it doesn't want to do twice, including autoflush as well 

2178 # as "pre fetch" for DML, etc. 

2179 ( 

2180 statement, 

2181 execution_options, 

2182 ) = compile_state_cls.orm_pre_session_exec( 

2183 self, 

2184 statement, 

2185 params, 

2186 execution_options, 

2187 bind_arguments, 

2188 True, 

2189 ) 

2190 

2191 orm_exec_state = ORMExecuteState( 

2192 self, 

2193 statement, 

2194 params, 

2195 execution_options, 

2196 bind_arguments, 

2197 compile_state_cls, 

2198 events_todo, 

2199 ) 

2200 for idx, fn in enumerate(events_todo): 

2201 orm_exec_state._starting_event_idx = idx 

2202 fn_result: Optional[Result[Unpack[TupleAny]]] = fn( 

2203 orm_exec_state 

2204 ) 

2205 if fn_result: 

2206 if _scalar_result: 

2207 return fn_result.scalar() 

2208 else: 

2209 return fn_result 

2210 

2211 statement = orm_exec_state.statement 

2212 execution_options = orm_exec_state.local_execution_options 

2213 

2214 if compile_state_cls is not None: 

2215 # now run orm_pre_session_exec() "for real". if there were 

2216 # event hooks, this will re-run the steps that interpret 

2217 # new execution_options into load_options / update_delete_options, 

2218 # which we assume the event hook might have updated. 

2219 # autoflush will also be invoked in this step if enabled. 

2220 ( 

2221 statement, 

2222 execution_options, 

2223 ) = compile_state_cls.orm_pre_session_exec( 

2224 self, 

2225 statement, 

2226 params, 

2227 execution_options, 

2228 bind_arguments, 

2229 False, 

2230 ) 

2231 else: 

2232 # Issue #9809: unconditionally autoflush for Core statements 

2233 self._autoflush() 

2234 

2235 bind = self.get_bind(**bind_arguments) 

2236 

2237 conn = self._connection_for_bind(bind) 

2238 

2239 if _scalar_result and not compile_state_cls: 

2240 if TYPE_CHECKING: 

2241 params = cast(_CoreSingleExecuteParams, params) 

2242 return conn.scalar( 

2243 statement, params or {}, execution_options=execution_options 

2244 ) 

2245 

2246 if compile_state_cls: 

2247 result: Result[Unpack[TupleAny]] = ( 

2248 compile_state_cls.orm_execute_statement( 

2249 self, 

2250 statement, 

2251 params or {}, 

2252 execution_options, 

2253 bind_arguments, 

2254 conn, 

2255 ) 

2256 ) 

2257 else: 

2258 result = conn.execute( 

2259 statement, params, execution_options=execution_options 

2260 ) 

2261 

2262 if _scalar_result: 

2263 return result.scalar() 

2264 else: 

2265 return result 

2266 

2267 @overload 

2268 def execute( 

2269 self, 

2270 statement: TypedReturnsRows[Unpack[_Ts]], 

2271 params: Optional[_CoreAnyExecuteParams] = None, 

2272 *, 

2273 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2274 bind_arguments: Optional[_BindArguments] = None, 

2275 _parent_execute_state: Optional[Any] = None, 

2276 _add_event: Optional[Any] = None, 

2277 ) -> Result[Unpack[_Ts]]: ... 

2278 

2279 @overload 

2280 def execute( 

2281 self, 

2282 statement: UpdateBase, 

2283 params: Optional[_CoreAnyExecuteParams] = None, 

2284 *, 

2285 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2286 bind_arguments: Optional[_BindArguments] = None, 

2287 _parent_execute_state: Optional[Any] = None, 

2288 _add_event: Optional[Any] = None, 

2289 ) -> CursorResult[Unpack[TupleAny]]: ... 

2290 

2291 @overload 

2292 def execute( 

2293 self, 

2294 statement: Executable, 

2295 params: Optional[_CoreAnyExecuteParams] = None, 

2296 *, 

2297 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2298 bind_arguments: Optional[_BindArguments] = None, 

2299 _parent_execute_state: Optional[Any] = None, 

2300 _add_event: Optional[Any] = None, 

2301 ) -> Result[Unpack[TupleAny]]: ... 

2302 

2303 def execute( 

2304 self, 

2305 statement: Executable, 

2306 params: Optional[_CoreAnyExecuteParams] = None, 

2307 *, 

2308 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2309 bind_arguments: Optional[_BindArguments] = None, 

2310 _parent_execute_state: Optional[Any] = None, 

2311 _add_event: Optional[Any] = None, 

2312 ) -> Result[Unpack[TupleAny]]: 

2313 r"""Execute a SQL expression construct. 

2314 

2315 Returns a :class:`_engine.Result` object representing 

2316 results of the statement execution. 

2317 

2318 E.g.:: 

2319 

2320 from sqlalchemy import select 

2321 

2322 result = session.execute(select(User).where(User.id == 5)) 

2323 

2324 The API contract of :meth:`_orm.Session.execute` is similar to that 

2325 of :meth:`_engine.Connection.execute`, the :term:`2.0 style` version 

2326 of :class:`_engine.Connection`. 

2327 

2328 .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is 

2329 now the primary point of ORM statement execution when using 

2330 :term:`2.0 style` ORM usage. 

2331 

2332 :param statement: 

2333 An executable statement (i.e. an :class:`.Executable` expression 

2334 such as :func:`_expression.select`). 

2335 

2336 :param params: 

2337 Optional dictionary, or list of dictionaries, containing 

2338 bound parameter values. If a single dictionary, single-row 

2339 execution occurs; if a list of dictionaries, an 

2340 "executemany" will be invoked. The keys in each dictionary 

2341 must correspond to parameter names present in the statement. 

2342 

2343 :param execution_options: optional dictionary of execution options, 

2344 which will be associated with the statement execution. This 

2345 dictionary can provide a subset of the options that are accepted 

2346 by :meth:`_engine.Connection.execution_options`, and may also 

2347 provide additional options understood only in an ORM context. 

2348 

2349 .. seealso:: 

2350 

2351 :ref:`orm_queryguide_execution_options` - ORM-specific execution 

2352 options 

2353 

2354 :param bind_arguments: dictionary of additional arguments to determine 

2355 the bind. May include "mapper", "bind", or other custom arguments. 

2356 Contents of this dictionary are passed to the 

2357 :meth:`.Session.get_bind` method. 

2358 

2359 :return: a :class:`_engine.Result` object. 

2360 

2361 

2362 """ 

2363 return self._execute_internal( 

2364 statement, 

2365 params, 

2366 execution_options=execution_options, 

2367 bind_arguments=bind_arguments, 

2368 _parent_execute_state=_parent_execute_state, 

2369 _add_event=_add_event, 

2370 ) 

2371 

2372 @overload 

2373 def scalar( 

2374 self, 

2375 statement: TypedReturnsRows[_T], 

2376 params: Optional[_CoreSingleExecuteParams] = None, 

2377 *, 

2378 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2379 bind_arguments: Optional[_BindArguments] = None, 

2380 **kw: Any, 

2381 ) -> Optional[_T]: ... 

2382 

2383 @overload 

2384 def scalar( 

2385 self, 

2386 statement: Executable, 

2387 params: Optional[_CoreSingleExecuteParams] = None, 

2388 *, 

2389 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2390 bind_arguments: Optional[_BindArguments] = None, 

2391 **kw: Any, 

2392 ) -> Any: ... 

2393 

2394 def scalar( 

2395 self, 

2396 statement: Executable, 

2397 params: Optional[_CoreSingleExecuteParams] = None, 

2398 *, 

2399 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2400 bind_arguments: Optional[_BindArguments] = None, 

2401 **kw: Any, 

2402 ) -> Any: 

2403 """Execute a statement and return a scalar result. 

2404 

2405 Usage and parameters are the same as that of 

2406 :meth:`_orm.Session.execute`; the return result is a scalar Python 

2407 value. 

2408 

2409 """ 

2410 

2411 return self._execute_internal( 

2412 statement, 

2413 params, 

2414 execution_options=execution_options, 

2415 bind_arguments=bind_arguments, 

2416 _scalar_result=True, 

2417 **kw, 

2418 ) 

2419 

2420 @overload 

2421 def scalars( 

2422 self, 

2423 statement: TypedReturnsRows[_T], 

2424 params: Optional[_CoreAnyExecuteParams] = None, 

2425 *, 

2426 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2427 bind_arguments: Optional[_BindArguments] = None, 

2428 **kw: Any, 

2429 ) -> ScalarResult[_T]: ... 

2430 

2431 @overload 

2432 def scalars( 

2433 self, 

2434 statement: Executable, 

2435 params: Optional[_CoreAnyExecuteParams] = None, 

2436 *, 

2437 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2438 bind_arguments: Optional[_BindArguments] = None, 

2439 **kw: Any, 

2440 ) -> ScalarResult[Any]: ... 

2441 

2442 def scalars( 

2443 self, 

2444 statement: Executable, 

2445 params: Optional[_CoreAnyExecuteParams] = None, 

2446 *, 

2447 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2448 bind_arguments: Optional[_BindArguments] = None, 

2449 **kw: Any, 

2450 ) -> ScalarResult[Any]: 

2451 """Execute a statement and return the results as scalars. 

2452 

2453 Usage and parameters are the same as that of 

2454 :meth:`_orm.Session.execute`; the return result is a 

2455 :class:`_result.ScalarResult` filtering object which 

2456 will return single elements rather than :class:`_row.Row` objects. 

2457 

2458 :return: a :class:`_result.ScalarResult` object 

2459 

2460 .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars` 

2461 

2462 .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars` 

2463 

2464 .. seealso:: 

2465 

2466 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

2467 of :meth:`_orm.Session.execute` to :meth:`_orm.Session.scalars` 

2468 

2469 """ 

2470 

2471 return self._execute_internal( 

2472 statement, 

2473 params=params, 

2474 execution_options=execution_options, 

2475 bind_arguments=bind_arguments, 

2476 _scalar_result=False, # mypy appreciates this 

2477 **kw, 

2478 ).scalars() 

2479 

2480 def close(self) -> None: 

2481 """Close out the transactional resources and ORM objects used by this 

2482 :class:`_orm.Session`. 

2483 

2484 This expunges all ORM objects associated with this 

2485 :class:`_orm.Session`, ends any transaction in progress and 

2486 :term:`releases` any :class:`_engine.Connection` objects which this 

2487 :class:`_orm.Session` itself has checked out from associated 

2488 :class:`_engine.Engine` objects. The operation then leaves the 

2489 :class:`_orm.Session` in a state which it may be used again. 

2490 

2491 .. tip:: 

2492 

2493 In the default running mode the :meth:`_orm.Session.close` 

2494 method **does not prevent the Session from being used again**. 

2495 The :class:`_orm.Session` itself does not actually have a 

2496 distinct "closed" state; it merely means 

2497 the :class:`_orm.Session` will release all database connections 

2498 and ORM objects. 

2499 

2500 Setting the parameter :paramref:`_orm.Session.close_resets_only` 

2501 to ``False`` will instead make the ``close`` final, meaning that 

2502 any further action on the session will be forbidden. 

2503 

2504 .. versionchanged:: 1.4 The :meth:`.Session.close` method does not 

2505 immediately create a new :class:`.SessionTransaction` object; 

2506 instead, the new :class:`.SessionTransaction` is created only if 

2507 the :class:`.Session` is used again for a database operation. 

2508 

2509 .. seealso:: 

2510 

2511 :ref:`session_closing` - detail on the semantics of 

2512 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`. 

2513 

2514 :meth:`_orm.Session.reset` - a similar method that behaves like 

2515 ``close()`` with the parameter 

2516 :paramref:`_orm.Session.close_resets_only` set to ``True``. 

2517 

2518 """ 

2519 self._close_impl(invalidate=False) 

2520 

2521 def reset(self) -> None: 

2522 """Close out the transactional resources and ORM objects used by this 

2523 :class:`_orm.Session`, resetting the session to its initial state. 

2524 

2525 This method provides for same "reset-only" behavior that the 

2526 :meth:`_orm.Session.close` method has provided historically, where the 

2527 state of the :class:`_orm.Session` is reset as though the object were 

2528 brand new, and ready to be used again. 

2529 This method may then be useful for :class:`_orm.Session` objects 

2530 which set :paramref:`_orm.Session.close_resets_only` to ``False``, 

2531 so that "reset only" behavior is still available. 

2532 

2533 .. versionadded:: 2.0.22 

2534 

2535 .. seealso:: 

2536 

2537 :ref:`session_closing` - detail on the semantics of 

2538 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`. 

2539 

2540 :meth:`_orm.Session.close` - a similar method will additionally 

2541 prevent re-use of the Session when the parameter 

2542 :paramref:`_orm.Session.close_resets_only` is set to ``False``. 

2543 """ 

2544 self._close_impl(invalidate=False, is_reset=True) 

2545 

2546 def invalidate(self) -> None: 

2547 """Close this Session, using connection invalidation. 

2548 

2549 This is a variant of :meth:`.Session.close` that will additionally 

2550 ensure that the :meth:`_engine.Connection.invalidate` 

2551 method will be called on each :class:`_engine.Connection` object 

2552 that is currently in use for a transaction (typically there is only 

2553 one connection unless the :class:`_orm.Session` is used with 

2554 multiple engines). 

2555 

2556 This can be called when the database is known to be in a state where 

2557 the connections are no longer safe to be used. 

2558 

2559 Below illustrates a scenario when using `gevent 

2560 <https://www.gevent.org/>`_, which can produce ``Timeout`` exceptions 

2561 that may mean the underlying connection should be discarded:: 

2562 

2563 import gevent 

2564 

2565 try: 

2566 sess = Session() 

2567 sess.add(User()) 

2568 sess.commit() 

2569 except gevent.Timeout: 

2570 sess.invalidate() 

2571 raise 

2572 except: 

2573 sess.rollback() 

2574 raise 

2575 

2576 The method additionally does everything that :meth:`_orm.Session.close` 

2577 does, including that all ORM objects are expunged. 

2578 

2579 """ 

2580 self._close_impl(invalidate=True) 

2581 

2582 def _close_impl(self, invalidate: bool, is_reset: bool = False) -> None: 

2583 if not is_reset and self._close_state is _SessionCloseState.ACTIVE: 

2584 self._close_state = _SessionCloseState.CLOSED 

2585 self.expunge_all() 

2586 if self._transaction is not None: 

2587 for transaction in self._transaction._iterate_self_and_parents(): 

2588 transaction.close(invalidate) 

2589 

2590 def expunge_all(self) -> None: 

2591 """Remove all object instances from this ``Session``. 

2592 

2593 This is equivalent to calling ``expunge(obj)`` on all objects in this 

2594 ``Session``. 

2595 

2596 """ 

2597 

2598 all_states = self.identity_map.all_states() + list(self._new) 

2599 self.identity_map._kill() 

2600 self.identity_map = identity._WeakInstanceDict() 

2601 self._new = {} 

2602 self._deleted = {} 

2603 

2604 statelib.InstanceState._detach_states(all_states, self) 

2605 

2606 def _add_bind(self, key: _SessionBindKey, bind: _SessionBind) -> None: 

2607 try: 

2608 insp = inspect(key) 

2609 except sa_exc.NoInspectionAvailable as err: 

2610 if not isinstance(key, type): 

2611 raise sa_exc.ArgumentError( 

2612 "Not an acceptable bind target: %s" % key 

2613 ) from err 

2614 else: 

2615 self.__binds[key] = bind 

2616 else: 

2617 if TYPE_CHECKING: 

2618 assert isinstance(insp, Inspectable) 

2619 

2620 if isinstance(insp, TableClause): 

2621 self.__binds[insp] = bind 

2622 elif insp_is_mapper(insp): 

2623 self.__binds[insp.class_] = bind 

2624 for _selectable in insp._all_tables: 

2625 self.__binds[_selectable] = bind 

2626 else: 

2627 raise sa_exc.ArgumentError( 

2628 "Not an acceptable bind target: %s" % key 

2629 ) 

2630 

2631 def bind_mapper( 

2632 self, mapper: _EntityBindKey[_O], bind: _SessionBind 

2633 ) -> None: 

2634 """Associate a :class:`_orm.Mapper` or arbitrary Python class with a 

2635 "bind", e.g. an :class:`_engine.Engine` or 

2636 :class:`_engine.Connection`. 

2637 

2638 The given entity is added to a lookup used by the 

2639 :meth:`.Session.get_bind` method. 

2640 

2641 :param mapper: a :class:`_orm.Mapper` object, 

2642 or an instance of a mapped 

2643 class, or any Python class that is the base of a set of mapped 

2644 classes. 

2645 

2646 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection` 

2647 object. 

2648 

2649 .. seealso:: 

2650 

2651 :ref:`session_partitioning` 

2652 

2653 :paramref:`.Session.binds` 

2654 

2655 :meth:`.Session.bind_table` 

2656 

2657 

2658 """ 

2659 self._add_bind(mapper, bind) 

2660 

2661 def bind_table(self, table: TableClause, bind: _SessionBind) -> None: 

2662 """Associate a :class:`_schema.Table` with a "bind", e.g. an 

2663 :class:`_engine.Engine` 

2664 or :class:`_engine.Connection`. 

2665 

2666 The given :class:`_schema.Table` is added to a lookup used by the 

2667 :meth:`.Session.get_bind` method. 

2668 

2669 :param table: a :class:`_schema.Table` object, 

2670 which is typically the target 

2671 of an ORM mapping, or is present within a selectable that is 

2672 mapped. 

2673 

2674 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection` 

2675 object. 

2676 

2677 .. seealso:: 

2678 

2679 :ref:`session_partitioning` 

2680 

2681 :paramref:`.Session.binds` 

2682 

2683 :meth:`.Session.bind_mapper` 

2684 

2685 

2686 """ 

2687 self._add_bind(table, bind) 

2688 

2689 def get_bind( 

2690 self, 

2691 mapper: Optional[_EntityBindKey[_O]] = None, 

2692 *, 

2693 clause: Optional[ClauseElement] = None, 

2694 bind: Optional[_SessionBind] = None, 

2695 _sa_skip_events: Optional[bool] = None, 

2696 _sa_skip_for_implicit_returning: bool = False, 

2697 **kw: Any, 

2698 ) -> Union[Engine, Connection]: 

2699 """Return a "bind" to which this :class:`.Session` is bound. 

2700 

2701 The "bind" is usually an instance of :class:`_engine.Engine`, 

2702 except in the case where the :class:`.Session` has been 

2703 explicitly bound directly to a :class:`_engine.Connection`. 

2704 

2705 For a multiply-bound or unbound :class:`.Session`, the 

2706 ``mapper`` or ``clause`` arguments are used to determine the 

2707 appropriate bind to return. 

2708 

2709 Note that the "mapper" argument is usually present 

2710 when :meth:`.Session.get_bind` is called via an ORM 

2711 operation such as a :meth:`.Session.query`, each 

2712 individual INSERT/UPDATE/DELETE operation within a 

2713 :meth:`.Session.flush`, call, etc. 

2714 

2715 The order of resolution is: 

2716 

2717 1. if mapper given and :paramref:`.Session.binds` is present, 

2718 locate a bind based first on the mapper in use, then 

2719 on the mapped class in use, then on any base classes that are 

2720 present in the ``__mro__`` of the mapped class, from more specific 

2721 superclasses to more general. 

2722 2. if clause given and ``Session.binds`` is present, 

2723 locate a bind based on :class:`_schema.Table` objects 

2724 found in the given clause present in ``Session.binds``. 

2725 3. if ``Session.binds`` is present, return that. 

2726 4. if clause given, attempt to return a bind 

2727 linked to the :class:`_schema.MetaData` ultimately 

2728 associated with the clause. 

2729 5. if mapper given, attempt to return a bind 

2730 linked to the :class:`_schema.MetaData` ultimately 

2731 associated with the :class:`_schema.Table` or other 

2732 selectable to which the mapper is mapped. 

2733 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError` 

2734 is raised. 

2735 

2736 Note that the :meth:`.Session.get_bind` method can be overridden on 

2737 a user-defined subclass of :class:`.Session` to provide any kind 

2738 of bind resolution scheme. See the example at 

2739 :ref:`session_custom_partitioning`. 

2740 

2741 :param mapper: 

2742 Optional mapped class or corresponding :class:`_orm.Mapper` instance. 

2743 The bind can be derived from a :class:`_orm.Mapper` first by 

2744 consulting the "binds" map associated with this :class:`.Session`, 

2745 and secondly by consulting the :class:`_schema.MetaData` associated 

2746 with the :class:`_schema.Table` to which the :class:`_orm.Mapper` is 

2747 mapped for a bind. 

2748 

2749 :param clause: 

2750 A :class:`_expression.ClauseElement` (i.e. 

2751 :func:`_expression.select`, 

2752 :func:`_expression.text`, 

2753 etc.). If the ``mapper`` argument is not present or could not 

2754 produce a bind, the given expression construct will be searched 

2755 for a bound element, typically a :class:`_schema.Table` 

2756 associated with 

2757 bound :class:`_schema.MetaData`. 

2758 

2759 .. seealso:: 

2760 

2761 :ref:`session_partitioning` 

2762 

2763 :paramref:`.Session.binds` 

2764 

2765 :meth:`.Session.bind_mapper` 

2766 

2767 :meth:`.Session.bind_table` 

2768 

2769 """ 

2770 

2771 # this function is documented as a subclassing hook, so we have 

2772 # to call this method even if the return is simple 

2773 if bind: 

2774 return bind 

2775 elif not self.__binds and self.bind: 

2776 # simplest and most common case, we have a bind and no 

2777 # per-mapper/table binds, we're done 

2778 return self.bind 

2779 

2780 # we don't have self.bind and either have self.__binds 

2781 # or we don't have self.__binds (which is legacy). Look at the 

2782 # mapper and the clause 

2783 if mapper is None and clause is None: 

2784 if self.bind: 

2785 return self.bind 

2786 else: 

2787 raise sa_exc.UnboundExecutionError( 

2788 "This session is not bound to a single Engine or " 

2789 "Connection, and no context was provided to locate " 

2790 "a binding." 

2791 ) 

2792 

2793 # look more closely at the mapper. 

2794 if mapper is not None: 

2795 try: 

2796 inspected_mapper = inspect(mapper) 

2797 except sa_exc.NoInspectionAvailable as err: 

2798 if isinstance(mapper, type): 

2799 raise exc.UnmappedClassError(mapper) from err 

2800 else: 

2801 raise 

2802 else: 

2803 inspected_mapper = None 

2804 

2805 # match up the mapper or clause in the __binds 

2806 if self.__binds: 

2807 # matching mappers and selectables to entries in the 

2808 # binds dictionary; supported use case. 

2809 if inspected_mapper: 

2810 for cls in inspected_mapper.class_.__mro__: 

2811 if cls in self.__binds: 

2812 return self.__binds[cls] 

2813 if clause is None: 

2814 clause = inspected_mapper.persist_selectable 

2815 

2816 if clause is not None: 

2817 plugin_subject = clause._propagate_attrs.get( 

2818 "plugin_subject", None 

2819 ) 

2820 

2821 if plugin_subject is not None: 

2822 for cls in plugin_subject.mapper.class_.__mro__: 

2823 if cls in self.__binds: 

2824 return self.__binds[cls] 

2825 

2826 for obj in visitors.iterate(clause): 

2827 if obj in self.__binds: 

2828 if TYPE_CHECKING: 

2829 assert isinstance(obj, Table) 

2830 return self.__binds[obj] 

2831 

2832 # none of the __binds matched, but we have a fallback bind. 

2833 # return that 

2834 if self.bind: 

2835 return self.bind 

2836 

2837 context = [] 

2838 if inspected_mapper is not None: 

2839 context.append(f"mapper {inspected_mapper}") 

2840 if clause is not None: 

2841 context.append("SQL expression") 

2842 

2843 raise sa_exc.UnboundExecutionError( 

2844 f"Could not locate a bind configured on " 

2845 f'{", ".join(context)} or this Session.' 

2846 ) 

2847 

2848 @overload 

2849 def query(self, _entity: _EntityType[_O]) -> Query[_O]: ... 

2850 

2851 @overload 

2852 def query( 

2853 self, _colexpr: TypedColumnsClauseRole[_T] 

2854 ) -> RowReturningQuery[_T]: ... 

2855 

2856 # START OVERLOADED FUNCTIONS self.query RowReturningQuery 2-8 

2857 

2858 # code within this block is **programmatically, 

2859 # statically generated** by tools/generate_tuple_map_overloads.py 

2860 

2861 @overload 

2862 def query( 

2863 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], / 

2864 ) -> RowReturningQuery[_T0, _T1]: ... 

2865 

2866 @overload 

2867 def query( 

2868 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], / 

2869 ) -> RowReturningQuery[_T0, _T1, _T2]: ... 

2870 

2871 @overload 

2872 def query( 

2873 self, 

2874 __ent0: _TCCA[_T0], 

2875 __ent1: _TCCA[_T1], 

2876 __ent2: _TCCA[_T2], 

2877 __ent3: _TCCA[_T3], 

2878 /, 

2879 ) -> RowReturningQuery[_T0, _T1, _T2, _T3]: ... 

2880 

2881 @overload 

2882 def query( 

2883 self, 

2884 __ent0: _TCCA[_T0], 

2885 __ent1: _TCCA[_T1], 

2886 __ent2: _TCCA[_T2], 

2887 __ent3: _TCCA[_T3], 

2888 __ent4: _TCCA[_T4], 

2889 /, 

2890 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4]: ... 

2891 

2892 @overload 

2893 def query( 

2894 self, 

2895 __ent0: _TCCA[_T0], 

2896 __ent1: _TCCA[_T1], 

2897 __ent2: _TCCA[_T2], 

2898 __ent3: _TCCA[_T3], 

2899 __ent4: _TCCA[_T4], 

2900 __ent5: _TCCA[_T5], 

2901 /, 

2902 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5]: ... 

2903 

2904 @overload 

2905 def query( 

2906 self, 

2907 __ent0: _TCCA[_T0], 

2908 __ent1: _TCCA[_T1], 

2909 __ent2: _TCCA[_T2], 

2910 __ent3: _TCCA[_T3], 

2911 __ent4: _TCCA[_T4], 

2912 __ent5: _TCCA[_T5], 

2913 __ent6: _TCCA[_T6], 

2914 /, 

2915 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5, _T6]: ... 

2916 

2917 @overload 

2918 def query( 

2919 self, 

2920 __ent0: _TCCA[_T0], 

2921 __ent1: _TCCA[_T1], 

2922 __ent2: _TCCA[_T2], 

2923 __ent3: _TCCA[_T3], 

2924 __ent4: _TCCA[_T4], 

2925 __ent5: _TCCA[_T5], 

2926 __ent6: _TCCA[_T6], 

2927 __ent7: _TCCA[_T7], 

2928 /, 

2929 *entities: _ColumnsClauseArgument[Any], 

2930 ) -> RowReturningQuery[ 

2931 _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny] 

2932 ]: ... 

2933 

2934 # END OVERLOADED FUNCTIONS self.query 

2935 

2936 @overload 

2937 def query( 

2938 self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any 

2939 ) -> Query[Any]: ... 

2940 

2941 def query( 

2942 self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any 

2943 ) -> Query[Any]: 

2944 """Return a new :class:`_query.Query` object corresponding to this 

2945 :class:`_orm.Session`. 

2946 

2947 Note that the :class:`_query.Query` object is legacy as of 

2948 SQLAlchemy 2.0; the :func:`_sql.select` construct is now used 

2949 to construct ORM queries. 

2950 

2951 .. seealso:: 

2952 

2953 :ref:`unified_tutorial` 

2954 

2955 :ref:`queryguide_toplevel` 

2956 

2957 :ref:`query_api_toplevel` - legacy API doc 

2958 

2959 """ 

2960 

2961 return self._query_cls(entities, self, **kwargs) 

2962 

2963 def _identity_lookup( 

2964 self, 

2965 mapper: Mapper[_O], 

2966 primary_key_identity: Union[Any, Tuple[Any, ...]], 

2967 identity_token: Any = None, 

2968 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

2969 lazy_loaded_from: Optional[InstanceState[Any]] = None, 

2970 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2971 bind_arguments: Optional[_BindArguments] = None, 

2972 ) -> Union[Optional[_O], LoaderCallableStatus]: 

2973 """Locate an object in the identity map. 

2974 

2975 Given a primary key identity, constructs an identity key and then 

2976 looks in the session's identity map. If present, the object may 

2977 be run through unexpiration rules (e.g. load unloaded attributes, 

2978 check if was deleted). 

2979 

2980 e.g.:: 

2981 

2982 obj = session._identity_lookup(inspect(SomeClass), (1,)) 

2983 

2984 :param mapper: mapper in use 

2985 :param primary_key_identity: the primary key we are searching for, as 

2986 a tuple. 

2987 :param identity_token: identity token that should be used to create 

2988 the identity key. Used as is, however overriding subclasses can 

2989 repurpose this in order to interpret the value in a special way, 

2990 such as if None then look among multiple target tokens. 

2991 :param passive: passive load flag passed to 

2992 :func:`.loading.get_from_identity`, which impacts the behavior if 

2993 the object is found; the object may be validated and/or unexpired 

2994 if the flag allows for SQL to be emitted. 

2995 :param lazy_loaded_from: an :class:`.InstanceState` that is 

2996 specifically asking for this identity as a related identity. Used 

2997 for sharding schemes where there is a correspondence between an object 

2998 and a related object being lazy-loaded (or otherwise 

2999 relationship-loaded). 

3000 

3001 :return: None if the object is not found in the identity map, *or* 

3002 if the object was unexpired and found to have been deleted. 

3003 if passive flags disallow SQL and the object is expired, returns 

3004 PASSIVE_NO_RESULT. In all other cases the instance is returned. 

3005 

3006 .. versionchanged:: 1.4.0 - the :meth:`.Session._identity_lookup` 

3007 method was moved from :class:`_query.Query` to 

3008 :class:`.Session`, to avoid having to instantiate the 

3009 :class:`_query.Query` object. 

3010 

3011 

3012 """ 

3013 

3014 key = mapper.identity_key_from_primary_key( 

3015 primary_key_identity, identity_token=identity_token 

3016 ) 

3017 

3018 # work around: https://github.com/python/typing/discussions/1143 

3019 return_value = loading.get_from_identity(self, mapper, key, passive) 

3020 return return_value 

3021 

3022 @util.non_memoized_property 

3023 @contextlib.contextmanager 

3024 def no_autoflush(self) -> Iterator[Session]: 

3025 """Return a context manager that disables autoflush. 

3026 

3027 e.g.:: 

3028 

3029 with session.no_autoflush: 

3030 

3031 some_object = SomeClass() 

3032 session.add(some_object) 

3033 # won't autoflush 

3034 some_object.related_thing = session.query(SomeRelated).first() 

3035 

3036 Operations that proceed within the ``with:`` block 

3037 will not be subject to flushes occurring upon query 

3038 access. This is useful when initializing a series 

3039 of objects which involve existing database queries, 

3040 where the uncompleted object should not yet be flushed. 

3041 

3042 """ 

3043 autoflush = self.autoflush 

3044 self.autoflush = False 

3045 try: 

3046 yield self 

3047 finally: 

3048 self.autoflush = autoflush 

3049 

3050 @util.langhelpers.tag_method_for_warnings( 

3051 "This warning originated from the Session 'autoflush' process, " 

3052 "which was invoked automatically in response to a user-initiated " 

3053 "operation. Consider using ``no_autoflush`` context manager if this " 

3054 "warning happened while initializing objects.", 

3055 sa_exc.SAWarning, 

3056 ) 

3057 def _autoflush(self) -> None: 

3058 if self.autoflush and not self._flushing: 

3059 try: 

3060 self.flush() 

3061 except sa_exc.StatementError as e: 

3062 # note we are reraising StatementError as opposed to 

3063 # raising FlushError with "chaining" to remain compatible 

3064 # with code that catches StatementError, IntegrityError, 

3065 # etc. 

3066 e.add_detail( 

3067 "raised as a result of Query-invoked autoflush; " 

3068 "consider using a session.no_autoflush block if this " 

3069 "flush is occurring prematurely" 

3070 ) 

3071 raise e.with_traceback(sys.exc_info()[2]) 

3072 

3073 def refresh( 

3074 self, 

3075 instance: object, 

3076 attribute_names: Optional[Iterable[str]] = None, 

3077 with_for_update: ForUpdateParameter = None, 

3078 ) -> None: 

3079 """Expire and refresh attributes on the given instance. 

3080 

3081 The selected attributes will first be expired as they would when using 

3082 :meth:`_orm.Session.expire`; then a SELECT statement will be issued to 

3083 the database to refresh column-oriented attributes with the current 

3084 value available in the current transaction. 

3085 

3086 :func:`_orm.relationship` oriented attributes will also be immediately 

3087 loaded if they were already eagerly loaded on the object, using the 

3088 same eager loading strategy that they were loaded with originally. 

3089 

3090 .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method 

3091 can also refresh eagerly loaded attributes. 

3092 

3093 :func:`_orm.relationship` oriented attributes that would normally 

3094 load using the ``select`` (or "lazy") loader strategy will also 

3095 load **if they are named explicitly in the attribute_names 

3096 collection**, emitting a SELECT statement for the attribute using the 

3097 ``immediate`` loader strategy. If lazy-loaded relationships are not 

3098 named in :paramref:`_orm.Session.refresh.attribute_names`, then 

3099 they remain as "lazy loaded" attributes and are not implicitly 

3100 refreshed. 

3101 

3102 .. versionchanged:: 2.0.4 The :meth:`_orm.Session.refresh` method 

3103 will now refresh lazy-loaded :func:`_orm.relationship` oriented 

3104 attributes for those which are named explicitly in the 

3105 :paramref:`_orm.Session.refresh.attribute_names` collection. 

3106 

3107 .. tip:: 

3108 

3109 While the :meth:`_orm.Session.refresh` method is capable of 

3110 refreshing both column and relationship oriented attributes, its 

3111 primary focus is on refreshing of local column-oriented attributes 

3112 on a single instance. For more open ended "refresh" functionality, 

3113 including the ability to refresh the attributes on many objects at 

3114 once while having explicit control over relationship loader 

3115 strategies, use the 

3116 :ref:`populate existing <orm_queryguide_populate_existing>` feature 

3117 instead. 

3118 

3119 Note that a highly isolated transaction will return the same values as 

3120 were previously read in that same transaction, regardless of changes 

3121 in database state outside of that transaction. Refreshing 

3122 attributes usually only makes sense at the start of a transaction 

3123 where database rows have not yet been accessed. 

3124 

3125 :param attribute_names: optional. An iterable collection of 

3126 string attribute names indicating a subset of attributes to 

3127 be refreshed. 

3128 

3129 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE 

3130 should be used, or may be a dictionary containing flags to 

3131 indicate a more specific set of FOR UPDATE flags for the SELECT; 

3132 flags should match the parameters of 

3133 :meth:`_query.Query.with_for_update`. 

3134 Supersedes the :paramref:`.Session.refresh.lockmode` parameter. 

3135 

3136 .. seealso:: 

3137 

3138 :ref:`session_expire` - introductory material 

3139 

3140 :meth:`.Session.expire` 

3141 

3142 :meth:`.Session.expire_all` 

3143 

3144 :ref:`orm_queryguide_populate_existing` - allows any ORM query 

3145 to refresh objects as they would be loaded normally. 

3146 

3147 """ 

3148 try: 

3149 state = attributes.instance_state(instance) 

3150 except exc.NO_STATE as err: 

3151 raise exc.UnmappedInstanceError(instance) from err 

3152 

3153 self._expire_state(state, attribute_names) 

3154 

3155 # this autoflush previously used to occur as a secondary effect 

3156 # of the load_on_ident below. Meaning we'd organize the SELECT 

3157 # based on current DB pks, then flush, then if pks changed in that 

3158 # flush, crash. this was unticketed but discovered as part of 

3159 # #8703. So here, autoflush up front, dont autoflush inside 

3160 # load_on_ident. 

3161 self._autoflush() 

3162 

3163 if with_for_update == {}: 

3164 raise sa_exc.ArgumentError( 

3165 "with_for_update should be the boolean value " 

3166 "True, or a dictionary with options. " 

3167 "A blank dictionary is ambiguous." 

3168 ) 

3169 

3170 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3171 

3172 stmt: Select[Unpack[TupleAny]] = sql.select(object_mapper(instance)) 

3173 if ( 

3174 loading._load_on_ident( 

3175 self, 

3176 stmt, 

3177 state.key, 

3178 refresh_state=state, 

3179 with_for_update=with_for_update, 

3180 only_load_props=attribute_names, 

3181 require_pk_cols=True, 

3182 # technically unnecessary as we just did autoflush 

3183 # above, however removes the additional unnecessary 

3184 # call to _autoflush() 

3185 no_autoflush=True, 

3186 is_user_refresh=True, 

3187 ) 

3188 is None 

3189 ): 

3190 raise sa_exc.InvalidRequestError( 

3191 "Could not refresh instance '%s'" % instance_str(instance) 

3192 ) 

3193 

3194 def expire_all(self) -> None: 

3195 """Expires all persistent instances within this Session. 

3196 

3197 When any attributes on a persistent instance is next accessed, 

3198 a query will be issued using the 

3199 :class:`.Session` object's current transactional context in order to 

3200 load all expired attributes for the given instance. Note that 

3201 a highly isolated transaction will return the same values as were 

3202 previously read in that same transaction, regardless of changes 

3203 in database state outside of that transaction. 

3204 

3205 To expire individual objects and individual attributes 

3206 on those objects, use :meth:`Session.expire`. 

3207 

3208 The :class:`.Session` object's default behavior is to 

3209 expire all state whenever the :meth:`Session.rollback` 

3210 or :meth:`Session.commit` methods are called, so that new 

3211 state can be loaded for the new transaction. For this reason, 

3212 calling :meth:`Session.expire_all` is not usually needed, 

3213 assuming the transaction is isolated. 

3214 

3215 .. seealso:: 

3216 

3217 :ref:`session_expire` - introductory material 

3218 

3219 :meth:`.Session.expire` 

3220 

3221 :meth:`.Session.refresh` 

3222 

3223 :meth:`_orm.Query.populate_existing` 

3224 

3225 """ 

3226 for state in self.identity_map.all_states(): 

3227 state._expire(state.dict, self.identity_map._modified) 

3228 

3229 def expire( 

3230 self, instance: object, attribute_names: Optional[Iterable[str]] = None 

3231 ) -> None: 

3232 """Expire the attributes on an instance. 

3233 

3234 Marks the attributes of an instance as out of date. When an expired 

3235 attribute is next accessed, a query will be issued to the 

3236 :class:`.Session` object's current transactional context in order to 

3237 load all expired attributes for the given instance. Note that 

3238 a highly isolated transaction will return the same values as were 

3239 previously read in that same transaction, regardless of changes 

3240 in database state outside of that transaction. 

3241 

3242 To expire all objects in the :class:`.Session` simultaneously, 

3243 use :meth:`Session.expire_all`. 

3244 

3245 The :class:`.Session` object's default behavior is to 

3246 expire all state whenever the :meth:`Session.rollback` 

3247 or :meth:`Session.commit` methods are called, so that new 

3248 state can be loaded for the new transaction. For this reason, 

3249 calling :meth:`Session.expire` only makes sense for the specific 

3250 case that a non-ORM SQL statement was emitted in the current 

3251 transaction. 

3252 

3253 :param instance: The instance to be refreshed. 

3254 :param attribute_names: optional list of string attribute names 

3255 indicating a subset of attributes to be expired. 

3256 

3257 .. seealso:: 

3258 

3259 :ref:`session_expire` - introductory material 

3260 

3261 :meth:`.Session.expire` 

3262 

3263 :meth:`.Session.refresh` 

3264 

3265 :meth:`_orm.Query.populate_existing` 

3266 

3267 """ 

3268 try: 

3269 state = attributes.instance_state(instance) 

3270 except exc.NO_STATE as err: 

3271 raise exc.UnmappedInstanceError(instance) from err 

3272 self._expire_state(state, attribute_names) 

3273 

3274 def _expire_state( 

3275 self, 

3276 state: InstanceState[Any], 

3277 attribute_names: Optional[Iterable[str]], 

3278 ) -> None: 

3279 self._validate_persistent(state) 

3280 if attribute_names: 

3281 state._expire_attributes(state.dict, attribute_names) 

3282 else: 

3283 # pre-fetch the full cascade since the expire is going to 

3284 # remove associations 

3285 cascaded = list( 

3286 state.manager.mapper.cascade_iterator("refresh-expire", state) 

3287 ) 

3288 self._conditional_expire(state) 

3289 for o, m, st_, dct_ in cascaded: 

3290 self._conditional_expire(st_) 

3291 

3292 def _conditional_expire( 

3293 self, state: InstanceState[Any], autoflush: Optional[bool] = None 

3294 ) -> None: 

3295 """Expire a state if persistent, else expunge if pending""" 

3296 

3297 if state.key: 

3298 state._expire(state.dict, self.identity_map._modified) 

3299 elif state in self._new: 

3300 self._new.pop(state) 

3301 state._detach(self) 

3302 

3303 def expunge(self, instance: object) -> None: 

3304 """Remove the `instance` from this ``Session``. 

3305 

3306 This will free all internal references to the instance. Cascading 

3307 will be applied according to the *expunge* cascade rule. 

3308 

3309 """ 

3310 try: 

3311 state = attributes.instance_state(instance) 

3312 except exc.NO_STATE as err: 

3313 raise exc.UnmappedInstanceError(instance) from err 

3314 if state.session_id is not self.hash_key: 

3315 raise sa_exc.InvalidRequestError( 

3316 "Instance %s is not present in this Session" % state_str(state) 

3317 ) 

3318 

3319 cascaded = list( 

3320 state.manager.mapper.cascade_iterator("expunge", state) 

3321 ) 

3322 self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded]) 

3323 

3324 def _expunge_states( 

3325 self, states: Iterable[InstanceState[Any]], to_transient: bool = False 

3326 ) -> None: 

3327 for state in states: 

3328 if state in self._new: 

3329 self._new.pop(state) 

3330 elif self.identity_map.contains_state(state): 

3331 self.identity_map.safe_discard(state) 

3332 self._deleted.pop(state, None) 

3333 elif self._transaction: 

3334 # state is "detached" from being deleted, but still present 

3335 # in the transaction snapshot 

3336 self._transaction._deleted.pop(state, None) 

3337 statelib.InstanceState._detach_states( 

3338 states, self, to_transient=to_transient 

3339 ) 

3340 

3341 def _register_persistent(self, states: Set[InstanceState[Any]]) -> None: 

3342 """Register all persistent objects from a flush. 

3343 

3344 This is used both for pending objects moving to the persistent 

3345 state as well as already persistent objects. 

3346 

3347 """ 

3348 

3349 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3350 for state in states: 

3351 mapper = _state_mapper(state) 

3352 

3353 # prevent against last minute dereferences of the object 

3354 obj = state.obj() 

3355 if obj is not None: 

3356 instance_key = mapper._identity_key_from_state(state) 

3357 

3358 if ( 

3359 _none_set.intersection(instance_key[1]) 

3360 and not mapper.allow_partial_pks 

3361 or _none_set.issuperset(instance_key[1]) 

3362 ): 

3363 raise exc.FlushError( 

3364 "Instance %s has a NULL identity key. If this is an " 

3365 "auto-generated value, check that the database table " 

3366 "allows generation of new primary key values, and " 

3367 "that the mapped Column object is configured to " 

3368 "expect these generated values. Ensure also that " 

3369 "this flush() is not occurring at an inappropriate " 

3370 "time, such as within a load() event." 

3371 % state_str(state) 

3372 ) 

3373 

3374 if state.key is None: 

3375 state.key = instance_key 

3376 elif state.key != instance_key: 

3377 # primary key switch. use safe_discard() in case another 

3378 # state has already replaced this one in the identity 

3379 # map (see test/orm/test_naturalpks.py ReversePKsTest) 

3380 self.identity_map.safe_discard(state) 

3381 trans = self._transaction 

3382 assert trans is not None 

3383 if state in trans._key_switches: 

3384 orig_key = trans._key_switches[state][0] 

3385 else: 

3386 orig_key = state.key 

3387 trans._key_switches[state] = ( 

3388 orig_key, 

3389 instance_key, 

3390 ) 

3391 state.key = instance_key 

3392 

3393 # there can be an existing state in the identity map 

3394 # that is replaced when the primary keys of two instances 

3395 # are swapped; see test/orm/test_naturalpks.py -> test_reverse 

3396 old = self.identity_map.replace(state) 

3397 if ( 

3398 old is not None 

3399 and mapper._identity_key_from_state(old) == instance_key 

3400 and old.obj() is not None 

3401 ): 

3402 util.warn( 

3403 "Identity map already had an identity for %s, " 

3404 "replacing it with newly flushed object. Are there " 

3405 "load operations occurring inside of an event handler " 

3406 "within the flush?" % (instance_key,) 

3407 ) 

3408 state._orphaned_outside_of_session = False 

3409 

3410 statelib.InstanceState._commit_all_states( 

3411 ((state, state.dict) for state in states), self.identity_map 

3412 ) 

3413 

3414 self._register_altered(states) 

3415 

3416 if pending_to_persistent is not None: 

3417 for state in states.intersection(self._new): 

3418 pending_to_persistent(self, state) 

3419 

3420 # remove from new last, might be the last strong ref 

3421 for state in set(states).intersection(self._new): 

3422 self._new.pop(state) 

3423 

3424 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None: 

3425 if self._transaction: 

3426 for state in states: 

3427 if state in self._new: 

3428 self._transaction._new[state] = True 

3429 else: 

3430 self._transaction._dirty[state] = True 

3431 

3432 def _remove_newly_deleted( 

3433 self, states: Iterable[InstanceState[Any]] 

3434 ) -> None: 

3435 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3436 for state in states: 

3437 if self._transaction: 

3438 self._transaction._deleted[state] = True 

3439 

3440 if persistent_to_deleted is not None: 

3441 # get a strong reference before we pop out of 

3442 # self._deleted 

3443 obj = state.obj() # noqa 

3444 

3445 self.identity_map.safe_discard(state) 

3446 self._deleted.pop(state, None) 

3447 state._deleted = True 

3448 # can't call state._detach() here, because this state 

3449 # is still in the transaction snapshot and needs to be 

3450 # tracked as part of that 

3451 if persistent_to_deleted is not None: 

3452 persistent_to_deleted(self, state) 

3453 

3454 def add(self, instance: object, *, _warn: bool = True) -> None: 

3455 """Place an object into this :class:`_orm.Session`. 

3456 

3457 Objects that are in the :term:`transient` state when passed to the 

3458 :meth:`_orm.Session.add` method will move to the 

3459 :term:`pending` state, until the next flush, at which point they 

3460 will move to the :term:`persistent` state. 

3461 

3462 Objects that are in the :term:`detached` state when passed to the 

3463 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3464 state directly. 

3465 

3466 If the transaction used by the :class:`_orm.Session` is rolled back, 

3467 objects which were transient when they were passed to 

3468 :meth:`_orm.Session.add` will be moved back to the 

3469 :term:`transient` state, and will no longer be present within this 

3470 :class:`_orm.Session`. 

3471 

3472 .. seealso:: 

3473 

3474 :meth:`_orm.Session.add_all` 

3475 

3476 :ref:`session_adding` - at :ref:`session_basics` 

3477 

3478 """ 

3479 if _warn and self._warn_on_events: 

3480 self._flush_warning("Session.add()") 

3481 

3482 try: 

3483 state = attributes.instance_state(instance) 

3484 except exc.NO_STATE as err: 

3485 raise exc.UnmappedInstanceError(instance) from err 

3486 

3487 self._save_or_update_state(state) 

3488 

3489 def add_all(self, instances: Iterable[object]) -> None: 

3490 """Add the given collection of instances to this :class:`_orm.Session`. 

3491 

3492 See the documentation for :meth:`_orm.Session.add` for a general 

3493 behavioral description. 

3494 

3495 .. seealso:: 

3496 

3497 :meth:`_orm.Session.add` 

3498 

3499 :ref:`session_adding` - at :ref:`session_basics` 

3500 

3501 """ 

3502 

3503 if self._warn_on_events: 

3504 self._flush_warning("Session.add_all()") 

3505 

3506 for instance in instances: 

3507 self.add(instance, _warn=False) 

3508 

3509 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3510 state._orphaned_outside_of_session = False 

3511 self._save_or_update_impl(state) 

3512 

3513 mapper = _state_mapper(state) 

3514 for o, m, st_, dct_ in mapper.cascade_iterator( 

3515 "save-update", state, halt_on=self._contains_state 

3516 ): 

3517 self._save_or_update_impl(st_) 

3518 

3519 def delete(self, instance: object) -> None: 

3520 """Mark an instance as deleted. 

3521 

3522 The object is assumed to be either :term:`persistent` or 

3523 :term:`detached` when passed; after the method is called, the 

3524 object will remain in the :term:`persistent` state until the next 

3525 flush proceeds. During this time, the object will also be a member 

3526 of the :attr:`_orm.Session.deleted` collection. 

3527 

3528 When the next flush proceeds, the object will move to the 

3529 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3530 for its row within the current transaction. When the transaction 

3531 is successfully committed, 

3532 the deleted object is moved to the :term:`detached` state and is 

3533 no longer present within this :class:`_orm.Session`. 

3534 

3535 .. seealso:: 

3536 

3537 :ref:`session_deleting` - at :ref:`session_basics` 

3538 

3539 :meth:`.Session.delete_all` - multiple instance version 

3540 

3541 """ 

3542 if self._warn_on_events: 

3543 self._flush_warning("Session.delete()") 

3544 

3545 self._delete_impl(object_state(instance), instance, head=True) 

3546 

3547 def delete_all(self, instances: Iterable[object]) -> None: 

3548 """Calls :meth:`.Session.delete` on multiple instances. 

3549 

3550 .. seealso:: 

3551 

3552 :meth:`.Session.delete` - main documentation on delete 

3553 

3554 .. versionadded:: 2.1 

3555 

3556 """ 

3557 

3558 if self._warn_on_events: 

3559 self._flush_warning("Session.delete_all()") 

3560 

3561 for instance in instances: 

3562 self._delete_impl(object_state(instance), instance, head=True) 

3563 

3564 def _delete_impl( 

3565 self, state: InstanceState[Any], obj: object, head: bool 

3566 ) -> None: 

3567 if state.key is None: 

3568 if head: 

3569 raise sa_exc.InvalidRequestError( 

3570 "Instance '%s' is not persisted" % state_str(state) 

3571 ) 

3572 else: 

3573 return 

3574 

3575 to_attach = self._before_attach(state, obj) 

3576 

3577 if state in self._deleted: 

3578 return 

3579 

3580 self.identity_map.add(state) 

3581 

3582 if to_attach: 

3583 self._after_attach(state, obj) 

3584 

3585 if head: 

3586 # grab the cascades before adding the item to the deleted list 

3587 # so that autoflush does not delete the item 

3588 # the strong reference to the instance itself is significant here 

3589 cascade_states = list( 

3590 state.manager.mapper.cascade_iterator("delete", state) 

3591 ) 

3592 else: 

3593 cascade_states = None 

3594 

3595 self._deleted[state] = obj 

3596 

3597 if head: 

3598 if TYPE_CHECKING: 

3599 assert cascade_states is not None 

3600 for o, m, st_, dct_ in cascade_states: 

3601 self._delete_impl(st_, o, False) 

3602 

3603 def get( 

3604 self, 

3605 entity: _EntityBindKey[_O], 

3606 ident: _PKIdentityArgument, 

3607 *, 

3608 options: Optional[Sequence[ORMOption]] = None, 

3609 populate_existing: bool = False, 

3610 with_for_update: ForUpdateParameter = None, 

3611 identity_token: Optional[Any] = None, 

3612 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3613 bind_arguments: Optional[_BindArguments] = None, 

3614 ) -> Optional[_O]: 

3615 """Return an instance based on the given primary key identifier, 

3616 or ``None`` if not found. 

3617 

3618 E.g.:: 

3619 

3620 my_user = session.get(User, 5) 

3621 

3622 some_object = session.get(VersionedFoo, (5, 10)) 

3623 

3624 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3625 

3626 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3627 from the now legacy :meth:`_orm.Query.get` method. 

3628 

3629 :meth:`_orm.Session.get` is special in that it provides direct 

3630 access to the identity map of the :class:`.Session`. 

3631 If the given primary key identifier is present 

3632 in the local identity map, the object is returned 

3633 directly from this collection and no SQL is emitted, 

3634 unless the object has been marked fully expired. 

3635 If not present, 

3636 a SELECT is performed in order to locate the object. 

3637 

3638 :meth:`_orm.Session.get` also will perform a check if 

3639 the object is present in the identity map and 

3640 marked as expired - a SELECT 

3641 is emitted to refresh the object as well as to 

3642 ensure that the row is still present. 

3643 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3644 

3645 :param entity: a mapped class or :class:`.Mapper` indicating the 

3646 type of entity to be loaded. 

3647 

3648 :param ident: A scalar, tuple, or dictionary representing the 

3649 primary key. For a composite (e.g. multiple column) primary key, 

3650 a tuple or dictionary should be passed. 

3651 

3652 For a single-column primary key, the scalar calling form is typically 

3653 the most expedient. If the primary key of a row is the value "5", 

3654 the call looks like:: 

3655 

3656 my_object = session.get(SomeClass, 5) 

3657 

3658 The tuple form contains primary key values typically in 

3659 the order in which they correspond to the mapped 

3660 :class:`_schema.Table` 

3661 object's primary key columns, or if the 

3662 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3663 used, in 

3664 the order used for that parameter. For example, if the primary key 

3665 of a row is represented by the integer 

3666 digits "5, 10" the call would look like:: 

3667 

3668 my_object = session.get(SomeClass, (5, 10)) 

3669 

3670 The dictionary form should include as keys the mapped attribute names 

3671 corresponding to each element of the primary key. If the mapped class 

3672 has the attributes ``id``, ``version_id`` as the attributes which 

3673 store the object's primary key value, the call would look like:: 

3674 

3675 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3676 

3677 :param options: optional sequence of loader options which will be 

3678 applied to the query, if one is emitted. 

3679 

3680 :param populate_existing: causes the method to unconditionally emit 

3681 a SQL query and refresh the object with the newly loaded data, 

3682 regardless of whether or not the object is already present. 

3683 

3684 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE 

3685 should be used, or may be a dictionary containing flags to 

3686 indicate a more specific set of FOR UPDATE flags for the SELECT; 

3687 flags should match the parameters of 

3688 :meth:`_query.Query.with_for_update`. 

3689 Supersedes the :paramref:`.Session.refresh.lockmode` parameter. 

3690 

3691 :param execution_options: optional dictionary of execution options, 

3692 which will be associated with the query execution if one is emitted. 

3693 This dictionary can provide a subset of the options that are 

3694 accepted by :meth:`_engine.Connection.execution_options`, and may 

3695 also provide additional options understood only in an ORM context. 

3696 

3697 .. versionadded:: 1.4.29 

3698 

3699 .. seealso:: 

3700 

3701 :ref:`orm_queryguide_execution_options` - ORM-specific execution 

3702 options 

3703 

3704 :param bind_arguments: dictionary of additional arguments to determine 

3705 the bind. May include "mapper", "bind", or other custom arguments. 

3706 Contents of this dictionary are passed to the 

3707 :meth:`.Session.get_bind` method. 

3708 

3709 .. versionadded:: 2.0.0rc1 

3710 

3711 :return: The object instance, or ``None``. 

3712 

3713 """ # noqa: E501 

3714 return self._get_impl( 

3715 entity, 

3716 ident, 

3717 loading._load_on_pk_identity, 

3718 options=options, 

3719 populate_existing=populate_existing, 

3720 with_for_update=with_for_update, 

3721 identity_token=identity_token, 

3722 execution_options=execution_options, 

3723 bind_arguments=bind_arguments, 

3724 ) 

3725 

3726 def get_one( 

3727 self, 

3728 entity: _EntityBindKey[_O], 

3729 ident: _PKIdentityArgument, 

3730 *, 

3731 options: Optional[Sequence[ORMOption]] = None, 

3732 populate_existing: bool = False, 

3733 with_for_update: ForUpdateParameter = None, 

3734 identity_token: Optional[Any] = None, 

3735 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3736 bind_arguments: Optional[_BindArguments] = None, 

3737 ) -> _O: 

3738 """Return exactly one instance based on the given primary key 

3739 identifier, or raise an exception if not found. 

3740 

3741 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3742 

3743 For a detailed documentation of the arguments see the 

3744 method :meth:`.Session.get`. 

3745 

3746 .. versionadded:: 2.0.22 

3747 

3748 :return: The object instance. 

3749 

3750 .. seealso:: 

3751 

3752 :meth:`.Session.get` - equivalent method that instead 

3753 returns ``None`` if no row was found with the provided primary 

3754 key 

3755 

3756 """ 

3757 

3758 instance = self.get( 

3759 entity, 

3760 ident, 

3761 options=options, 

3762 populate_existing=populate_existing, 

3763 with_for_update=with_for_update, 

3764 identity_token=identity_token, 

3765 execution_options=execution_options, 

3766 bind_arguments=bind_arguments, 

3767 ) 

3768 

3769 if instance is None: 

3770 raise sa_exc.NoResultFound( 

3771 "No row was found when one was required" 

3772 ) 

3773 

3774 return instance 

3775 

3776 def _get_impl( 

3777 self, 

3778 entity: _EntityBindKey[_O], 

3779 primary_key_identity: _PKIdentityArgument, 

3780 db_load_fn: Callable[..., _O], 

3781 *, 

3782 options: Optional[Sequence[ExecutableOption]] = None, 

3783 populate_existing: bool = False, 

3784 with_for_update: ForUpdateParameter = None, 

3785 identity_token: Optional[Any] = None, 

3786 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3787 bind_arguments: Optional[_BindArguments] = None, 

3788 ) -> Optional[_O]: 

3789 # convert composite types to individual args 

3790 if ( 

3791 is_composite_class(primary_key_identity) 

3792 and type(primary_key_identity) 

3793 in descriptor_props._composite_getters 

3794 ): 

3795 getter = descriptor_props._composite_getters[ 

3796 type(primary_key_identity) 

3797 ] 

3798 primary_key_identity = getter(primary_key_identity) 

3799 

3800 mapper: Optional[Mapper[_O]] = inspect(entity) 

3801 

3802 if mapper is None or not mapper.is_mapper: 

3803 raise sa_exc.ArgumentError( 

3804 "Expected mapped class or mapper, got: %r" % entity 

3805 ) 

3806 

3807 is_dict = isinstance(primary_key_identity, dict) 

3808 if not is_dict: 

3809 primary_key_identity = util.to_list( 

3810 primary_key_identity, default=[None] 

3811 ) 

3812 

3813 if len(primary_key_identity) != len(mapper.primary_key): 

3814 raise sa_exc.InvalidRequestError( 

3815 "Incorrect number of values in identifier to formulate " 

3816 "primary key for session.get(); primary key columns " 

3817 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3818 ) 

3819 

3820 if is_dict: 

3821 pk_synonyms = mapper._pk_synonyms 

3822 

3823 if pk_synonyms: 

3824 correct_keys = set(pk_synonyms).intersection( 

3825 primary_key_identity 

3826 ) 

3827 

3828 if correct_keys: 

3829 primary_key_identity = dict(primary_key_identity) 

3830 for k in correct_keys: 

3831 primary_key_identity[pk_synonyms[k]] = ( 

3832 primary_key_identity[k] 

3833 ) 

3834 

3835 try: 

3836 primary_key_identity = list( 

3837 primary_key_identity[prop.key] 

3838 for prop in mapper._identity_key_props 

3839 ) 

3840 

3841 except KeyError as err: 

3842 raise sa_exc.InvalidRequestError( 

3843 "Incorrect names of values in identifier to formulate " 

3844 "primary key for session.get(); primary key attribute " 

3845 "names are %s (synonym names are also accepted)" 

3846 % ",".join( 

3847 "'%s'" % prop.key 

3848 for prop in mapper._identity_key_props 

3849 ) 

3850 ) from err 

3851 

3852 if ( 

3853 not populate_existing 

3854 and not mapper.always_refresh 

3855 and with_for_update is None 

3856 ): 

3857 instance = self._identity_lookup( 

3858 mapper, 

3859 primary_key_identity, 

3860 identity_token=identity_token, 

3861 execution_options=execution_options, 

3862 bind_arguments=bind_arguments, 

3863 ) 

3864 

3865 if instance is not None: 

3866 # reject calls for id in identity map but class 

3867 # mismatch. 

3868 if not isinstance(instance, mapper.class_): 

3869 return None 

3870 return instance 

3871 

3872 # TODO: this was being tested before, but this is not possible 

3873 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3874 

3875 # set_label_style() not strictly necessary, however this will ensure 

3876 # that tablename_colname style is used which at the moment is 

3877 # asserted in a lot of unit tests :) 

3878 

3879 load_options = context.QueryContext.default_load_options 

3880 

3881 if populate_existing: 

3882 load_options += {"_populate_existing": populate_existing} 

3883 statement = sql.select(mapper).set_label_style( 

3884 LABEL_STYLE_TABLENAME_PLUS_COL 

3885 ) 

3886 if with_for_update is not None: 

3887 statement._for_update_arg = ForUpdateArg._from_argument( 

3888 with_for_update 

3889 ) 

3890 

3891 if options: 

3892 statement = statement.options(*options) 

3893 return db_load_fn( 

3894 self, 

3895 statement, 

3896 primary_key_identity, 

3897 load_options=load_options, 

3898 identity_token=identity_token, 

3899 execution_options=execution_options, 

3900 bind_arguments=bind_arguments, 

3901 ) 

3902 

3903 def merge( 

3904 self, 

3905 instance: _O, 

3906 *, 

3907 load: bool = True, 

3908 options: Optional[Sequence[ORMOption]] = None, 

3909 ) -> _O: 

3910 """Copy the state of a given instance into a corresponding instance 

3911 within this :class:`.Session`. 

3912 

3913 :meth:`.Session.merge` examines the primary key attributes of the 

3914 source instance, and attempts to reconcile it with an instance of the 

3915 same primary key in the session. If not found locally, it attempts 

3916 to load the object from the database based on primary key, and if 

3917 none can be located, creates a new instance. The state of each 

3918 attribute on the source instance is then copied to the target 

3919 instance. The resulting target instance is then returned by the 

3920 method; the original source instance is left unmodified, and 

3921 un-associated with the :class:`.Session` if not already. 

3922 

3923 This operation cascades to associated instances if the association is 

3924 mapped with ``cascade="merge"``. 

3925 

3926 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3927 

3928 :param instance: Instance to be merged. 

3929 :param load: Boolean, when False, :meth:`.merge` switches into 

3930 a "high performance" mode which causes it to forego emitting history 

3931 events as well as all database access. This flag is used for 

3932 cases such as transferring graphs of objects into a :class:`.Session` 

3933 from a second level cache, or to transfer just-loaded objects 

3934 into the :class:`.Session` owned by a worker thread or process 

3935 without re-querying the database. 

3936 

3937 The ``load=False`` use case adds the caveat that the given 

3938 object has to be in a "clean" state, that is, has no pending changes 

3939 to be flushed - even if the incoming object is detached from any 

3940 :class:`.Session`. This is so that when 

3941 the merge operation populates local attributes and 

3942 cascades to related objects and 

3943 collections, the values can be "stamped" onto the 

3944 target object as is, without generating any history or attribute 

3945 events, and without the need to reconcile the incoming data with 

3946 any existing related objects or collections that might not 

3947 be loaded. The resulting objects from ``load=False`` are always 

3948 produced as "clean", so it is only appropriate that the given objects 

3949 should be "clean" as well, else this suggests a mis-use of the 

3950 method. 

3951 :param options: optional sequence of loader options which will be 

3952 applied to the :meth:`_orm.Session.get` method when the merge 

3953 operation loads the existing version of the object from the database. 

3954 

3955 .. versionadded:: 1.4.24 

3956 

3957 

3958 .. seealso:: 

3959 

3960 :func:`.make_transient_to_detached` - provides for an alternative 

3961 means of "merging" a single object into the :class:`.Session` 

3962 

3963 :meth:`.Session.merge_all` - multiple instance version 

3964 

3965 """ 

3966 

3967 if self._warn_on_events: 

3968 self._flush_warning("Session.merge()") 

3969 

3970 if load: 

3971 # flush current contents if we expect to load data 

3972 self._autoflush() 

3973 

3974 with self.no_autoflush: 

3975 return self._merge( 

3976 object_state(instance), 

3977 attributes.instance_dict(instance), 

3978 load=load, 

3979 options=options, 

3980 _recursive={}, 

3981 _resolve_conflict_map={}, 

3982 ) 

3983 

3984 def merge_all( 

3985 self, 

3986 instances: Iterable[_O], 

3987 *, 

3988 load: bool = True, 

3989 options: Optional[Sequence[ORMOption]] = None, 

3990 ) -> Sequence[_O]: 

3991 """Calls :meth:`.Session.merge` on multiple instances. 

3992 

3993 .. seealso:: 

3994 

3995 :meth:`.Session.merge` - main documentation on merge 

3996 

3997 .. versionadded:: 2.1 

3998 

3999 """ 

4000 

4001 if self._warn_on_events: 

4002 self._flush_warning("Session.merge_all()") 

4003 

4004 if load: 

4005 # flush current contents if we expect to load data 

4006 self._autoflush() 

4007 

4008 return [ 

4009 self._merge( 

4010 object_state(instance), 

4011 attributes.instance_dict(instance), 

4012 load=load, 

4013 options=options, 

4014 _recursive={}, 

4015 _resolve_conflict_map={}, 

4016 ) 

4017 for instance in instances 

4018 ] 

4019 

4020 def _merge( 

4021 self, 

4022 state: InstanceState[_O], 

4023 state_dict: _InstanceDict, 

4024 *, 

4025 options: Optional[Sequence[ORMOption]] = None, 

4026 load: bool, 

4027 _recursive: Dict[Any, object], 

4028 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4029 ) -> _O: 

4030 mapper: Mapper[_O] = _state_mapper(state) 

4031 if state in _recursive: 

4032 return cast(_O, _recursive[state]) 

4033 

4034 new_instance = False 

4035 key = state.key 

4036 

4037 merged: Optional[_O] 

4038 

4039 if key is None: 

4040 if state in self._new: 

4041 util.warn( 

4042 "Instance %s is already pending in this Session yet is " 

4043 "being merged again; this is probably not what you want " 

4044 "to do" % state_str(state) 

4045 ) 

4046 

4047 if not load: 

4048 raise sa_exc.InvalidRequestError( 

4049 "merge() with load=False option does not support " 

4050 "objects transient (i.e. unpersisted) objects. flush() " 

4051 "all changes on mapped instances before merging with " 

4052 "load=False." 

4053 ) 

4054 key = mapper._identity_key_from_state(state) 

4055 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4056 1 

4057 ] and ( 

4058 not _none_set.intersection(key[1]) 

4059 or ( 

4060 mapper.allow_partial_pks 

4061 and not _none_set.issuperset(key[1]) 

4062 ) 

4063 ) 

4064 else: 

4065 key_is_persistent = True 

4066 

4067 merged = self.identity_map.get(key) 

4068 

4069 if merged is None: 

4070 if key_is_persistent and key in _resolve_conflict_map: 

4071 merged = cast(_O, _resolve_conflict_map[key]) 

4072 

4073 elif not load: 

4074 if state.modified: 

4075 raise sa_exc.InvalidRequestError( 

4076 "merge() with load=False option does not support " 

4077 "objects marked as 'dirty'. flush() all changes on " 

4078 "mapped instances before merging with load=False." 

4079 ) 

4080 merged = mapper.class_manager.new_instance() 

4081 merged_state = attributes.instance_state(merged) 

4082 merged_state.key = key 

4083 self._update_impl(merged_state) 

4084 new_instance = True 

4085 

4086 elif key_is_persistent: 

4087 merged = self.get( 

4088 mapper.class_, 

4089 key[1], 

4090 identity_token=key[2], 

4091 options=options, 

4092 ) 

4093 

4094 if merged is None: 

4095 merged = mapper.class_manager.new_instance() 

4096 merged_state = attributes.instance_state(merged) 

4097 merged_dict = attributes.instance_dict(merged) 

4098 new_instance = True 

4099 self._save_or_update_state(merged_state) 

4100 else: 

4101 merged_state = attributes.instance_state(merged) 

4102 merged_dict = attributes.instance_dict(merged) 

4103 

4104 _recursive[state] = merged 

4105 _resolve_conflict_map[key] = merged 

4106 

4107 # check that we didn't just pull the exact same 

4108 # state out. 

4109 if state is not merged_state: 

4110 # version check if applicable 

4111 if mapper.version_id_col is not None: 

4112 existing_version = mapper._get_state_attr_by_column( 

4113 state, 

4114 state_dict, 

4115 mapper.version_id_col, 

4116 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4117 ) 

4118 

4119 merged_version = mapper._get_state_attr_by_column( 

4120 merged_state, 

4121 merged_dict, 

4122 mapper.version_id_col, 

4123 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4124 ) 

4125 

4126 if ( 

4127 existing_version 

4128 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4129 and merged_version 

4130 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4131 and existing_version != merged_version 

4132 ): 

4133 raise exc.StaleDataError( 

4134 "Version id '%s' on merged state %s " 

4135 "does not match existing version '%s'. " 

4136 "Leave the version attribute unset when " 

4137 "merging to update the most recent version." 

4138 % ( 

4139 existing_version, 

4140 state_str(merged_state), 

4141 merged_version, 

4142 ) 

4143 ) 

4144 

4145 merged_state.load_path = state.load_path 

4146 merged_state.load_options = state.load_options 

4147 

4148 # since we are copying load_options, we need to copy 

4149 # the callables_ that would have been generated by those 

4150 # load_options. 

4151 # assumes that the callables we put in state.callables_ 

4152 # are not instance-specific (which they should not be) 

4153 merged_state._copy_callables(state) 

4154 

4155 for prop in mapper.iterate_properties: 

4156 prop.merge( 

4157 self, 

4158 state, 

4159 state_dict, 

4160 merged_state, 

4161 merged_dict, 

4162 load, 

4163 _recursive, 

4164 _resolve_conflict_map, 

4165 ) 

4166 

4167 if not load: 

4168 # remove any history 

4169 merged_state._commit_all(merged_dict, self.identity_map) 

4170 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4171 merged_state, None 

4172 ) 

4173 

4174 if new_instance: 

4175 merged_state.manager.dispatch.load(merged_state, None) 

4176 

4177 return merged 

4178 

4179 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4180 if not self.identity_map.contains_state(state): 

4181 raise sa_exc.InvalidRequestError( 

4182 "Instance '%s' is not persistent within this Session" 

4183 % state_str(state) 

4184 ) 

4185 

4186 def _save_impl(self, state: InstanceState[Any]) -> None: 

4187 if state.key is not None: 

4188 raise sa_exc.InvalidRequestError( 

4189 "Object '%s' already has an identity - " 

4190 "it can't be registered as pending" % state_str(state) 

4191 ) 

4192 

4193 obj = state.obj() 

4194 to_attach = self._before_attach(state, obj) 

4195 if state not in self._new: 

4196 self._new[state] = obj 

4197 state.insert_order = len(self._new) 

4198 if to_attach: 

4199 self._after_attach(state, obj) 

4200 

4201 def _update_impl( 

4202 self, state: InstanceState[Any], revert_deletion: bool = False 

4203 ) -> None: 

4204 if state.key is None: 

4205 raise sa_exc.InvalidRequestError( 

4206 "Instance '%s' is not persisted" % state_str(state) 

4207 ) 

4208 

4209 if state._deleted: 

4210 if revert_deletion: 

4211 if not state._attached: 

4212 return 

4213 del state._deleted 

4214 else: 

4215 raise sa_exc.InvalidRequestError( 

4216 "Instance '%s' has been deleted. " 

4217 "Use the make_transient() " 

4218 "function to send this object back " 

4219 "to the transient state." % state_str(state) 

4220 ) 

4221 

4222 obj = state.obj() 

4223 

4224 # check for late gc 

4225 if obj is None: 

4226 return 

4227 

4228 to_attach = self._before_attach(state, obj) 

4229 

4230 self._deleted.pop(state, None) 

4231 if revert_deletion: 

4232 self.identity_map.replace(state) 

4233 else: 

4234 self.identity_map.add(state) 

4235 

4236 if to_attach: 

4237 self._after_attach(state, obj) 

4238 elif revert_deletion: 

4239 self.dispatch.deleted_to_persistent(self, state) 

4240 

4241 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4242 if state.key is None: 

4243 self._save_impl(state) 

4244 else: 

4245 self._update_impl(state) 

4246 

4247 def enable_relationship_loading(self, obj: object) -> None: 

4248 """Associate an object with this :class:`.Session` for related 

4249 object loading. 

4250 

4251 .. warning:: 

4252 

4253 :meth:`.enable_relationship_loading` exists to serve special 

4254 use cases and is not recommended for general use. 

4255 

4256 Accesses of attributes mapped with :func:`_orm.relationship` 

4257 will attempt to load a value from the database using this 

4258 :class:`.Session` as the source of connectivity. The values 

4259 will be loaded based on foreign key and primary key values 

4260 present on this object - if not present, then those relationships 

4261 will be unavailable. 

4262 

4263 The object will be attached to this session, but will 

4264 **not** participate in any persistence operations; its state 

4265 for almost all purposes will remain either "transient" or 

4266 "detached", except for the case of relationship loading. 

4267 

4268 Also note that backrefs will often not work as expected. 

4269 Altering a relationship-bound attribute on the target object 

4270 may not fire off a backref event, if the effective value 

4271 is what was already loaded from a foreign-key-holding value. 

4272 

4273 The :meth:`.Session.enable_relationship_loading` method is 

4274 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4275 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4276 an object to remain transient while still being able to load 

4277 related items. 

4278 

4279 To make a transient object associated with a :class:`.Session` 

4280 via :meth:`.Session.enable_relationship_loading` pending, add 

4281 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4282 If the object instead represents an existing identity in the database, 

4283 it should be merged using :meth:`.Session.merge`. 

4284 

4285 :meth:`.Session.enable_relationship_loading` does not improve 

4286 behavior when the ORM is used normally - object references should be 

4287 constructed at the object level, not at the foreign key level, so 

4288 that they are present in an ordinary way before flush() 

4289 proceeds. This method is not intended for general use. 

4290 

4291 .. seealso:: 

4292 

4293 :paramref:`_orm.relationship.load_on_pending` - this flag 

4294 allows per-relationship loading of many-to-ones on items that 

4295 are pending. 

4296 

4297 :func:`.make_transient_to_detached` - allows for an object to 

4298 be added to a :class:`.Session` without SQL emitted, which then 

4299 will unexpire attributes on access. 

4300 

4301 """ 

4302 try: 

4303 state = attributes.instance_state(obj) 

4304 except exc.NO_STATE as err: 

4305 raise exc.UnmappedInstanceError(obj) from err 

4306 

4307 to_attach = self._before_attach(state, obj) 

4308 state._load_pending = True 

4309 if to_attach: 

4310 self._after_attach(state, obj) 

4311 

4312 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4313 self._autobegin_t() 

4314 

4315 if state.session_id == self.hash_key: 

4316 return False 

4317 

4318 if state.session_id and state.session_id in _sessions: 

4319 raise sa_exc.InvalidRequestError( 

4320 "Object '%s' is already attached to session '%s' " 

4321 "(this is '%s')" 

4322 % (state_str(state), state.session_id, self.hash_key) 

4323 ) 

4324 

4325 self.dispatch.before_attach(self, state) 

4326 

4327 return True 

4328 

4329 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4330 state.session_id = self.hash_key 

4331 if state.modified and state._strong_obj is None: 

4332 state._strong_obj = obj 

4333 self.dispatch.after_attach(self, state) 

4334 

4335 if state.key: 

4336 self.dispatch.detached_to_persistent(self, state) 

4337 else: 

4338 self.dispatch.transient_to_pending(self, state) 

4339 

4340 def __contains__(self, instance: object) -> bool: 

4341 """Return True if the instance is associated with this session. 

4342 

4343 The instance may be pending or persistent within the Session for a 

4344 result of True. 

4345 

4346 """ 

4347 try: 

4348 state = attributes.instance_state(instance) 

4349 except exc.NO_STATE as err: 

4350 raise exc.UnmappedInstanceError(instance) from err 

4351 return self._contains_state(state) 

4352 

4353 def __iter__(self) -> Iterator[object]: 

4354 """Iterate over all pending or persistent instances within this 

4355 Session. 

4356 

4357 """ 

4358 return iter( 

4359 list(self._new.values()) + list(self.identity_map.values()) 

4360 ) 

4361 

4362 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4363 return state in self._new or self.identity_map.contains_state(state) 

4364 

4365 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4366 """Flush all the object changes to the database. 

4367 

4368 Writes out all pending object creations, deletions and modifications 

4369 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4370 automatically ordered by the Session's unit of work dependency 

4371 solver. 

4372 

4373 Database operations will be issued in the current transactional 

4374 context and do not affect the state of the transaction, unless an 

4375 error occurs, in which case the entire transaction is rolled back. 

4376 You may flush() as often as you like within a transaction to move 

4377 changes from Python to the database's transaction buffer. 

4378 

4379 :param objects: Optional; restricts the flush operation to operate 

4380 only on elements that are in the given collection. 

4381 

4382 This feature is for an extremely narrow set of use cases where 

4383 particular objects may need to be operated upon before the 

4384 full flush() occurs. It is not intended for general use. 

4385 

4386 .. deprecated:: 2.1 

4387 

4388 """ 

4389 

4390 if self._flushing: 

4391 raise sa_exc.InvalidRequestError("Session is already flushing") 

4392 

4393 if self._is_clean(): 

4394 return 

4395 try: 

4396 self._flushing = True 

4397 self._flush(objects) 

4398 finally: 

4399 self._flushing = False 

4400 

4401 def _flush_warning(self, method: Any) -> None: 

4402 util.warn( 

4403 "Usage of the '%s' operation is not currently supported " 

4404 "within the execution stage of the flush process. " 

4405 "Results may not be consistent. Consider using alternative " 

4406 "event listeners or connection-level operations instead." % method 

4407 ) 

4408 

4409 def _is_clean(self) -> bool: 

4410 return ( 

4411 not self.identity_map.check_modified() 

4412 and not self._deleted 

4413 and not self._new 

4414 ) 

4415 

4416 # have this here since it otherwise causes issues with the proxy 

4417 # method generation 

4418 @deprecated_params( 

4419 objects=( 

4420 "2.1", 

4421 "The `objects` parameter of `Session.flush` is deprecated", 

4422 ) 

4423 ) 

4424 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4425 dirty = self._dirty_states 

4426 if not dirty and not self._deleted and not self._new: 

4427 self.identity_map._modified.clear() 

4428 return 

4429 

4430 flush_context = UOWTransaction(self) 

4431 

4432 if self.dispatch.before_flush: 

4433 self.dispatch.before_flush(self, flush_context, objects) 

4434 # re-establish "dirty states" in case the listeners 

4435 # added 

4436 dirty = self._dirty_states 

4437 

4438 deleted = set(self._deleted) 

4439 new = set(self._new) 

4440 

4441 dirty = set(dirty).difference(deleted) 

4442 

4443 # create the set of all objects we want to operate upon 

4444 if objects: 

4445 # specific list passed in 

4446 objset = set() 

4447 for o in objects: 

4448 try: 

4449 state = attributes.instance_state(o) 

4450 

4451 except exc.NO_STATE as err: 

4452 raise exc.UnmappedInstanceError(o) from err 

4453 objset.add(state) 

4454 else: 

4455 objset = None 

4456 

4457 # store objects whose fate has been decided 

4458 processed = set() 

4459 

4460 # put all saves/updates into the flush context. detect top-level 

4461 # orphans and throw them into deleted. 

4462 if objset: 

4463 proc = new.union(dirty).intersection(objset).difference(deleted) 

4464 else: 

4465 proc = new.union(dirty).difference(deleted) 

4466 

4467 for state in proc: 

4468 is_orphan = _state_mapper(state)._is_orphan(state) 

4469 

4470 is_persistent_orphan = is_orphan and state.has_identity 

4471 

4472 if ( 

4473 is_orphan 

4474 and not is_persistent_orphan 

4475 and state._orphaned_outside_of_session 

4476 ): 

4477 self._expunge_states([state]) 

4478 else: 

4479 _reg = flush_context.register_object( 

4480 state, isdelete=is_persistent_orphan 

4481 ) 

4482 assert _reg, "Failed to add object to the flush context!" 

4483 processed.add(state) 

4484 

4485 # put all remaining deletes into the flush context. 

4486 if objset: 

4487 proc = deleted.intersection(objset).difference(processed) 

4488 else: 

4489 proc = deleted.difference(processed) 

4490 for state in proc: 

4491 _reg = flush_context.register_object(state, isdelete=True) 

4492 assert _reg, "Failed to add object to the flush context!" 

4493 

4494 if not flush_context.has_work: 

4495 return 

4496 

4497 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4498 try: 

4499 self._warn_on_events = True 

4500 try: 

4501 flush_context.execute() 

4502 finally: 

4503 self._warn_on_events = False 

4504 

4505 self.dispatch.after_flush(self, flush_context) 

4506 

4507 flush_context.finalize_flush_changes() 

4508 

4509 if not objects and self.identity_map._modified: 

4510 len_ = len(self.identity_map._modified) 

4511 

4512 statelib.InstanceState._commit_all_states( 

4513 [ 

4514 (state, state.dict) 

4515 for state in self.identity_map._modified 

4516 ], 

4517 instance_dict=self.identity_map, 

4518 ) 

4519 util.warn( 

4520 "Attribute history events accumulated on %d " 

4521 "previously clean instances " 

4522 "within inner-flush event handlers have been " 

4523 "reset, and will not result in database updates. " 

4524 "Consider using set_committed_value() within " 

4525 "inner-flush event handlers to avoid this warning." % len_ 

4526 ) 

4527 

4528 # useful assertions: 

4529 # if not objects: 

4530 # assert not self.identity_map._modified 

4531 # else: 

4532 # assert self.identity_map._modified == \ 

4533 # self.identity_map._modified.difference(objects) 

4534 

4535 self.dispatch.after_flush_postexec(self, flush_context) 

4536 

4537 transaction.commit() 

4538 

4539 except: 

4540 with util.safe_reraise(): 

4541 transaction.rollback(_capture_exception=True) 

4542 

4543 def bulk_save_objects( 

4544 self, 

4545 objects: Iterable[object], 

4546 return_defaults: bool = False, 

4547 update_changed_only: bool = True, 

4548 preserve_order: bool = True, 

4549 ) -> None: 

4550 """Perform a bulk save of the given list of objects. 

4551 

4552 .. legacy:: 

4553 

4554 This method is a legacy feature as of the 2.0 series of 

4555 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4556 the sections :ref:`orm_queryguide_bulk_insert` and 

4557 :ref:`orm_queryguide_bulk_update`. 

4558 

4559 For general INSERT and UPDATE of existing ORM mapped objects, 

4560 prefer standard :term:`unit of work` data management patterns, 

4561 introduced in the :ref:`unified_tutorial` at 

4562 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4563 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4564 which solves previous issues of bulk INSERT slowness. 

4565 

4566 :param objects: a sequence of mapped object instances. The mapped 

4567 objects are persisted as is, and are **not** associated with the 

4568 :class:`.Session` afterwards. 

4569 

4570 For each object, whether the object is sent as an INSERT or an 

4571 UPDATE is dependent on the same rules used by the :class:`.Session` 

4572 in traditional operation; if the object has the 

4573 :attr:`.InstanceState.key` 

4574 attribute set, then the object is assumed to be "detached" and 

4575 will result in an UPDATE. Otherwise, an INSERT is used. 

4576 

4577 In the case of an UPDATE, statements are grouped based on which 

4578 attributes have changed, and are thus to be the subject of each 

4579 SET clause. If ``update_changed_only`` is False, then all 

4580 attributes present within each object are applied to the UPDATE 

4581 statement, which may help in allowing the statements to be grouped 

4582 together into a larger executemany(), and will also reduce the 

4583 overhead of checking history on attributes. 

4584 

4585 :param return_defaults: when True, rows that are missing values which 

4586 generate defaults, namely integer primary key defaults and sequences, 

4587 will be inserted **one at a time**, so that the primary key value 

4588 is available. In particular this will allow joined-inheritance 

4589 and other multi-table mappings to insert correctly without the need 

4590 to provide primary key values ahead of time; however, 

4591 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4592 reduces the performance gains** of the method overall. It is strongly 

4593 advised to please use the standard :meth:`_orm.Session.add_all` 

4594 approach. 

4595 

4596 :param update_changed_only: when True, UPDATE statements are rendered 

4597 based on those attributes in each state that have logged changes. 

4598 When False, all attributes present are rendered into the SET clause 

4599 with the exception of primary key attributes. 

4600 

4601 :param preserve_order: when True, the order of inserts and updates 

4602 matches exactly the order in which the objects are given. When 

4603 False, common types of objects are grouped into inserts 

4604 and updates, to allow for more batching opportunities. 

4605 

4606 .. seealso:: 

4607 

4608 :doc:`queryguide/dml` 

4609 

4610 :meth:`.Session.bulk_insert_mappings` 

4611 

4612 :meth:`.Session.bulk_update_mappings` 

4613 

4614 """ 

4615 

4616 obj_states: Iterable[InstanceState[Any]] 

4617 

4618 obj_states = (attributes.instance_state(obj) for obj in objects) 

4619 

4620 if not preserve_order: 

4621 # the purpose of this sort is just so that common mappers 

4622 # and persistence states are grouped together, so that groupby 

4623 # will return a single group for a particular type of mapper. 

4624 # it's not trying to be deterministic beyond that. 

4625 obj_states = sorted( 

4626 obj_states, 

4627 key=lambda state: (id(state.mapper), state.key is not None), 

4628 ) 

4629 

4630 def grouping_key( 

4631 state: InstanceState[_O], 

4632 ) -> Tuple[Mapper[_O], bool]: 

4633 return (state.mapper, state.key is not None) 

4634 

4635 for (mapper, isupdate), states in itertools.groupby( 

4636 obj_states, grouping_key 

4637 ): 

4638 self._bulk_save_mappings( 

4639 mapper, 

4640 states, 

4641 isupdate=isupdate, 

4642 isstates=True, 

4643 return_defaults=return_defaults, 

4644 update_changed_only=update_changed_only, 

4645 render_nulls=False, 

4646 ) 

4647 

4648 def bulk_insert_mappings( 

4649 self, 

4650 mapper: Mapper[Any], 

4651 mappings: Iterable[Dict[str, Any]], 

4652 return_defaults: bool = False, 

4653 render_nulls: bool = False, 

4654 ) -> None: 

4655 """Perform a bulk insert of the given list of mapping dictionaries. 

4656 

4657 .. legacy:: 

4658 

4659 This method is a legacy feature as of the 2.0 series of 

4660 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4661 the sections :ref:`orm_queryguide_bulk_insert` and 

4662 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4663 implementation details with this method and adds new features 

4664 as well. 

4665 

4666 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4667 object, 

4668 representing the single kind of object represented within the mapping 

4669 list. 

4670 

4671 :param mappings: a sequence of dictionaries, each one containing the 

4672 state of the mapped row to be inserted, in terms of the attribute 

4673 names on the mapped class. If the mapping refers to multiple tables, 

4674 such as a joined-inheritance mapping, each dictionary must contain all 

4675 keys to be populated into all tables. 

4676 

4677 :param return_defaults: when True, the INSERT process will be altered 

4678 to ensure that newly generated primary key values will be fetched. 

4679 The rationale for this parameter is typically to enable 

4680 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4681 be bulk inserted. 

4682 

4683 .. note:: for backends that don't support RETURNING, the 

4684 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4685 parameter can significantly decrease performance as INSERT 

4686 statements can no longer be batched. See 

4687 :ref:`engine_insertmanyvalues` 

4688 for background on which backends are affected. 

4689 

4690 :param render_nulls: When True, a value of ``None`` will result 

4691 in a NULL value being included in the INSERT statement, rather 

4692 than the column being omitted from the INSERT. This allows all 

4693 the rows being INSERTed to have the identical set of columns which 

4694 allows the full set of rows to be batched to the DBAPI. Normally, 

4695 each column-set that contains a different combination of NULL values 

4696 than the previous row must omit a different series of columns from 

4697 the rendered INSERT statement, which means it must be emitted as a 

4698 separate statement. By passing this flag, the full set of rows 

4699 are guaranteed to be batchable into one batch; the cost however is 

4700 that server-side defaults which are invoked by an omitted column will 

4701 be skipped, so care must be taken to ensure that these are not 

4702 necessary. 

4703 

4704 .. warning:: 

4705 

4706 When this flag is set, **server side default SQL values will 

4707 not be invoked** for those columns that are inserted as NULL; 

4708 the NULL value will be sent explicitly. Care must be taken 

4709 to ensure that no server-side default functions need to be 

4710 invoked for the operation as a whole. 

4711 

4712 .. seealso:: 

4713 

4714 :doc:`queryguide/dml` 

4715 

4716 :meth:`.Session.bulk_save_objects` 

4717 

4718 :meth:`.Session.bulk_update_mappings` 

4719 

4720 """ 

4721 self._bulk_save_mappings( 

4722 mapper, 

4723 mappings, 

4724 isupdate=False, 

4725 isstates=False, 

4726 return_defaults=return_defaults, 

4727 update_changed_only=False, 

4728 render_nulls=render_nulls, 

4729 ) 

4730 

4731 def bulk_update_mappings( 

4732 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4733 ) -> None: 

4734 """Perform a bulk update of the given list of mapping dictionaries. 

4735 

4736 .. legacy:: 

4737 

4738 This method is a legacy feature as of the 2.0 series of 

4739 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4740 the sections :ref:`orm_queryguide_bulk_insert` and 

4741 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4742 implementation details with this method and adds new features 

4743 as well. 

4744 

4745 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4746 object, 

4747 representing the single kind of object represented within the mapping 

4748 list. 

4749 

4750 :param mappings: a sequence of dictionaries, each one containing the 

4751 state of the mapped row to be updated, in terms of the attribute names 

4752 on the mapped class. If the mapping refers to multiple tables, such 

4753 as a joined-inheritance mapping, each dictionary may contain keys 

4754 corresponding to all tables. All those keys which are present and 

4755 are not part of the primary key are applied to the SET clause of the 

4756 UPDATE statement; the primary key values, which are required, are 

4757 applied to the WHERE clause. 

4758 

4759 

4760 .. seealso:: 

4761 

4762 :doc:`queryguide/dml` 

4763 

4764 :meth:`.Session.bulk_insert_mappings` 

4765 

4766 :meth:`.Session.bulk_save_objects` 

4767 

4768 """ 

4769 self._bulk_save_mappings( 

4770 mapper, 

4771 mappings, 

4772 isupdate=True, 

4773 isstates=False, 

4774 return_defaults=False, 

4775 update_changed_only=False, 

4776 render_nulls=False, 

4777 ) 

4778 

4779 def _bulk_save_mappings( 

4780 self, 

4781 mapper: Mapper[_O], 

4782 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4783 *, 

4784 isupdate: bool, 

4785 isstates: bool, 

4786 return_defaults: bool, 

4787 update_changed_only: bool, 

4788 render_nulls: bool, 

4789 ) -> None: 

4790 mapper = _class_to_mapper(mapper) 

4791 self._flushing = True 

4792 

4793 transaction = self._autobegin_t()._begin() 

4794 try: 

4795 if isupdate: 

4796 bulk_persistence._bulk_update( 

4797 mapper, 

4798 mappings, 

4799 transaction, 

4800 isstates=isstates, 

4801 update_changed_only=update_changed_only, 

4802 ) 

4803 else: 

4804 bulk_persistence._bulk_insert( 

4805 mapper, 

4806 mappings, 

4807 transaction, 

4808 isstates=isstates, 

4809 return_defaults=return_defaults, 

4810 render_nulls=render_nulls, 

4811 ) 

4812 transaction.commit() 

4813 

4814 except: 

4815 with util.safe_reraise(): 

4816 transaction.rollback(_capture_exception=True) 

4817 finally: 

4818 self._flushing = False 

4819 

4820 def is_modified( 

4821 self, instance: object, include_collections: bool = True 

4822 ) -> bool: 

4823 r"""Return ``True`` if the given instance has locally 

4824 modified attributes. 

4825 

4826 This method retrieves the history for each instrumented 

4827 attribute on the instance and performs a comparison of the current 

4828 value to its previously flushed or committed value, if any. 

4829 

4830 It is in effect a more expensive and accurate 

4831 version of checking for the given instance in the 

4832 :attr:`.Session.dirty` collection; a full test for 

4833 each attribute's net "dirty" status is performed. 

4834 

4835 E.g.:: 

4836 

4837 return session.is_modified(someobject) 

4838 

4839 A few caveats to this method apply: 

4840 

4841 * Instances present in the :attr:`.Session.dirty` collection may 

4842 report ``False`` when tested with this method. This is because 

4843 the object may have received change events via attribute mutation, 

4844 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4845 is the same as that loaded from the database, resulting in no net 

4846 change here. 

4847 * Scalar attributes may not have recorded the previously set 

4848 value when a new value was applied, if the attribute was not loaded, 

4849 or was expired, at the time the new value was received - in these 

4850 cases, the attribute is assumed to have a change, even if there is 

4851 ultimately no net change against its database value. SQLAlchemy in 

4852 most cases does not need the "old" value when a set event occurs, so 

4853 it skips the expense of a SQL call if the old value isn't present, 

4854 based on the assumption that an UPDATE of the scalar value is 

4855 usually needed, and in those few cases where it isn't, is less 

4856 expensive on average than issuing a defensive SELECT. 

4857 

4858 The "old" value is fetched unconditionally upon set only if the 

4859 attribute container has the ``active_history`` flag set to ``True``. 

4860 This flag is set typically for primary key attributes and scalar 

4861 object references that are not a simple many-to-one. To set this 

4862 flag for any arbitrary mapped column, use the ``active_history`` 

4863 argument with :func:`.column_property`. 

4864 

4865 :param instance: mapped instance to be tested for pending changes. 

4866 :param include_collections: Indicates if multivalued collections 

4867 should be included in the operation. Setting this to ``False`` is a 

4868 way to detect only local-column based properties (i.e. scalar columns 

4869 or many-to-one foreign keys) that would result in an UPDATE for this 

4870 instance upon flush. 

4871 

4872 """ 

4873 state = object_state(instance) 

4874 

4875 if not state.modified: 

4876 return False 

4877 

4878 dict_ = state.dict 

4879 

4880 for attr in state.manager.attributes: 

4881 if ( 

4882 not include_collections 

4883 and hasattr(attr.impl, "get_collection") 

4884 ) or not hasattr(attr.impl, "get_history"): 

4885 continue 

4886 

4887 (added, unchanged, deleted) = attr.impl.get_history( 

4888 state, dict_, passive=PassiveFlag.NO_CHANGE 

4889 ) 

4890 

4891 if added or deleted: 

4892 return True 

4893 else: 

4894 return False 

4895 

4896 @property 

4897 def is_active(self) -> bool: 

4898 """True if this :class:`.Session` not in "partial rollback" state. 

4899 

4900 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4901 a new transaction immediately, so this attribute will be False 

4902 when the :class:`_orm.Session` is first instantiated. 

4903 

4904 "partial rollback" state typically indicates that the flush process 

4905 of the :class:`_orm.Session` has failed, and that the 

4906 :meth:`_orm.Session.rollback` method must be emitted in order to 

4907 fully roll back the transaction. 

4908 

4909 If this :class:`_orm.Session` is not in a transaction at all, the 

4910 :class:`_orm.Session` will autobegin when it is first used, so in this 

4911 case :attr:`_orm.Session.is_active` will return True. 

4912 

4913 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4914 and that transaction has not been rolled back internally, the 

4915 :attr:`_orm.Session.is_active` will also return True. 

4916 

4917 .. seealso:: 

4918 

4919 :ref:`faq_session_rollback` 

4920 

4921 :meth:`_orm.Session.in_transaction` 

4922 

4923 """ 

4924 return self._transaction is None or self._transaction.is_active 

4925 

4926 @property 

4927 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4928 """The set of all persistent states considered dirty. 

4929 

4930 This method returns all states that were modified including 

4931 those that were possibly deleted. 

4932 

4933 """ 

4934 return self.identity_map._dirty_states() 

4935 

4936 @property 

4937 def dirty(self) -> IdentitySet: 

4938 """The set of all persistent instances considered dirty. 

4939 

4940 E.g.:: 

4941 

4942 some_mapped_object in session.dirty 

4943 

4944 Instances are considered dirty when they were modified but not 

4945 deleted. 

4946 

4947 Note that this 'dirty' calculation is 'optimistic'; most 

4948 attribute-setting or collection modification operations will 

4949 mark an instance as 'dirty' and place it in this set, even if 

4950 there is no net change to the attribute's value. At flush 

4951 time, the value of each attribute is compared to its 

4952 previously saved value, and if there's no net change, no SQL 

4953 operation will occur (this is a more expensive operation so 

4954 it's only done at flush time). 

4955 

4956 To check if an instance has actionable net changes to its 

4957 attributes, use the :meth:`.Session.is_modified` method. 

4958 

4959 """ 

4960 return IdentitySet( 

4961 [ 

4962 state.obj() 

4963 for state in self._dirty_states 

4964 if state not in self._deleted 

4965 ] 

4966 ) 

4967 

4968 @property 

4969 def deleted(self) -> IdentitySet: 

4970 "The set of all instances marked as 'deleted' within this ``Session``" 

4971 

4972 return util.IdentitySet(list(self._deleted.values())) 

4973 

4974 @property 

4975 def new(self) -> IdentitySet: 

4976 "The set of all instances marked as 'new' within this ``Session``." 

4977 

4978 return util.IdentitySet(list(self._new.values())) 

4979 

4980 

4981_S = TypeVar("_S", bound="Session") 

4982 

4983 

4984class sessionmaker(_SessionClassMethods, Generic[_S]): 

4985 """A configurable :class:`.Session` factory. 

4986 

4987 The :class:`.sessionmaker` factory generates new 

4988 :class:`.Session` objects when called, creating them given 

4989 the configurational arguments established here. 

4990 

4991 e.g.:: 

4992 

4993 from sqlalchemy import create_engine 

4994 from sqlalchemy.orm import sessionmaker 

4995 

4996 # an Engine, which the Session will use for connection 

4997 # resources 

4998 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

4999 

5000 Session = sessionmaker(engine) 

5001 

5002 with Session() as session: 

5003 session.add(some_object) 

5004 session.add(some_other_object) 

5005 session.commit() 

5006 

5007 Context manager use is optional; otherwise, the returned 

5008 :class:`_orm.Session` object may be closed explicitly via the 

5009 :meth:`_orm.Session.close` method. Using a 

5010 ``try:/finally:`` block is optional, however will ensure that the close 

5011 takes place even if there are database errors:: 

5012 

5013 session = Session() 

5014 try: 

5015 session.add(some_object) 

5016 session.add(some_other_object) 

5017 session.commit() 

5018 finally: 

5019 session.close() 

5020 

5021 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5022 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5023 for :class:`_engine.Connection` objects. In this way it also includes 

5024 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5025 manager which both begins and commits a transaction, as well as closes 

5026 out the :class:`_orm.Session` when complete, rolling back the transaction 

5027 if any errors occur:: 

5028 

5029 Session = sessionmaker(engine) 

5030 

5031 with Session.begin() as session: 

5032 session.add(some_object) 

5033 session.add(some_other_object) 

5034 # commits transaction, closes session 

5035 

5036 .. versionadded:: 1.4 

5037 

5038 When calling upon :class:`_orm.sessionmaker` to construct a 

5039 :class:`_orm.Session`, keyword arguments may also be passed to the 

5040 method; these arguments will override that of the globally configured 

5041 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5042 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5043 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5044 

5045 Session = sessionmaker(engine) 

5046 

5047 # bind an individual session to a connection 

5048 

5049 with engine.connect() as connection: 

5050 with Session(bind=connection) as session: 

5051 ... # work with session 

5052 

5053 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5054 can be used to specify additional keyword arguments to the factory, which 

5055 will take effect for subsequent :class:`.Session` objects generated. This 

5056 is usually used to associate one or more :class:`_engine.Engine` objects 

5057 with an existing 

5058 :class:`.sessionmaker` factory before it is first used:: 

5059 

5060 # application starts, sessionmaker does not have 

5061 # an engine bound yet 

5062 Session = sessionmaker() 

5063 

5064 # ... later, when an engine URL is read from a configuration 

5065 # file or other events allow the engine to be created 

5066 engine = create_engine("sqlite:///foo.db") 

5067 Session.configure(bind=engine) 

5068 

5069 sess = Session() 

5070 # work with session 

5071 

5072 .. seealso:: 

5073 

5074 :ref:`session_getting` - introductory text on creating 

5075 sessions using :class:`.sessionmaker`. 

5076 

5077 """ 

5078 

5079 class_: Type[_S] 

5080 

5081 @overload 

5082 def __init__( 

5083 self, 

5084 bind: Optional[_SessionBind] = ..., 

5085 *, 

5086 class_: Type[_S], 

5087 autoflush: bool = ..., 

5088 expire_on_commit: bool = ..., 

5089 info: Optional[_InfoType] = ..., 

5090 **kw: Any, 

5091 ): ... 

5092 

5093 @overload 

5094 def __init__( 

5095 self: "sessionmaker[Session]", 

5096 bind: Optional[_SessionBind] = ..., 

5097 *, 

5098 autoflush: bool = ..., 

5099 expire_on_commit: bool = ..., 

5100 info: Optional[_InfoType] = ..., 

5101 **kw: Any, 

5102 ): ... 

5103 

5104 def __init__( 

5105 self, 

5106 bind: Optional[_SessionBind] = None, 

5107 *, 

5108 class_: Type[_S] = Session, # type: ignore 

5109 autoflush: bool = True, 

5110 expire_on_commit: bool = True, 

5111 info: Optional[_InfoType] = None, 

5112 **kw: Any, 

5113 ): 

5114 r"""Construct a new :class:`.sessionmaker`. 

5115 

5116 All arguments here except for ``class_`` correspond to arguments 

5117 accepted by :class:`.Session` directly. See the 

5118 :meth:`.Session.__init__` docstring for more details on parameters. 

5119 

5120 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5121 with 

5122 which newly created :class:`.Session` objects will be associated. 

5123 :param class\_: class to use in order to create new :class:`.Session` 

5124 objects. Defaults to :class:`.Session`. 

5125 :param autoflush: The autoflush setting to use with newly created 

5126 :class:`.Session` objects. 

5127 

5128 .. seealso:: 

5129 

5130 :ref:`session_flushing` - additional background on autoflush 

5131 

5132 :param expire_on_commit=True: the 

5133 :paramref:`_orm.Session.expire_on_commit` setting to use 

5134 with newly created :class:`.Session` objects. 

5135 

5136 :param info: optional dictionary of information that will be available 

5137 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5138 replaced, when the ``info`` parameter is specified to the specific 

5139 :class:`.Session` construction operation. 

5140 

5141 :param \**kw: all other keyword arguments are passed to the 

5142 constructor of newly created :class:`.Session` objects. 

5143 

5144 """ 

5145 kw["bind"] = bind 

5146 kw["autoflush"] = autoflush 

5147 kw["expire_on_commit"] = expire_on_commit 

5148 if info is not None: 

5149 kw["info"] = info 

5150 self.kw = kw 

5151 # make our own subclass of the given class, so that 

5152 # events can be associated with it specifically. 

5153 self.class_ = type(class_.__name__, (class_,), {}) 

5154 

5155 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5156 """Produce a context manager that both provides a new 

5157 :class:`_orm.Session` as well as a transaction that commits. 

5158 

5159 

5160 e.g.:: 

5161 

5162 Session = sessionmaker(some_engine) 

5163 

5164 with Session.begin() as session: 

5165 session.add(some_object) 

5166 

5167 # commits transaction, closes session 

5168 

5169 .. versionadded:: 1.4 

5170 

5171 

5172 """ 

5173 

5174 session = self() 

5175 return session._maker_context_manager() 

5176 

5177 def __call__(self, **local_kw: Any) -> _S: 

5178 """Produce a new :class:`.Session` object using the configuration 

5179 established in this :class:`.sessionmaker`. 

5180 

5181 In Python, the ``__call__`` method is invoked on an object when 

5182 it is "called" in the same way as a function:: 

5183 

5184 Session = sessionmaker(some_engine) 

5185 session = Session() # invokes sessionmaker.__call__() 

5186 

5187 """ 

5188 for k, v in self.kw.items(): 

5189 if k == "info" and "info" in local_kw: 

5190 d = v.copy() 

5191 d.update(local_kw["info"]) 

5192 local_kw["info"] = d 

5193 else: 

5194 local_kw.setdefault(k, v) 

5195 return self.class_(**local_kw) 

5196 

5197 def configure(self, **new_kw: Any) -> None: 

5198 """(Re)configure the arguments for this sessionmaker. 

5199 

5200 e.g.:: 

5201 

5202 Session = sessionmaker() 

5203 

5204 Session.configure(bind=create_engine("sqlite://")) 

5205 """ 

5206 self.kw.update(new_kw) 

5207 

5208 def __repr__(self) -> str: 

5209 return "%s(class_=%r, %s)" % ( 

5210 self.__class__.__name__, 

5211 self.class_.__name__, 

5212 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5213 ) 

5214 

5215 

5216def close_all_sessions() -> None: 

5217 """Close all sessions in memory. 

5218 

5219 This function consults a global registry of all :class:`.Session` objects 

5220 and calls :meth:`.Session.close` on them, which resets them to a clean 

5221 state. 

5222 

5223 This function is not for general use but may be useful for test suites 

5224 within the teardown scheme. 

5225 

5226 """ 

5227 

5228 for sess in _sessions.values(): 

5229 sess.close() 

5230 

5231 

5232def make_transient(instance: object) -> None: 

5233 """Alter the state of the given instance so that it is :term:`transient`. 

5234 

5235 .. note:: 

5236 

5237 :func:`.make_transient` is a special-case function for 

5238 advanced use cases only. 

5239 

5240 The given mapped instance is assumed to be in the :term:`persistent` or 

5241 :term:`detached` state. The function will remove its association with any 

5242 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5243 effect is that the object will behave as though it were newly constructed, 

5244 except retaining any attribute / collection values that were loaded at the 

5245 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5246 if this object had been deleted as a result of using 

5247 :meth:`.Session.delete`. 

5248 

5249 .. warning:: 

5250 

5251 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5252 load ORM-mapped attributes that are not currently loaded at the time 

5253 the function is called. This includes attributes which: 

5254 

5255 * were expired via :meth:`.Session.expire` 

5256 

5257 * were expired as the natural effect of committing a session 

5258 transaction, e.g. :meth:`.Session.commit` 

5259 

5260 * are normally :term:`lazy loaded` but are not currently loaded 

5261 

5262 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5263 not yet loaded 

5264 

5265 * were not present in the query which loaded this object, such as that 

5266 which is common in joined table inheritance and other scenarios. 

5267 

5268 After :func:`.make_transient` is called, unloaded attributes such 

5269 as those above will normally resolve to the value ``None`` when 

5270 accessed, or an empty collection for a collection-oriented attribute. 

5271 As the object is transient and un-associated with any database 

5272 identity, it will no longer retrieve these values. 

5273 

5274 .. seealso:: 

5275 

5276 :func:`.make_transient_to_detached` 

5277 

5278 """ 

5279 state = attributes.instance_state(instance) 

5280 s = _state_session(state) 

5281 if s: 

5282 s._expunge_states([state]) 

5283 

5284 # remove expired state 

5285 state.expired_attributes.clear() 

5286 

5287 # remove deferred callables 

5288 if state.callables: 

5289 del state.callables 

5290 

5291 if state.key: 

5292 del state.key 

5293 if state._deleted: 

5294 del state._deleted 

5295 

5296 

5297def make_transient_to_detached(instance: object) -> None: 

5298 """Make the given transient instance :term:`detached`. 

5299 

5300 .. note:: 

5301 

5302 :func:`.make_transient_to_detached` is a special-case function for 

5303 advanced use cases only. 

5304 

5305 All attribute history on the given instance 

5306 will be reset as though the instance were freshly loaded 

5307 from a query. Missing attributes will be marked as expired. 

5308 The primary key attributes of the object, which are required, will be made 

5309 into the "key" of the instance. 

5310 

5311 The object can then be added to a session, or merged 

5312 possibly with the load=False flag, at which point it will look 

5313 as if it were loaded that way, without emitting SQL. 

5314 

5315 This is a special use case function that differs from a normal 

5316 call to :meth:`.Session.merge` in that a given persistent state 

5317 can be manufactured without any SQL calls. 

5318 

5319 .. seealso:: 

5320 

5321 :func:`.make_transient` 

5322 

5323 :meth:`.Session.enable_relationship_loading` 

5324 

5325 """ 

5326 state = attributes.instance_state(instance) 

5327 if state.session_id or state.key: 

5328 raise sa_exc.InvalidRequestError("Given object must be transient") 

5329 state.key = state.mapper._identity_key_from_state(state) 

5330 if state._deleted: 

5331 del state._deleted 

5332 state._commit_all(state.dict) 

5333 state._expire_attributes(state.dict, state.unloaded) 

5334 

5335 

5336def object_session(instance: object) -> Optional[Session]: 

5337 """Return the :class:`.Session` to which the given instance belongs. 

5338 

5339 This is essentially the same as the :attr:`.InstanceState.session` 

5340 accessor. See that attribute for details. 

5341 

5342 """ 

5343 

5344 try: 

5345 state = attributes.instance_state(instance) 

5346 except exc.NO_STATE as err: 

5347 raise exc.UnmappedInstanceError(instance) from err 

5348 else: 

5349 return _state_session(state) 

5350 

5351 

5352_new_sessionid = util.counter()