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

1449 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 Literal 

26from typing import NoReturn 

27from typing import Optional 

28from typing import overload 

29from typing import Protocol 

30from typing import Sequence 

31from typing import Set 

32from typing import Tuple 

33from typing import Type 

34from typing import TYPE_CHECKING 

35from typing import TypeVar 

36from typing import Union 

37import weakref 

38 

39from . import attributes 

40from . import bulk_persistence 

41from . import context 

42from . import descriptor_props 

43from . import exc 

44from . import identity 

45from . import loading 

46from . import query 

47from . import state as statelib 

48from ._typing import _O 

49from ._typing import insp_is_mapper 

50from ._typing import is_composite_class 

51from ._typing import is_orm_option 

52from ._typing import is_user_defined_option 

53from .base import _class_to_mapper 

54from .base import _none_set 

55from .base import _state_mapper 

56from .base import instance_str 

57from .base import LoaderCallableStatus 

58from .base import object_mapper 

59from .base import object_state 

60from .base import PassiveFlag 

61from .base import state_str 

62from .context import _ORMCompileState 

63from .context import FromStatement 

64from .identity import IdentityMap 

65from .query import Query 

66from .state import InstanceState 

67from .state_changes import _StateChange 

68from .state_changes import _StateChangeState 

69from .state_changes import _StateChangeStates 

70from .unitofwork import UOWTransaction 

71from .. import engine 

72from .. import exc as sa_exc 

73from .. import sql 

74from .. import util 

75from ..engine import Connection 

76from ..engine import Engine 

77from ..engine.util import TransactionalContext 

78from ..event import dispatcher 

79from ..event import EventTarget 

80from ..inspection import inspect 

81from ..inspection import Inspectable 

82from ..sql import coercions 

83from ..sql import dml 

84from ..sql import roles 

85from ..sql import Select 

86from ..sql import TableClause 

87from ..sql import visitors 

88from ..sql.base import _NoArg 

89from ..sql.base import CompileState 

90from ..sql.schema import Table 

91from ..sql.selectable import ForUpdateArg 

92from ..util import deprecated_params 

93from ..util import IdentitySet 

94from ..util.typing import TupleAny 

95from ..util.typing import TypeVarTuple 

96from ..util.typing import Unpack 

97 

98 

99if typing.TYPE_CHECKING: 

100 from ._typing import _EntityType 

101 from ._typing import _IdentityKeyType 

102 from ._typing import _InstanceDict 

103 from ._typing import OrmExecuteOptionsParameter 

104 from .interfaces import ORMOption 

105 from .interfaces import UserDefinedOption 

106 from .mapper import Mapper 

107 from .path_registry import PathRegistry 

108 from .query import RowReturningQuery 

109 from ..engine import Result 

110 from ..engine import Row 

111 from ..engine import RowMapping 

112 from ..engine.base import Transaction 

113 from ..engine.base import TwoPhaseTransaction 

114 from ..engine.interfaces import _CoreAnyExecuteParams 

115 from ..engine.interfaces import _CoreSingleExecuteParams 

116 from ..engine.interfaces import _ExecuteOptions 

117 from ..engine.interfaces import CoreExecuteOptionsParameter 

118 from ..engine.result import ScalarResult 

119 from ..event import _InstanceLevelDispatch 

120 from ..sql._typing import _ColumnsClauseArgument 

121 from ..sql._typing import _InfoType 

122 from ..sql._typing import _T0 

123 from ..sql._typing import _T1 

124 from ..sql._typing import _T2 

125 from ..sql._typing import _T3 

126 from ..sql._typing import _T4 

127 from ..sql._typing import _T5 

128 from ..sql._typing import _T6 

129 from ..sql._typing import _T7 

130 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

131 from ..sql.base import Executable 

132 from ..sql.base import ExecutableOption 

133 from ..sql.elements import ClauseElement 

134 from ..sql.roles import TypedColumnsClauseRole 

135 from ..sql.selectable import ForUpdateParameter 

136 from ..sql.selectable import TypedReturnsRows 

137 

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

139_Ts = TypeVarTuple("_Ts") 

140 

141__all__ = [ 

142 "Session", 

143 "SessionTransaction", 

144 "sessionmaker", 

145 "ORMExecuteState", 

146 "close_all_sessions", 

147 "make_transient", 

148 "make_transient_to_detached", 

149 "object_session", 

150] 

151 

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

153 weakref.WeakValueDictionary() 

154) 

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

156""" 

157 

158statelib._sessions = _sessions 

159 

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

161 

162_BindArguments = Dict[str, Any] 

163 

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

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

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

167 

168JoinTransactionMode = Literal[ 

169 "conditional_savepoint", 

170 "rollback_only", 

171 "control_fully", 

172 "create_savepoint", 

173] 

174 

175 

176class _ConnectionCallableProto(Protocol): 

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

178 

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

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

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

182 as persistence time. 

183 

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

185 is established when using the horizontal sharding extension. 

186 

187 """ 

188 

189 def __call__( 

190 self, 

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

192 instance: Optional[object] = None, 

193 **kw: Any, 

194 ) -> Connection: ... 

195 

196 

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

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

199 associated, if any. 

200 """ 

201 return state.session 

202 

203 

204class _SessionClassMethods: 

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

206 

207 @classmethod 

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

209 def identity_key( 

210 cls, 

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

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

213 *, 

214 instance: Optional[Any] = None, 

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

216 identity_token: Optional[Any] = None, 

217 ) -> _IdentityKeyType[Any]: 

218 """Return an identity key. 

219 

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

221 

222 """ 

223 return util.preloaded.orm_util.identity_key( 

224 class_, 

225 ident, 

226 instance=instance, 

227 row=row, 

228 identity_token=identity_token, 

229 ) 

230 

231 @classmethod 

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

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

234 

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

236 

237 """ 

238 

239 return object_session(instance) 

240 

241 

242class SessionTransactionState(_StateChangeState): 

243 ACTIVE = 1 

244 PREPARED = 2 

245 COMMITTED = 3 

246 DEACTIVE = 4 

247 CLOSED = 5 

248 PROVISIONING_CONNECTION = 6 

249 

250 

251# backwards compatibility 

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

253 SessionTransactionState 

254) 

255 

256 

257class ORMExecuteState(util.MemoizedSlots): 

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

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

260 

261 .. versionadded:: 1.4 

262 

263 .. seealso:: 

264 

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

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

267 

268 """ 

269 

270 __slots__ = ( 

271 "session", 

272 "statement", 

273 "parameters", 

274 "execution_options", 

275 "local_execution_options", 

276 "bind_arguments", 

277 "identity_token", 

278 "_compile_state_cls", 

279 "_starting_event_idx", 

280 "_events_todo", 

281 "_update_execution_options", 

282 ) 

283 

284 session: Session 

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

286 

287 statement: Executable 

288 """The SQL statement being invoked. 

289 

290 For an ORM selection as would 

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

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

293 """ 

294 

295 parameters: Optional[_CoreAnyExecuteParams] 

296 """Dictionary of parameters that was passed to 

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

298 

299 execution_options: _ExecuteOptions 

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

301 

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

303 locally passed execution options. 

304 

305 .. seealso:: 

306 

307 :attr:`_orm.ORMExecuteState.local_execution_options` 

308 

309 :meth:`_sql.Executable.execution_options` 

310 

311 :ref:`orm_queryguide_execution_options` 

312 

313 """ 

314 

315 local_execution_options: _ExecuteOptions 

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

317 :meth:`.Session.execute` method. 

318 

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

320 being invoked. 

321 

322 .. seealso:: 

323 

324 :attr:`_orm.ORMExecuteState.execution_options` 

325 

326 """ 

327 

328 bind_arguments: _BindArguments 

329 """The dictionary passed as the 

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

331 

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

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

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

335 

336 """ 

337 

338 _compile_state_cls: Optional[Type[_ORMCompileState]] 

339 _starting_event_idx: int 

340 _events_todo: List[Any] 

341 _update_execution_options: Optional[_ExecuteOptions] 

342 

343 def __init__( 

344 self, 

345 session: Session, 

346 statement: Executable, 

347 parameters: Optional[_CoreAnyExecuteParams], 

348 execution_options: _ExecuteOptions, 

349 bind_arguments: _BindArguments, 

350 compile_state_cls: Optional[Type[_ORMCompileState]], 

351 events_todo: List[_InstanceLevelDispatch[Session]], 

352 ): 

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

354 

355 this object is constructed internally. 

356 

357 """ 

358 self.session = session 

359 self.statement = statement 

360 self.parameters = parameters 

361 self.local_execution_options = execution_options 

362 self.execution_options = statement._execution_options.union( 

363 execution_options 

364 ) 

365 self.bind_arguments = bind_arguments 

366 self._compile_state_cls = compile_state_cls 

367 self._events_todo = list(events_todo) 

368 

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

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

371 

372 def invoke_statement( 

373 self, 

374 statement: Optional[Executable] = None, 

375 params: Optional[_CoreAnyExecuteParams] = None, 

376 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

377 bind_arguments: Optional[_BindArguments] = None, 

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

379 """Execute the statement represented by this 

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

381 already proceeded. 

382 

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

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

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

386 that want to override how the ultimate 

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

388 retrieve results from an offline cache or which concatenate results 

389 from multiple executions. 

390 

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

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

393 is propagated to the calling 

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

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

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

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

398 

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

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

401 

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

403 which will be merged into the existing 

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

405 

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

407 for executemany executions. 

408 

409 :param execution_options: optional dictionary of execution options 

410 will be merged into the existing 

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

412 :class:`.ORMExecuteState`. 

413 

414 :param bind_arguments: optional dictionary of bind_arguments 

415 which will be merged amongst the current 

416 :attr:`.ORMExecuteState.bind_arguments` 

417 of this :class:`.ORMExecuteState`. 

418 

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

420 

421 .. seealso:: 

422 

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

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

425 

426 

427 """ 

428 

429 if statement is None: 

430 statement = self.statement 

431 

432 _bind_arguments = dict(self.bind_arguments) 

433 if bind_arguments: 

434 _bind_arguments.update(bind_arguments) 

435 _bind_arguments["_sa_skip_events"] = True 

436 

437 _params: Optional[_CoreAnyExecuteParams] 

438 if params: 

439 if self.is_executemany: 

440 _params = [] 

441 exec_many_parameters = cast( 

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

443 ) 

444 for _existing_params, _new_params in itertools.zip_longest( 

445 exec_many_parameters, 

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

447 ): 

448 if _existing_params is None or _new_params is None: 

449 raise sa_exc.InvalidRequestError( 

450 f"Can't apply executemany parameters to " 

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

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

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

454 f"to ORMExecuteState.invoke_statement() " 

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

456 ) 

457 _existing_params = dict(_existing_params) 

458 _existing_params.update(_new_params) 

459 _params.append(_existing_params) 

460 else: 

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

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

463 else: 

464 _params = self.parameters 

465 

466 _execution_options = self.local_execution_options 

467 if execution_options: 

468 _execution_options = _execution_options.union(execution_options) 

469 

470 return self.session._execute_internal( 

471 statement, 

472 _params, 

473 execution_options=_execution_options, 

474 bind_arguments=_bind_arguments, 

475 _parent_execute_state=self, 

476 ) 

477 

478 @property 

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

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

481 

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

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

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

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

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

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

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

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

490 would be selected. 

491 

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

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

494 way of getting this mapper. 

495 

496 .. versionadded:: 1.4.0b2 

497 

498 .. seealso:: 

499 

500 :attr:`_orm.ORMExecuteState.all_mappers` 

501 

502 

503 """ 

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

505 return mp 

506 

507 @property 

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

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

510 involved at the top level of this statement. 

511 

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

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

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

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

516 

517 .. versionadded:: 1.4.0b2 

518 

519 .. seealso:: 

520 

521 :attr:`_orm.ORMExecuteState.bind_mapper` 

522 

523 

524 

525 """ 

526 if not self.is_orm_statement: 

527 return [] 

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

529 result = [] 

530 seen = set() 

531 for d in self.statement.column_descriptions: 

532 ent = d["entity"] 

533 if ent: 

534 insp = inspect(ent, raiseerr=False) 

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

536 seen.add(insp.mapper) 

537 result.append(insp.mapper) 

538 return result 

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

540 return [self.bind_mapper] 

541 else: 

542 return [] 

543 

544 @property 

545 def is_orm_statement(self) -> bool: 

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

547 

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

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

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

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

552 and no ORM-level automation takes place. 

553 

554 """ 

555 return self._compile_state_cls is not None 

556 

557 @property 

558 def is_executemany(self) -> bool: 

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

560 dictionaries with more than one dictionary. 

561 

562 .. versionadded:: 2.0 

563 

564 """ 

565 return isinstance(self.parameters, list) 

566 

567 @property 

568 def is_select(self) -> bool: 

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

570 

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

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

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

574 ``select(Entity).from_statement(select(..))`` 

575 

576 """ 

577 return self.statement.is_select 

578 

579 @property 

580 def is_from_statement(self) -> bool: 

581 """return True if this operation is a 

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

583 

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

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

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

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

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

589 :class:`_sql.Select` construct. 

590 

591 .. versionadded:: 2.0.30 

592 

593 """ 

594 return self.statement.is_from_statement 

595 

596 @property 

597 def is_insert(self) -> bool: 

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

599 

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

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

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

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

604 

605 """ 

606 return self.statement.is_dml and self.statement.is_insert 

607 

608 @property 

609 def is_update(self) -> bool: 

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

611 

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

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

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

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

616 

617 """ 

618 return self.statement.is_dml and self.statement.is_update 

619 

620 @property 

621 def is_delete(self) -> bool: 

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

623 

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

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

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

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

628 

629 """ 

630 return self.statement.is_dml and self.statement.is_delete 

631 

632 @property 

633 def _is_crud(self) -> bool: 

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

635 

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

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

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

639 

640 def _orm_compile_options( 

641 self, 

642 ) -> Optional[ 

643 Union[ 

644 context._ORMCompileState.default_compile_options, 

645 Type[context._ORMCompileState.default_compile_options], 

646 ] 

647 ]: 

648 if not self.is_select: 

649 return None 

650 try: 

651 opts = self.statement._compile_options 

652 except AttributeError: 

653 return None 

654 

655 if opts is not None and opts.isinstance( 

656 context._ORMCompileState.default_compile_options 

657 ): 

658 return opts # type: ignore 

659 else: 

660 return None 

661 

662 @property 

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

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

665 for a lazy load operation. 

666 

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

668 sharding extension, where it is available within specific query 

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

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

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

672 compilation time. 

673 

674 """ 

675 return self.load_options._lazy_loaded_from 

676 

677 @property 

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

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

680 

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

682 when a particular object or collection is being loaded. 

683 

684 """ 

685 opts = self._orm_compile_options() 

686 if opts is not None: 

687 return opts._current_path 

688 else: 

689 return None 

690 

691 @property 

692 def is_column_load(self) -> bool: 

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

694 attributes on an existing ORM object. 

695 

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

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

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

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

700 loaded. 

701 

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

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

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

705 and loader options travelling with the instance 

706 will have already been added to the query. 

707 

708 .. versionadded:: 1.4.0b2 

709 

710 .. seealso:: 

711 

712 :attr:`_orm.ORMExecuteState.is_relationship_load` 

713 

714 """ 

715 opts = self._orm_compile_options() 

716 return opts is not None and opts._for_refresh_state 

717 

718 @property 

719 def is_relationship_load(self) -> bool: 

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

721 relationship. 

722 

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

724 SelectInLoader, SubqueryLoader, or similar, and the entire 

725 SELECT statement being emitted is on behalf of a relationship 

726 load. 

727 

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

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

730 capable of being propagated to relationship loaders and should 

731 be already present. 

732 

733 .. seealso:: 

734 

735 :attr:`_orm.ORMExecuteState.is_column_load` 

736 

737 """ 

738 opts = self._orm_compile_options() 

739 if opts is None: 

740 return False 

741 path = self.loader_strategy_path 

742 return path is not None and not path.is_root 

743 

744 @property 

745 def load_options( 

746 self, 

747 ) -> Union[ 

748 context.QueryContext.default_load_options, 

749 Type[context.QueryContext.default_load_options], 

750 ]: 

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

752 

753 if not self.is_select: 

754 raise sa_exc.InvalidRequestError( 

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

756 "so there are no load options." 

757 ) 

758 

759 lo: Union[ 

760 context.QueryContext.default_load_options, 

761 Type[context.QueryContext.default_load_options], 

762 ] = self.execution_options.get( 

763 "_sa_orm_load_options", context.QueryContext.default_load_options 

764 ) 

765 return lo 

766 

767 @property 

768 def update_delete_options( 

769 self, 

770 ) -> Union[ 

771 bulk_persistence._BulkUDCompileState.default_update_options, 

772 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

773 ]: 

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

775 execution.""" 

776 

777 if not self._is_crud: 

778 raise sa_exc.InvalidRequestError( 

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

780 "statement so there are no update options." 

781 ) 

782 uo: Union[ 

783 bulk_persistence._BulkUDCompileState.default_update_options, 

784 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

785 ] = self.execution_options.get( 

786 "_sa_orm_update_options", 

787 bulk_persistence._BulkUDCompileState.default_update_options, 

788 ) 

789 return uo 

790 

791 @property 

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

793 return [ 

794 opt 

795 for opt in self.statement._with_options 

796 if is_orm_option(opt) and not opt._is_compile_state 

797 ] 

798 

799 @property 

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

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

802 associated with the statement being invoked. 

803 

804 """ 

805 return [ 

806 opt 

807 for opt in self.statement._with_options 

808 if is_user_defined_option(opt) 

809 ] 

810 

811 

812class SessionTransactionOrigin(Enum): 

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

814 

815 This enumeration is present on the 

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

817 :class:`.SessionTransaction` object. 

818 

819 .. versionadded:: 2.0 

820 

821 """ 

822 

823 AUTOBEGIN = 0 

824 """transaction were started by autobegin""" 

825 

826 BEGIN = 1 

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

828 

829 BEGIN_NESTED = 2 

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

831 

832 SUBTRANSACTION = 3 

833 """transaction is an internal "subtransaction" """ 

834 

835 

836class SessionTransaction(_StateChange, TransactionalContext): 

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

838 

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

840 :meth:`_orm.Session.begin` 

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

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

843 transactions. 

844 

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

846 at: :ref:`unitofwork_transaction`. 

847 

848 

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

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

851 

852 .. seealso:: 

853 

854 :ref:`unitofwork_transaction` 

855 

856 :meth:`.Session.begin` 

857 

858 :meth:`.Session.begin_nested` 

859 

860 :meth:`.Session.rollback` 

861 

862 :meth:`.Session.commit` 

863 

864 :meth:`.Session.in_transaction` 

865 

866 :meth:`.Session.in_nested_transaction` 

867 

868 :meth:`.Session.get_transaction` 

869 

870 :meth:`.Session.get_nested_transaction` 

871 

872 

873 """ 

874 

875 _rollback_exception: Optional[BaseException] = None 

876 

877 _connections: Dict[ 

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

879 ] 

880 session: Session 

881 _parent: Optional[SessionTransaction] 

882 

883 _state: SessionTransactionState 

884 

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

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

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

888 _key_switches: weakref.WeakKeyDictionary[ 

889 InstanceState[Any], Tuple[Any, Any] 

890 ] 

891 

892 origin: SessionTransactionOrigin 

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

894 

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

896 enumeration indicating the source event that led to constructing 

897 this :class:`_orm.SessionTransaction`. 

898 

899 .. versionadded:: 2.0 

900 

901 """ 

902 

903 nested: bool = False 

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

905 

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

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

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

909 

910 .. seealso:: 

911 

912 :attr:`.SessionTransaction.origin` 

913 

914 """ 

915 

916 def __init__( 

917 self, 

918 session: Session, 

919 origin: SessionTransactionOrigin, 

920 parent: Optional[SessionTransaction] = None, 

921 ): 

922 TransactionalContext._trans_ctx_check(session) 

923 

924 self.session = session 

925 self._connections = {} 

926 self._parent = parent 

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

928 self.origin = origin 

929 

930 if session._close_state is _SessionCloseState.CLOSED: 

931 raise sa_exc.InvalidRequestError( 

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

933 "to handle any more transaction requests." 

934 ) 

935 

936 if nested: 

937 if not parent: 

938 raise sa_exc.InvalidRequestError( 

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

940 "transaction is in progress" 

941 ) 

942 

943 self._previous_nested_transaction = session._nested_transaction 

944 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

945 assert parent is not None 

946 else: 

947 assert parent is None 

948 

949 self._state = SessionTransactionState.ACTIVE 

950 

951 self._take_snapshot() 

952 

953 # make sure transaction is assigned before we call the 

954 # dispatch 

955 self.session._transaction = self 

956 

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

958 

959 def _raise_for_prerequisite_state( 

960 self, operation_name: str, state: _StateChangeState 

961 ) -> NoReturn: 

962 if state is SessionTransactionState.DEACTIVE: 

963 if self._rollback_exception: 

964 raise sa_exc.PendingRollbackError( 

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

966 "due to a previous exception during flush." 

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

968 "first issue Session.rollback()." 

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

970 code="7s2a", 

971 ) 

972 else: 

973 raise sa_exc.InvalidRequestError( 

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

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

976 "can be emitted within this transaction." 

977 ) 

978 elif state is SessionTransactionState.CLOSED: 

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

980 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

981 raise sa_exc.InvalidRequestError( 

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

983 "operations are not permitted", 

984 code="isce", 

985 ) 

986 else: 

987 raise sa_exc.InvalidRequestError( 

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

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

990 ) 

991 

992 @property 

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

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

995 :class:`.SessionTransaction`. 

996 

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

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

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

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

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

1002 "nested" / SAVEPOINT transaction. If the 

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

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

1005 

1006 """ 

1007 return self._parent 

1008 

1009 @property 

1010 def is_active(self) -> bool: 

1011 return ( 

1012 self.session is not None 

1013 and self._state is SessionTransactionState.ACTIVE 

1014 ) 

1015 

1016 @property 

1017 def _is_transaction_boundary(self) -> bool: 

1018 return self.nested or not self._parent 

1019 

1020 @_StateChange.declare_states( 

1021 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1022 ) 

1023 def connection( 

1024 self, 

1025 bindkey: Optional[Mapper[Any]], 

1026 execution_options: Optional[_ExecuteOptions] = None, 

1027 **kwargs: Any, 

1028 ) -> Connection: 

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

1030 return self._connection_for_bind(bind, execution_options) 

1031 

1032 @_StateChange.declare_states( 

1033 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1034 ) 

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

1036 return SessionTransaction( 

1037 self.session, 

1038 ( 

1039 SessionTransactionOrigin.BEGIN_NESTED 

1040 if nested 

1041 else SessionTransactionOrigin.SUBTRANSACTION 

1042 ), 

1043 self, 

1044 ) 

1045 

1046 def _iterate_self_and_parents( 

1047 self, upto: Optional[SessionTransaction] = None 

1048 ) -> Iterable[SessionTransaction]: 

1049 current = self 

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

1051 while current: 

1052 result += (current,) 

1053 if current._parent is upto: 

1054 break 

1055 elif current._parent is None: 

1056 raise sa_exc.InvalidRequestError( 

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

1058 % (upto) 

1059 ) 

1060 else: 

1061 current = current._parent 

1062 

1063 return result 

1064 

1065 def _take_snapshot(self) -> None: 

1066 if not self._is_transaction_boundary: 

1067 parent = self._parent 

1068 assert parent is not None 

1069 self._new = parent._new 

1070 self._deleted = parent._deleted 

1071 self._dirty = parent._dirty 

1072 self._key_switches = parent._key_switches 

1073 return 

1074 

1075 is_begin = self.origin in ( 

1076 SessionTransactionOrigin.BEGIN, 

1077 SessionTransactionOrigin.AUTOBEGIN, 

1078 ) 

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

1080 self.session.flush() 

1081 

1082 self._new = weakref.WeakKeyDictionary() 

1083 self._deleted = weakref.WeakKeyDictionary() 

1084 self._dirty = weakref.WeakKeyDictionary() 

1085 self._key_switches = weakref.WeakKeyDictionary() 

1086 

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

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

1089 

1090 Corresponds to a rollback. 

1091 

1092 """ 

1093 assert self._is_transaction_boundary 

1094 

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

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

1097 

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

1099 # we probably can do this conditionally based on 

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

1101 self.session.identity_map.safe_discard(s) 

1102 

1103 # restore the old key 

1104 s.key = oldkey 

1105 

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

1107 if s not in to_expunge: 

1108 self.session.identity_map.replace(s) 

1109 

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

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

1112 

1113 assert not self.session._deleted 

1114 

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

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

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

1118 

1119 def _remove_snapshot(self) -> None: 

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

1121 

1122 Corresponds to a commit. 

1123 

1124 """ 

1125 assert self._is_transaction_boundary 

1126 

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

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

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

1130 

1131 statelib.InstanceState._detach_states( 

1132 list(self._deleted), self.session 

1133 ) 

1134 self._deleted.clear() 

1135 elif self.nested: 

1136 parent = self._parent 

1137 assert parent is not None 

1138 parent._new.update(self._new) 

1139 parent._dirty.update(self._dirty) 

1140 parent._deleted.update(self._deleted) 

1141 parent._key_switches.update(self._key_switches) 

1142 

1143 @_StateChange.declare_states( 

1144 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1145 ) 

1146 def _connection_for_bind( 

1147 self, 

1148 bind: _SessionBind, 

1149 execution_options: Optional[CoreExecuteOptionsParameter], 

1150 ) -> Connection: 

1151 if bind in self._connections: 

1152 if execution_options: 

1153 util.warn( 

1154 "Connection is already established for the " 

1155 "given bind; execution_options ignored" 

1156 ) 

1157 return self._connections[bind][0] 

1158 

1159 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1160 

1161 local_connect = False 

1162 should_commit = True 

1163 

1164 try: 

1165 if self._parent: 

1166 conn = self._parent._connection_for_bind( 

1167 bind, execution_options 

1168 ) 

1169 if not self.nested: 

1170 return conn 

1171 else: 

1172 if isinstance(bind, engine.Connection): 

1173 conn = bind 

1174 if conn.engine in self._connections: 

1175 raise sa_exc.InvalidRequestError( 

1176 "Session already has a Connection associated " 

1177 "for the given Connection's Engine" 

1178 ) 

1179 else: 

1180 conn = bind.connect() 

1181 local_connect = True 

1182 

1183 try: 

1184 if execution_options: 

1185 conn = conn.execution_options(**execution_options) 

1186 

1187 transaction: Transaction 

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

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

1190 # conn.in_transaction() ? 

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

1192 # that it is in fact twophase. 

1193 transaction = conn.begin_twophase() 

1194 elif self.nested: 

1195 transaction = conn.begin_nested() 

1196 elif conn.in_transaction(): 

1197 

1198 if local_connect: 

1199 _trans = conn.get_transaction() 

1200 assert _trans is not None 

1201 transaction = _trans 

1202 else: 

1203 join_transaction_mode = ( 

1204 self.session.join_transaction_mode 

1205 ) 

1206 

1207 if join_transaction_mode == "conditional_savepoint": 

1208 if conn.in_nested_transaction(): 

1209 join_transaction_mode = "create_savepoint" 

1210 else: 

1211 join_transaction_mode = "rollback_only" 

1212 

1213 if join_transaction_mode in ( 

1214 "control_fully", 

1215 "rollback_only", 

1216 ): 

1217 if conn.in_nested_transaction(): 

1218 transaction = ( 

1219 conn._get_required_nested_transaction() 

1220 ) 

1221 else: 

1222 transaction = conn._get_required_transaction() 

1223 if join_transaction_mode == "rollback_only": 

1224 should_commit = False 

1225 elif join_transaction_mode == "create_savepoint": 

1226 transaction = conn.begin_nested() 

1227 else: 

1228 assert False, join_transaction_mode 

1229 else: 

1230 transaction = conn.begin() 

1231 except: 

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

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

1234 if local_connect: 

1235 conn.close() 

1236 raise 

1237 else: 

1238 bind_is_connection = isinstance(bind, engine.Connection) 

1239 

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

1241 conn, 

1242 transaction, 

1243 should_commit, 

1244 not bind_is_connection, 

1245 ) 

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

1247 return conn 

1248 finally: 

1249 self._state = SessionTransactionState.ACTIVE 

1250 

1251 def prepare(self) -> None: 

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

1253 raise sa_exc.InvalidRequestError( 

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

1255 "can't prepare." 

1256 ) 

1257 self._prepare_impl() 

1258 

1259 @_StateChange.declare_states( 

1260 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1261 ) 

1262 def _prepare_impl(self) -> None: 

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

1264 self.session.dispatch.before_commit(self.session) 

1265 

1266 stx = self.session._transaction 

1267 assert stx is not None 

1268 if stx is not self: 

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

1270 subtransaction.commit() 

1271 

1272 if not self.session._flushing: 

1273 for _flush_guard in range(100): 

1274 if self.session._is_clean(): 

1275 break 

1276 self.session.flush() 

1277 else: 

1278 raise exc.FlushError( 

1279 "Over 100 subsequent flushes have occurred within " 

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

1281 "creating new objects?" 

1282 ) 

1283 

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

1285 try: 

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

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

1288 except: 

1289 with util.safe_reraise(): 

1290 self.rollback() 

1291 

1292 self._state = SessionTransactionState.PREPARED 

1293 

1294 @_StateChange.declare_states( 

1295 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1296 SessionTransactionState.CLOSED, 

1297 ) 

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

1299 if self._state is not SessionTransactionState.PREPARED: 

1300 with self._expect_state(SessionTransactionState.PREPARED): 

1301 self._prepare_impl() 

1302 

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

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

1305 self._connections.values() 

1306 ): 

1307 if should_commit: 

1308 trans.commit() 

1309 

1310 self._state = SessionTransactionState.COMMITTED 

1311 self.session.dispatch.after_commit(self.session) 

1312 

1313 self._remove_snapshot() 

1314 

1315 with self._expect_state(SessionTransactionState.CLOSED): 

1316 self.close() 

1317 

1318 if _to_root and self._parent: 

1319 self._parent.commit(_to_root=True) 

1320 

1321 @_StateChange.declare_states( 

1322 ( 

1323 SessionTransactionState.ACTIVE, 

1324 SessionTransactionState.DEACTIVE, 

1325 SessionTransactionState.PREPARED, 

1326 ), 

1327 SessionTransactionState.CLOSED, 

1328 ) 

1329 def rollback( 

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

1331 ) -> None: 

1332 stx = self.session._transaction 

1333 assert stx is not None 

1334 if stx is not self: 

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

1336 subtransaction.close() 

1337 

1338 boundary = self 

1339 rollback_err = None 

1340 if self._state in ( 

1341 SessionTransactionState.ACTIVE, 

1342 SessionTransactionState.PREPARED, 

1343 ): 

1344 for transaction in self._iterate_self_and_parents(): 

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

1346 try: 

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

1348 t[1].rollback() 

1349 

1350 transaction._state = SessionTransactionState.DEACTIVE 

1351 self.session.dispatch.after_rollback(self.session) 

1352 except: 

1353 rollback_err = sys.exc_info() 

1354 finally: 

1355 transaction._state = SessionTransactionState.DEACTIVE 

1356 transaction._restore_snapshot( 

1357 dirty_only=transaction.nested 

1358 ) 

1359 boundary = transaction 

1360 break 

1361 else: 

1362 transaction._state = SessionTransactionState.DEACTIVE 

1363 

1364 sess = self.session 

1365 

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

1367 # if items were added, deleted, or mutated 

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

1369 util.warn( 

1370 "Session's state has been changed on " 

1371 "a non-active transaction - this state " 

1372 "will be discarded." 

1373 ) 

1374 boundary._restore_snapshot(dirty_only=boundary.nested) 

1375 

1376 with self._expect_state(SessionTransactionState.CLOSED): 

1377 self.close() 

1378 

1379 if self._parent and _capture_exception: 

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

1381 

1382 if rollback_err and rollback_err[1]: 

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

1384 

1385 sess.dispatch.after_soft_rollback(sess, self) 

1386 

1387 if _to_root and self._parent: 

1388 self._parent.rollback(_to_root=True) 

1389 

1390 @_StateChange.declare_states( 

1391 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1392 ) 

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

1394 if self.nested: 

1395 self.session._nested_transaction = ( 

1396 self._previous_nested_transaction 

1397 ) 

1398 

1399 self.session._transaction = self._parent 

1400 

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

1402 self._connections.values() 

1403 ): 

1404 if invalidate and self._parent is None: 

1405 connection.invalidate() 

1406 if should_commit and transaction.is_active: 

1407 transaction.close() 

1408 if autoclose and self._parent is None: 

1409 connection.close() 

1410 

1411 self._state = SessionTransactionState.CLOSED 

1412 sess = self.session 

1413 

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

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

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

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

1418 # passes with these commented out. 

1419 # self.session = None # type: ignore 

1420 # self._connections = None # type: ignore 

1421 

1422 sess.dispatch.after_transaction_end(sess, self) 

1423 

1424 def _get_subject(self) -> Session: 

1425 return self.session 

1426 

1427 def _transaction_is_active(self) -> bool: 

1428 return self._state is SessionTransactionState.ACTIVE 

1429 

1430 def _transaction_is_closed(self) -> bool: 

1431 return self._state is SessionTransactionState.CLOSED 

1432 

1433 def _rollback_can_be_called(self) -> bool: 

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

1435 

1436 

1437class _SessionCloseState(Enum): 

1438 ACTIVE = 1 

1439 CLOSED = 2 

1440 CLOSE_IS_RESET = 3 

1441 

1442 

1443class Session(_SessionClassMethods, EventTarget): 

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

1445 

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

1447 See :ref:`session_faq_threadsafe` for background. 

1448 

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

1450 

1451 

1452 """ 

1453 

1454 _is_asyncio = False 

1455 

1456 dispatch: dispatcher[Session] 

1457 

1458 identity_map: IdentityMap 

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

1460 

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

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

1463 that have row identity) currently in the session. 

1464 

1465 .. seealso:: 

1466 

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

1468 in this dictionary. 

1469 

1470 """ 

1471 

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

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

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

1475 __binds: Dict[_SessionBindKey, _SessionBind] 

1476 _flushing: bool 

1477 _warn_on_events: bool 

1478 _transaction: Optional[SessionTransaction] 

1479 _nested_transaction: Optional[SessionTransaction] 

1480 hash_key: int 

1481 autoflush: bool 

1482 expire_on_commit: bool 

1483 enable_baked_queries: bool 

1484 twophase: bool 

1485 join_transaction_mode: JoinTransactionMode 

1486 execution_options: _ExecuteOptions = util.EMPTY_DICT 

1487 _query_cls: Type[Query[Any]] 

1488 _close_state: _SessionCloseState 

1489 

1490 def __init__( 

1491 self, 

1492 bind: Optional[_SessionBind] = None, 

1493 *, 

1494 autoflush: bool = True, 

1495 future: Literal[True] = True, 

1496 expire_on_commit: bool = True, 

1497 autobegin: bool = True, 

1498 twophase: bool = False, 

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

1500 enable_baked_queries: bool = True, 

1501 info: Optional[_InfoType] = None, 

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

1503 autocommit: Literal[False] = False, 

1504 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1506 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

1507 ): 

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

1509 

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

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

1512 set of arguments. 

1513 

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

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

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

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

1518 results. 

1519 

1520 .. seealso:: 

1521 

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

1523 

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

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

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

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

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

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

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

1531 

1532 .. versionadded:: 2.0 

1533 

1534 .. seealso:: 

1535 

1536 :ref:`session_autobegin_disable` 

1537 

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

1539 :class:`_engine.Connection` to 

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

1541 operations performed by this session will execute via this 

1542 connectable. 

1543 

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

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

1546 objects as the source of 

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

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

1549 arbitrary Python classes that are bases for mapped classes, 

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

1551 The 

1552 values of the dictionary are then instances of 

1553 :class:`_engine.Engine` 

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

1555 Operations which 

1556 proceed relative to a particular mapped class will consult this 

1557 dictionary for the closest matching entity in order to determine 

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

1559 operation. The complete heuristics for resolution are 

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

1561 

1562 Session = sessionmaker( 

1563 binds={ 

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

1565 SomeDeclarativeBase: create_engine( 

1566 "postgresql+psycopg2://engine2" 

1567 ), 

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

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

1570 } 

1571 ) 

1572 

1573 .. seealso:: 

1574 

1575 :ref:`session_partitioning` 

1576 

1577 :meth:`.Session.bind_mapper` 

1578 

1579 :meth:`.Session.bind_table` 

1580 

1581 :meth:`.Session.get_bind` 

1582 

1583 

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

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

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

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

1588 constructor for ``Session``. 

1589 

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

1591 A parameter consumed 

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

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

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

1595 this particular extension is disabled. 

1596 

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

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

1599 flag therefore only affects applications that are making explicit 

1600 use of this extension within their own code. 

1601 

1602 :param execution_options: optional dictionary of execution options 

1603 that will be applied to all calls to :meth:`_orm.Session.execute`, 

1604 :meth:`_orm.Session.scalars`, and similar. Execution options 

1605 present in statements as well as options passed to methods like 

1606 :meth:`_orm.Session.execute` explicitly take precedence over 

1607 the session-wide options. 

1608 

1609 .. versionadded:: 2.1 

1610 

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

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

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

1614 transaction will load from the most recent database state. 

1615 

1616 .. seealso:: 

1617 

1618 :ref:`session_committing` 

1619 

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

1621 

1622 .. seealso:: 

1623 

1624 :ref:`migration_20_toplevel` 

1625 

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

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

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

1629 construction time so that modifications to the per- 

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

1631 :class:`.Session`. 

1632 

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

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

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

1636 

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

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

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

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

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

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

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

1644 transaction, before each transaction is committed. 

1645 

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

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

1648 

1649 :param join_transaction_mode: Describes the transactional behavior to 

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

1651 has already begun a transaction outside the scope of this 

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

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

1654 

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

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

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

1658 etc. are actually invoked: 

1659 

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

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

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

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

1664 a SAVEPOINT, in other words 

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

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

1667 

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

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

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

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

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

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

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

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

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

1677 

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

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

1680 its own transaction. This transaction by its nature rides 

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

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

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

1684 external transaction will remain unaffected throughout the 

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

1686 

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

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

1689 initiated transaction should remain unaffected; however, it relies 

1690 on proper SAVEPOINT support from the underlying driver and 

1691 database. 

1692 

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

1694 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1695 without workarounds. See the sections 

1696 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1697 for details on current workarounds. 

1698 

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

1700 control of the given transaction as its own; 

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

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

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

1704 call ``.rollback`` on the transaction. 

1705 

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

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

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

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

1710 SAVEPOINT. 

1711 

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

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

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

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

1716 given transaction. 

1717 

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

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

1720 regular database transaction (i.e. 

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

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

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

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

1725 

1726 .. versionadded:: 2.0.0rc1 

1727 

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

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

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

1731 

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

1733 A future SQLAlchemy version may change the default value of 

1734 this flag to ``False``. 

1735 

1736 .. seealso:: 

1737 

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

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

1740 

1741 """ # noqa 

1742 

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

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

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

1746 # of cases including in our own test suite 

1747 if autocommit: 

1748 raise sa_exc.ArgumentError( 

1749 "autocommit=True is no longer supported" 

1750 ) 

1751 self.identity_map = identity._WeakInstanceDict() 

1752 

1753 if not future: 

1754 raise sa_exc.ArgumentError( 

1755 "The 'future' parameter passed to " 

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

1757 ) 

1758 

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

1760 self._deleted = {} # same 

1761 self.bind = bind 

1762 self.__binds = {} 

1763 self._flushing = False 

1764 self._warn_on_events = False 

1765 self._transaction = None 

1766 self._nested_transaction = None 

1767 self.hash_key = _new_sessionid() 

1768 self.autobegin = autobegin 

1769 self.autoflush = autoflush 

1770 self.expire_on_commit = expire_on_commit 

1771 self.enable_baked_queries = enable_baked_queries 

1772 if execution_options: 

1773 self.execution_options = self.execution_options.union( 

1774 execution_options 

1775 ) 

1776 

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

1778 # the default will switch to close_resets_only=False. 

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

1780 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1781 else: 

1782 self._close_state = _SessionCloseState.ACTIVE 

1783 if ( 

1784 join_transaction_mode 

1785 and join_transaction_mode 

1786 not in JoinTransactionMode.__args__ # type: ignore 

1787 ): 

1788 raise sa_exc.ArgumentError( 

1789 f"invalid selection for join_transaction_mode: " 

1790 f'"{join_transaction_mode}"' 

1791 ) 

1792 self.join_transaction_mode = join_transaction_mode 

1793 

1794 self.twophase = twophase 

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

1796 if info: 

1797 self.info.update(info) 

1798 

1799 if binds is not None: 

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

1801 self._add_bind(key, bind) 

1802 

1803 _sessions[self.hash_key] = self 

1804 

1805 # used by sqlalchemy.engine.util.TransactionalContext 

1806 _trans_context_manager: Optional[TransactionalContext] = None 

1807 

1808 connection_callable: Optional[_ConnectionCallableProto] = None 

1809 

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

1811 return self 

1812 

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

1814 self.close() 

1815 

1816 @contextlib.contextmanager 

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

1818 with self: 

1819 with self.begin(): 

1820 yield self 

1821 

1822 def in_transaction(self) -> bool: 

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

1824 

1825 .. versionadded:: 1.4 

1826 

1827 .. seealso:: 

1828 

1829 :attr:`_orm.Session.is_active` 

1830 

1831 

1832 """ 

1833 return self._transaction is not None 

1834 

1835 def in_nested_transaction(self) -> bool: 

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

1837 transaction, e.g. SAVEPOINT. 

1838 

1839 .. versionadded:: 1.4 

1840 

1841 """ 

1842 return self._nested_transaction is not None 

1843 

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

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

1846 

1847 .. versionadded:: 1.4 

1848 

1849 """ 

1850 trans = self._transaction 

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

1852 trans = trans._parent 

1853 return trans 

1854 

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

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

1857 

1858 .. versionadded:: 1.4 

1859 

1860 """ 

1861 

1862 return self._nested_transaction 

1863 

1864 @util.memoized_property 

1865 def info(self) -> _InfoType: 

1866 """A user-modifiable dictionary. 

1867 

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

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

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

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

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

1873 

1874 """ 

1875 return {} 

1876 

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

1878 if self._transaction is None: 

1879 if not begin and not self.autobegin: 

1880 raise sa_exc.InvalidRequestError( 

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

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

1883 ) 

1884 trans = SessionTransaction( 

1885 self, 

1886 ( 

1887 SessionTransactionOrigin.BEGIN 

1888 if begin 

1889 else SessionTransactionOrigin.AUTOBEGIN 

1890 ), 

1891 ) 

1892 assert self._transaction is trans 

1893 return trans 

1894 

1895 return self._transaction 

1896 

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

1898 """Begin a transaction, or nested transaction, 

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

1900 

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

1902 so that normally it is not necessary to call the 

1903 :meth:`_orm.Session.begin` 

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

1905 the scope of when the transactional state is begun. 

1906 

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

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

1909 

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

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

1912 documentation on SAVEPOINT transactions, please see 

1913 :ref:`session_begin_nested`. 

1914 

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

1916 :class:`.SessionTransaction` 

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

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

1919 an example. 

1920 

1921 .. seealso:: 

1922 

1923 :ref:`session_autobegin` 

1924 

1925 :ref:`unitofwork_transaction` 

1926 

1927 :meth:`.Session.begin_nested` 

1928 

1929 

1930 """ 

1931 

1932 trans = self._transaction 

1933 if trans is None: 

1934 trans = self._autobegin_t(begin=True) 

1935 

1936 if not nested: 

1937 return trans 

1938 

1939 assert trans is not None 

1940 

1941 if nested: 

1942 trans = trans._begin(nested=nested) 

1943 assert self._transaction is trans 

1944 self._nested_transaction = trans 

1945 else: 

1946 raise sa_exc.InvalidRequestError( 

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

1948 ) 

1949 

1950 return trans # needed for __enter__/__exit__ hook 

1951 

1952 def begin_nested(self) -> SessionTransaction: 

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

1954 

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

1956 SAVEPOINT for this method to function correctly. 

1957 

1958 For documentation on SAVEPOINT 

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

1960 

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

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

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

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

1965 

1966 .. seealso:: 

1967 

1968 :ref:`session_begin_nested` 

1969 

1970 :ref:`pysqlite_serializable` - special workarounds required 

1971 with the SQLite driver in order for SAVEPOINT to work 

1972 correctly. For asyncio use cases, see the section 

1973 :ref:`aiosqlite_serializable`. 

1974 

1975 """ 

1976 return self.begin(nested=True) 

1977 

1978 def rollback(self) -> None: 

1979 """Rollback the current transaction in progress. 

1980 

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

1982 

1983 The method always rolls back 

1984 the topmost database transaction, discarding any nested 

1985 transactions that may be in progress. 

1986 

1987 .. seealso:: 

1988 

1989 :ref:`session_rollback` 

1990 

1991 :ref:`unitofwork_transaction` 

1992 

1993 """ 

1994 if self._transaction is None: 

1995 pass 

1996 else: 

1997 self._transaction.rollback(_to_root=True) 

1998 

1999 def commit(self) -> None: 

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

2001 

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

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

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

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

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

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

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

2009 to disable this behavior. 

2010 

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

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

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

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

2015 normally affect the database unless pending flush changes were 

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

2017 rules. 

2018 

2019 The outermost database transaction is committed unconditionally, 

2020 automatically releasing any SAVEPOINTs in effect. 

2021 

2022 .. seealso:: 

2023 

2024 :ref:`session_committing` 

2025 

2026 :ref:`unitofwork_transaction` 

2027 

2028 :ref:`asyncio_orm_avoid_lazyloads` 

2029 

2030 """ 

2031 trans = self._transaction 

2032 if trans is None: 

2033 trans = self._autobegin_t() 

2034 

2035 trans.commit(_to_root=True) 

2036 

2037 def prepare(self) -> None: 

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

2039 

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

2041 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2042 

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

2044 current transaction is not such, an 

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

2046 

2047 """ 

2048 trans = self._transaction 

2049 if trans is None: 

2050 trans = self._autobegin_t() 

2051 

2052 trans.prepare() 

2053 

2054 def connection( 

2055 self, 

2056 bind_arguments: Optional[_BindArguments] = None, 

2057 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2058 ) -> Connection: 

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

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

2061 

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

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

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

2065 returned (note that no 

2066 transactional state is established with the DBAPI until the first 

2067 SQL statement is emitted). 

2068 

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

2070 resolved through any of the optional keyword arguments. This 

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

2072 

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

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

2075 to :meth:`.Session.get_bind`. 

2076 

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

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

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

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

2081 the arguments are ignored. 

2082 

2083 .. seealso:: 

2084 

2085 :ref:`session_transaction_isolation` 

2086 

2087 """ 

2088 

2089 if bind_arguments: 

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

2091 

2092 if bind is None: 

2093 bind = self.get_bind(**bind_arguments) 

2094 else: 

2095 bind = self.get_bind() 

2096 

2097 return self._connection_for_bind( 

2098 bind, 

2099 execution_options=execution_options, 

2100 ) 

2101 

2102 def _connection_for_bind( 

2103 self, 

2104 engine: _SessionBind, 

2105 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2106 **kw: Any, 

2107 ) -> Connection: 

2108 TransactionalContext._trans_ctx_check(self) 

2109 

2110 trans = self._transaction 

2111 if trans is None: 

2112 trans = self._autobegin_t() 

2113 return trans._connection_for_bind(engine, execution_options) 

2114 

2115 @overload 

2116 def _execute_internal( 

2117 self, 

2118 statement: Executable, 

2119 params: Optional[_CoreSingleExecuteParams] = None, 

2120 *, 

2121 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2122 bind_arguments: Optional[_BindArguments] = None, 

2123 _parent_execute_state: Optional[Any] = None, 

2124 _add_event: Optional[Any] = None, 

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

2126 ) -> Any: ... 

2127 

2128 @overload 

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 = ..., 

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

2140 

2141 def _execute_internal( 

2142 self, 

2143 statement: Executable, 

2144 params: Optional[_CoreAnyExecuteParams] = None, 

2145 *, 

2146 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2147 bind_arguments: Optional[_BindArguments] = None, 

2148 _parent_execute_state: Optional[Any] = None, 

2149 _add_event: Optional[Any] = None, 

2150 _scalar_result: bool = False, 

2151 ) -> Any: 

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

2153 

2154 if not bind_arguments: 

2155 bind_arguments = {} 

2156 else: 

2157 bind_arguments = dict(bind_arguments) 

2158 

2159 if ( 

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

2161 == "orm" 

2162 ): 

2163 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2164 statement, "orm" 

2165 ) 

2166 if TYPE_CHECKING: 

2167 assert isinstance( 

2168 compile_state_cls, context._AbstractORMCompileState 

2169 ) 

2170 else: 

2171 compile_state_cls = None 

2172 bind_arguments.setdefault("clause", statement) 

2173 

2174 combined_execution_options: util.immutabledict[str, Any] = ( 

2175 util.coerce_to_immutabledict(execution_options) 

2176 ) 

2177 if self.execution_options: 

2178 # merge given execution options with session-wide execution 

2179 # options. if the statement also has execution_options, 

2180 # maintain priority of session.execution_options -> 

2181 # statement.execution_options -> method passed execution_options 

2182 # by omitting from the base execution options those keys that 

2183 # will come from the statement 

2184 if statement._execution_options: 

2185 combined_execution_options = util.immutabledict( 

2186 { 

2187 k: v 

2188 for k, v in self.execution_options.items() 

2189 if k not in statement._execution_options 

2190 } 

2191 ).union(combined_execution_options) 

2192 else: 

2193 combined_execution_options = self.execution_options.union( 

2194 combined_execution_options 

2195 ) 

2196 

2197 if _parent_execute_state: 

2198 events_todo = _parent_execute_state._remaining_events() 

2199 else: 

2200 events_todo = self.dispatch.do_orm_execute 

2201 if _add_event: 

2202 events_todo = list(events_todo) + [_add_event] 

2203 

2204 if events_todo: 

2205 if compile_state_cls is not None: 

2206 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2212 ( 

2213 statement, 

2214 combined_execution_options, 

2215 ) = compile_state_cls.orm_pre_session_exec( 

2216 self, 

2217 statement, 

2218 params, 

2219 combined_execution_options, 

2220 bind_arguments, 

2221 True, 

2222 ) 

2223 

2224 orm_exec_state = ORMExecuteState( 

2225 self, 

2226 statement, 

2227 params, 

2228 combined_execution_options, 

2229 bind_arguments, 

2230 compile_state_cls, 

2231 events_todo, 

2232 ) 

2233 for idx, fn in enumerate(events_todo): 

2234 orm_exec_state._starting_event_idx = idx 

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

2236 orm_exec_state 

2237 ) 

2238 if fn_result: 

2239 if _scalar_result: 

2240 return fn_result.scalar() 

2241 else: 

2242 return fn_result 

2243 

2244 statement = orm_exec_state.statement 

2245 combined_execution_options = orm_exec_state.local_execution_options 

2246 

2247 if compile_state_cls is not None: 

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

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

2250 # new execution_options into load_options / update_delete_options, 

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

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

2253 ( 

2254 statement, 

2255 combined_execution_options, 

2256 ) = compile_state_cls.orm_pre_session_exec( 

2257 self, 

2258 statement, 

2259 params, 

2260 combined_execution_options, 

2261 bind_arguments, 

2262 False, 

2263 ) 

2264 else: 

2265 # Issue #9809: unconditionally autoflush for Core statements 

2266 self._autoflush() 

2267 

2268 bind = self.get_bind(**bind_arguments) 

2269 

2270 conn = self._connection_for_bind(bind) 

2271 

2272 if _scalar_result and not compile_state_cls: 

2273 if TYPE_CHECKING: 

2274 params = cast(_CoreSingleExecuteParams, params) 

2275 return conn.scalar( 

2276 statement, 

2277 params or {}, 

2278 execution_options=combined_execution_options, 

2279 ) 

2280 

2281 if compile_state_cls: 

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

2283 compile_state_cls.orm_execute_statement( 

2284 self, 

2285 statement, 

2286 params or {}, 

2287 combined_execution_options, 

2288 bind_arguments, 

2289 conn, 

2290 ) 

2291 ) 

2292 else: 

2293 result = conn.execute( 

2294 statement, params, execution_options=combined_execution_options 

2295 ) 

2296 

2297 if _scalar_result: 

2298 return result.scalar() 

2299 else: 

2300 return result 

2301 

2302 @overload 

2303 def execute( 

2304 self, 

2305 statement: TypedReturnsRows[Unpack[_Ts]], 

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[_Ts]]: ... 

2313 

2314 @overload 

2315 def execute( 

2316 self, 

2317 statement: Executable, 

2318 params: Optional[_CoreAnyExecuteParams] = None, 

2319 *, 

2320 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2321 bind_arguments: Optional[_BindArguments] = None, 

2322 _parent_execute_state: Optional[Any] = None, 

2323 _add_event: Optional[Any] = None, 

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

2325 

2326 def execute( 

2327 self, 

2328 statement: Executable, 

2329 params: Optional[_CoreAnyExecuteParams] = None, 

2330 *, 

2331 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2332 bind_arguments: Optional[_BindArguments] = None, 

2333 _parent_execute_state: Optional[Any] = None, 

2334 _add_event: Optional[Any] = None, 

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

2336 r"""Execute a SQL expression construct. 

2337 

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

2339 results of the statement execution. 

2340 

2341 E.g.:: 

2342 

2343 from sqlalchemy import select 

2344 

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

2346 

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

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

2349 of :class:`_engine.Connection`. 

2350 

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

2352 now the primary point of ORM statement execution when using 

2353 :term:`2.0 style` ORM usage. 

2354 

2355 :param statement: 

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

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

2358 

2359 :param params: 

2360 Optional dictionary, or list of dictionaries, containing 

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

2362 execution occurs; if a list of dictionaries, an 

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

2364 must correspond to parameter names present in the statement. 

2365 

2366 :param execution_options: optional dictionary of execution options, 

2367 which will be associated with the statement execution. This 

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

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

2370 provide additional options understood only in an ORM context. 

2371 

2372 The execution_options are passed along to methods like 

2373 :meth:`.Connection.execute` on :class:`.Connection` giving the 

2374 highest priority to execution_options that are passed to this 

2375 method explicitly, then the options that are present on the 

2376 statement object if any, and finally those options present 

2377 session-wide. 

2378 

2379 .. seealso:: 

2380 

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

2382 options 

2383 

2384 :param bind_arguments: dictionary of additional arguments to determine 

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

2386 Contents of this dictionary are passed to the 

2387 :meth:`.Session.get_bind` method. 

2388 

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

2390 

2391 

2392 """ 

2393 return self._execute_internal( 

2394 statement, 

2395 params, 

2396 execution_options=execution_options, 

2397 bind_arguments=bind_arguments, 

2398 _parent_execute_state=_parent_execute_state, 

2399 _add_event=_add_event, 

2400 ) 

2401 

2402 @overload 

2403 def scalar( 

2404 self, 

2405 statement: TypedReturnsRows[_T], 

2406 params: Optional[_CoreSingleExecuteParams] = None, 

2407 *, 

2408 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2409 bind_arguments: Optional[_BindArguments] = None, 

2410 **kw: Any, 

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

2412 

2413 @overload 

2414 def scalar( 

2415 self, 

2416 statement: Executable, 

2417 params: Optional[_CoreSingleExecuteParams] = None, 

2418 *, 

2419 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2420 bind_arguments: Optional[_BindArguments] = None, 

2421 **kw: Any, 

2422 ) -> Any: ... 

2423 

2424 def scalar( 

2425 self, 

2426 statement: Executable, 

2427 params: Optional[_CoreSingleExecuteParams] = None, 

2428 *, 

2429 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2430 bind_arguments: Optional[_BindArguments] = None, 

2431 **kw: Any, 

2432 ) -> Any: 

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

2434 

2435 Usage and parameters are the same as that of 

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

2437 value. 

2438 

2439 """ 

2440 

2441 return self._execute_internal( 

2442 statement, 

2443 params, 

2444 execution_options=execution_options, 

2445 bind_arguments=bind_arguments, 

2446 _scalar_result=True, 

2447 **kw, 

2448 ) 

2449 

2450 @overload 

2451 def scalars( 

2452 self, 

2453 statement: TypedReturnsRows[_T], 

2454 params: Optional[_CoreAnyExecuteParams] = None, 

2455 *, 

2456 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2457 bind_arguments: Optional[_BindArguments] = None, 

2458 **kw: Any, 

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

2460 

2461 @overload 

2462 def scalars( 

2463 self, 

2464 statement: Executable, 

2465 params: Optional[_CoreAnyExecuteParams] = None, 

2466 *, 

2467 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2468 bind_arguments: Optional[_BindArguments] = None, 

2469 **kw: Any, 

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

2471 

2472 def scalars( 

2473 self, 

2474 statement: Executable, 

2475 params: Optional[_CoreAnyExecuteParams] = None, 

2476 *, 

2477 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2478 bind_arguments: Optional[_BindArguments] = None, 

2479 **kw: Any, 

2480 ) -> ScalarResult[Any]: 

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

2482 

2483 Usage and parameters are the same as that of 

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

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

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

2487 

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

2489 

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

2491 

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

2493 

2494 .. seealso:: 

2495 

2496 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2498 

2499 """ 

2500 

2501 return self._execute_internal( 

2502 statement, 

2503 params=params, 

2504 execution_options=execution_options, 

2505 bind_arguments=bind_arguments, 

2506 _scalar_result=False, # mypy appreciates this 

2507 **kw, 

2508 ).scalars() 

2509 

2510 def close(self) -> None: 

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

2512 :class:`_orm.Session`. 

2513 

2514 This expunges all ORM objects associated with this 

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

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

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

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

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

2520 

2521 .. tip:: 

2522 

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

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

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

2526 distinct "closed" state; it merely means 

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

2528 and ORM objects. 

2529 

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

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

2532 any further action on the session will be forbidden. 

2533 

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

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

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

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

2538 

2539 .. seealso:: 

2540 

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

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

2543 

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

2545 ``close()`` with the parameter 

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

2547 

2548 """ 

2549 self._close_impl(invalidate=False) 

2550 

2551 def reset(self) -> None: 

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

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

2554 

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

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

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

2558 brand new, and ready to be used again. 

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

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

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

2562 

2563 .. versionadded:: 2.0.22 

2564 

2565 .. seealso:: 

2566 

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

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

2569 

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

2571 prevent re-use of the Session when the parameter 

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

2573 """ 

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

2575 

2576 def invalidate(self) -> None: 

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

2578 

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

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

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

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

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

2584 multiple engines). 

2585 

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

2587 the connections are no longer safe to be used. 

2588 

2589 Below illustrates a scenario when using `gevent 

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

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

2592 

2593 import gevent 

2594 

2595 try: 

2596 sess = Session() 

2597 sess.add(User()) 

2598 sess.commit() 

2599 except gevent.Timeout: 

2600 sess.invalidate() 

2601 raise 

2602 except: 

2603 sess.rollback() 

2604 raise 

2605 

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

2607 does, including that all ORM objects are expunged. 

2608 

2609 """ 

2610 self._close_impl(invalidate=True) 

2611 

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

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

2614 self._close_state = _SessionCloseState.CLOSED 

2615 self.expunge_all() 

2616 if self._transaction is not None: 

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

2618 transaction.close(invalidate) 

2619 

2620 def expunge_all(self) -> None: 

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

2622 

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

2624 ``Session``. 

2625 

2626 """ 

2627 

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

2629 self.identity_map._kill() 

2630 self.identity_map = identity._WeakInstanceDict() 

2631 self._new = {} 

2632 self._deleted = {} 

2633 

2634 statelib.InstanceState._detach_states(all_states, self) 

2635 

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

2637 try: 

2638 insp = inspect(key) 

2639 except sa_exc.NoInspectionAvailable as err: 

2640 if not isinstance(key, type): 

2641 raise sa_exc.ArgumentError( 

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

2643 ) from err 

2644 else: 

2645 self.__binds[key] = bind 

2646 else: 

2647 if TYPE_CHECKING: 

2648 assert isinstance(insp, Inspectable) 

2649 

2650 if isinstance(insp, TableClause): 

2651 self.__binds[insp] = bind 

2652 elif insp_is_mapper(insp): 

2653 self.__binds[insp.class_] = bind 

2654 for _selectable in insp._all_tables: 

2655 self.__binds[_selectable] = bind 

2656 else: 

2657 raise sa_exc.ArgumentError( 

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

2659 ) 

2660 

2661 def bind_mapper( 

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

2663 ) -> None: 

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

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

2666 :class:`_engine.Connection`. 

2667 

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

2669 :meth:`.Session.get_bind` method. 

2670 

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

2672 or an instance of a mapped 

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

2674 classes. 

2675 

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

2677 object. 

2678 

2679 .. seealso:: 

2680 

2681 :ref:`session_partitioning` 

2682 

2683 :paramref:`.Session.binds` 

2684 

2685 :meth:`.Session.bind_table` 

2686 

2687 

2688 """ 

2689 self._add_bind(mapper, bind) 

2690 

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

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

2693 :class:`_engine.Engine` 

2694 or :class:`_engine.Connection`. 

2695 

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

2697 :meth:`.Session.get_bind` method. 

2698 

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

2700 which is typically the target 

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

2702 mapped. 

2703 

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

2705 object. 

2706 

2707 .. seealso:: 

2708 

2709 :ref:`session_partitioning` 

2710 

2711 :paramref:`.Session.binds` 

2712 

2713 :meth:`.Session.bind_mapper` 

2714 

2715 

2716 """ 

2717 self._add_bind(table, bind) 

2718 

2719 def get_bind( 

2720 self, 

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

2722 *, 

2723 clause: Optional[ClauseElement] = None, 

2724 bind: Optional[_SessionBind] = None, 

2725 _sa_skip_events: Optional[bool] = None, 

2726 _sa_skip_for_implicit_returning: bool = False, 

2727 **kw: Any, 

2728 ) -> Union[Engine, Connection]: 

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

2730 

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

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

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

2734 

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

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

2737 appropriate bind to return. 

2738 

2739 Note that the "mapper" argument is usually present 

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

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

2742 individual INSERT/UPDATE/DELETE operation within a 

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

2744 

2745 The order of resolution is: 

2746 

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

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

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

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

2751 superclasses to more general. 

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

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

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

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

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

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

2758 associated with the clause. 

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

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

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

2762 selectable to which the mapper is mapped. 

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

2764 is raised. 

2765 

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

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

2768 of bind resolution scheme. See the example at 

2769 :ref:`session_custom_partitioning`. 

2770 

2771 :param mapper: 

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

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

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

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

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

2777 mapped for a bind. 

2778 

2779 :param clause: 

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

2781 :func:`_expression.select`, 

2782 :func:`_expression.text`, 

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

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

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

2786 associated with 

2787 bound :class:`_schema.MetaData`. 

2788 

2789 .. seealso:: 

2790 

2791 :ref:`session_partitioning` 

2792 

2793 :paramref:`.Session.binds` 

2794 

2795 :meth:`.Session.bind_mapper` 

2796 

2797 :meth:`.Session.bind_table` 

2798 

2799 """ 

2800 

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

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

2803 if bind: 

2804 return bind 

2805 elif not self.__binds and self.bind: 

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

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

2808 return self.bind 

2809 

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

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

2812 # mapper and the clause 

2813 if mapper is None and clause is None: 

2814 if self.bind: 

2815 return self.bind 

2816 else: 

2817 raise sa_exc.UnboundExecutionError( 

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

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

2820 "a binding." 

2821 ) 

2822 

2823 # look more closely at the mapper. 

2824 if mapper is not None: 

2825 try: 

2826 inspected_mapper = inspect(mapper) 

2827 except sa_exc.NoInspectionAvailable as err: 

2828 if isinstance(mapper, type): 

2829 raise exc.UnmappedClassError(mapper) from err 

2830 else: 

2831 raise 

2832 else: 

2833 inspected_mapper = None 

2834 

2835 # match up the mapper or clause in the __binds 

2836 if self.__binds: 

2837 # matching mappers and selectables to entries in the 

2838 # binds dictionary; supported use case. 

2839 if inspected_mapper: 

2840 for cls in inspected_mapper.class_.__mro__: 

2841 if cls in self.__binds: 

2842 return self.__binds[cls] 

2843 if clause is None: 

2844 clause = inspected_mapper.persist_selectable 

2845 

2846 if clause is not None: 

2847 plugin_subject = clause._propagate_attrs.get( 

2848 "plugin_subject", None 

2849 ) 

2850 

2851 if plugin_subject is not None: 

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

2853 if cls in self.__binds: 

2854 return self.__binds[cls] 

2855 

2856 for obj in visitors.iterate(clause): 

2857 if obj in self.__binds: 

2858 if TYPE_CHECKING: 

2859 assert isinstance(obj, Table) 

2860 return self.__binds[obj] 

2861 

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

2863 # return that 

2864 if self.bind: 

2865 return self.bind 

2866 

2867 context = [] 

2868 if inspected_mapper is not None: 

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

2870 if clause is not None: 

2871 context.append("SQL expression") 

2872 

2873 raise sa_exc.UnboundExecutionError( 

2874 f"Could not locate a bind configured on " 

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

2876 ) 

2877 

2878 @overload 

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

2880 

2881 @overload 

2882 def query( 

2883 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2885 

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

2887 

2888 # code within this block is **programmatically, 

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

2890 

2891 @overload 

2892 def query( 

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

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

2895 

2896 @overload 

2897 def query( 

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

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

2900 

2901 @overload 

2902 def query( 

2903 self, 

2904 __ent0: _TCCA[_T0], 

2905 __ent1: _TCCA[_T1], 

2906 __ent2: _TCCA[_T2], 

2907 __ent3: _TCCA[_T3], 

2908 /, 

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

2910 

2911 @overload 

2912 def query( 

2913 self, 

2914 __ent0: _TCCA[_T0], 

2915 __ent1: _TCCA[_T1], 

2916 __ent2: _TCCA[_T2], 

2917 __ent3: _TCCA[_T3], 

2918 __ent4: _TCCA[_T4], 

2919 /, 

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

2921 

2922 @overload 

2923 def query( 

2924 self, 

2925 __ent0: _TCCA[_T0], 

2926 __ent1: _TCCA[_T1], 

2927 __ent2: _TCCA[_T2], 

2928 __ent3: _TCCA[_T3], 

2929 __ent4: _TCCA[_T4], 

2930 __ent5: _TCCA[_T5], 

2931 /, 

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

2933 

2934 @overload 

2935 def query( 

2936 self, 

2937 __ent0: _TCCA[_T0], 

2938 __ent1: _TCCA[_T1], 

2939 __ent2: _TCCA[_T2], 

2940 __ent3: _TCCA[_T3], 

2941 __ent4: _TCCA[_T4], 

2942 __ent5: _TCCA[_T5], 

2943 __ent6: _TCCA[_T6], 

2944 /, 

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

2946 

2947 @overload 

2948 def query( 

2949 self, 

2950 __ent0: _TCCA[_T0], 

2951 __ent1: _TCCA[_T1], 

2952 __ent2: _TCCA[_T2], 

2953 __ent3: _TCCA[_T3], 

2954 __ent4: _TCCA[_T4], 

2955 __ent5: _TCCA[_T5], 

2956 __ent6: _TCCA[_T6], 

2957 __ent7: _TCCA[_T7], 

2958 /, 

2959 *entities: _ColumnsClauseArgument[Any], 

2960 ) -> RowReturningQuery[ 

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

2962 ]: ... 

2963 

2964 # END OVERLOADED FUNCTIONS self.query 

2965 

2966 @overload 

2967 def query( 

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

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

2970 

2971 def query( 

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

2973 ) -> Query[Any]: 

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

2975 :class:`_orm.Session`. 

2976 

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

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

2979 to construct ORM queries. 

2980 

2981 .. seealso:: 

2982 

2983 :ref:`unified_tutorial` 

2984 

2985 :ref:`queryguide_toplevel` 

2986 

2987 :ref:`query_api_toplevel` - legacy API doc 

2988 

2989 """ 

2990 

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

2992 

2993 def _identity_lookup( 

2994 self, 

2995 mapper: Mapper[_O], 

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

2997 identity_token: Any = None, 

2998 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

3000 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3001 bind_arguments: Optional[_BindArguments] = None, 

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

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

3004 

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

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

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

3008 check if was deleted). 

3009 

3010 e.g.:: 

3011 

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

3013 

3014 :param mapper: mapper in use 

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

3016 a tuple. 

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

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

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

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

3021 :param passive: passive load flag passed to 

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

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

3024 if the flag allows for SQL to be emitted. 

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

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

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

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

3029 relationship-loaded). 

3030 

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

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

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

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

3035 

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

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

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

3039 :class:`_query.Query` object. 

3040 

3041 

3042 """ 

3043 

3044 key = mapper.identity_key_from_primary_key( 

3045 primary_key_identity, identity_token=identity_token 

3046 ) 

3047 

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

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

3050 return return_value 

3051 

3052 @util.non_memoized_property 

3053 @contextlib.contextmanager 

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

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

3056 

3057 e.g.:: 

3058 

3059 with session.no_autoflush: 

3060 

3061 some_object = SomeClass() 

3062 session.add(some_object) 

3063 # won't autoflush 

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

3065 

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

3067 will not be subject to flushes occurring upon query 

3068 access. This is useful when initializing a series 

3069 of objects which involve existing database queries, 

3070 where the uncompleted object should not yet be flushed. 

3071 

3072 """ 

3073 autoflush = self.autoflush 

3074 self.autoflush = False 

3075 try: 

3076 yield self 

3077 finally: 

3078 self.autoflush = autoflush 

3079 

3080 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3084 "warning happened while initializing objects.", 

3085 sa_exc.SAWarning, 

3086 ) 

3087 def _autoflush(self) -> None: 

3088 if self.autoflush and not self._flushing: 

3089 try: 

3090 self.flush() 

3091 except sa_exc.StatementError as e: 

3092 # note we are reraising StatementError as opposed to 

3093 # raising FlushError with "chaining" to remain compatible 

3094 # with code that catches StatementError, IntegrityError, 

3095 # etc. 

3096 e.add_detail( 

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

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

3099 "flush is occurring prematurely" 

3100 ) 

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

3102 

3103 def refresh( 

3104 self, 

3105 instance: object, 

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

3107 with_for_update: ForUpdateParameter = None, 

3108 ) -> None: 

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

3110 

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

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

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

3114 value available in the current transaction. 

3115 

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

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

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

3119 

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

3121 can also refresh eagerly loaded attributes. 

3122 

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

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

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

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

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

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

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

3130 refreshed. 

3131 

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

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

3134 attributes for those which are named explicitly in the 

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

3136 

3137 .. tip:: 

3138 

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

3140 refreshing both column and relationship oriented attributes, its 

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

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

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

3144 once while having explicit control over relationship loader 

3145 strategies, use the 

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

3147 instead. 

3148 

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

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

3151 in database state outside of that transaction. Refreshing 

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

3153 where database rows have not yet been accessed. 

3154 

3155 :param attribute_names: optional. An iterable collection of 

3156 string attribute names indicating a subset of attributes to 

3157 be refreshed. 

3158 

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

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

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

3162 flags should match the parameters of 

3163 :meth:`_query.Query.with_for_update`. 

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

3165 

3166 .. seealso:: 

3167 

3168 :ref:`session_expire` - introductory material 

3169 

3170 :meth:`.Session.expire` 

3171 

3172 :meth:`.Session.expire_all` 

3173 

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

3175 to refresh objects as they would be loaded normally. 

3176 

3177 """ 

3178 try: 

3179 state = attributes.instance_state(instance) 

3180 except exc.NO_STATE as err: 

3181 raise exc.UnmappedInstanceError(instance) from err 

3182 

3183 self._expire_state(state, attribute_names) 

3184 

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

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

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

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

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

3190 # load_on_ident. 

3191 self._autoflush() 

3192 

3193 if with_for_update == {}: 

3194 raise sa_exc.ArgumentError( 

3195 "with_for_update should be the boolean value " 

3196 "True, or a dictionary with options. " 

3197 "A blank dictionary is ambiguous." 

3198 ) 

3199 

3200 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3201 

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

3203 if ( 

3204 loading._load_on_ident( 

3205 self, 

3206 stmt, 

3207 state.key, 

3208 refresh_state=state, 

3209 with_for_update=with_for_update, 

3210 only_load_props=attribute_names, 

3211 require_pk_cols=True, 

3212 # technically unnecessary as we just did autoflush 

3213 # above, however removes the additional unnecessary 

3214 # call to _autoflush() 

3215 no_autoflush=True, 

3216 is_user_refresh=True, 

3217 ) 

3218 is None 

3219 ): 

3220 raise sa_exc.InvalidRequestError( 

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

3222 ) 

3223 

3224 def expire_all(self) -> None: 

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

3226 

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

3228 a query will be issued using the 

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

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

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

3232 previously read in that same transaction, regardless of changes 

3233 in database state outside of that transaction. 

3234 

3235 To expire individual objects and individual attributes 

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

3237 

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

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

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

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

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

3243 assuming the transaction is isolated. 

3244 

3245 .. seealso:: 

3246 

3247 :ref:`session_expire` - introductory material 

3248 

3249 :meth:`.Session.expire` 

3250 

3251 :meth:`.Session.refresh` 

3252 

3253 :meth:`_orm.Query.populate_existing` 

3254 

3255 """ 

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

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

3258 

3259 def expire( 

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

3261 ) -> None: 

3262 """Expire the attributes on an instance. 

3263 

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

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

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

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

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

3269 previously read in that same transaction, regardless of changes 

3270 in database state outside of that transaction. 

3271 

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

3273 use :meth:`Session.expire_all`. 

3274 

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

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

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

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

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

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

3281 transaction. 

3282 

3283 :param instance: The instance to be refreshed. 

3284 :param attribute_names: optional list of string attribute names 

3285 indicating a subset of attributes to be expired. 

3286 

3287 .. seealso:: 

3288 

3289 :ref:`session_expire` - introductory material 

3290 

3291 :meth:`.Session.expire` 

3292 

3293 :meth:`.Session.refresh` 

3294 

3295 :meth:`_orm.Query.populate_existing` 

3296 

3297 """ 

3298 try: 

3299 state = attributes.instance_state(instance) 

3300 except exc.NO_STATE as err: 

3301 raise exc.UnmappedInstanceError(instance) from err 

3302 self._expire_state(state, attribute_names) 

3303 

3304 def _expire_state( 

3305 self, 

3306 state: InstanceState[Any], 

3307 attribute_names: Optional[Iterable[str]], 

3308 ) -> None: 

3309 self._validate_persistent(state) 

3310 if attribute_names: 

3311 state._expire_attributes(state.dict, attribute_names) 

3312 else: 

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

3314 # remove associations 

3315 cascaded = list( 

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

3317 ) 

3318 self._conditional_expire(state) 

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

3320 self._conditional_expire(st_) 

3321 

3322 def _conditional_expire( 

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

3324 ) -> None: 

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

3326 

3327 if state.key: 

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

3329 elif state in self._new: 

3330 self._new.pop(state) 

3331 state._detach(self) 

3332 

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

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

3335 

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

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

3338 

3339 """ 

3340 try: 

3341 state = attributes.instance_state(instance) 

3342 except exc.NO_STATE as err: 

3343 raise exc.UnmappedInstanceError(instance) from err 

3344 if state.session_id is not self.hash_key: 

3345 raise sa_exc.InvalidRequestError( 

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

3347 ) 

3348 

3349 cascaded = list( 

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

3351 ) 

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

3353 

3354 def _expunge_states( 

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

3356 ) -> None: 

3357 for state in states: 

3358 if state in self._new: 

3359 self._new.pop(state) 

3360 elif self.identity_map.contains_state(state): 

3361 self.identity_map.safe_discard(state) 

3362 self._deleted.pop(state, None) 

3363 elif self._transaction: 

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

3365 # in the transaction snapshot 

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

3367 statelib.InstanceState._detach_states( 

3368 states, self, to_transient=to_transient 

3369 ) 

3370 

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

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

3373 

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

3375 state as well as already persistent objects. 

3376 

3377 """ 

3378 

3379 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3380 for state in states: 

3381 mapper = _state_mapper(state) 

3382 

3383 # prevent against last minute dereferences of the object 

3384 obj = state.obj() 

3385 if obj is not None: 

3386 instance_key = mapper._identity_key_from_state(state) 

3387 

3388 if ( 

3389 _none_set.intersection(instance_key[1]) 

3390 and not mapper.allow_partial_pks 

3391 or _none_set.issuperset(instance_key[1]) 

3392 ): 

3393 raise exc.FlushError( 

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

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

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

3397 "that the mapped Column object is configured to " 

3398 "expect these generated values. Ensure also that " 

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

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

3401 % state_str(state) 

3402 ) 

3403 

3404 if state.key is None: 

3405 state.key = instance_key 

3406 elif state.key != instance_key: 

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

3408 # state has already replaced this one in the identity 

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

3410 self.identity_map.safe_discard(state) 

3411 trans = self._transaction 

3412 assert trans is not None 

3413 if state in trans._key_switches: 

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

3415 else: 

3416 orig_key = state.key 

3417 trans._key_switches[state] = ( 

3418 orig_key, 

3419 instance_key, 

3420 ) 

3421 state.key = instance_key 

3422 

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

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

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

3426 old = self.identity_map.replace(state) 

3427 if ( 

3428 old is not None 

3429 and mapper._identity_key_from_state(old) == instance_key 

3430 and old.obj() is not None 

3431 ): 

3432 util.warn( 

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

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

3435 "load operations occurring inside of an event handler " 

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

3437 ) 

3438 state._orphaned_outside_of_session = False 

3439 

3440 statelib.InstanceState._commit_all_states( 

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

3442 ) 

3443 

3444 self._register_altered(states) 

3445 

3446 if pending_to_persistent is not None: 

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

3448 pending_to_persistent(self, state) 

3449 

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

3451 for state in set(states).intersection(self._new): 

3452 self._new.pop(state) 

3453 

3454 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None: 

3455 if self._transaction: 

3456 for state in states: 

3457 if state in self._new: 

3458 self._transaction._new[state] = True 

3459 else: 

3460 self._transaction._dirty[state] = True 

3461 

3462 def _remove_newly_deleted( 

3463 self, states: Iterable[InstanceState[Any]] 

3464 ) -> None: 

3465 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3466 for state in states: 

3467 if self._transaction: 

3468 self._transaction._deleted[state] = True 

3469 

3470 if persistent_to_deleted is not None: 

3471 # get a strong reference before we pop out of 

3472 # self._deleted 

3473 obj = state.obj() # noqa 

3474 

3475 self.identity_map.safe_discard(state) 

3476 self._deleted.pop(state, None) 

3477 state._deleted = True 

3478 # can't call state._detach() here, because this state 

3479 # is still in the transaction snapshot and needs to be 

3480 # tracked as part of that 

3481 if persistent_to_deleted is not None: 

3482 persistent_to_deleted(self, state) 

3483 

3484 def add(self, instance: object, *, _warn: bool = True) -> None: 

3485 """Place an object into this :class:`_orm.Session`. 

3486 

3487 Objects that are in the :term:`transient` state when passed to the 

3488 :meth:`_orm.Session.add` method will move to the 

3489 :term:`pending` state, until the next flush, at which point they 

3490 will move to the :term:`persistent` state. 

3491 

3492 Objects that are in the :term:`detached` state when passed to the 

3493 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3494 state directly. 

3495 

3496 If the transaction used by the :class:`_orm.Session` is rolled back, 

3497 objects which were transient when they were passed to 

3498 :meth:`_orm.Session.add` will be moved back to the 

3499 :term:`transient` state, and will no longer be present within this 

3500 :class:`_orm.Session`. 

3501 

3502 .. seealso:: 

3503 

3504 :meth:`_orm.Session.add_all` 

3505 

3506 :ref:`session_adding` - at :ref:`session_basics` 

3507 

3508 """ 

3509 if _warn and self._warn_on_events: 

3510 self._flush_warning("Session.add()") 

3511 

3512 try: 

3513 state = attributes.instance_state(instance) 

3514 except exc.NO_STATE as err: 

3515 raise exc.UnmappedInstanceError(instance) from err 

3516 

3517 self._save_or_update_state(state) 

3518 

3519 def add_all(self, instances: Iterable[object]) -> None: 

3520 """Add the given collection of instances to this :class:`_orm.Session`. 

3521 

3522 See the documentation for :meth:`_orm.Session.add` for a general 

3523 behavioral description. 

3524 

3525 .. seealso:: 

3526 

3527 :meth:`_orm.Session.add` 

3528 

3529 :ref:`session_adding` - at :ref:`session_basics` 

3530 

3531 """ 

3532 

3533 if self._warn_on_events: 

3534 self._flush_warning("Session.add_all()") 

3535 

3536 for instance in instances: 

3537 self.add(instance, _warn=False) 

3538 

3539 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3540 state._orphaned_outside_of_session = False 

3541 self._save_or_update_impl(state) 

3542 

3543 mapper = _state_mapper(state) 

3544 for o, m, st_, dct_ in mapper.cascade_iterator( 

3545 "save-update", state, halt_on=self._contains_state 

3546 ): 

3547 self._save_or_update_impl(st_) 

3548 

3549 def delete(self, instance: object) -> None: 

3550 """Mark an instance as deleted. 

3551 

3552 The object is assumed to be either :term:`persistent` or 

3553 :term:`detached` when passed; after the method is called, the 

3554 object will remain in the :term:`persistent` state until the next 

3555 flush proceeds. During this time, the object will also be a member 

3556 of the :attr:`_orm.Session.deleted` collection. 

3557 

3558 When the next flush proceeds, the object will move to the 

3559 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3560 for its row within the current transaction. When the transaction 

3561 is successfully committed, 

3562 the deleted object is moved to the :term:`detached` state and is 

3563 no longer present within this :class:`_orm.Session`. 

3564 

3565 .. seealso:: 

3566 

3567 :ref:`session_deleting` - at :ref:`session_basics` 

3568 

3569 :meth:`.Session.delete_all` - multiple instance version 

3570 

3571 """ 

3572 if self._warn_on_events: 

3573 self._flush_warning("Session.delete()") 

3574 

3575 self._delete_impl(object_state(instance), instance, head=True) 

3576 

3577 def delete_all(self, instances: Iterable[object]) -> None: 

3578 """Calls :meth:`.Session.delete` on multiple instances. 

3579 

3580 .. seealso:: 

3581 

3582 :meth:`.Session.delete` - main documentation on delete 

3583 

3584 .. versionadded:: 2.1 

3585 

3586 """ 

3587 

3588 if self._warn_on_events: 

3589 self._flush_warning("Session.delete_all()") 

3590 

3591 for instance in instances: 

3592 self._delete_impl(object_state(instance), instance, head=True) 

3593 

3594 def _delete_impl( 

3595 self, state: InstanceState[Any], obj: object, head: bool 

3596 ) -> None: 

3597 if state.key is None: 

3598 if head: 

3599 raise sa_exc.InvalidRequestError( 

3600 "Instance '%s' is not persisted" % state_str(state) 

3601 ) 

3602 else: 

3603 return 

3604 

3605 to_attach = self._before_attach(state, obj) 

3606 

3607 if state in self._deleted: 

3608 return 

3609 

3610 self.identity_map.add(state) 

3611 

3612 if to_attach: 

3613 self._after_attach(state, obj) 

3614 

3615 if head: 

3616 # grab the cascades before adding the item to the deleted list 

3617 # so that autoflush does not delete the item 

3618 # the strong reference to the instance itself is significant here 

3619 cascade_states = list( 

3620 state.manager.mapper.cascade_iterator("delete", state) 

3621 ) 

3622 else: 

3623 cascade_states = None 

3624 

3625 self._deleted[state] = obj 

3626 

3627 if head: 

3628 if TYPE_CHECKING: 

3629 assert cascade_states is not None 

3630 for o, m, st_, dct_ in cascade_states: 

3631 self._delete_impl(st_, o, False) 

3632 

3633 def get( 

3634 self, 

3635 entity: _EntityBindKey[_O], 

3636 ident: _PKIdentityArgument, 

3637 *, 

3638 options: Optional[Sequence[ORMOption]] = None, 

3639 populate_existing: bool = False, 

3640 with_for_update: ForUpdateParameter = None, 

3641 identity_token: Optional[Any] = None, 

3642 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3643 bind_arguments: Optional[_BindArguments] = None, 

3644 ) -> Optional[_O]: 

3645 """Return an instance based on the given primary key identifier, 

3646 or ``None`` if not found. 

3647 

3648 E.g.:: 

3649 

3650 my_user = session.get(User, 5) 

3651 

3652 some_object = session.get(VersionedFoo, (5, 10)) 

3653 

3654 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3655 

3656 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3657 from the now legacy :meth:`_orm.Query.get` method. 

3658 

3659 :meth:`_orm.Session.get` is special in that it provides direct 

3660 access to the identity map of the :class:`.Session`. 

3661 If the given primary key identifier is present 

3662 in the local identity map, the object is returned 

3663 directly from this collection and no SQL is emitted, 

3664 unless the object has been marked fully expired. 

3665 If not present, 

3666 a SELECT is performed in order to locate the object. 

3667 

3668 :meth:`_orm.Session.get` also will perform a check if 

3669 the object is present in the identity map and 

3670 marked as expired - a SELECT 

3671 is emitted to refresh the object as well as to 

3672 ensure that the row is still present. 

3673 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3674 

3675 :param entity: a mapped class or :class:`.Mapper` indicating the 

3676 type of entity to be loaded. 

3677 

3678 :param ident: A scalar, tuple, or dictionary representing the 

3679 primary key. For a composite (e.g. multiple column) primary key, 

3680 a tuple or dictionary should be passed. 

3681 

3682 For a single-column primary key, the scalar calling form is typically 

3683 the most expedient. If the primary key of a row is the value "5", 

3684 the call looks like:: 

3685 

3686 my_object = session.get(SomeClass, 5) 

3687 

3688 The tuple form contains primary key values typically in 

3689 the order in which they correspond to the mapped 

3690 :class:`_schema.Table` 

3691 object's primary key columns, or if the 

3692 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3693 used, in 

3694 the order used for that parameter. For example, if the primary key 

3695 of a row is represented by the integer 

3696 digits "5, 10" the call would look like:: 

3697 

3698 my_object = session.get(SomeClass, (5, 10)) 

3699 

3700 The dictionary form should include as keys the mapped attribute names 

3701 corresponding to each element of the primary key. If the mapped class 

3702 has the attributes ``id``, ``version_id`` as the attributes which 

3703 store the object's primary key value, the call would look like:: 

3704 

3705 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3706 

3707 :param options: optional sequence of loader options which will be 

3708 applied to the query, if one is emitted. 

3709 

3710 :param populate_existing: causes the method to unconditionally emit 

3711 a SQL query and refresh the object with the newly loaded data, 

3712 regardless of whether or not the object is already present. 

3713 

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

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

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

3717 flags should match the parameters of 

3718 :meth:`_query.Query.with_for_update`. 

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

3720 

3721 :param execution_options: optional dictionary of execution options, 

3722 which will be associated with the query execution if one is emitted. 

3723 This dictionary can provide a subset of the options that are 

3724 accepted by :meth:`_engine.Connection.execution_options`, and may 

3725 also provide additional options understood only in an ORM context. 

3726 

3727 .. versionadded:: 1.4.29 

3728 

3729 .. seealso:: 

3730 

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

3732 options 

3733 

3734 :param bind_arguments: dictionary of additional arguments to determine 

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

3736 Contents of this dictionary are passed to the 

3737 :meth:`.Session.get_bind` method. 

3738 

3739 .. versionadded:: 2.0.0rc1 

3740 

3741 :return: The object instance, or ``None``. 

3742 

3743 """ # noqa: E501 

3744 return self._get_impl( 

3745 entity, 

3746 ident, 

3747 loading._load_on_pk_identity, 

3748 options=options, 

3749 populate_existing=populate_existing, 

3750 with_for_update=with_for_update, 

3751 identity_token=identity_token, 

3752 execution_options=execution_options, 

3753 bind_arguments=bind_arguments, 

3754 ) 

3755 

3756 def get_one( 

3757 self, 

3758 entity: _EntityBindKey[_O], 

3759 ident: _PKIdentityArgument, 

3760 *, 

3761 options: Optional[Sequence[ORMOption]] = None, 

3762 populate_existing: bool = False, 

3763 with_for_update: ForUpdateParameter = None, 

3764 identity_token: Optional[Any] = None, 

3765 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3766 bind_arguments: Optional[_BindArguments] = None, 

3767 ) -> _O: 

3768 """Return exactly one instance based on the given primary key 

3769 identifier, or raise an exception if not found. 

3770 

3771 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3772 

3773 For a detailed documentation of the arguments see the 

3774 method :meth:`.Session.get`. 

3775 

3776 .. versionadded:: 2.0.22 

3777 

3778 :return: The object instance. 

3779 

3780 .. seealso:: 

3781 

3782 :meth:`.Session.get` - equivalent method that instead 

3783 returns ``None`` if no row was found with the provided primary 

3784 key 

3785 

3786 """ 

3787 

3788 instance = self.get( 

3789 entity, 

3790 ident, 

3791 options=options, 

3792 populate_existing=populate_existing, 

3793 with_for_update=with_for_update, 

3794 identity_token=identity_token, 

3795 execution_options=execution_options, 

3796 bind_arguments=bind_arguments, 

3797 ) 

3798 

3799 if instance is None: 

3800 raise sa_exc.NoResultFound( 

3801 "No row was found when one was required" 

3802 ) 

3803 

3804 return instance 

3805 

3806 def _get_impl( 

3807 self, 

3808 entity: _EntityBindKey[_O], 

3809 primary_key_identity: _PKIdentityArgument, 

3810 db_load_fn: Callable[..., _O], 

3811 *, 

3812 options: Optional[Sequence[ExecutableOption]] = None, 

3813 populate_existing: bool = False, 

3814 with_for_update: ForUpdateParameter = None, 

3815 identity_token: Optional[Any] = None, 

3816 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3817 bind_arguments: Optional[_BindArguments] = None, 

3818 ) -> Optional[_O]: 

3819 # convert composite types to individual args 

3820 if ( 

3821 is_composite_class(primary_key_identity) 

3822 and type(primary_key_identity) 

3823 in descriptor_props._composite_getters 

3824 ): 

3825 getter = descriptor_props._composite_getters[ 

3826 type(primary_key_identity) 

3827 ] 

3828 primary_key_identity = getter(primary_key_identity) 

3829 

3830 mapper: Optional[Mapper[_O]] = inspect(entity) 

3831 

3832 if mapper is None or not mapper.is_mapper: 

3833 raise sa_exc.ArgumentError( 

3834 "Expected mapped class or mapper, got: %r" % entity 

3835 ) 

3836 

3837 is_dict = isinstance(primary_key_identity, dict) 

3838 if not is_dict: 

3839 primary_key_identity = util.to_list( 

3840 primary_key_identity, default=[None] 

3841 ) 

3842 

3843 if len(primary_key_identity) != len(mapper.primary_key): 

3844 raise sa_exc.InvalidRequestError( 

3845 "Incorrect number of values in identifier to formulate " 

3846 "primary key for session.get(); primary key columns " 

3847 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3848 ) 

3849 

3850 if is_dict: 

3851 pk_synonyms = mapper._pk_synonyms 

3852 

3853 if pk_synonyms: 

3854 correct_keys = set(pk_synonyms).intersection( 

3855 primary_key_identity 

3856 ) 

3857 

3858 if correct_keys: 

3859 primary_key_identity = dict(primary_key_identity) 

3860 for k in correct_keys: 

3861 primary_key_identity[pk_synonyms[k]] = ( 

3862 primary_key_identity[k] 

3863 ) 

3864 

3865 try: 

3866 primary_key_identity = list( 

3867 primary_key_identity[prop.key] 

3868 for prop in mapper._identity_key_props 

3869 ) 

3870 

3871 except KeyError as err: 

3872 raise sa_exc.InvalidRequestError( 

3873 "Incorrect names of values in identifier to formulate " 

3874 "primary key for session.get(); primary key attribute " 

3875 "names are %s (synonym names are also accepted)" 

3876 % ",".join( 

3877 "'%s'" % prop.key 

3878 for prop in mapper._identity_key_props 

3879 ) 

3880 ) from err 

3881 

3882 if ( 

3883 not populate_existing 

3884 and not mapper.always_refresh 

3885 and with_for_update is None 

3886 ): 

3887 instance = self._identity_lookup( 

3888 mapper, 

3889 primary_key_identity, 

3890 identity_token=identity_token, 

3891 execution_options=execution_options, 

3892 bind_arguments=bind_arguments, 

3893 ) 

3894 

3895 if instance is not None: 

3896 # reject calls for id in identity map but class 

3897 # mismatch. 

3898 if not isinstance(instance, mapper.class_): 

3899 return None 

3900 return instance 

3901 

3902 # TODO: this was being tested before, but this is not possible 

3903 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3904 

3905 load_options = context.QueryContext.default_load_options 

3906 

3907 if populate_existing: 

3908 load_options += {"_populate_existing": populate_existing} 

3909 statement = sql.select(mapper) 

3910 if with_for_update is not None: 

3911 statement._for_update_arg = ForUpdateArg._from_argument( 

3912 with_for_update 

3913 ) 

3914 

3915 if options: 

3916 statement = statement.options(*options) 

3917 if self.execution_options: 

3918 execution_options = self.execution_options.union(execution_options) 

3919 return db_load_fn( 

3920 self, 

3921 statement, 

3922 primary_key_identity, 

3923 load_options=load_options, 

3924 identity_token=identity_token, 

3925 execution_options=execution_options, 

3926 bind_arguments=bind_arguments, 

3927 ) 

3928 

3929 def merge( 

3930 self, 

3931 instance: _O, 

3932 *, 

3933 load: bool = True, 

3934 options: Optional[Sequence[ORMOption]] = None, 

3935 ) -> _O: 

3936 """Copy the state of a given instance into a corresponding instance 

3937 within this :class:`.Session`. 

3938 

3939 :meth:`.Session.merge` examines the primary key attributes of the 

3940 source instance, and attempts to reconcile it with an instance of the 

3941 same primary key in the session. If not found locally, it attempts 

3942 to load the object from the database based on primary key, and if 

3943 none can be located, creates a new instance. The state of each 

3944 attribute on the source instance is then copied to the target 

3945 instance. The resulting target instance is then returned by the 

3946 method; the original source instance is left unmodified, and 

3947 un-associated with the :class:`.Session` if not already. 

3948 

3949 This operation cascades to associated instances if the association is 

3950 mapped with ``cascade="merge"``. 

3951 

3952 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3953 

3954 :param instance: Instance to be merged. 

3955 :param load: Boolean, when False, :meth:`.merge` switches into 

3956 a "high performance" mode which causes it to forego emitting history 

3957 events as well as all database access. This flag is used for 

3958 cases such as transferring graphs of objects into a :class:`.Session` 

3959 from a second level cache, or to transfer just-loaded objects 

3960 into the :class:`.Session` owned by a worker thread or process 

3961 without re-querying the database. 

3962 

3963 The ``load=False`` use case adds the caveat that the given 

3964 object has to be in a "clean" state, that is, has no pending changes 

3965 to be flushed - even if the incoming object is detached from any 

3966 :class:`.Session`. This is so that when 

3967 the merge operation populates local attributes and 

3968 cascades to related objects and 

3969 collections, the values can be "stamped" onto the 

3970 target object as is, without generating any history or attribute 

3971 events, and without the need to reconcile the incoming data with 

3972 any existing related objects or collections that might not 

3973 be loaded. The resulting objects from ``load=False`` are always 

3974 produced as "clean", so it is only appropriate that the given objects 

3975 should be "clean" as well, else this suggests a mis-use of the 

3976 method. 

3977 :param options: optional sequence of loader options which will be 

3978 applied to the :meth:`_orm.Session.get` method when the merge 

3979 operation loads the existing version of the object from the database. 

3980 

3981 .. versionadded:: 1.4.24 

3982 

3983 

3984 .. seealso:: 

3985 

3986 :func:`.make_transient_to_detached` - provides for an alternative 

3987 means of "merging" a single object into the :class:`.Session` 

3988 

3989 :meth:`.Session.merge_all` - multiple instance version 

3990 

3991 """ 

3992 

3993 if self._warn_on_events: 

3994 self._flush_warning("Session.merge()") 

3995 

3996 if load: 

3997 # flush current contents if we expect to load data 

3998 self._autoflush() 

3999 

4000 with self.no_autoflush: 

4001 return self._merge( 

4002 object_state(instance), 

4003 attributes.instance_dict(instance), 

4004 load=load, 

4005 options=options, 

4006 _recursive={}, 

4007 _resolve_conflict_map={}, 

4008 ) 

4009 

4010 def merge_all( 

4011 self, 

4012 instances: Iterable[_O], 

4013 *, 

4014 load: bool = True, 

4015 options: Optional[Sequence[ORMOption]] = None, 

4016 ) -> Sequence[_O]: 

4017 """Calls :meth:`.Session.merge` on multiple instances. 

4018 

4019 .. seealso:: 

4020 

4021 :meth:`.Session.merge` - main documentation on merge 

4022 

4023 .. versionadded:: 2.1 

4024 

4025 """ 

4026 

4027 if self._warn_on_events: 

4028 self._flush_warning("Session.merge_all()") 

4029 

4030 if load: 

4031 # flush current contents if we expect to load data 

4032 self._autoflush() 

4033 

4034 return [ 

4035 self._merge( 

4036 object_state(instance), 

4037 attributes.instance_dict(instance), 

4038 load=load, 

4039 options=options, 

4040 _recursive={}, 

4041 _resolve_conflict_map={}, 

4042 ) 

4043 for instance in instances 

4044 ] 

4045 

4046 def _merge( 

4047 self, 

4048 state: InstanceState[_O], 

4049 state_dict: _InstanceDict, 

4050 *, 

4051 options: Optional[Sequence[ORMOption]] = None, 

4052 load: bool, 

4053 _recursive: Dict[Any, object], 

4054 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4055 ) -> _O: 

4056 mapper: Mapper[_O] = _state_mapper(state) 

4057 if state in _recursive: 

4058 return cast(_O, _recursive[state]) 

4059 

4060 new_instance = False 

4061 key = state.key 

4062 

4063 merged: Optional[_O] 

4064 

4065 if key is None: 

4066 if state in self._new: 

4067 util.warn( 

4068 "Instance %s is already pending in this Session yet is " 

4069 "being merged again; this is probably not what you want " 

4070 "to do" % state_str(state) 

4071 ) 

4072 

4073 if not load: 

4074 raise sa_exc.InvalidRequestError( 

4075 "merge() with load=False option does not support " 

4076 "objects transient (i.e. unpersisted) objects. flush() " 

4077 "all changes on mapped instances before merging with " 

4078 "load=False." 

4079 ) 

4080 key = mapper._identity_key_from_state(state) 

4081 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4082 1 

4083 ] and ( 

4084 not _none_set.intersection(key[1]) 

4085 or ( 

4086 mapper.allow_partial_pks 

4087 and not _none_set.issuperset(key[1]) 

4088 ) 

4089 ) 

4090 else: 

4091 key_is_persistent = True 

4092 

4093 merged = self.identity_map.get(key) 

4094 

4095 if merged is None: 

4096 if key_is_persistent and key in _resolve_conflict_map: 

4097 merged = cast(_O, _resolve_conflict_map[key]) 

4098 

4099 elif not load: 

4100 if state.modified: 

4101 raise sa_exc.InvalidRequestError( 

4102 "merge() with load=False option does not support " 

4103 "objects marked as 'dirty'. flush() all changes on " 

4104 "mapped instances before merging with load=False." 

4105 ) 

4106 merged = mapper.class_manager.new_instance() 

4107 merged_state = attributes.instance_state(merged) 

4108 merged_state.key = key 

4109 self._update_impl(merged_state) 

4110 new_instance = True 

4111 

4112 elif key_is_persistent: 

4113 merged = self.get( 

4114 mapper.class_, 

4115 key[1], 

4116 identity_token=key[2], 

4117 options=options, 

4118 ) 

4119 

4120 if merged is None: 

4121 merged = mapper.class_manager.new_instance() 

4122 merged_state = attributes.instance_state(merged) 

4123 merged_dict = attributes.instance_dict(merged) 

4124 new_instance = True 

4125 self._save_or_update_state(merged_state) 

4126 else: 

4127 merged_state = attributes.instance_state(merged) 

4128 merged_dict = attributes.instance_dict(merged) 

4129 

4130 _recursive[state] = merged 

4131 _resolve_conflict_map[key] = merged 

4132 

4133 # check that we didn't just pull the exact same 

4134 # state out. 

4135 if state is not merged_state: 

4136 # version check if applicable 

4137 if mapper.version_id_col is not None: 

4138 existing_version = mapper._get_state_attr_by_column( 

4139 state, 

4140 state_dict, 

4141 mapper.version_id_col, 

4142 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4143 ) 

4144 

4145 merged_version = mapper._get_state_attr_by_column( 

4146 merged_state, 

4147 merged_dict, 

4148 mapper.version_id_col, 

4149 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4150 ) 

4151 

4152 if ( 

4153 existing_version 

4154 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4155 and merged_version 

4156 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4157 and existing_version != merged_version 

4158 ): 

4159 raise exc.StaleDataError( 

4160 "Version id '%s' on merged state %s " 

4161 "does not match existing version '%s'. " 

4162 "Leave the version attribute unset when " 

4163 "merging to update the most recent version." 

4164 % ( 

4165 existing_version, 

4166 state_str(merged_state), 

4167 merged_version, 

4168 ) 

4169 ) 

4170 

4171 merged_state.load_path = state.load_path 

4172 merged_state.load_options = state.load_options 

4173 

4174 # since we are copying load_options, we need to copy 

4175 # the callables_ that would have been generated by those 

4176 # load_options. 

4177 # assumes that the callables we put in state.callables_ 

4178 # are not instance-specific (which they should not be) 

4179 merged_state._copy_callables(state) 

4180 

4181 for prop in mapper.iterate_properties: 

4182 prop.merge( 

4183 self, 

4184 state, 

4185 state_dict, 

4186 merged_state, 

4187 merged_dict, 

4188 load, 

4189 _recursive, 

4190 _resolve_conflict_map, 

4191 ) 

4192 

4193 if not load: 

4194 # remove any history 

4195 merged_state._commit_all(merged_dict, self.identity_map) 

4196 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4197 merged_state, None 

4198 ) 

4199 

4200 if new_instance: 

4201 merged_state.manager.dispatch.load(merged_state, None) 

4202 

4203 return merged 

4204 

4205 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4206 if not self.identity_map.contains_state(state): 

4207 raise sa_exc.InvalidRequestError( 

4208 "Instance '%s' is not persistent within this Session" 

4209 % state_str(state) 

4210 ) 

4211 

4212 def _save_impl(self, state: InstanceState[Any]) -> None: 

4213 if state.key is not None: 

4214 raise sa_exc.InvalidRequestError( 

4215 "Object '%s' already has an identity - " 

4216 "it can't be registered as pending" % state_str(state) 

4217 ) 

4218 

4219 obj = state.obj() 

4220 to_attach = self._before_attach(state, obj) 

4221 if state not in self._new: 

4222 self._new[state] = obj 

4223 state.insert_order = len(self._new) 

4224 if to_attach: 

4225 self._after_attach(state, obj) 

4226 

4227 def _update_impl( 

4228 self, state: InstanceState[Any], revert_deletion: bool = False 

4229 ) -> None: 

4230 if state.key is None: 

4231 raise sa_exc.InvalidRequestError( 

4232 "Instance '%s' is not persisted" % state_str(state) 

4233 ) 

4234 

4235 if state._deleted: 

4236 if revert_deletion: 

4237 if not state._attached: 

4238 return 

4239 del state._deleted 

4240 else: 

4241 raise sa_exc.InvalidRequestError( 

4242 "Instance '%s' has been deleted. " 

4243 "Use the make_transient() " 

4244 "function to send this object back " 

4245 "to the transient state." % state_str(state) 

4246 ) 

4247 

4248 obj = state.obj() 

4249 

4250 # check for late gc 

4251 if obj is None: 

4252 return 

4253 

4254 to_attach = self._before_attach(state, obj) 

4255 

4256 self._deleted.pop(state, None) 

4257 if revert_deletion: 

4258 self.identity_map.replace(state) 

4259 else: 

4260 self.identity_map.add(state) 

4261 

4262 if to_attach: 

4263 self._after_attach(state, obj) 

4264 elif revert_deletion: 

4265 self.dispatch.deleted_to_persistent(self, state) 

4266 

4267 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4268 if state.key is None: 

4269 self._save_impl(state) 

4270 else: 

4271 self._update_impl(state) 

4272 

4273 def enable_relationship_loading(self, obj: object) -> None: 

4274 """Associate an object with this :class:`.Session` for related 

4275 object loading. 

4276 

4277 .. warning:: 

4278 

4279 :meth:`.enable_relationship_loading` exists to serve special 

4280 use cases and is not recommended for general use. 

4281 

4282 Accesses of attributes mapped with :func:`_orm.relationship` 

4283 will attempt to load a value from the database using this 

4284 :class:`.Session` as the source of connectivity. The values 

4285 will be loaded based on foreign key and primary key values 

4286 present on this object - if not present, then those relationships 

4287 will be unavailable. 

4288 

4289 The object will be attached to this session, but will 

4290 **not** participate in any persistence operations; its state 

4291 for almost all purposes will remain either "transient" or 

4292 "detached", except for the case of relationship loading. 

4293 

4294 Also note that backrefs will often not work as expected. 

4295 Altering a relationship-bound attribute on the target object 

4296 may not fire off a backref event, if the effective value 

4297 is what was already loaded from a foreign-key-holding value. 

4298 

4299 The :meth:`.Session.enable_relationship_loading` method is 

4300 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4301 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4302 an object to remain transient while still being able to load 

4303 related items. 

4304 

4305 To make a transient object associated with a :class:`.Session` 

4306 via :meth:`.Session.enable_relationship_loading` pending, add 

4307 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4308 If the object instead represents an existing identity in the database, 

4309 it should be merged using :meth:`.Session.merge`. 

4310 

4311 :meth:`.Session.enable_relationship_loading` does not improve 

4312 behavior when the ORM is used normally - object references should be 

4313 constructed at the object level, not at the foreign key level, so 

4314 that they are present in an ordinary way before flush() 

4315 proceeds. This method is not intended for general use. 

4316 

4317 .. seealso:: 

4318 

4319 :paramref:`_orm.relationship.load_on_pending` - this flag 

4320 allows per-relationship loading of many-to-ones on items that 

4321 are pending. 

4322 

4323 :func:`.make_transient_to_detached` - allows for an object to 

4324 be added to a :class:`.Session` without SQL emitted, which then 

4325 will unexpire attributes on access. 

4326 

4327 """ 

4328 try: 

4329 state = attributes.instance_state(obj) 

4330 except exc.NO_STATE as err: 

4331 raise exc.UnmappedInstanceError(obj) from err 

4332 

4333 to_attach = self._before_attach(state, obj) 

4334 state._load_pending = True 

4335 if to_attach: 

4336 self._after_attach(state, obj) 

4337 

4338 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4339 self._autobegin_t() 

4340 

4341 if state.session_id == self.hash_key: 

4342 return False 

4343 

4344 if state.session_id and state.session_id in _sessions: 

4345 raise sa_exc.InvalidRequestError( 

4346 "Object '%s' is already attached to session '%s' " 

4347 "(this is '%s')" 

4348 % (state_str(state), state.session_id, self.hash_key) 

4349 ) 

4350 

4351 self.dispatch.before_attach(self, state) 

4352 

4353 return True 

4354 

4355 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4356 state.session_id = self.hash_key 

4357 if state.modified and state._strong_obj is None: 

4358 state._strong_obj = obj 

4359 self.dispatch.after_attach(self, state) 

4360 

4361 if state.key: 

4362 self.dispatch.detached_to_persistent(self, state) 

4363 else: 

4364 self.dispatch.transient_to_pending(self, state) 

4365 

4366 def __contains__(self, instance: object) -> bool: 

4367 """Return True if the instance is associated with this session. 

4368 

4369 The instance may be pending or persistent within the Session for a 

4370 result of True. 

4371 

4372 """ 

4373 try: 

4374 state = attributes.instance_state(instance) 

4375 except exc.NO_STATE as err: 

4376 raise exc.UnmappedInstanceError(instance) from err 

4377 return self._contains_state(state) 

4378 

4379 def __iter__(self) -> Iterator[object]: 

4380 """Iterate over all pending or persistent instances within this 

4381 Session. 

4382 

4383 """ 

4384 return iter( 

4385 list(self._new.values()) + list(self.identity_map.values()) 

4386 ) 

4387 

4388 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4389 return state in self._new or self.identity_map.contains_state(state) 

4390 

4391 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4392 """Flush all the object changes to the database. 

4393 

4394 Writes out all pending object creations, deletions and modifications 

4395 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4396 automatically ordered by the Session's unit of work dependency 

4397 solver. 

4398 

4399 Database operations will be issued in the current transactional 

4400 context and do not affect the state of the transaction, unless an 

4401 error occurs, in which case the entire transaction is rolled back. 

4402 You may flush() as often as you like within a transaction to move 

4403 changes from Python to the database's transaction buffer. 

4404 

4405 :param objects: Optional; restricts the flush operation to operate 

4406 only on elements that are in the given collection. 

4407 

4408 This feature is for an extremely narrow set of use cases where 

4409 particular objects may need to be operated upon before the 

4410 full flush() occurs. It is not intended for general use. 

4411 

4412 .. deprecated:: 2.1 

4413 

4414 """ 

4415 

4416 if self._flushing: 

4417 raise sa_exc.InvalidRequestError("Session is already flushing") 

4418 

4419 if self._is_clean(): 

4420 return 

4421 try: 

4422 self._flushing = True 

4423 self._flush(objects) 

4424 finally: 

4425 self._flushing = False 

4426 

4427 def _flush_warning(self, method: Any) -> None: 

4428 util.warn( 

4429 "Usage of the '%s' operation is not currently supported " 

4430 "within the execution stage of the flush process. " 

4431 "Results may not be consistent. Consider using alternative " 

4432 "event listeners or connection-level operations instead." % method 

4433 ) 

4434 

4435 def _is_clean(self) -> bool: 

4436 return ( 

4437 not self.identity_map.check_modified() 

4438 and not self._deleted 

4439 and not self._new 

4440 ) 

4441 

4442 # have this here since it otherwise causes issues with the proxy 

4443 # method generation 

4444 @deprecated_params( 

4445 objects=( 

4446 "2.1", 

4447 "The `objects` parameter of `Session.flush` is deprecated", 

4448 ) 

4449 ) 

4450 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4451 dirty = self._dirty_states 

4452 if not dirty and not self._deleted and not self._new: 

4453 self.identity_map._modified.clear() 

4454 return 

4455 

4456 flush_context = UOWTransaction(self) 

4457 

4458 if self.dispatch.before_flush: 

4459 self.dispatch.before_flush(self, flush_context, objects) 

4460 # re-establish "dirty states" in case the listeners 

4461 # added 

4462 dirty = self._dirty_states 

4463 

4464 deleted = set(self._deleted) 

4465 new = set(self._new) 

4466 

4467 dirty = set(dirty).difference(deleted) 

4468 

4469 # create the set of all objects we want to operate upon 

4470 if objects: 

4471 # specific list passed in 

4472 objset = set() 

4473 for o in objects: 

4474 try: 

4475 state = attributes.instance_state(o) 

4476 

4477 except exc.NO_STATE as err: 

4478 raise exc.UnmappedInstanceError(o) from err 

4479 objset.add(state) 

4480 else: 

4481 objset = None 

4482 

4483 # store objects whose fate has been decided 

4484 processed = set() 

4485 

4486 # put all saves/updates into the flush context. detect top-level 

4487 # orphans and throw them into deleted. 

4488 if objset: 

4489 proc = new.union(dirty).intersection(objset).difference(deleted) 

4490 else: 

4491 proc = new.union(dirty).difference(deleted) 

4492 

4493 for state in proc: 

4494 is_orphan = _state_mapper(state)._is_orphan(state) 

4495 

4496 is_persistent_orphan = is_orphan and state.has_identity 

4497 

4498 if ( 

4499 is_orphan 

4500 and not is_persistent_orphan 

4501 and state._orphaned_outside_of_session 

4502 ): 

4503 self._expunge_states([state]) 

4504 else: 

4505 _reg = flush_context.register_object( 

4506 state, isdelete=is_persistent_orphan 

4507 ) 

4508 assert _reg, "Failed to add object to the flush context!" 

4509 processed.add(state) 

4510 

4511 # put all remaining deletes into the flush context. 

4512 if objset: 

4513 proc = deleted.intersection(objset).difference(processed) 

4514 else: 

4515 proc = deleted.difference(processed) 

4516 for state in proc: 

4517 _reg = flush_context.register_object(state, isdelete=True) 

4518 assert _reg, "Failed to add object to the flush context!" 

4519 

4520 if not flush_context.has_work: 

4521 return 

4522 

4523 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4524 try: 

4525 self._warn_on_events = True 

4526 try: 

4527 flush_context.execute() 

4528 finally: 

4529 self._warn_on_events = False 

4530 

4531 self.dispatch.after_flush(self, flush_context) 

4532 

4533 flush_context.finalize_flush_changes() 

4534 

4535 if not objects and self.identity_map._modified: 

4536 len_ = len(self.identity_map._modified) 

4537 

4538 statelib.InstanceState._commit_all_states( 

4539 [ 

4540 (state, state.dict) 

4541 for state in self.identity_map._modified 

4542 ], 

4543 instance_dict=self.identity_map, 

4544 ) 

4545 util.warn( 

4546 "Attribute history events accumulated on %d " 

4547 "previously clean instances " 

4548 "within inner-flush event handlers have been " 

4549 "reset, and will not result in database updates. " 

4550 "Consider using set_committed_value() within " 

4551 "inner-flush event handlers to avoid this warning." % len_ 

4552 ) 

4553 

4554 # useful assertions: 

4555 # if not objects: 

4556 # assert not self.identity_map._modified 

4557 # else: 

4558 # assert self.identity_map._modified == \ 

4559 # self.identity_map._modified.difference(objects) 

4560 

4561 self.dispatch.after_flush_postexec(self, flush_context) 

4562 

4563 transaction.commit() 

4564 

4565 except: 

4566 with util.safe_reraise(): 

4567 transaction.rollback(_capture_exception=True) 

4568 

4569 def bulk_save_objects( 

4570 self, 

4571 objects: Iterable[object], 

4572 return_defaults: bool = False, 

4573 update_changed_only: bool = True, 

4574 preserve_order: bool = True, 

4575 ) -> None: 

4576 """Perform a bulk save of the given list of objects. 

4577 

4578 .. legacy:: 

4579 

4580 This method is a legacy feature as of the 2.0 series of 

4581 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4582 the sections :ref:`orm_queryguide_bulk_insert` and 

4583 :ref:`orm_queryguide_bulk_update`. 

4584 

4585 For general INSERT and UPDATE of existing ORM mapped objects, 

4586 prefer standard :term:`unit of work` data management patterns, 

4587 introduced in the :ref:`unified_tutorial` at 

4588 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4589 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4590 which solves previous issues of bulk INSERT slowness. 

4591 

4592 :param objects: a sequence of mapped object instances. The mapped 

4593 objects are persisted as is, and are **not** associated with the 

4594 :class:`.Session` afterwards. 

4595 

4596 For each object, whether the object is sent as an INSERT or an 

4597 UPDATE is dependent on the same rules used by the :class:`.Session` 

4598 in traditional operation; if the object has the 

4599 :attr:`.InstanceState.key` 

4600 attribute set, then the object is assumed to be "detached" and 

4601 will result in an UPDATE. Otherwise, an INSERT is used. 

4602 

4603 In the case of an UPDATE, statements are grouped based on which 

4604 attributes have changed, and are thus to be the subject of each 

4605 SET clause. If ``update_changed_only`` is False, then all 

4606 attributes present within each object are applied to the UPDATE 

4607 statement, which may help in allowing the statements to be grouped 

4608 together into a larger executemany(), and will also reduce the 

4609 overhead of checking history on attributes. 

4610 

4611 :param return_defaults: when True, rows that are missing values which 

4612 generate defaults, namely integer primary key defaults and sequences, 

4613 will be inserted **one at a time**, so that the primary key value 

4614 is available. In particular this will allow joined-inheritance 

4615 and other multi-table mappings to insert correctly without the need 

4616 to provide primary key values ahead of time; however, 

4617 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4618 reduces the performance gains** of the method overall. It is strongly 

4619 advised to please use the standard :meth:`_orm.Session.add_all` 

4620 approach. 

4621 

4622 :param update_changed_only: when True, UPDATE statements are rendered 

4623 based on those attributes in each state that have logged changes. 

4624 When False, all attributes present are rendered into the SET clause 

4625 with the exception of primary key attributes. 

4626 

4627 :param preserve_order: when True, the order of inserts and updates 

4628 matches exactly the order in which the objects are given. When 

4629 False, common types of objects are grouped into inserts 

4630 and updates, to allow for more batching opportunities. 

4631 

4632 .. seealso:: 

4633 

4634 :doc:`queryguide/dml` 

4635 

4636 :meth:`.Session.bulk_insert_mappings` 

4637 

4638 :meth:`.Session.bulk_update_mappings` 

4639 

4640 """ 

4641 

4642 obj_states: Iterable[InstanceState[Any]] 

4643 

4644 obj_states = (attributes.instance_state(obj) for obj in objects) 

4645 

4646 if not preserve_order: 

4647 # the purpose of this sort is just so that common mappers 

4648 # and persistence states are grouped together, so that groupby 

4649 # will return a single group for a particular type of mapper. 

4650 # it's not trying to be deterministic beyond that. 

4651 obj_states = sorted( 

4652 obj_states, 

4653 key=lambda state: (id(state.mapper), state.key is not None), 

4654 ) 

4655 

4656 def grouping_key( 

4657 state: InstanceState[_O], 

4658 ) -> Tuple[Mapper[_O], bool]: 

4659 return (state.mapper, state.key is not None) 

4660 

4661 for (mapper, isupdate), states in itertools.groupby( 

4662 obj_states, grouping_key 

4663 ): 

4664 self._bulk_save_mappings( 

4665 mapper, 

4666 states, 

4667 isupdate=isupdate, 

4668 isstates=True, 

4669 return_defaults=return_defaults, 

4670 update_changed_only=update_changed_only, 

4671 render_nulls=False, 

4672 ) 

4673 

4674 def bulk_insert_mappings( 

4675 self, 

4676 mapper: Mapper[Any], 

4677 mappings: Iterable[Dict[str, Any]], 

4678 return_defaults: bool = False, 

4679 render_nulls: bool = False, 

4680 ) -> None: 

4681 """Perform a bulk insert of the given list of mapping dictionaries. 

4682 

4683 .. legacy:: 

4684 

4685 This method is a legacy feature as of the 2.0 series of 

4686 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4687 the sections :ref:`orm_queryguide_bulk_insert` and 

4688 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4689 implementation details with this method and adds new features 

4690 as well. 

4691 

4692 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4693 object, 

4694 representing the single kind of object represented within the mapping 

4695 list. 

4696 

4697 :param mappings: a sequence of dictionaries, each one containing the 

4698 state of the mapped row to be inserted, in terms of the attribute 

4699 names on the mapped class. If the mapping refers to multiple tables, 

4700 such as a joined-inheritance mapping, each dictionary must contain all 

4701 keys to be populated into all tables. 

4702 

4703 :param return_defaults: when True, the INSERT process will be altered 

4704 to ensure that newly generated primary key values will be fetched. 

4705 The rationale for this parameter is typically to enable 

4706 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4707 be bulk inserted. 

4708 

4709 .. note:: for backends that don't support RETURNING, the 

4710 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4711 parameter can significantly decrease performance as INSERT 

4712 statements can no longer be batched. See 

4713 :ref:`engine_insertmanyvalues` 

4714 for background on which backends are affected. 

4715 

4716 :param render_nulls: When True, a value of ``None`` will result 

4717 in a NULL value being included in the INSERT statement, rather 

4718 than the column being omitted from the INSERT. This allows all 

4719 the rows being INSERTed to have the identical set of columns which 

4720 allows the full set of rows to be batched to the DBAPI. Normally, 

4721 each column-set that contains a different combination of NULL values 

4722 than the previous row must omit a different series of columns from 

4723 the rendered INSERT statement, which means it must be emitted as a 

4724 separate statement. By passing this flag, the full set of rows 

4725 are guaranteed to be batchable into one batch; the cost however is 

4726 that server-side defaults which are invoked by an omitted column will 

4727 be skipped, so care must be taken to ensure that these are not 

4728 necessary. 

4729 

4730 .. warning:: 

4731 

4732 When this flag is set, **server side default SQL values will 

4733 not be invoked** for those columns that are inserted as NULL; 

4734 the NULL value will be sent explicitly. Care must be taken 

4735 to ensure that no server-side default functions need to be 

4736 invoked for the operation as a whole. 

4737 

4738 .. seealso:: 

4739 

4740 :doc:`queryguide/dml` 

4741 

4742 :meth:`.Session.bulk_save_objects` 

4743 

4744 :meth:`.Session.bulk_update_mappings` 

4745 

4746 """ 

4747 self._bulk_save_mappings( 

4748 mapper, 

4749 mappings, 

4750 isupdate=False, 

4751 isstates=False, 

4752 return_defaults=return_defaults, 

4753 update_changed_only=False, 

4754 render_nulls=render_nulls, 

4755 ) 

4756 

4757 def bulk_update_mappings( 

4758 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4759 ) -> None: 

4760 """Perform a bulk update of the given list of mapping dictionaries. 

4761 

4762 .. legacy:: 

4763 

4764 This method is a legacy feature as of the 2.0 series of 

4765 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4766 the sections :ref:`orm_queryguide_bulk_insert` and 

4767 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4768 implementation details with this method and adds new features 

4769 as well. 

4770 

4771 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4772 object, 

4773 representing the single kind of object represented within the mapping 

4774 list. 

4775 

4776 :param mappings: a sequence of dictionaries, each one containing the 

4777 state of the mapped row to be updated, in terms of the attribute names 

4778 on the mapped class. If the mapping refers to multiple tables, such 

4779 as a joined-inheritance mapping, each dictionary may contain keys 

4780 corresponding to all tables. All those keys which are present and 

4781 are not part of the primary key are applied to the SET clause of the 

4782 UPDATE statement; the primary key values, which are required, are 

4783 applied to the WHERE clause. 

4784 

4785 

4786 .. seealso:: 

4787 

4788 :doc:`queryguide/dml` 

4789 

4790 :meth:`.Session.bulk_insert_mappings` 

4791 

4792 :meth:`.Session.bulk_save_objects` 

4793 

4794 """ 

4795 self._bulk_save_mappings( 

4796 mapper, 

4797 mappings, 

4798 isupdate=True, 

4799 isstates=False, 

4800 return_defaults=False, 

4801 update_changed_only=False, 

4802 render_nulls=False, 

4803 ) 

4804 

4805 def _bulk_save_mappings( 

4806 self, 

4807 mapper: Mapper[_O], 

4808 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4809 *, 

4810 isupdate: bool, 

4811 isstates: bool, 

4812 return_defaults: bool, 

4813 update_changed_only: bool, 

4814 render_nulls: bool, 

4815 ) -> None: 

4816 mapper = _class_to_mapper(mapper) 

4817 self._flushing = True 

4818 

4819 transaction = self._autobegin_t()._begin() 

4820 try: 

4821 if isupdate: 

4822 bulk_persistence._bulk_update( 

4823 mapper, 

4824 mappings, 

4825 transaction, 

4826 isstates=isstates, 

4827 update_changed_only=update_changed_only, 

4828 ) 

4829 else: 

4830 bulk_persistence._bulk_insert( 

4831 mapper, 

4832 mappings, 

4833 transaction, 

4834 isstates=isstates, 

4835 return_defaults=return_defaults, 

4836 render_nulls=render_nulls, 

4837 ) 

4838 transaction.commit() 

4839 

4840 except: 

4841 with util.safe_reraise(): 

4842 transaction.rollback(_capture_exception=True) 

4843 finally: 

4844 self._flushing = False 

4845 

4846 def is_modified( 

4847 self, instance: object, include_collections: bool = True 

4848 ) -> bool: 

4849 r"""Return ``True`` if the given instance has locally 

4850 modified attributes. 

4851 

4852 This method retrieves the history for each instrumented 

4853 attribute on the instance and performs a comparison of the current 

4854 value to its previously flushed or committed value, if any. 

4855 

4856 It is in effect a more expensive and accurate 

4857 version of checking for the given instance in the 

4858 :attr:`.Session.dirty` collection; a full test for 

4859 each attribute's net "dirty" status is performed. 

4860 

4861 E.g.:: 

4862 

4863 return session.is_modified(someobject) 

4864 

4865 A few caveats to this method apply: 

4866 

4867 * Instances present in the :attr:`.Session.dirty` collection may 

4868 report ``False`` when tested with this method. This is because 

4869 the object may have received change events via attribute mutation, 

4870 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4871 is the same as that loaded from the database, resulting in no net 

4872 change here. 

4873 * Scalar attributes may not have recorded the previously set 

4874 value when a new value was applied, if the attribute was not loaded, 

4875 or was expired, at the time the new value was received - in these 

4876 cases, the attribute is assumed to have a change, even if there is 

4877 ultimately no net change against its database value. SQLAlchemy in 

4878 most cases does not need the "old" value when a set event occurs, so 

4879 it skips the expense of a SQL call if the old value isn't present, 

4880 based on the assumption that an UPDATE of the scalar value is 

4881 usually needed, and in those few cases where it isn't, is less 

4882 expensive on average than issuing a defensive SELECT. 

4883 

4884 The "old" value is fetched unconditionally upon set only if the 

4885 attribute container has the ``active_history`` flag set to ``True``. 

4886 This flag is set typically for primary key attributes and scalar 

4887 object references that are not a simple many-to-one. To set this 

4888 flag for any arbitrary mapped column, use the ``active_history`` 

4889 argument with :func:`.column_property`. 

4890 

4891 :param instance: mapped instance to be tested for pending changes. 

4892 :param include_collections: Indicates if multivalued collections 

4893 should be included in the operation. Setting this to ``False`` is a 

4894 way to detect only local-column based properties (i.e. scalar columns 

4895 or many-to-one foreign keys) that would result in an UPDATE for this 

4896 instance upon flush. 

4897 

4898 """ 

4899 state = object_state(instance) 

4900 

4901 if not state.modified: 

4902 return False 

4903 

4904 dict_ = state.dict 

4905 

4906 for attr in state.manager.attributes: 

4907 if ( 

4908 not include_collections 

4909 and hasattr(attr.impl, "get_collection") 

4910 ) or not hasattr(attr.impl, "get_history"): 

4911 continue 

4912 

4913 (added, unchanged, deleted) = attr.impl.get_history( 

4914 state, dict_, passive=PassiveFlag.NO_CHANGE 

4915 ) 

4916 

4917 if added or deleted: 

4918 return True 

4919 else: 

4920 return False 

4921 

4922 @property 

4923 def is_active(self) -> bool: 

4924 """True if this :class:`.Session` not in "partial rollback" state. 

4925 

4926 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4927 a new transaction immediately, so this attribute will be False 

4928 when the :class:`_orm.Session` is first instantiated. 

4929 

4930 "partial rollback" state typically indicates that the flush process 

4931 of the :class:`_orm.Session` has failed, and that the 

4932 :meth:`_orm.Session.rollback` method must be emitted in order to 

4933 fully roll back the transaction. 

4934 

4935 If this :class:`_orm.Session` is not in a transaction at all, the 

4936 :class:`_orm.Session` will autobegin when it is first used, so in this 

4937 case :attr:`_orm.Session.is_active` will return True. 

4938 

4939 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4940 and that transaction has not been rolled back internally, the 

4941 :attr:`_orm.Session.is_active` will also return True. 

4942 

4943 .. seealso:: 

4944 

4945 :ref:`faq_session_rollback` 

4946 

4947 :meth:`_orm.Session.in_transaction` 

4948 

4949 """ 

4950 return self._transaction is None or self._transaction.is_active 

4951 

4952 @property 

4953 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4954 """The set of all persistent states considered dirty. 

4955 

4956 This method returns all states that were modified including 

4957 those that were possibly deleted. 

4958 

4959 """ 

4960 return self.identity_map._dirty_states() 

4961 

4962 @property 

4963 def dirty(self) -> IdentitySet: 

4964 """The set of all persistent instances considered dirty. 

4965 

4966 E.g.:: 

4967 

4968 some_mapped_object in session.dirty 

4969 

4970 Instances are considered dirty when they were modified but not 

4971 deleted. 

4972 

4973 Note that this 'dirty' calculation is 'optimistic'; most 

4974 attribute-setting or collection modification operations will 

4975 mark an instance as 'dirty' and place it in this set, even if 

4976 there is no net change to the attribute's value. At flush 

4977 time, the value of each attribute is compared to its 

4978 previously saved value, and if there's no net change, no SQL 

4979 operation will occur (this is a more expensive operation so 

4980 it's only done at flush time). 

4981 

4982 To check if an instance has actionable net changes to its 

4983 attributes, use the :meth:`.Session.is_modified` method. 

4984 

4985 """ 

4986 return IdentitySet( 

4987 [ 

4988 state.obj() 

4989 for state in self._dirty_states 

4990 if state not in self._deleted 

4991 ] 

4992 ) 

4993 

4994 @property 

4995 def deleted(self) -> IdentitySet: 

4996 "The set of all instances marked as 'deleted' within this ``Session``" 

4997 

4998 return util.IdentitySet(list(self._deleted.values())) 

4999 

5000 @property 

5001 def new(self) -> IdentitySet: 

5002 "The set of all instances marked as 'new' within this ``Session``." 

5003 

5004 return util.IdentitySet(list(self._new.values())) 

5005 

5006 

5007_S = TypeVar("_S", bound="Session") 

5008 

5009 

5010class sessionmaker(_SessionClassMethods, Generic[_S]): 

5011 """A configurable :class:`.Session` factory. 

5012 

5013 The :class:`.sessionmaker` factory generates new 

5014 :class:`.Session` objects when called, creating them given 

5015 the configurational arguments established here. 

5016 

5017 e.g.:: 

5018 

5019 from sqlalchemy import create_engine 

5020 from sqlalchemy.orm import sessionmaker 

5021 

5022 # an Engine, which the Session will use for connection 

5023 # resources 

5024 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

5025 

5026 Session = sessionmaker(engine) 

5027 

5028 with Session() as session: 

5029 session.add(some_object) 

5030 session.add(some_other_object) 

5031 session.commit() 

5032 

5033 Context manager use is optional; otherwise, the returned 

5034 :class:`_orm.Session` object may be closed explicitly via the 

5035 :meth:`_orm.Session.close` method. Using a 

5036 ``try:/finally:`` block is optional, however will ensure that the close 

5037 takes place even if there are database errors:: 

5038 

5039 session = Session() 

5040 try: 

5041 session.add(some_object) 

5042 session.add(some_other_object) 

5043 session.commit() 

5044 finally: 

5045 session.close() 

5046 

5047 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5048 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5049 for :class:`_engine.Connection` objects. In this way it also includes 

5050 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5051 manager which both begins and commits a transaction, as well as closes 

5052 out the :class:`_orm.Session` when complete, rolling back the transaction 

5053 if any errors occur:: 

5054 

5055 Session = sessionmaker(engine) 

5056 

5057 with Session.begin() as session: 

5058 session.add(some_object) 

5059 session.add(some_other_object) 

5060 # commits transaction, closes session 

5061 

5062 .. versionadded:: 1.4 

5063 

5064 When calling upon :class:`_orm.sessionmaker` to construct a 

5065 :class:`_orm.Session`, keyword arguments may also be passed to the 

5066 method; these arguments will override that of the globally configured 

5067 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5068 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5069 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5070 

5071 Session = sessionmaker(engine) 

5072 

5073 # bind an individual session to a connection 

5074 

5075 with engine.connect() as connection: 

5076 with Session(bind=connection) as session: 

5077 ... # work with session 

5078 

5079 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5080 can be used to specify additional keyword arguments to the factory, which 

5081 will take effect for subsequent :class:`.Session` objects generated. This 

5082 is usually used to associate one or more :class:`_engine.Engine` objects 

5083 with an existing 

5084 :class:`.sessionmaker` factory before it is first used:: 

5085 

5086 # application starts, sessionmaker does not have 

5087 # an engine bound yet 

5088 Session = sessionmaker() 

5089 

5090 # ... later, when an engine URL is read from a configuration 

5091 # file or other events allow the engine to be created 

5092 engine = create_engine("sqlite:///foo.db") 

5093 Session.configure(bind=engine) 

5094 

5095 sess = Session() 

5096 # work with session 

5097 

5098 .. seealso:: 

5099 

5100 :ref:`session_getting` - introductory text on creating 

5101 sessions using :class:`.sessionmaker`. 

5102 

5103 """ 

5104 

5105 class_: Type[_S] 

5106 

5107 @overload 

5108 def __init__( 

5109 self, 

5110 bind: Optional[_SessionBind] = ..., 

5111 *, 

5112 class_: Type[_S], 

5113 autoflush: bool = ..., 

5114 expire_on_commit: bool = ..., 

5115 info: Optional[_InfoType] = ..., 

5116 **kw: Any, 

5117 ): ... 

5118 

5119 @overload 

5120 def __init__( 

5121 self: "sessionmaker[Session]", 

5122 bind: Optional[_SessionBind] = ..., 

5123 *, 

5124 autoflush: bool = ..., 

5125 expire_on_commit: bool = ..., 

5126 info: Optional[_InfoType] = ..., 

5127 **kw: Any, 

5128 ): ... 

5129 

5130 def __init__( 

5131 self, 

5132 bind: Optional[_SessionBind] = None, 

5133 *, 

5134 class_: Type[_S] = Session, # type: ignore 

5135 autoflush: bool = True, 

5136 expire_on_commit: bool = True, 

5137 info: Optional[_InfoType] = None, 

5138 **kw: Any, 

5139 ): 

5140 r"""Construct a new :class:`.sessionmaker`. 

5141 

5142 All arguments here except for ``class_`` correspond to arguments 

5143 accepted by :class:`.Session` directly. See the 

5144 :meth:`.Session.__init__` docstring for more details on parameters. 

5145 

5146 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5147 with 

5148 which newly created :class:`.Session` objects will be associated. 

5149 :param class\_: class to use in order to create new :class:`.Session` 

5150 objects. Defaults to :class:`.Session`. 

5151 :param autoflush: The autoflush setting to use with newly created 

5152 :class:`.Session` objects. 

5153 

5154 .. seealso:: 

5155 

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

5157 

5158 :param expire_on_commit=True: the 

5159 :paramref:`_orm.Session.expire_on_commit` setting to use 

5160 with newly created :class:`.Session` objects. 

5161 

5162 :param info: optional dictionary of information that will be available 

5163 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5164 replaced, when the ``info`` parameter is specified to the specific 

5165 :class:`.Session` construction operation. 

5166 

5167 :param \**kw: all other keyword arguments are passed to the 

5168 constructor of newly created :class:`.Session` objects. 

5169 

5170 """ 

5171 kw["bind"] = bind 

5172 kw["autoflush"] = autoflush 

5173 kw["expire_on_commit"] = expire_on_commit 

5174 if info is not None: 

5175 kw["info"] = info 

5176 self.kw = kw 

5177 # make our own subclass of the given class, so that 

5178 # events can be associated with it specifically. 

5179 self.class_ = type(class_.__name__, (class_,), {}) 

5180 

5181 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5182 """Produce a context manager that both provides a new 

5183 :class:`_orm.Session` as well as a transaction that commits. 

5184 

5185 

5186 e.g.:: 

5187 

5188 Session = sessionmaker(some_engine) 

5189 

5190 with Session.begin() as session: 

5191 session.add(some_object) 

5192 

5193 # commits transaction, closes session 

5194 

5195 .. versionadded:: 1.4 

5196 

5197 

5198 """ 

5199 

5200 session = self() 

5201 return session._maker_context_manager() 

5202 

5203 def __call__(self, **local_kw: Any) -> _S: 

5204 """Produce a new :class:`.Session` object using the configuration 

5205 established in this :class:`.sessionmaker`. 

5206 

5207 In Python, the ``__call__`` method is invoked on an object when 

5208 it is "called" in the same way as a function:: 

5209 

5210 Session = sessionmaker(some_engine) 

5211 session = Session() # invokes sessionmaker.__call__() 

5212 

5213 """ 

5214 for k, v in self.kw.items(): 

5215 if k == "info" and "info" in local_kw: 

5216 d = v.copy() 

5217 d.update(local_kw["info"]) 

5218 local_kw["info"] = d 

5219 else: 

5220 local_kw.setdefault(k, v) 

5221 return self.class_(**local_kw) 

5222 

5223 def configure(self, **new_kw: Any) -> None: 

5224 """(Re)configure the arguments for this sessionmaker. 

5225 

5226 e.g.:: 

5227 

5228 Session = sessionmaker() 

5229 

5230 Session.configure(bind=create_engine("sqlite://")) 

5231 """ 

5232 self.kw.update(new_kw) 

5233 

5234 def __repr__(self) -> str: 

5235 return "%s(class_=%r, %s)" % ( 

5236 self.__class__.__name__, 

5237 self.class_.__name__, 

5238 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5239 ) 

5240 

5241 

5242def close_all_sessions() -> None: 

5243 """Close all sessions in memory. 

5244 

5245 This function consults a global registry of all :class:`.Session` objects 

5246 and calls :meth:`.Session.close` on them, which resets them to a clean 

5247 state. 

5248 

5249 This function is not for general use but may be useful for test suites 

5250 within the teardown scheme. 

5251 

5252 """ 

5253 

5254 for sess in _sessions.values(): 

5255 sess.close() 

5256 

5257 

5258def make_transient(instance: object) -> None: 

5259 """Alter the state of the given instance so that it is :term:`transient`. 

5260 

5261 .. note:: 

5262 

5263 :func:`.make_transient` is a special-case function for 

5264 advanced use cases only. 

5265 

5266 The given mapped instance is assumed to be in the :term:`persistent` or 

5267 :term:`detached` state. The function will remove its association with any 

5268 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5269 effect is that the object will behave as though it were newly constructed, 

5270 except retaining any attribute / collection values that were loaded at the 

5271 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5272 if this object had been deleted as a result of using 

5273 :meth:`.Session.delete`. 

5274 

5275 .. warning:: 

5276 

5277 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5278 load ORM-mapped attributes that are not currently loaded at the time 

5279 the function is called. This includes attributes which: 

5280 

5281 * were expired via :meth:`.Session.expire` 

5282 

5283 * were expired as the natural effect of committing a session 

5284 transaction, e.g. :meth:`.Session.commit` 

5285 

5286 * are normally :term:`lazy loaded` but are not currently loaded 

5287 

5288 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5289 not yet loaded 

5290 

5291 * were not present in the query which loaded this object, such as that 

5292 which is common in joined table inheritance and other scenarios. 

5293 

5294 After :func:`.make_transient` is called, unloaded attributes such 

5295 as those above will normally resolve to the value ``None`` when 

5296 accessed, or an empty collection for a collection-oriented attribute. 

5297 As the object is transient and un-associated with any database 

5298 identity, it will no longer retrieve these values. 

5299 

5300 .. seealso:: 

5301 

5302 :func:`.make_transient_to_detached` 

5303 

5304 """ 

5305 state = attributes.instance_state(instance) 

5306 s = _state_session(state) 

5307 if s: 

5308 s._expunge_states([state]) 

5309 

5310 # remove expired state 

5311 state.expired_attributes.clear() 

5312 

5313 # remove deferred callables 

5314 if state.callables: 

5315 del state.callables 

5316 

5317 if state.key: 

5318 del state.key 

5319 if state._deleted: 

5320 del state._deleted 

5321 

5322 

5323def make_transient_to_detached(instance: object) -> None: 

5324 """Make the given transient instance :term:`detached`. 

5325 

5326 .. note:: 

5327 

5328 :func:`.make_transient_to_detached` is a special-case function for 

5329 advanced use cases only. 

5330 

5331 All attribute history on the given instance 

5332 will be reset as though the instance were freshly loaded 

5333 from a query. Missing attributes will be marked as expired. 

5334 The primary key attributes of the object, which are required, will be made 

5335 into the "key" of the instance. 

5336 

5337 The object can then be added to a session, or merged 

5338 possibly with the load=False flag, at which point it will look 

5339 as if it were loaded that way, without emitting SQL. 

5340 

5341 This is a special use case function that differs from a normal 

5342 call to :meth:`.Session.merge` in that a given persistent state 

5343 can be manufactured without any SQL calls. 

5344 

5345 .. seealso:: 

5346 

5347 :func:`.make_transient` 

5348 

5349 :meth:`.Session.enable_relationship_loading` 

5350 

5351 """ 

5352 state = attributes.instance_state(instance) 

5353 if state.session_id or state.key: 

5354 raise sa_exc.InvalidRequestError("Given object must be transient") 

5355 state.key = state.mapper._identity_key_from_state(state) 

5356 if state._deleted: 

5357 del state._deleted 

5358 state._commit_all(state.dict) 

5359 state._expire_attributes(state.dict, state.unloaded) 

5360 

5361 

5362def object_session(instance: object) -> Optional[Session]: 

5363 """Return the :class:`.Session` to which the given instance belongs. 

5364 

5365 This is essentially the same as the :attr:`.InstanceState.session` 

5366 accessor. See that attribute for details. 

5367 

5368 """ 

5369 

5370 try: 

5371 state = attributes.instance_state(instance) 

5372 except exc.NO_STATE as err: 

5373 raise exc.UnmappedInstanceError(instance) from err 

5374 else: 

5375 return _state_session(state) 

5376 

5377 

5378_new_sessionid = util.counter()