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

1450 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 ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

93from ..util import deprecated_params 

94from ..util import IdentitySet 

95from ..util.typing import TupleAny 

96from ..util.typing import TypeVarTuple 

97from ..util.typing import Unpack 

98 

99 

100if typing.TYPE_CHECKING: 

101 from ._typing import _EntityType 

102 from ._typing import _IdentityKeyType 

103 from ._typing import _InstanceDict 

104 from ._typing import OrmExecuteOptionsParameter 

105 from .interfaces import ORMOption 

106 from .interfaces import UserDefinedOption 

107 from .mapper import Mapper 

108 from .path_registry import PathRegistry 

109 from .query import RowReturningQuery 

110 from ..engine import Result 

111 from ..engine import Row 

112 from ..engine import RowMapping 

113 from ..engine.base import Transaction 

114 from ..engine.base import TwoPhaseTransaction 

115 from ..engine.interfaces import _CoreAnyExecuteParams 

116 from ..engine.interfaces import _CoreSingleExecuteParams 

117 from ..engine.interfaces import _ExecuteOptions 

118 from ..engine.interfaces import CoreExecuteOptionsParameter 

119 from ..engine.result import ScalarResult 

120 from ..event import _InstanceLevelDispatch 

121 from ..sql._typing import _ColumnsClauseArgument 

122 from ..sql._typing import _InfoType 

123 from ..sql._typing import _T0 

124 from ..sql._typing import _T1 

125 from ..sql._typing import _T2 

126 from ..sql._typing import _T3 

127 from ..sql._typing import _T4 

128 from ..sql._typing import _T5 

129 from ..sql._typing import _T6 

130 from ..sql._typing import _T7 

131 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

132 from ..sql.base import Executable 

133 from ..sql.base import ExecutableOption 

134 from ..sql.elements import ClauseElement 

135 from ..sql.roles import TypedColumnsClauseRole 

136 from ..sql.selectable import ForUpdateParameter 

137 from ..sql.selectable import TypedReturnsRows 

138 

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

140_Ts = TypeVarTuple("_Ts") 

141 

142__all__ = [ 

143 "Session", 

144 "SessionTransaction", 

145 "sessionmaker", 

146 "ORMExecuteState", 

147 "close_all_sessions", 

148 "make_transient", 

149 "make_transient_to_detached", 

150 "object_session", 

151] 

152 

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

154 weakref.WeakValueDictionary() 

155) 

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

157""" 

158 

159statelib._sessions = _sessions 

160 

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

162 

163_BindArguments = Dict[str, Any] 

164 

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

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

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

168 

169JoinTransactionMode = Literal[ 

170 "conditional_savepoint", 

171 "rollback_only", 

172 "control_fully", 

173 "create_savepoint", 

174] 

175 

176 

177class _ConnectionCallableProto(Protocol): 

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

179 

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

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

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

183 as persistence time. 

184 

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

186 is established when using the horizontal sharding extension. 

187 

188 """ 

189 

190 def __call__( 

191 self, 

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

193 instance: Optional[object] = None, 

194 **kw: Any, 

195 ) -> Connection: ... 

196 

197 

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

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

200 associated, if any. 

201 """ 

202 return state.session 

203 

204 

205class _SessionClassMethods: 

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

207 

208 @classmethod 

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

210 def identity_key( 

211 cls, 

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

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

214 *, 

215 instance: Optional[Any] = None, 

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

217 identity_token: Optional[Any] = None, 

218 ) -> _IdentityKeyType[Any]: 

219 """Return an identity key. 

220 

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

222 

223 """ 

224 return util.preloaded.orm_util.identity_key( 

225 class_, 

226 ident, 

227 instance=instance, 

228 row=row, 

229 identity_token=identity_token, 

230 ) 

231 

232 @classmethod 

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

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

235 

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

237 

238 """ 

239 

240 return object_session(instance) 

241 

242 

243class SessionTransactionState(_StateChangeState): 

244 ACTIVE = 1 

245 PREPARED = 2 

246 COMMITTED = 3 

247 DEACTIVE = 4 

248 CLOSED = 5 

249 PROVISIONING_CONNECTION = 6 

250 

251 

252# backwards compatibility 

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

254 SessionTransactionState 

255) 

256 

257 

258class ORMExecuteState(util.MemoizedSlots): 

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

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

261 

262 .. versionadded:: 1.4 

263 

264 .. seealso:: 

265 

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

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

268 

269 """ 

270 

271 __slots__ = ( 

272 "session", 

273 "statement", 

274 "parameters", 

275 "execution_options", 

276 "local_execution_options", 

277 "bind_arguments", 

278 "identity_token", 

279 "_compile_state_cls", 

280 "_starting_event_idx", 

281 "_events_todo", 

282 "_update_execution_options", 

283 ) 

284 

285 session: Session 

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

287 

288 statement: Executable 

289 """The SQL statement being invoked. 

290 

291 For an ORM selection as would 

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

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

294 """ 

295 

296 parameters: Optional[_CoreAnyExecuteParams] 

297 """Dictionary of parameters that was passed to 

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

299 

300 execution_options: _ExecuteOptions 

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

302 

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

304 locally passed execution options. 

305 

306 .. seealso:: 

307 

308 :attr:`_orm.ORMExecuteState.local_execution_options` 

309 

310 :meth:`_sql.Executable.execution_options` 

311 

312 :ref:`orm_queryguide_execution_options` 

313 

314 """ 

315 

316 local_execution_options: _ExecuteOptions 

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

318 :meth:`.Session.execute` method. 

319 

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

321 being invoked. 

322 

323 .. seealso:: 

324 

325 :attr:`_orm.ORMExecuteState.execution_options` 

326 

327 """ 

328 

329 bind_arguments: _BindArguments 

330 """The dictionary passed as the 

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

332 

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

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

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

336 

337 """ 

338 

339 _compile_state_cls: Optional[Type[_ORMCompileState]] 

340 _starting_event_idx: int 

341 _events_todo: List[Any] 

342 _update_execution_options: Optional[_ExecuteOptions] 

343 

344 def __init__( 

345 self, 

346 session: Session, 

347 statement: Executable, 

348 parameters: Optional[_CoreAnyExecuteParams], 

349 execution_options: _ExecuteOptions, 

350 bind_arguments: _BindArguments, 

351 compile_state_cls: Optional[Type[_ORMCompileState]], 

352 events_todo: List[_InstanceLevelDispatch[Session]], 

353 ): 

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

355 

356 this object is constructed internally. 

357 

358 """ 

359 self.session = session 

360 self.statement = statement 

361 self.parameters = parameters 

362 self.local_execution_options = execution_options 

363 self.execution_options = statement._execution_options.union( 

364 execution_options 

365 ) 

366 self.bind_arguments = bind_arguments 

367 self._compile_state_cls = compile_state_cls 

368 self._events_todo = list(events_todo) 

369 

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

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

372 

373 def invoke_statement( 

374 self, 

375 statement: Optional[Executable] = None, 

376 params: Optional[_CoreAnyExecuteParams] = None, 

377 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

378 bind_arguments: Optional[_BindArguments] = None, 

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

380 """Execute the statement represented by this 

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

382 already proceeded. 

383 

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

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

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

387 that want to override how the ultimate 

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

389 retrieve results from an offline cache or which concatenate results 

390 from multiple executions. 

391 

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

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

394 is propagated to the calling 

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

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

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

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

399 

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

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

402 

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

404 which will be merged into the existing 

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

406 

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

408 for executemany executions. 

409 

410 :param execution_options: optional dictionary of execution options 

411 will be merged into the existing 

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

413 :class:`.ORMExecuteState`. 

414 

415 :param bind_arguments: optional dictionary of bind_arguments 

416 which will be merged amongst the current 

417 :attr:`.ORMExecuteState.bind_arguments` 

418 of this :class:`.ORMExecuteState`. 

419 

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

421 

422 .. seealso:: 

423 

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

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

426 

427 

428 """ 

429 

430 if statement is None: 

431 statement = self.statement 

432 

433 _bind_arguments = dict(self.bind_arguments) 

434 if bind_arguments: 

435 _bind_arguments.update(bind_arguments) 

436 _bind_arguments["_sa_skip_events"] = True 

437 

438 _params: Optional[_CoreAnyExecuteParams] 

439 if params: 

440 if self.is_executemany: 

441 _params = [] 

442 exec_many_parameters = cast( 

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

444 ) 

445 for _existing_params, _new_params in itertools.zip_longest( 

446 exec_many_parameters, 

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

448 ): 

449 if _existing_params is None or _new_params is None: 

450 raise sa_exc.InvalidRequestError( 

451 f"Can't apply executemany parameters to " 

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

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

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

455 f"to ORMExecuteState.invoke_statement() " 

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

457 ) 

458 _existing_params = dict(_existing_params) 

459 _existing_params.update(_new_params) 

460 _params.append(_existing_params) 

461 else: 

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

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

464 else: 

465 _params = self.parameters 

466 

467 _execution_options = self.local_execution_options 

468 if execution_options: 

469 _execution_options = _execution_options.union(execution_options) 

470 

471 return self.session._execute_internal( 

472 statement, 

473 _params, 

474 execution_options=_execution_options, 

475 bind_arguments=_bind_arguments, 

476 _parent_execute_state=self, 

477 ) 

478 

479 @property 

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

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

482 

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

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

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

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

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

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

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

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

491 would be selected. 

492 

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

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

495 way of getting this mapper. 

496 

497 .. versionadded:: 1.4.0b2 

498 

499 .. seealso:: 

500 

501 :attr:`_orm.ORMExecuteState.all_mappers` 

502 

503 

504 """ 

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

506 return mp 

507 

508 @property 

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

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

511 involved at the top level of this statement. 

512 

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

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

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

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

517 

518 .. versionadded:: 1.4.0b2 

519 

520 .. seealso:: 

521 

522 :attr:`_orm.ORMExecuteState.bind_mapper` 

523 

524 

525 

526 """ 

527 if not self.is_orm_statement: 

528 return [] 

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

530 result = [] 

531 seen = set() 

532 for d in self.statement.column_descriptions: 

533 ent = d["entity"] 

534 if ent: 

535 insp = inspect(ent, raiseerr=False) 

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

537 seen.add(insp.mapper) 

538 result.append(insp.mapper) 

539 return result 

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

541 return [self.bind_mapper] 

542 else: 

543 return [] 

544 

545 @property 

546 def is_orm_statement(self) -> bool: 

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

548 

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

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

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

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

553 and no ORM-level automation takes place. 

554 

555 """ 

556 return self._compile_state_cls is not None 

557 

558 @property 

559 def is_executemany(self) -> bool: 

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

561 dictionaries with more than one dictionary. 

562 

563 .. versionadded:: 2.0 

564 

565 """ 

566 return isinstance(self.parameters, list) 

567 

568 @property 

569 def is_select(self) -> bool: 

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

571 

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

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

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

575 ``select(Entity).from_statement(select(..))`` 

576 

577 """ 

578 return self.statement.is_select 

579 

580 @property 

581 def is_from_statement(self) -> bool: 

582 """return True if this operation is a 

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

584 

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

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

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

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

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

590 :class:`_sql.Select` construct. 

591 

592 .. versionadded:: 2.0.30 

593 

594 """ 

595 return self.statement.is_from_statement 

596 

597 @property 

598 def is_insert(self) -> bool: 

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

600 

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

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

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

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

605 

606 """ 

607 return self.statement.is_dml and self.statement.is_insert 

608 

609 @property 

610 def is_update(self) -> bool: 

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

612 

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

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

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

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

617 

618 """ 

619 return self.statement.is_dml and self.statement.is_update 

620 

621 @property 

622 def is_delete(self) -> bool: 

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

624 

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

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

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

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

629 

630 """ 

631 return self.statement.is_dml and self.statement.is_delete 

632 

633 @property 

634 def _is_crud(self) -> bool: 

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

636 

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

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

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

640 

641 def _orm_compile_options( 

642 self, 

643 ) -> Optional[ 

644 Union[ 

645 context._ORMCompileState.default_compile_options, 

646 Type[context._ORMCompileState.default_compile_options], 

647 ] 

648 ]: 

649 if not self.is_select: 

650 return None 

651 try: 

652 opts = self.statement._compile_options 

653 except AttributeError: 

654 return None 

655 

656 if opts is not None and opts.isinstance( 

657 context._ORMCompileState.default_compile_options 

658 ): 

659 return opts # type: ignore 

660 else: 

661 return None 

662 

663 @property 

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

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

666 for a lazy load operation. 

667 

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

669 sharding extension, where it is available within specific query 

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

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

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

673 compilation time. 

674 

675 """ 

676 return self.load_options._lazy_loaded_from 

677 

678 @property 

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

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

681 

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

683 when a particular object or collection is being loaded. 

684 

685 """ 

686 opts = self._orm_compile_options() 

687 if opts is not None: 

688 return opts._current_path 

689 else: 

690 return None 

691 

692 @property 

693 def is_column_load(self) -> bool: 

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

695 attributes on an existing ORM object. 

696 

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

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

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

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

701 loaded. 

702 

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

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

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

706 and loader options travelling with the instance 

707 will have already been added to the query. 

708 

709 .. versionadded:: 1.4.0b2 

710 

711 .. seealso:: 

712 

713 :attr:`_orm.ORMExecuteState.is_relationship_load` 

714 

715 """ 

716 opts = self._orm_compile_options() 

717 return opts is not None and opts._for_refresh_state 

718 

719 @property 

720 def is_relationship_load(self) -> bool: 

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

722 relationship. 

723 

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

725 SelectInLoader, SubqueryLoader, or similar, and the entire 

726 SELECT statement being emitted is on behalf of a relationship 

727 load. 

728 

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

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

731 capable of being propagated to relationship loaders and should 

732 be already present. 

733 

734 .. seealso:: 

735 

736 :attr:`_orm.ORMExecuteState.is_column_load` 

737 

738 """ 

739 opts = self._orm_compile_options() 

740 if opts is None: 

741 return False 

742 path = self.loader_strategy_path 

743 return path is not None and not path.is_root 

744 

745 @property 

746 def load_options( 

747 self, 

748 ) -> Union[ 

749 context.QueryContext.default_load_options, 

750 Type[context.QueryContext.default_load_options], 

751 ]: 

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

753 

754 if not self.is_select: 

755 raise sa_exc.InvalidRequestError( 

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

757 "so there are no load options." 

758 ) 

759 

760 lo: Union[ 

761 context.QueryContext.default_load_options, 

762 Type[context.QueryContext.default_load_options], 

763 ] = self.execution_options.get( 

764 "_sa_orm_load_options", context.QueryContext.default_load_options 

765 ) 

766 return lo 

767 

768 @property 

769 def update_delete_options( 

770 self, 

771 ) -> Union[ 

772 bulk_persistence._BulkUDCompileState.default_update_options, 

773 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

774 ]: 

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

776 execution.""" 

777 

778 if not self._is_crud: 

779 raise sa_exc.InvalidRequestError( 

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

781 "statement so there are no update options." 

782 ) 

783 uo: Union[ 

784 bulk_persistence._BulkUDCompileState.default_update_options, 

785 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

786 ] = self.execution_options.get( 

787 "_sa_orm_update_options", 

788 bulk_persistence._BulkUDCompileState.default_update_options, 

789 ) 

790 return uo 

791 

792 @property 

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

794 return [ 

795 opt 

796 for opt in self.statement._with_options 

797 if is_orm_option(opt) and not opt._is_compile_state 

798 ] 

799 

800 @property 

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

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

803 associated with the statement being invoked. 

804 

805 """ 

806 return [ 

807 opt 

808 for opt in self.statement._with_options 

809 if is_user_defined_option(opt) 

810 ] 

811 

812 

813class SessionTransactionOrigin(Enum): 

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

815 

816 This enumeration is present on the 

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

818 :class:`.SessionTransaction` object. 

819 

820 .. versionadded:: 2.0 

821 

822 """ 

823 

824 AUTOBEGIN = 0 

825 """transaction were started by autobegin""" 

826 

827 BEGIN = 1 

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

829 

830 BEGIN_NESTED = 2 

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

832 

833 SUBTRANSACTION = 3 

834 """transaction is an internal "subtransaction" """ 

835 

836 

837class SessionTransaction(_StateChange, TransactionalContext): 

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

839 

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

841 :meth:`_orm.Session.begin` 

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

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

844 transactions. 

845 

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

847 at: :ref:`unitofwork_transaction`. 

848 

849 

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

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

852 

853 .. seealso:: 

854 

855 :ref:`unitofwork_transaction` 

856 

857 :meth:`.Session.begin` 

858 

859 :meth:`.Session.begin_nested` 

860 

861 :meth:`.Session.rollback` 

862 

863 :meth:`.Session.commit` 

864 

865 :meth:`.Session.in_transaction` 

866 

867 :meth:`.Session.in_nested_transaction` 

868 

869 :meth:`.Session.get_transaction` 

870 

871 :meth:`.Session.get_nested_transaction` 

872 

873 

874 """ 

875 

876 _rollback_exception: Optional[BaseException] = None 

877 

878 _connections: Dict[ 

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

880 ] 

881 session: Session 

882 _parent: Optional[SessionTransaction] 

883 

884 _state: SessionTransactionState 

885 

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

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

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

889 _key_switches: weakref.WeakKeyDictionary[ 

890 InstanceState[Any], Tuple[Any, Any] 

891 ] 

892 

893 origin: SessionTransactionOrigin 

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

895 

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

897 enumeration indicating the source event that led to constructing 

898 this :class:`_orm.SessionTransaction`. 

899 

900 .. versionadded:: 2.0 

901 

902 """ 

903 

904 nested: bool = False 

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

906 

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

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

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

910 

911 .. seealso:: 

912 

913 :attr:`.SessionTransaction.origin` 

914 

915 """ 

916 

917 def __init__( 

918 self, 

919 session: Session, 

920 origin: SessionTransactionOrigin, 

921 parent: Optional[SessionTransaction] = None, 

922 ): 

923 TransactionalContext._trans_ctx_check(session) 

924 

925 self.session = session 

926 self._connections = {} 

927 self._parent = parent 

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

929 self.origin = origin 

930 

931 if session._close_state is _SessionCloseState.CLOSED: 

932 raise sa_exc.InvalidRequestError( 

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

934 "to handle any more transaction requests." 

935 ) 

936 

937 if nested: 

938 if not parent: 

939 raise sa_exc.InvalidRequestError( 

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

941 "transaction is in progress" 

942 ) 

943 

944 self._previous_nested_transaction = session._nested_transaction 

945 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

946 assert parent is not None 

947 else: 

948 assert parent is None 

949 

950 self._state = SessionTransactionState.ACTIVE 

951 

952 self._take_snapshot() 

953 

954 # make sure transaction is assigned before we call the 

955 # dispatch 

956 self.session._transaction = self 

957 

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

959 

960 def _raise_for_prerequisite_state( 

961 self, operation_name: str, state: _StateChangeState 

962 ) -> NoReturn: 

963 if state is SessionTransactionState.DEACTIVE: 

964 if self._rollback_exception: 

965 raise sa_exc.PendingRollbackError( 

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

967 "due to a previous exception during flush." 

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

969 "first issue Session.rollback()." 

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

971 code="7s2a", 

972 ) 

973 else: 

974 raise sa_exc.InvalidRequestError( 

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

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

977 "can be emitted within this transaction." 

978 ) 

979 elif state is SessionTransactionState.CLOSED: 

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

981 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

982 raise sa_exc.InvalidRequestError( 

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

984 "operations are not permitted", 

985 code="isce", 

986 ) 

987 else: 

988 raise sa_exc.InvalidRequestError( 

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

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

991 ) 

992 

993 @property 

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

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

996 :class:`.SessionTransaction`. 

997 

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

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

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

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

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

1003 "nested" / SAVEPOINT transaction. If the 

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

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

1006 

1007 """ 

1008 return self._parent 

1009 

1010 @property 

1011 def is_active(self) -> bool: 

1012 return ( 

1013 self.session is not None 

1014 and self._state is SessionTransactionState.ACTIVE 

1015 ) 

1016 

1017 @property 

1018 def _is_transaction_boundary(self) -> bool: 

1019 return self.nested or not self._parent 

1020 

1021 @_StateChange.declare_states( 

1022 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1023 ) 

1024 def connection( 

1025 self, 

1026 bindkey: Optional[Mapper[Any]], 

1027 execution_options: Optional[_ExecuteOptions] = None, 

1028 **kwargs: Any, 

1029 ) -> Connection: 

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

1031 return self._connection_for_bind(bind, execution_options) 

1032 

1033 @_StateChange.declare_states( 

1034 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1035 ) 

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

1037 return SessionTransaction( 

1038 self.session, 

1039 ( 

1040 SessionTransactionOrigin.BEGIN_NESTED 

1041 if nested 

1042 else SessionTransactionOrigin.SUBTRANSACTION 

1043 ), 

1044 self, 

1045 ) 

1046 

1047 def _iterate_self_and_parents( 

1048 self, upto: Optional[SessionTransaction] = None 

1049 ) -> Iterable[SessionTransaction]: 

1050 current = self 

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

1052 while current: 

1053 result += (current,) 

1054 if current._parent is upto: 

1055 break 

1056 elif current._parent is None: 

1057 raise sa_exc.InvalidRequestError( 

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

1059 % (upto) 

1060 ) 

1061 else: 

1062 current = current._parent 

1063 

1064 return result 

1065 

1066 def _take_snapshot(self) -> None: 

1067 if not self._is_transaction_boundary: 

1068 parent = self._parent 

1069 assert parent is not None 

1070 self._new = parent._new 

1071 self._deleted = parent._deleted 

1072 self._dirty = parent._dirty 

1073 self._key_switches = parent._key_switches 

1074 return 

1075 

1076 is_begin = self.origin in ( 

1077 SessionTransactionOrigin.BEGIN, 

1078 SessionTransactionOrigin.AUTOBEGIN, 

1079 ) 

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

1081 self.session.flush() 

1082 

1083 self._new = weakref.WeakKeyDictionary() 

1084 self._deleted = weakref.WeakKeyDictionary() 

1085 self._dirty = weakref.WeakKeyDictionary() 

1086 self._key_switches = weakref.WeakKeyDictionary() 

1087 

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

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

1090 

1091 Corresponds to a rollback. 

1092 

1093 """ 

1094 assert self._is_transaction_boundary 

1095 

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

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

1098 

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

1100 # we probably can do this conditionally based on 

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

1102 self.session.identity_map.safe_discard(s) 

1103 

1104 # restore the old key 

1105 s.key = oldkey 

1106 

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

1108 if s not in to_expunge: 

1109 self.session.identity_map.replace(s) 

1110 

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

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

1113 

1114 assert not self.session._deleted 

1115 

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

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

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

1119 

1120 def _remove_snapshot(self) -> None: 

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

1122 

1123 Corresponds to a commit. 

1124 

1125 """ 

1126 assert self._is_transaction_boundary 

1127 

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

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

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

1131 

1132 statelib.InstanceState._detach_states( 

1133 list(self._deleted), self.session 

1134 ) 

1135 self._deleted.clear() 

1136 elif self.nested: 

1137 parent = self._parent 

1138 assert parent is not None 

1139 parent._new.update(self._new) 

1140 parent._dirty.update(self._dirty) 

1141 parent._deleted.update(self._deleted) 

1142 parent._key_switches.update(self._key_switches) 

1143 

1144 @_StateChange.declare_states( 

1145 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1146 ) 

1147 def _connection_for_bind( 

1148 self, 

1149 bind: _SessionBind, 

1150 execution_options: Optional[CoreExecuteOptionsParameter], 

1151 ) -> Connection: 

1152 if bind in self._connections: 

1153 if execution_options: 

1154 util.warn( 

1155 "Connection is already established for the " 

1156 "given bind; execution_options ignored" 

1157 ) 

1158 return self._connections[bind][0] 

1159 

1160 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1161 

1162 local_connect = False 

1163 should_commit = True 

1164 

1165 try: 

1166 if self._parent: 

1167 conn = self._parent._connection_for_bind( 

1168 bind, execution_options 

1169 ) 

1170 if not self.nested: 

1171 return conn 

1172 else: 

1173 if isinstance(bind, engine.Connection): 

1174 conn = bind 

1175 if conn.engine in self._connections: 

1176 raise sa_exc.InvalidRequestError( 

1177 "Session already has a Connection associated " 

1178 "for the given Connection's Engine" 

1179 ) 

1180 else: 

1181 conn = bind.connect() 

1182 local_connect = True 

1183 

1184 try: 

1185 if execution_options: 

1186 conn = conn.execution_options(**execution_options) 

1187 

1188 transaction: Transaction 

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

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

1191 # conn.in_transaction() ? 

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

1193 # that it is in fact twophase. 

1194 transaction = conn.begin_twophase() 

1195 elif self.nested: 

1196 transaction = conn.begin_nested() 

1197 elif conn.in_transaction(): 

1198 

1199 if local_connect: 

1200 _trans = conn.get_transaction() 

1201 assert _trans is not None 

1202 transaction = _trans 

1203 else: 

1204 join_transaction_mode = ( 

1205 self.session.join_transaction_mode 

1206 ) 

1207 

1208 if join_transaction_mode == "conditional_savepoint": 

1209 if conn.in_nested_transaction(): 

1210 join_transaction_mode = "create_savepoint" 

1211 else: 

1212 join_transaction_mode = "rollback_only" 

1213 

1214 if join_transaction_mode in ( 

1215 "control_fully", 

1216 "rollback_only", 

1217 ): 

1218 if conn.in_nested_transaction(): 

1219 transaction = ( 

1220 conn._get_required_nested_transaction() 

1221 ) 

1222 else: 

1223 transaction = conn._get_required_transaction() 

1224 if join_transaction_mode == "rollback_only": 

1225 should_commit = False 

1226 elif join_transaction_mode == "create_savepoint": 

1227 transaction = conn.begin_nested() 

1228 else: 

1229 assert False, join_transaction_mode 

1230 else: 

1231 transaction = conn.begin() 

1232 except: 

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

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

1235 if local_connect: 

1236 conn.close() 

1237 raise 

1238 else: 

1239 bind_is_connection = isinstance(bind, engine.Connection) 

1240 

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

1242 conn, 

1243 transaction, 

1244 should_commit, 

1245 not bind_is_connection, 

1246 ) 

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

1248 return conn 

1249 finally: 

1250 self._state = SessionTransactionState.ACTIVE 

1251 

1252 def prepare(self) -> None: 

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

1254 raise sa_exc.InvalidRequestError( 

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

1256 "can't prepare." 

1257 ) 

1258 self._prepare_impl() 

1259 

1260 @_StateChange.declare_states( 

1261 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1262 ) 

1263 def _prepare_impl(self) -> None: 

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

1265 self.session.dispatch.before_commit(self.session) 

1266 

1267 stx = self.session._transaction 

1268 assert stx is not None 

1269 if stx is not self: 

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

1271 subtransaction.commit() 

1272 

1273 if not self.session._flushing: 

1274 for _flush_guard in range(100): 

1275 if self.session._is_clean(): 

1276 break 

1277 self.session.flush() 

1278 else: 

1279 raise exc.FlushError( 

1280 "Over 100 subsequent flushes have occurred within " 

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

1282 "creating new objects?" 

1283 ) 

1284 

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

1286 try: 

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

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

1289 except: 

1290 with util.safe_reraise(): 

1291 self.rollback() 

1292 

1293 self._state = SessionTransactionState.PREPARED 

1294 

1295 @_StateChange.declare_states( 

1296 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1297 SessionTransactionState.CLOSED, 

1298 ) 

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

1300 if self._state is not SessionTransactionState.PREPARED: 

1301 with self._expect_state(SessionTransactionState.PREPARED): 

1302 self._prepare_impl() 

1303 

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

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

1306 self._connections.values() 

1307 ): 

1308 if should_commit: 

1309 trans.commit() 

1310 

1311 self._state = SessionTransactionState.COMMITTED 

1312 self.session.dispatch.after_commit(self.session) 

1313 

1314 self._remove_snapshot() 

1315 

1316 with self._expect_state(SessionTransactionState.CLOSED): 

1317 self.close() 

1318 

1319 if _to_root and self._parent: 

1320 self._parent.commit(_to_root=True) 

1321 

1322 @_StateChange.declare_states( 

1323 ( 

1324 SessionTransactionState.ACTIVE, 

1325 SessionTransactionState.DEACTIVE, 

1326 SessionTransactionState.PREPARED, 

1327 ), 

1328 SessionTransactionState.CLOSED, 

1329 ) 

1330 def rollback( 

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

1332 ) -> None: 

1333 stx = self.session._transaction 

1334 assert stx is not None 

1335 if stx is not self: 

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

1337 subtransaction.close() 

1338 

1339 boundary = self 

1340 rollback_err = None 

1341 if self._state in ( 

1342 SessionTransactionState.ACTIVE, 

1343 SessionTransactionState.PREPARED, 

1344 ): 

1345 for transaction in self._iterate_self_and_parents(): 

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

1347 try: 

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

1349 t[1].rollback() 

1350 

1351 transaction._state = SessionTransactionState.DEACTIVE 

1352 self.session.dispatch.after_rollback(self.session) 

1353 except: 

1354 rollback_err = sys.exc_info() 

1355 finally: 

1356 transaction._state = SessionTransactionState.DEACTIVE 

1357 transaction._restore_snapshot( 

1358 dirty_only=transaction.nested 

1359 ) 

1360 boundary = transaction 

1361 break 

1362 else: 

1363 transaction._state = SessionTransactionState.DEACTIVE 

1364 

1365 sess = self.session 

1366 

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

1368 # if items were added, deleted, or mutated 

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

1370 util.warn( 

1371 "Session's state has been changed on " 

1372 "a non-active transaction - this state " 

1373 "will be discarded." 

1374 ) 

1375 boundary._restore_snapshot(dirty_only=boundary.nested) 

1376 

1377 with self._expect_state(SessionTransactionState.CLOSED): 

1378 self.close() 

1379 

1380 if self._parent and _capture_exception: 

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

1382 

1383 if rollback_err and rollback_err[1]: 

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

1385 

1386 sess.dispatch.after_soft_rollback(sess, self) 

1387 

1388 if _to_root and self._parent: 

1389 self._parent.rollback(_to_root=True) 

1390 

1391 @_StateChange.declare_states( 

1392 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1393 ) 

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

1395 if self.nested: 

1396 self.session._nested_transaction = ( 

1397 self._previous_nested_transaction 

1398 ) 

1399 

1400 self.session._transaction = self._parent 

1401 

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

1403 self._connections.values() 

1404 ): 

1405 if invalidate and self._parent is None: 

1406 connection.invalidate() 

1407 if should_commit and transaction.is_active: 

1408 transaction.close() 

1409 if autoclose and self._parent is None: 

1410 connection.close() 

1411 

1412 self._state = SessionTransactionState.CLOSED 

1413 sess = self.session 

1414 

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

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

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

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

1419 # passes with these commented out. 

1420 # self.session = None # type: ignore 

1421 # self._connections = None # type: ignore 

1422 

1423 sess.dispatch.after_transaction_end(sess, self) 

1424 

1425 def _get_subject(self) -> Session: 

1426 return self.session 

1427 

1428 def _transaction_is_active(self) -> bool: 

1429 return self._state is SessionTransactionState.ACTIVE 

1430 

1431 def _transaction_is_closed(self) -> bool: 

1432 return self._state is SessionTransactionState.CLOSED 

1433 

1434 def _rollback_can_be_called(self) -> bool: 

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

1436 

1437 

1438class _SessionCloseState(Enum): 

1439 ACTIVE = 1 

1440 CLOSED = 2 

1441 CLOSE_IS_RESET = 3 

1442 

1443 

1444class Session(_SessionClassMethods, EventTarget): 

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

1446 

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

1448 See :ref:`session_faq_threadsafe` for background. 

1449 

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

1451 

1452 

1453 """ 

1454 

1455 _is_asyncio = False 

1456 

1457 dispatch: dispatcher[Session] 

1458 

1459 identity_map: IdentityMap 

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

1461 

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

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

1464 that have row identity) currently in the session. 

1465 

1466 .. seealso:: 

1467 

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

1469 in this dictionary. 

1470 

1471 """ 

1472 

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

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

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

1476 __binds: Dict[_SessionBindKey, _SessionBind] 

1477 _flushing: bool 

1478 _warn_on_events: bool 

1479 _transaction: Optional[SessionTransaction] 

1480 _nested_transaction: Optional[SessionTransaction] 

1481 hash_key: int 

1482 autoflush: bool 

1483 expire_on_commit: bool 

1484 enable_baked_queries: bool 

1485 twophase: bool 

1486 join_transaction_mode: JoinTransactionMode 

1487 execution_options: _ExecuteOptions = util.EMPTY_DICT 

1488 _query_cls: Type[Query[Any]] 

1489 _close_state: _SessionCloseState 

1490 

1491 def __init__( 

1492 self, 

1493 bind: Optional[_SessionBind] = None, 

1494 *, 

1495 autoflush: bool = True, 

1496 future: Literal[True] = True, 

1497 expire_on_commit: bool = True, 

1498 autobegin: bool = True, 

1499 twophase: bool = False, 

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

1501 enable_baked_queries: bool = True, 

1502 info: Optional[_InfoType] = None, 

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

1504 autocommit: Literal[False] = False, 

1505 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1507 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

1508 ): 

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

1510 

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

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

1513 set of arguments. 

1514 

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

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

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

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

1519 results. 

1520 

1521 .. seealso:: 

1522 

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

1524 

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

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

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

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

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

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

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

1532 

1533 .. versionadded:: 2.0 

1534 

1535 .. seealso:: 

1536 

1537 :ref:`session_autobegin_disable` 

1538 

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

1540 :class:`_engine.Connection` to 

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

1542 operations performed by this session will execute via this 

1543 connectable. 

1544 

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

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

1547 objects as the source of 

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

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

1550 arbitrary Python classes that are bases for mapped classes, 

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

1552 The 

1553 values of the dictionary are then instances of 

1554 :class:`_engine.Engine` 

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

1556 Operations which 

1557 proceed relative to a particular mapped class will consult this 

1558 dictionary for the closest matching entity in order to determine 

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

1560 operation. The complete heuristics for resolution are 

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

1562 

1563 Session = sessionmaker( 

1564 binds={ 

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

1566 SomeDeclarativeBase: create_engine( 

1567 "postgresql+psycopg2://engine2" 

1568 ), 

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

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

1571 } 

1572 ) 

1573 

1574 .. seealso:: 

1575 

1576 :ref:`session_partitioning` 

1577 

1578 :meth:`.Session.bind_mapper` 

1579 

1580 :meth:`.Session.bind_table` 

1581 

1582 :meth:`.Session.get_bind` 

1583 

1584 

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

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

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

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

1589 constructor for ``Session``. 

1590 

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

1592 A parameter consumed 

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

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

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

1596 this particular extension is disabled. 

1597 

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

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

1600 flag therefore only affects applications that are making explicit 

1601 use of this extension within their own code. 

1602 

1603 :param execution_options: optional dictionary of execution options 

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

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

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

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

1608 the session-wide options. 

1609 

1610 .. versionadded:: 2.1 

1611 

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

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

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

1615 transaction will load from the most recent database state. 

1616 

1617 .. seealso:: 

1618 

1619 :ref:`session_committing` 

1620 

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

1622 

1623 .. seealso:: 

1624 

1625 :ref:`migration_20_toplevel` 

1626 

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

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

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

1630 construction time so that modifications to the per- 

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

1632 :class:`.Session`. 

1633 

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

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

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

1637 

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

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

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

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

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

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

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

1645 transaction, before each transaction is committed. 

1646 

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

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

1649 

1650 :param join_transaction_mode: Describes the transactional behavior to 

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

1652 has already begun a transaction outside the scope of this 

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

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

1655 

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

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

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

1659 etc. are actually invoked: 

1660 

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

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

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

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

1665 a SAVEPOINT, in other words 

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

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

1668 

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

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

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

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

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

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

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

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

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

1678 

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

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

1681 its own transaction. This transaction by its nature rides 

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

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

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

1685 external transaction will remain unaffected throughout the 

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

1687 

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

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

1690 initiated transaction should remain unaffected; however, it relies 

1691 on proper SAVEPOINT support from the underlying driver and 

1692 database. 

1693 

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

1695 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1696 without workarounds. See the sections 

1697 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1698 for details on current workarounds. 

1699 

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

1701 control of the given transaction as its own; 

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

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

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

1705 call ``.rollback`` on the transaction. 

1706 

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

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

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

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

1711 SAVEPOINT. 

1712 

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

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

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

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

1717 given transaction. 

1718 

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

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

1721 regular database transaction (i.e. 

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

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

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

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

1726 

1727 .. versionadded:: 2.0.0rc1 

1728 

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

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

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

1732 

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

1734 A future SQLAlchemy version may change the default value of 

1735 this flag to ``False``. 

1736 

1737 .. seealso:: 

1738 

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

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

1741 

1742 """ # noqa 

1743 

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

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

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

1747 # of cases including in our own test suite 

1748 if autocommit: 

1749 raise sa_exc.ArgumentError( 

1750 "autocommit=True is no longer supported" 

1751 ) 

1752 self.identity_map = identity._WeakInstanceDict() 

1753 

1754 if not future: 

1755 raise sa_exc.ArgumentError( 

1756 "The 'future' parameter passed to " 

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

1758 ) 

1759 

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

1761 self._deleted = {} # same 

1762 self.bind = bind 

1763 self.__binds = {} 

1764 self._flushing = False 

1765 self._warn_on_events = False 

1766 self._transaction = None 

1767 self._nested_transaction = None 

1768 self.hash_key = _new_sessionid() 

1769 self.autobegin = autobegin 

1770 self.autoflush = autoflush 

1771 self.expire_on_commit = expire_on_commit 

1772 self.enable_baked_queries = enable_baked_queries 

1773 if execution_options: 

1774 self.execution_options = self.execution_options.union( 

1775 execution_options 

1776 ) 

1777 

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

1779 # the default will switch to close_resets_only=False. 

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

1781 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1782 else: 

1783 self._close_state = _SessionCloseState.ACTIVE 

1784 if ( 

1785 join_transaction_mode 

1786 and join_transaction_mode 

1787 not in JoinTransactionMode.__args__ # type: ignore 

1788 ): 

1789 raise sa_exc.ArgumentError( 

1790 f"invalid selection for join_transaction_mode: " 

1791 f'"{join_transaction_mode}"' 

1792 ) 

1793 self.join_transaction_mode = join_transaction_mode 

1794 

1795 self.twophase = twophase 

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

1797 if info: 

1798 self.info.update(info) 

1799 

1800 if binds is not None: 

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

1802 self._add_bind(key, bind) 

1803 

1804 _sessions[self.hash_key] = self 

1805 

1806 # used by sqlalchemy.engine.util.TransactionalContext 

1807 _trans_context_manager: Optional[TransactionalContext] = None 

1808 

1809 connection_callable: Optional[_ConnectionCallableProto] = None 

1810 

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

1812 return self 

1813 

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

1815 self.close() 

1816 

1817 @contextlib.contextmanager 

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

1819 with self: 

1820 with self.begin(): 

1821 yield self 

1822 

1823 def in_transaction(self) -> bool: 

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

1825 

1826 .. versionadded:: 1.4 

1827 

1828 .. seealso:: 

1829 

1830 :attr:`_orm.Session.is_active` 

1831 

1832 

1833 """ 

1834 return self._transaction is not None 

1835 

1836 def in_nested_transaction(self) -> bool: 

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

1838 transaction, e.g. SAVEPOINT. 

1839 

1840 .. versionadded:: 1.4 

1841 

1842 """ 

1843 return self._nested_transaction is not None 

1844 

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

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

1847 

1848 .. versionadded:: 1.4 

1849 

1850 """ 

1851 trans = self._transaction 

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

1853 trans = trans._parent 

1854 return trans 

1855 

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

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

1858 

1859 .. versionadded:: 1.4 

1860 

1861 """ 

1862 

1863 return self._nested_transaction 

1864 

1865 @util.memoized_property 

1866 def info(self) -> _InfoType: 

1867 """A user-modifiable dictionary. 

1868 

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

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

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

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

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

1874 

1875 """ 

1876 return {} 

1877 

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

1879 if self._transaction is None: 

1880 if not begin and not self.autobegin: 

1881 raise sa_exc.InvalidRequestError( 

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

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

1884 ) 

1885 trans = SessionTransaction( 

1886 self, 

1887 ( 

1888 SessionTransactionOrigin.BEGIN 

1889 if begin 

1890 else SessionTransactionOrigin.AUTOBEGIN 

1891 ), 

1892 ) 

1893 assert self._transaction is trans 

1894 return trans 

1895 

1896 return self._transaction 

1897 

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

1899 """Begin a transaction, or nested transaction, 

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

1901 

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

1903 so that normally it is not necessary to call the 

1904 :meth:`_orm.Session.begin` 

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

1906 the scope of when the transactional state is begun. 

1907 

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

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

1910 

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

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

1913 documentation on SAVEPOINT transactions, please see 

1914 :ref:`session_begin_nested`. 

1915 

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

1917 :class:`.SessionTransaction` 

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

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

1920 an example. 

1921 

1922 .. seealso:: 

1923 

1924 :ref:`session_autobegin` 

1925 

1926 :ref:`unitofwork_transaction` 

1927 

1928 :meth:`.Session.begin_nested` 

1929 

1930 

1931 """ 

1932 

1933 trans = self._transaction 

1934 if trans is None: 

1935 trans = self._autobegin_t(begin=True) 

1936 

1937 if not nested: 

1938 return trans 

1939 

1940 assert trans is not None 

1941 

1942 if nested: 

1943 trans = trans._begin(nested=nested) 

1944 assert self._transaction is trans 

1945 self._nested_transaction = trans 

1946 else: 

1947 raise sa_exc.InvalidRequestError( 

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

1949 ) 

1950 

1951 return trans # needed for __enter__/__exit__ hook 

1952 

1953 def begin_nested(self) -> SessionTransaction: 

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

1955 

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

1957 SAVEPOINT for this method to function correctly. 

1958 

1959 For documentation on SAVEPOINT 

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

1961 

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

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

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

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

1966 

1967 .. seealso:: 

1968 

1969 :ref:`session_begin_nested` 

1970 

1971 :ref:`pysqlite_serializable` - special workarounds required 

1972 with the SQLite driver in order for SAVEPOINT to work 

1973 correctly. For asyncio use cases, see the section 

1974 :ref:`aiosqlite_serializable`. 

1975 

1976 """ 

1977 return self.begin(nested=True) 

1978 

1979 def rollback(self) -> None: 

1980 """Rollback the current transaction in progress. 

1981 

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

1983 

1984 The method always rolls back 

1985 the topmost database transaction, discarding any nested 

1986 transactions that may be in progress. 

1987 

1988 .. seealso:: 

1989 

1990 :ref:`session_rollback` 

1991 

1992 :ref:`unitofwork_transaction` 

1993 

1994 """ 

1995 if self._transaction is None: 

1996 pass 

1997 else: 

1998 self._transaction.rollback(_to_root=True) 

1999 

2000 def commit(self) -> None: 

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

2002 

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

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

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

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

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

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

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

2010 to disable this behavior. 

2011 

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

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

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

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

2016 normally affect the database unless pending flush changes were 

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

2018 rules. 

2019 

2020 The outermost database transaction is committed unconditionally, 

2021 automatically releasing any SAVEPOINTs in effect. 

2022 

2023 .. seealso:: 

2024 

2025 :ref:`session_committing` 

2026 

2027 :ref:`unitofwork_transaction` 

2028 

2029 :ref:`asyncio_orm_avoid_lazyloads` 

2030 

2031 """ 

2032 trans = self._transaction 

2033 if trans is None: 

2034 trans = self._autobegin_t() 

2035 

2036 trans.commit(_to_root=True) 

2037 

2038 def prepare(self) -> None: 

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

2040 

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

2042 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2043 

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

2045 current transaction is not such, an 

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

2047 

2048 """ 

2049 trans = self._transaction 

2050 if trans is None: 

2051 trans = self._autobegin_t() 

2052 

2053 trans.prepare() 

2054 

2055 def connection( 

2056 self, 

2057 bind_arguments: Optional[_BindArguments] = None, 

2058 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2059 ) -> Connection: 

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

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

2062 

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

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

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

2066 returned (note that no 

2067 transactional state is established with the DBAPI until the first 

2068 SQL statement is emitted). 

2069 

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

2071 resolved through any of the optional keyword arguments. This 

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

2073 

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

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

2076 to :meth:`.Session.get_bind`. 

2077 

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

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

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

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

2082 the arguments are ignored. 

2083 

2084 .. seealso:: 

2085 

2086 :ref:`session_transaction_isolation` 

2087 

2088 """ 

2089 

2090 if bind_arguments: 

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

2092 

2093 if bind is None: 

2094 bind = self.get_bind(**bind_arguments) 

2095 else: 

2096 bind = self.get_bind() 

2097 

2098 return self._connection_for_bind( 

2099 bind, 

2100 execution_options=execution_options, 

2101 ) 

2102 

2103 def _connection_for_bind( 

2104 self, 

2105 engine: _SessionBind, 

2106 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2107 **kw: Any, 

2108 ) -> Connection: 

2109 TransactionalContext._trans_ctx_check(self) 

2110 

2111 trans = self._transaction 

2112 if trans is None: 

2113 trans = self._autobegin_t() 

2114 return trans._connection_for_bind(engine, execution_options) 

2115 

2116 @overload 

2117 def _execute_internal( 

2118 self, 

2119 statement: Executable, 

2120 params: Optional[_CoreSingleExecuteParams] = None, 

2121 *, 

2122 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2123 bind_arguments: Optional[_BindArguments] = None, 

2124 _parent_execute_state: Optional[Any] = None, 

2125 _add_event: Optional[Any] = None, 

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

2127 ) -> Any: ... 

2128 

2129 @overload 

2130 def _execute_internal( 

2131 self, 

2132 statement: Executable, 

2133 params: Optional[_CoreAnyExecuteParams] = None, 

2134 *, 

2135 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2136 bind_arguments: Optional[_BindArguments] = None, 

2137 _parent_execute_state: Optional[Any] = None, 

2138 _add_event: Optional[Any] = None, 

2139 _scalar_result: bool = ..., 

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

2141 

2142 def _execute_internal( 

2143 self, 

2144 statement: Executable, 

2145 params: Optional[_CoreAnyExecuteParams] = None, 

2146 *, 

2147 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2148 bind_arguments: Optional[_BindArguments] = None, 

2149 _parent_execute_state: Optional[Any] = None, 

2150 _add_event: Optional[Any] = None, 

2151 _scalar_result: bool = False, 

2152 ) -> Any: 

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

2154 

2155 if not bind_arguments: 

2156 bind_arguments = {} 

2157 else: 

2158 bind_arguments = dict(bind_arguments) 

2159 

2160 if ( 

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

2162 == "orm" 

2163 ): 

2164 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2165 statement, "orm" 

2166 ) 

2167 if TYPE_CHECKING: 

2168 assert isinstance( 

2169 compile_state_cls, context._AbstractORMCompileState 

2170 ) 

2171 else: 

2172 compile_state_cls = None 

2173 bind_arguments.setdefault("clause", statement) 

2174 

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

2176 util.coerce_to_immutabledict(execution_options) 

2177 ) 

2178 if self.execution_options: 

2179 # merge given execution options with session-wide execution 

2180 # options. if the statement also has execution_options, 

2181 # maintain priority of session.execution_options -> 

2182 # statement.execution_options -> method passed execution_options 

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

2184 # will come from the statement 

2185 if statement._execution_options: 

2186 combined_execution_options = util.immutabledict( 

2187 { 

2188 k: v 

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

2190 if k not in statement._execution_options 

2191 } 

2192 ).union(combined_execution_options) 

2193 else: 

2194 combined_execution_options = self.execution_options.union( 

2195 combined_execution_options 

2196 ) 

2197 

2198 if _parent_execute_state: 

2199 events_todo = _parent_execute_state._remaining_events() 

2200 else: 

2201 events_todo = self.dispatch.do_orm_execute 

2202 if _add_event: 

2203 events_todo = list(events_todo) + [_add_event] 

2204 

2205 if events_todo: 

2206 if compile_state_cls is not None: 

2207 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2213 ( 

2214 statement, 

2215 combined_execution_options, 

2216 ) = compile_state_cls.orm_pre_session_exec( 

2217 self, 

2218 statement, 

2219 params, 

2220 combined_execution_options, 

2221 bind_arguments, 

2222 True, 

2223 ) 

2224 

2225 orm_exec_state = ORMExecuteState( 

2226 self, 

2227 statement, 

2228 params, 

2229 combined_execution_options, 

2230 bind_arguments, 

2231 compile_state_cls, 

2232 events_todo, 

2233 ) 

2234 for idx, fn in enumerate(events_todo): 

2235 orm_exec_state._starting_event_idx = idx 

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

2237 orm_exec_state 

2238 ) 

2239 if fn_result: 

2240 if _scalar_result: 

2241 return fn_result.scalar() 

2242 else: 

2243 return fn_result 

2244 

2245 statement = orm_exec_state.statement 

2246 combined_execution_options = orm_exec_state.local_execution_options 

2247 

2248 if compile_state_cls is not None: 

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

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

2251 # new execution_options into load_options / update_delete_options, 

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

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

2254 ( 

2255 statement, 

2256 combined_execution_options, 

2257 ) = compile_state_cls.orm_pre_session_exec( 

2258 self, 

2259 statement, 

2260 params, 

2261 combined_execution_options, 

2262 bind_arguments, 

2263 False, 

2264 ) 

2265 else: 

2266 # Issue #9809: unconditionally autoflush for Core statements 

2267 self._autoflush() 

2268 

2269 bind = self.get_bind(**bind_arguments) 

2270 

2271 conn = self._connection_for_bind(bind) 

2272 

2273 if _scalar_result and not compile_state_cls: 

2274 if TYPE_CHECKING: 

2275 params = cast(_CoreSingleExecuteParams, params) 

2276 return conn.scalar( 

2277 statement, 

2278 params or {}, 

2279 execution_options=combined_execution_options, 

2280 ) 

2281 

2282 if compile_state_cls: 

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

2284 compile_state_cls.orm_execute_statement( 

2285 self, 

2286 statement, 

2287 params or {}, 

2288 combined_execution_options, 

2289 bind_arguments, 

2290 conn, 

2291 ) 

2292 ) 

2293 else: 

2294 result = conn.execute( 

2295 statement, params, execution_options=combined_execution_options 

2296 ) 

2297 

2298 if _scalar_result: 

2299 return result.scalar() 

2300 else: 

2301 return result 

2302 

2303 @overload 

2304 def execute( 

2305 self, 

2306 statement: TypedReturnsRows[Unpack[_Ts]], 

2307 params: Optional[_CoreAnyExecuteParams] = None, 

2308 *, 

2309 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2310 bind_arguments: Optional[_BindArguments] = None, 

2311 _parent_execute_state: Optional[Any] = None, 

2312 _add_event: Optional[Any] = None, 

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

2314 

2315 @overload 

2316 def execute( 

2317 self, 

2318 statement: Executable, 

2319 params: Optional[_CoreAnyExecuteParams] = None, 

2320 *, 

2321 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2322 bind_arguments: Optional[_BindArguments] = None, 

2323 _parent_execute_state: Optional[Any] = None, 

2324 _add_event: Optional[Any] = None, 

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

2326 

2327 def execute( 

2328 self, 

2329 statement: Executable, 

2330 params: Optional[_CoreAnyExecuteParams] = None, 

2331 *, 

2332 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2333 bind_arguments: Optional[_BindArguments] = None, 

2334 _parent_execute_state: Optional[Any] = None, 

2335 _add_event: Optional[Any] = None, 

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

2337 r"""Execute a SQL expression construct. 

2338 

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

2340 results of the statement execution. 

2341 

2342 E.g.:: 

2343 

2344 from sqlalchemy import select 

2345 

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

2347 

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

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

2350 of :class:`_engine.Connection`. 

2351 

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

2353 now the primary point of ORM statement execution when using 

2354 :term:`2.0 style` ORM usage. 

2355 

2356 :param statement: 

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

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

2359 

2360 :param params: 

2361 Optional dictionary, or list of dictionaries, containing 

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

2363 execution occurs; if a list of dictionaries, an 

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

2365 must correspond to parameter names present in the statement. 

2366 

2367 :param execution_options: optional dictionary of execution options, 

2368 which will be associated with the statement execution. This 

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

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

2371 provide additional options understood only in an ORM context. 

2372 

2373 The execution_options are passed along to methods like 

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

2375 highest priority to execution_options that are passed to this 

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

2377 statement object if any, and finally those options present 

2378 session-wide. 

2379 

2380 .. seealso:: 

2381 

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

2383 options 

2384 

2385 :param bind_arguments: dictionary of additional arguments to determine 

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

2387 Contents of this dictionary are passed to the 

2388 :meth:`.Session.get_bind` method. 

2389 

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

2391 

2392 

2393 """ 

2394 return self._execute_internal( 

2395 statement, 

2396 params, 

2397 execution_options=execution_options, 

2398 bind_arguments=bind_arguments, 

2399 _parent_execute_state=_parent_execute_state, 

2400 _add_event=_add_event, 

2401 ) 

2402 

2403 @overload 

2404 def scalar( 

2405 self, 

2406 statement: TypedReturnsRows[_T], 

2407 params: Optional[_CoreSingleExecuteParams] = None, 

2408 *, 

2409 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2410 bind_arguments: Optional[_BindArguments] = None, 

2411 **kw: Any, 

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

2413 

2414 @overload 

2415 def scalar( 

2416 self, 

2417 statement: Executable, 

2418 params: Optional[_CoreSingleExecuteParams] = None, 

2419 *, 

2420 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2421 bind_arguments: Optional[_BindArguments] = None, 

2422 **kw: Any, 

2423 ) -> Any: ... 

2424 

2425 def scalar( 

2426 self, 

2427 statement: Executable, 

2428 params: Optional[_CoreSingleExecuteParams] = None, 

2429 *, 

2430 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2431 bind_arguments: Optional[_BindArguments] = None, 

2432 **kw: Any, 

2433 ) -> Any: 

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

2435 

2436 Usage and parameters are the same as that of 

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

2438 value. 

2439 

2440 """ 

2441 

2442 return self._execute_internal( 

2443 statement, 

2444 params, 

2445 execution_options=execution_options, 

2446 bind_arguments=bind_arguments, 

2447 _scalar_result=True, 

2448 **kw, 

2449 ) 

2450 

2451 @overload 

2452 def scalars( 

2453 self, 

2454 statement: TypedReturnsRows[_T], 

2455 params: Optional[_CoreAnyExecuteParams] = None, 

2456 *, 

2457 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2458 bind_arguments: Optional[_BindArguments] = None, 

2459 **kw: Any, 

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

2461 

2462 @overload 

2463 def scalars( 

2464 self, 

2465 statement: Executable, 

2466 params: Optional[_CoreAnyExecuteParams] = None, 

2467 *, 

2468 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2469 bind_arguments: Optional[_BindArguments] = None, 

2470 **kw: Any, 

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

2472 

2473 def scalars( 

2474 self, 

2475 statement: Executable, 

2476 params: Optional[_CoreAnyExecuteParams] = None, 

2477 *, 

2478 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2479 bind_arguments: Optional[_BindArguments] = None, 

2480 **kw: Any, 

2481 ) -> ScalarResult[Any]: 

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

2483 

2484 Usage and parameters are the same as that of 

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

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

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

2488 

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

2490 

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

2492 

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

2494 

2495 .. seealso:: 

2496 

2497 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2499 

2500 """ 

2501 

2502 return self._execute_internal( 

2503 statement, 

2504 params=params, 

2505 execution_options=execution_options, 

2506 bind_arguments=bind_arguments, 

2507 _scalar_result=False, # mypy appreciates this 

2508 **kw, 

2509 ).scalars() 

2510 

2511 def close(self) -> None: 

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

2513 :class:`_orm.Session`. 

2514 

2515 This expunges all ORM objects associated with this 

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

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

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

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

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

2521 

2522 .. tip:: 

2523 

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

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

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

2527 distinct "closed" state; it merely means 

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

2529 and ORM objects. 

2530 

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

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

2533 any further action on the session will be forbidden. 

2534 

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

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

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

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

2539 

2540 .. seealso:: 

2541 

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

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

2544 

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

2546 ``close()`` with the parameter 

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

2548 

2549 """ 

2550 self._close_impl(invalidate=False) 

2551 

2552 def reset(self) -> None: 

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

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

2555 

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

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

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

2559 brand new, and ready to be used again. 

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

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

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

2563 

2564 .. versionadded:: 2.0.22 

2565 

2566 .. seealso:: 

2567 

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

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

2570 

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

2572 prevent re-use of the Session when the parameter 

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

2574 """ 

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

2576 

2577 def invalidate(self) -> None: 

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

2579 

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

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

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

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

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

2585 multiple engines). 

2586 

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

2588 the connections are no longer safe to be used. 

2589 

2590 Below illustrates a scenario when using `gevent 

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

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

2593 

2594 import gevent 

2595 

2596 try: 

2597 sess = Session() 

2598 sess.add(User()) 

2599 sess.commit() 

2600 except gevent.Timeout: 

2601 sess.invalidate() 

2602 raise 

2603 except: 

2604 sess.rollback() 

2605 raise 

2606 

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

2608 does, including that all ORM objects are expunged. 

2609 

2610 """ 

2611 self._close_impl(invalidate=True) 

2612 

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

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

2615 self._close_state = _SessionCloseState.CLOSED 

2616 self.expunge_all() 

2617 if self._transaction is not None: 

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

2619 transaction.close(invalidate) 

2620 

2621 def expunge_all(self) -> None: 

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

2623 

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

2625 ``Session``. 

2626 

2627 """ 

2628 

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

2630 self.identity_map._kill() 

2631 self.identity_map = identity._WeakInstanceDict() 

2632 self._new = {} 

2633 self._deleted = {} 

2634 

2635 statelib.InstanceState._detach_states(all_states, self) 

2636 

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

2638 try: 

2639 insp = inspect(key) 

2640 except sa_exc.NoInspectionAvailable as err: 

2641 if not isinstance(key, type): 

2642 raise sa_exc.ArgumentError( 

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

2644 ) from err 

2645 else: 

2646 self.__binds[key] = bind 

2647 else: 

2648 if TYPE_CHECKING: 

2649 assert isinstance(insp, Inspectable) 

2650 

2651 if isinstance(insp, TableClause): 

2652 self.__binds[insp] = bind 

2653 elif insp_is_mapper(insp): 

2654 self.__binds[insp.class_] = bind 

2655 for _selectable in insp._all_tables: 

2656 self.__binds[_selectable] = bind 

2657 else: 

2658 raise sa_exc.ArgumentError( 

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

2660 ) 

2661 

2662 def bind_mapper( 

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

2664 ) -> None: 

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

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

2667 :class:`_engine.Connection`. 

2668 

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

2670 :meth:`.Session.get_bind` method. 

2671 

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

2673 or an instance of a mapped 

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

2675 classes. 

2676 

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

2678 object. 

2679 

2680 .. seealso:: 

2681 

2682 :ref:`session_partitioning` 

2683 

2684 :paramref:`.Session.binds` 

2685 

2686 :meth:`.Session.bind_table` 

2687 

2688 

2689 """ 

2690 self._add_bind(mapper, bind) 

2691 

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

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

2694 :class:`_engine.Engine` 

2695 or :class:`_engine.Connection`. 

2696 

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

2698 :meth:`.Session.get_bind` method. 

2699 

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

2701 which is typically the target 

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

2703 mapped. 

2704 

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

2706 object. 

2707 

2708 .. seealso:: 

2709 

2710 :ref:`session_partitioning` 

2711 

2712 :paramref:`.Session.binds` 

2713 

2714 :meth:`.Session.bind_mapper` 

2715 

2716 

2717 """ 

2718 self._add_bind(table, bind) 

2719 

2720 def get_bind( 

2721 self, 

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

2723 *, 

2724 clause: Optional[ClauseElement] = None, 

2725 bind: Optional[_SessionBind] = None, 

2726 _sa_skip_events: Optional[bool] = None, 

2727 _sa_skip_for_implicit_returning: bool = False, 

2728 **kw: Any, 

2729 ) -> Union[Engine, Connection]: 

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

2731 

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

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

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

2735 

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

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

2738 appropriate bind to return. 

2739 

2740 Note that the "mapper" argument is usually present 

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

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

2743 individual INSERT/UPDATE/DELETE operation within a 

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

2745 

2746 The order of resolution is: 

2747 

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

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

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

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

2752 superclasses to more general. 

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

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

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

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

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

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

2759 associated with the clause. 

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

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

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

2763 selectable to which the mapper is mapped. 

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

2765 is raised. 

2766 

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

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

2769 of bind resolution scheme. See the example at 

2770 :ref:`session_custom_partitioning`. 

2771 

2772 :param mapper: 

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

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

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

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

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

2778 mapped for a bind. 

2779 

2780 :param clause: 

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

2782 :func:`_expression.select`, 

2783 :func:`_expression.text`, 

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

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

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

2787 associated with 

2788 bound :class:`_schema.MetaData`. 

2789 

2790 .. seealso:: 

2791 

2792 :ref:`session_partitioning` 

2793 

2794 :paramref:`.Session.binds` 

2795 

2796 :meth:`.Session.bind_mapper` 

2797 

2798 :meth:`.Session.bind_table` 

2799 

2800 """ 

2801 

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

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

2804 if bind: 

2805 return bind 

2806 elif not self.__binds and self.bind: 

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

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

2809 return self.bind 

2810 

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

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

2813 # mapper and the clause 

2814 if mapper is None and clause is None: 

2815 if self.bind: 

2816 return self.bind 

2817 else: 

2818 raise sa_exc.UnboundExecutionError( 

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

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

2821 "a binding." 

2822 ) 

2823 

2824 # look more closely at the mapper. 

2825 if mapper is not None: 

2826 try: 

2827 inspected_mapper = inspect(mapper) 

2828 except sa_exc.NoInspectionAvailable as err: 

2829 if isinstance(mapper, type): 

2830 raise exc.UnmappedClassError(mapper) from err 

2831 else: 

2832 raise 

2833 else: 

2834 inspected_mapper = None 

2835 

2836 # match up the mapper or clause in the __binds 

2837 if self.__binds: 

2838 # matching mappers and selectables to entries in the 

2839 # binds dictionary; supported use case. 

2840 if inspected_mapper: 

2841 for cls in inspected_mapper.class_.__mro__: 

2842 if cls in self.__binds: 

2843 return self.__binds[cls] 

2844 if clause is None: 

2845 clause = inspected_mapper.persist_selectable 

2846 

2847 if clause is not None: 

2848 plugin_subject = clause._propagate_attrs.get( 

2849 "plugin_subject", None 

2850 ) 

2851 

2852 if plugin_subject is not None: 

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

2854 if cls in self.__binds: 

2855 return self.__binds[cls] 

2856 

2857 for obj in visitors.iterate(clause): 

2858 if obj in self.__binds: 

2859 if TYPE_CHECKING: 

2860 assert isinstance(obj, Table) 

2861 return self.__binds[obj] 

2862 

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

2864 # return that 

2865 if self.bind: 

2866 return self.bind 

2867 

2868 context = [] 

2869 if inspected_mapper is not None: 

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

2871 if clause is not None: 

2872 context.append("SQL expression") 

2873 

2874 raise sa_exc.UnboundExecutionError( 

2875 f"Could not locate a bind configured on " 

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

2877 ) 

2878 

2879 @overload 

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

2881 

2882 @overload 

2883 def query( 

2884 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2886 

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

2888 

2889 # code within this block is **programmatically, 

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

2891 

2892 @overload 

2893 def query( 

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

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

2896 

2897 @overload 

2898 def query( 

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

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

2901 

2902 @overload 

2903 def query( 

2904 self, 

2905 __ent0: _TCCA[_T0], 

2906 __ent1: _TCCA[_T1], 

2907 __ent2: _TCCA[_T2], 

2908 __ent3: _TCCA[_T3], 

2909 /, 

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

2911 

2912 @overload 

2913 def query( 

2914 self, 

2915 __ent0: _TCCA[_T0], 

2916 __ent1: _TCCA[_T1], 

2917 __ent2: _TCCA[_T2], 

2918 __ent3: _TCCA[_T3], 

2919 __ent4: _TCCA[_T4], 

2920 /, 

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

2922 

2923 @overload 

2924 def query( 

2925 self, 

2926 __ent0: _TCCA[_T0], 

2927 __ent1: _TCCA[_T1], 

2928 __ent2: _TCCA[_T2], 

2929 __ent3: _TCCA[_T3], 

2930 __ent4: _TCCA[_T4], 

2931 __ent5: _TCCA[_T5], 

2932 /, 

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

2934 

2935 @overload 

2936 def query( 

2937 self, 

2938 __ent0: _TCCA[_T0], 

2939 __ent1: _TCCA[_T1], 

2940 __ent2: _TCCA[_T2], 

2941 __ent3: _TCCA[_T3], 

2942 __ent4: _TCCA[_T4], 

2943 __ent5: _TCCA[_T5], 

2944 __ent6: _TCCA[_T6], 

2945 /, 

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

2947 

2948 @overload 

2949 def query( 

2950 self, 

2951 __ent0: _TCCA[_T0], 

2952 __ent1: _TCCA[_T1], 

2953 __ent2: _TCCA[_T2], 

2954 __ent3: _TCCA[_T3], 

2955 __ent4: _TCCA[_T4], 

2956 __ent5: _TCCA[_T5], 

2957 __ent6: _TCCA[_T6], 

2958 __ent7: _TCCA[_T7], 

2959 /, 

2960 *entities: _ColumnsClauseArgument[Any], 

2961 ) -> RowReturningQuery[ 

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

2963 ]: ... 

2964 

2965 # END OVERLOADED FUNCTIONS self.query 

2966 

2967 @overload 

2968 def query( 

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

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

2971 

2972 def query( 

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

2974 ) -> Query[Any]: 

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

2976 :class:`_orm.Session`. 

2977 

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

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

2980 to construct ORM queries. 

2981 

2982 .. seealso:: 

2983 

2984 :ref:`unified_tutorial` 

2985 

2986 :ref:`queryguide_toplevel` 

2987 

2988 :ref:`query_api_toplevel` - legacy API doc 

2989 

2990 """ 

2991 

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

2993 

2994 def _identity_lookup( 

2995 self, 

2996 mapper: Mapper[_O], 

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

2998 identity_token: Any = None, 

2999 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

3001 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3002 bind_arguments: Optional[_BindArguments] = None, 

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

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

3005 

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

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

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

3009 check if was deleted). 

3010 

3011 e.g.:: 

3012 

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

3014 

3015 :param mapper: mapper in use 

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

3017 a tuple. 

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

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

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

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

3022 :param passive: passive load flag passed to 

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

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

3025 if the flag allows for SQL to be emitted. 

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

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

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

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

3030 relationship-loaded). 

3031 

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

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

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

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

3036 

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

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

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

3040 :class:`_query.Query` object. 

3041 

3042 

3043 """ 

3044 

3045 key = mapper.identity_key_from_primary_key( 

3046 primary_key_identity, identity_token=identity_token 

3047 ) 

3048 

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

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

3051 return return_value 

3052 

3053 @util.non_memoized_property 

3054 @contextlib.contextmanager 

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

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

3057 

3058 e.g.:: 

3059 

3060 with session.no_autoflush: 

3061 

3062 some_object = SomeClass() 

3063 session.add(some_object) 

3064 # won't autoflush 

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

3066 

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

3068 will not be subject to flushes occurring upon query 

3069 access. This is useful when initializing a series 

3070 of objects which involve existing database queries, 

3071 where the uncompleted object should not yet be flushed. 

3072 

3073 """ 

3074 autoflush = self.autoflush 

3075 self.autoflush = False 

3076 try: 

3077 yield self 

3078 finally: 

3079 self.autoflush = autoflush 

3080 

3081 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3085 "warning happened while initializing objects.", 

3086 sa_exc.SAWarning, 

3087 ) 

3088 def _autoflush(self) -> None: 

3089 if self.autoflush and not self._flushing: 

3090 try: 

3091 self.flush() 

3092 except sa_exc.StatementError as e: 

3093 # note we are reraising StatementError as opposed to 

3094 # raising FlushError with "chaining" to remain compatible 

3095 # with code that catches StatementError, IntegrityError, 

3096 # etc. 

3097 e.add_detail( 

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

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

3100 "flush is occurring prematurely" 

3101 ) 

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

3103 

3104 def refresh( 

3105 self, 

3106 instance: object, 

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

3108 with_for_update: ForUpdateParameter = None, 

3109 ) -> None: 

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

3111 

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

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

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

3115 value available in the current transaction. 

3116 

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

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

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

3120 

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

3122 can also refresh eagerly loaded attributes. 

3123 

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

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

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

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

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

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

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

3131 refreshed. 

3132 

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

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

3135 attributes for those which are named explicitly in the 

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

3137 

3138 .. tip:: 

3139 

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

3141 refreshing both column and relationship oriented attributes, its 

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

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

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

3145 once while having explicit control over relationship loader 

3146 strategies, use the 

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

3148 instead. 

3149 

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

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

3152 in database state outside of that transaction. Refreshing 

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

3154 where database rows have not yet been accessed. 

3155 

3156 :param attribute_names: optional. An iterable collection of 

3157 string attribute names indicating a subset of attributes to 

3158 be refreshed. 

3159 

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

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

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

3163 flags should match the parameters of 

3164 :meth:`_query.Query.with_for_update`. 

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

3166 

3167 .. seealso:: 

3168 

3169 :ref:`session_expire` - introductory material 

3170 

3171 :meth:`.Session.expire` 

3172 

3173 :meth:`.Session.expire_all` 

3174 

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

3176 to refresh objects as they would be loaded normally. 

3177 

3178 """ 

3179 try: 

3180 state = attributes.instance_state(instance) 

3181 except exc.NO_STATE as err: 

3182 raise exc.UnmappedInstanceError(instance) from err 

3183 

3184 self._expire_state(state, attribute_names) 

3185 

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

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

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

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

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

3191 # load_on_ident. 

3192 self._autoflush() 

3193 

3194 if with_for_update == {}: 

3195 raise sa_exc.ArgumentError( 

3196 "with_for_update should be the boolean value " 

3197 "True, or a dictionary with options. " 

3198 "A blank dictionary is ambiguous." 

3199 ) 

3200 

3201 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3202 

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

3204 if ( 

3205 loading._load_on_ident( 

3206 self, 

3207 stmt, 

3208 state.key, 

3209 refresh_state=state, 

3210 with_for_update=with_for_update, 

3211 only_load_props=attribute_names, 

3212 require_pk_cols=True, 

3213 # technically unnecessary as we just did autoflush 

3214 # above, however removes the additional unnecessary 

3215 # call to _autoflush() 

3216 no_autoflush=True, 

3217 is_user_refresh=True, 

3218 ) 

3219 is None 

3220 ): 

3221 raise sa_exc.InvalidRequestError( 

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

3223 ) 

3224 

3225 def expire_all(self) -> None: 

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

3227 

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

3229 a query will be issued using the 

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

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

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

3233 previously read in that same transaction, regardless of changes 

3234 in database state outside of that transaction. 

3235 

3236 To expire individual objects and individual attributes 

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

3238 

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

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

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

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

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

3244 assuming the transaction is isolated. 

3245 

3246 .. seealso:: 

3247 

3248 :ref:`session_expire` - introductory material 

3249 

3250 :meth:`.Session.expire` 

3251 

3252 :meth:`.Session.refresh` 

3253 

3254 :meth:`_orm.Query.populate_existing` 

3255 

3256 """ 

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

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

3259 

3260 def expire( 

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

3262 ) -> None: 

3263 """Expire the attributes on an instance. 

3264 

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

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

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

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

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

3270 previously read in that same transaction, regardless of changes 

3271 in database state outside of that transaction. 

3272 

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

3274 use :meth:`Session.expire_all`. 

3275 

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

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

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

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

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

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

3282 transaction. 

3283 

3284 :param instance: The instance to be refreshed. 

3285 :param attribute_names: optional list of string attribute names 

3286 indicating a subset of attributes to be expired. 

3287 

3288 .. seealso:: 

3289 

3290 :ref:`session_expire` - introductory material 

3291 

3292 :meth:`.Session.expire` 

3293 

3294 :meth:`.Session.refresh` 

3295 

3296 :meth:`_orm.Query.populate_existing` 

3297 

3298 """ 

3299 try: 

3300 state = attributes.instance_state(instance) 

3301 except exc.NO_STATE as err: 

3302 raise exc.UnmappedInstanceError(instance) from err 

3303 self._expire_state(state, attribute_names) 

3304 

3305 def _expire_state( 

3306 self, 

3307 state: InstanceState[Any], 

3308 attribute_names: Optional[Iterable[str]], 

3309 ) -> None: 

3310 self._validate_persistent(state) 

3311 if attribute_names: 

3312 state._expire_attributes(state.dict, attribute_names) 

3313 else: 

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

3315 # remove associations 

3316 cascaded = list( 

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

3318 ) 

3319 self._conditional_expire(state) 

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

3321 self._conditional_expire(st_) 

3322 

3323 def _conditional_expire( 

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

3325 ) -> None: 

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

3327 

3328 if state.key: 

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

3330 elif state in self._new: 

3331 self._new.pop(state) 

3332 state._detach(self) 

3333 

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

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

3336 

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

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

3339 

3340 """ 

3341 try: 

3342 state = attributes.instance_state(instance) 

3343 except exc.NO_STATE as err: 

3344 raise exc.UnmappedInstanceError(instance) from err 

3345 if state.session_id is not self.hash_key: 

3346 raise sa_exc.InvalidRequestError( 

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

3348 ) 

3349 

3350 cascaded = list( 

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

3352 ) 

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

3354 

3355 def _expunge_states( 

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

3357 ) -> None: 

3358 for state in states: 

3359 if state in self._new: 

3360 self._new.pop(state) 

3361 elif self.identity_map.contains_state(state): 

3362 self.identity_map.safe_discard(state) 

3363 self._deleted.pop(state, None) 

3364 elif self._transaction: 

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

3366 # in the transaction snapshot 

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

3368 statelib.InstanceState._detach_states( 

3369 states, self, to_transient=to_transient 

3370 ) 

3371 

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

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

3374 

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

3376 state as well as already persistent objects. 

3377 

3378 """ 

3379 

3380 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3381 for state in states: 

3382 mapper = _state_mapper(state) 

3383 

3384 # prevent against last minute dereferences of the object 

3385 obj = state.obj() 

3386 if obj is not None: 

3387 instance_key = mapper._identity_key_from_state(state) 

3388 

3389 if ( 

3390 _none_set.intersection(instance_key[1]) 

3391 and not mapper.allow_partial_pks 

3392 or _none_set.issuperset(instance_key[1]) 

3393 ): 

3394 raise exc.FlushError( 

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

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

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

3398 "that the mapped Column object is configured to " 

3399 "expect these generated values. Ensure also that " 

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

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

3402 % state_str(state) 

3403 ) 

3404 

3405 if state.key is None: 

3406 state.key = instance_key 

3407 elif state.key != instance_key: 

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

3409 # state has already replaced this one in the identity 

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

3411 self.identity_map.safe_discard(state) 

3412 trans = self._transaction 

3413 assert trans is not None 

3414 if state in trans._key_switches: 

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

3416 else: 

3417 orig_key = state.key 

3418 trans._key_switches[state] = ( 

3419 orig_key, 

3420 instance_key, 

3421 ) 

3422 state.key = instance_key 

3423 

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

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

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

3427 old = self.identity_map.replace(state) 

3428 if ( 

3429 old is not None 

3430 and mapper._identity_key_from_state(old) == instance_key 

3431 and old.obj() is not None 

3432 ): 

3433 util.warn( 

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

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

3436 "load operations occurring inside of an event handler " 

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

3438 ) 

3439 state._orphaned_outside_of_session = False 

3440 

3441 statelib.InstanceState._commit_all_states( 

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

3443 ) 

3444 

3445 self._register_altered(states) 

3446 

3447 if pending_to_persistent is not None: 

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

3449 pending_to_persistent(self, state) 

3450 

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

3452 for state in set(states).intersection(self._new): 

3453 self._new.pop(state) 

3454 

3455 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None: 

3456 if self._transaction: 

3457 for state in states: 

3458 if state in self._new: 

3459 self._transaction._new[state] = True 

3460 else: 

3461 self._transaction._dirty[state] = True 

3462 

3463 def _remove_newly_deleted( 

3464 self, states: Iterable[InstanceState[Any]] 

3465 ) -> None: 

3466 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3467 for state in states: 

3468 if self._transaction: 

3469 self._transaction._deleted[state] = True 

3470 

3471 if persistent_to_deleted is not None: 

3472 # get a strong reference before we pop out of 

3473 # self._deleted 

3474 obj = state.obj() # noqa 

3475 

3476 self.identity_map.safe_discard(state) 

3477 self._deleted.pop(state, None) 

3478 state._deleted = True 

3479 # can't call state._detach() here, because this state 

3480 # is still in the transaction snapshot and needs to be 

3481 # tracked as part of that 

3482 if persistent_to_deleted is not None: 

3483 persistent_to_deleted(self, state) 

3484 

3485 def add(self, instance: object, *, _warn: bool = True) -> None: 

3486 """Place an object into this :class:`_orm.Session`. 

3487 

3488 Objects that are in the :term:`transient` state when passed to the 

3489 :meth:`_orm.Session.add` method will move to the 

3490 :term:`pending` state, until the next flush, at which point they 

3491 will move to the :term:`persistent` state. 

3492 

3493 Objects that are in the :term:`detached` state when passed to the 

3494 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3495 state directly. 

3496 

3497 If the transaction used by the :class:`_orm.Session` is rolled back, 

3498 objects which were transient when they were passed to 

3499 :meth:`_orm.Session.add` will be moved back to the 

3500 :term:`transient` state, and will no longer be present within this 

3501 :class:`_orm.Session`. 

3502 

3503 .. seealso:: 

3504 

3505 :meth:`_orm.Session.add_all` 

3506 

3507 :ref:`session_adding` - at :ref:`session_basics` 

3508 

3509 """ 

3510 if _warn and self._warn_on_events: 

3511 self._flush_warning("Session.add()") 

3512 

3513 try: 

3514 state = attributes.instance_state(instance) 

3515 except exc.NO_STATE as err: 

3516 raise exc.UnmappedInstanceError(instance) from err 

3517 

3518 self._save_or_update_state(state) 

3519 

3520 def add_all(self, instances: Iterable[object]) -> None: 

3521 """Add the given collection of instances to this :class:`_orm.Session`. 

3522 

3523 See the documentation for :meth:`_orm.Session.add` for a general 

3524 behavioral description. 

3525 

3526 .. seealso:: 

3527 

3528 :meth:`_orm.Session.add` 

3529 

3530 :ref:`session_adding` - at :ref:`session_basics` 

3531 

3532 """ 

3533 

3534 if self._warn_on_events: 

3535 self._flush_warning("Session.add_all()") 

3536 

3537 for instance in instances: 

3538 self.add(instance, _warn=False) 

3539 

3540 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3541 state._orphaned_outside_of_session = False 

3542 self._save_or_update_impl(state) 

3543 

3544 mapper = _state_mapper(state) 

3545 for o, m, st_, dct_ in mapper.cascade_iterator( 

3546 "save-update", state, halt_on=self._contains_state 

3547 ): 

3548 self._save_or_update_impl(st_) 

3549 

3550 def delete(self, instance: object) -> None: 

3551 """Mark an instance as deleted. 

3552 

3553 The object is assumed to be either :term:`persistent` or 

3554 :term:`detached` when passed; after the method is called, the 

3555 object will remain in the :term:`persistent` state until the next 

3556 flush proceeds. During this time, the object will also be a member 

3557 of the :attr:`_orm.Session.deleted` collection. 

3558 

3559 When the next flush proceeds, the object will move to the 

3560 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3561 for its row within the current transaction. When the transaction 

3562 is successfully committed, 

3563 the deleted object is moved to the :term:`detached` state and is 

3564 no longer present within this :class:`_orm.Session`. 

3565 

3566 .. seealso:: 

3567 

3568 :ref:`session_deleting` - at :ref:`session_basics` 

3569 

3570 :meth:`.Session.delete_all` - multiple instance version 

3571 

3572 """ 

3573 if self._warn_on_events: 

3574 self._flush_warning("Session.delete()") 

3575 

3576 self._delete_impl(object_state(instance), instance, head=True) 

3577 

3578 def delete_all(self, instances: Iterable[object]) -> None: 

3579 """Calls :meth:`.Session.delete` on multiple instances. 

3580 

3581 .. seealso:: 

3582 

3583 :meth:`.Session.delete` - main documentation on delete 

3584 

3585 .. versionadded:: 2.1 

3586 

3587 """ 

3588 

3589 if self._warn_on_events: 

3590 self._flush_warning("Session.delete_all()") 

3591 

3592 for instance in instances: 

3593 self._delete_impl(object_state(instance), instance, head=True) 

3594 

3595 def _delete_impl( 

3596 self, state: InstanceState[Any], obj: object, head: bool 

3597 ) -> None: 

3598 if state.key is None: 

3599 if head: 

3600 raise sa_exc.InvalidRequestError( 

3601 "Instance '%s' is not persisted" % state_str(state) 

3602 ) 

3603 else: 

3604 return 

3605 

3606 to_attach = self._before_attach(state, obj) 

3607 

3608 if state in self._deleted: 

3609 return 

3610 

3611 self.identity_map.add(state) 

3612 

3613 if to_attach: 

3614 self._after_attach(state, obj) 

3615 

3616 if head: 

3617 # grab the cascades before adding the item to the deleted list 

3618 # so that autoflush does not delete the item 

3619 # the strong reference to the instance itself is significant here 

3620 cascade_states = list( 

3621 state.manager.mapper.cascade_iterator("delete", state) 

3622 ) 

3623 else: 

3624 cascade_states = None 

3625 

3626 self._deleted[state] = obj 

3627 

3628 if head: 

3629 if TYPE_CHECKING: 

3630 assert cascade_states is not None 

3631 for o, m, st_, dct_ in cascade_states: 

3632 self._delete_impl(st_, o, False) 

3633 

3634 def get( 

3635 self, 

3636 entity: _EntityBindKey[_O], 

3637 ident: _PKIdentityArgument, 

3638 *, 

3639 options: Optional[Sequence[ORMOption]] = None, 

3640 populate_existing: bool = False, 

3641 with_for_update: ForUpdateParameter = None, 

3642 identity_token: Optional[Any] = None, 

3643 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3644 bind_arguments: Optional[_BindArguments] = None, 

3645 ) -> Optional[_O]: 

3646 """Return an instance based on the given primary key identifier, 

3647 or ``None`` if not found. 

3648 

3649 E.g.:: 

3650 

3651 my_user = session.get(User, 5) 

3652 

3653 some_object = session.get(VersionedFoo, (5, 10)) 

3654 

3655 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3656 

3657 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3658 from the now legacy :meth:`_orm.Query.get` method. 

3659 

3660 :meth:`_orm.Session.get` is special in that it provides direct 

3661 access to the identity map of the :class:`.Session`. 

3662 If the given primary key identifier is present 

3663 in the local identity map, the object is returned 

3664 directly from this collection and no SQL is emitted, 

3665 unless the object has been marked fully expired. 

3666 If not present, 

3667 a SELECT is performed in order to locate the object. 

3668 

3669 :meth:`_orm.Session.get` also will perform a check if 

3670 the object is present in the identity map and 

3671 marked as expired - a SELECT 

3672 is emitted to refresh the object as well as to 

3673 ensure that the row is still present. 

3674 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3675 

3676 :param entity: a mapped class or :class:`.Mapper` indicating the 

3677 type of entity to be loaded. 

3678 

3679 :param ident: A scalar, tuple, or dictionary representing the 

3680 primary key. For a composite (e.g. multiple column) primary key, 

3681 a tuple or dictionary should be passed. 

3682 

3683 For a single-column primary key, the scalar calling form is typically 

3684 the most expedient. If the primary key of a row is the value "5", 

3685 the call looks like:: 

3686 

3687 my_object = session.get(SomeClass, 5) 

3688 

3689 The tuple form contains primary key values typically in 

3690 the order in which they correspond to the mapped 

3691 :class:`_schema.Table` 

3692 object's primary key columns, or if the 

3693 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3694 used, in 

3695 the order used for that parameter. For example, if the primary key 

3696 of a row is represented by the integer 

3697 digits "5, 10" the call would look like:: 

3698 

3699 my_object = session.get(SomeClass, (5, 10)) 

3700 

3701 The dictionary form should include as keys the mapped attribute names 

3702 corresponding to each element of the primary key. If the mapped class 

3703 has the attributes ``id``, ``version_id`` as the attributes which 

3704 store the object's primary key value, the call would look like:: 

3705 

3706 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3707 

3708 :param options: optional sequence of loader options which will be 

3709 applied to the query, if one is emitted. 

3710 

3711 :param populate_existing: causes the method to unconditionally emit 

3712 a SQL query and refresh the object with the newly loaded data, 

3713 regardless of whether or not the object is already present. 

3714 

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

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

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

3718 flags should match the parameters of 

3719 :meth:`_query.Query.with_for_update`. 

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

3721 

3722 :param execution_options: optional dictionary of execution options, 

3723 which will be associated with the query execution if one is emitted. 

3724 This dictionary can provide a subset of the options that are 

3725 accepted by :meth:`_engine.Connection.execution_options`, and may 

3726 also provide additional options understood only in an ORM context. 

3727 

3728 .. versionadded:: 1.4.29 

3729 

3730 .. seealso:: 

3731 

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

3733 options 

3734 

3735 :param bind_arguments: dictionary of additional arguments to determine 

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

3737 Contents of this dictionary are passed to the 

3738 :meth:`.Session.get_bind` method. 

3739 

3740 .. versionadded:: 2.0.0rc1 

3741 

3742 :return: The object instance, or ``None``. 

3743 

3744 """ # noqa: E501 

3745 return self._get_impl( 

3746 entity, 

3747 ident, 

3748 loading._load_on_pk_identity, 

3749 options=options, 

3750 populate_existing=populate_existing, 

3751 with_for_update=with_for_update, 

3752 identity_token=identity_token, 

3753 execution_options=execution_options, 

3754 bind_arguments=bind_arguments, 

3755 ) 

3756 

3757 def get_one( 

3758 self, 

3759 entity: _EntityBindKey[_O], 

3760 ident: _PKIdentityArgument, 

3761 *, 

3762 options: Optional[Sequence[ORMOption]] = None, 

3763 populate_existing: bool = False, 

3764 with_for_update: ForUpdateParameter = None, 

3765 identity_token: Optional[Any] = None, 

3766 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3767 bind_arguments: Optional[_BindArguments] = None, 

3768 ) -> _O: 

3769 """Return exactly one instance based on the given primary key 

3770 identifier, or raise an exception if not found. 

3771 

3772 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3773 

3774 For a detailed documentation of the arguments see the 

3775 method :meth:`.Session.get`. 

3776 

3777 .. versionadded:: 2.0.22 

3778 

3779 :return: The object instance. 

3780 

3781 .. seealso:: 

3782 

3783 :meth:`.Session.get` - equivalent method that instead 

3784 returns ``None`` if no row was found with the provided primary 

3785 key 

3786 

3787 """ 

3788 

3789 instance = self.get( 

3790 entity, 

3791 ident, 

3792 options=options, 

3793 populate_existing=populate_existing, 

3794 with_for_update=with_for_update, 

3795 identity_token=identity_token, 

3796 execution_options=execution_options, 

3797 bind_arguments=bind_arguments, 

3798 ) 

3799 

3800 if instance is None: 

3801 raise sa_exc.NoResultFound( 

3802 "No row was found when one was required" 

3803 ) 

3804 

3805 return instance 

3806 

3807 def _get_impl( 

3808 self, 

3809 entity: _EntityBindKey[_O], 

3810 primary_key_identity: _PKIdentityArgument, 

3811 db_load_fn: Callable[..., _O], 

3812 *, 

3813 options: Optional[Sequence[ExecutableOption]] = None, 

3814 populate_existing: bool = False, 

3815 with_for_update: ForUpdateParameter = None, 

3816 identity_token: Optional[Any] = None, 

3817 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3818 bind_arguments: Optional[_BindArguments] = None, 

3819 ) -> Optional[_O]: 

3820 # convert composite types to individual args 

3821 if ( 

3822 is_composite_class(primary_key_identity) 

3823 and type(primary_key_identity) 

3824 in descriptor_props._composite_getters 

3825 ): 

3826 getter = descriptor_props._composite_getters[ 

3827 type(primary_key_identity) 

3828 ] 

3829 primary_key_identity = getter(primary_key_identity) 

3830 

3831 mapper: Optional[Mapper[_O]] = inspect(entity) 

3832 

3833 if mapper is None or not mapper.is_mapper: 

3834 raise sa_exc.ArgumentError( 

3835 "Expected mapped class or mapper, got: %r" % entity 

3836 ) 

3837 

3838 is_dict = isinstance(primary_key_identity, dict) 

3839 if not is_dict: 

3840 primary_key_identity = util.to_list( 

3841 primary_key_identity, default=[None] 

3842 ) 

3843 

3844 if len(primary_key_identity) != len(mapper.primary_key): 

3845 raise sa_exc.InvalidRequestError( 

3846 "Incorrect number of values in identifier to formulate " 

3847 "primary key for session.get(); primary key columns " 

3848 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3849 ) 

3850 

3851 if is_dict: 

3852 pk_synonyms = mapper._pk_synonyms 

3853 

3854 if pk_synonyms: 

3855 correct_keys = set(pk_synonyms).intersection( 

3856 primary_key_identity 

3857 ) 

3858 

3859 if correct_keys: 

3860 primary_key_identity = dict(primary_key_identity) 

3861 for k in correct_keys: 

3862 primary_key_identity[pk_synonyms[k]] = ( 

3863 primary_key_identity[k] 

3864 ) 

3865 

3866 try: 

3867 primary_key_identity = list( 

3868 primary_key_identity[prop.key] 

3869 for prop in mapper._identity_key_props 

3870 ) 

3871 

3872 except KeyError as err: 

3873 raise sa_exc.InvalidRequestError( 

3874 "Incorrect names of values in identifier to formulate " 

3875 "primary key for session.get(); primary key attribute " 

3876 "names are %s (synonym names are also accepted)" 

3877 % ",".join( 

3878 "'%s'" % prop.key 

3879 for prop in mapper._identity_key_props 

3880 ) 

3881 ) from err 

3882 

3883 if ( 

3884 not populate_existing 

3885 and not mapper.always_refresh 

3886 and with_for_update is None 

3887 ): 

3888 instance = self._identity_lookup( 

3889 mapper, 

3890 primary_key_identity, 

3891 identity_token=identity_token, 

3892 execution_options=execution_options, 

3893 bind_arguments=bind_arguments, 

3894 ) 

3895 

3896 if instance is not None: 

3897 # reject calls for id in identity map but class 

3898 # mismatch. 

3899 if not isinstance(instance, mapper.class_): 

3900 return None 

3901 return instance 

3902 

3903 # TODO: this was being tested before, but this is not possible 

3904 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3905 

3906 # set_label_style() not strictly necessary, however this will ensure 

3907 # that tablename_colname style is used which at the moment is 

3908 # asserted in a lot of unit tests :) 

3909 

3910 load_options = context.QueryContext.default_load_options 

3911 

3912 if populate_existing: 

3913 load_options += {"_populate_existing": populate_existing} 

3914 statement = sql.select(mapper).set_label_style( 

3915 LABEL_STYLE_TABLENAME_PLUS_COL 

3916 ) 

3917 if with_for_update is not None: 

3918 statement._for_update_arg = ForUpdateArg._from_argument( 

3919 with_for_update 

3920 ) 

3921 

3922 if options: 

3923 statement = statement.options(*options) 

3924 if self.execution_options: 

3925 execution_options = self.execution_options.union(execution_options) 

3926 return db_load_fn( 

3927 self, 

3928 statement, 

3929 primary_key_identity, 

3930 load_options=load_options, 

3931 identity_token=identity_token, 

3932 execution_options=execution_options, 

3933 bind_arguments=bind_arguments, 

3934 ) 

3935 

3936 def merge( 

3937 self, 

3938 instance: _O, 

3939 *, 

3940 load: bool = True, 

3941 options: Optional[Sequence[ORMOption]] = None, 

3942 ) -> _O: 

3943 """Copy the state of a given instance into a corresponding instance 

3944 within this :class:`.Session`. 

3945 

3946 :meth:`.Session.merge` examines the primary key attributes of the 

3947 source instance, and attempts to reconcile it with an instance of the 

3948 same primary key in the session. If not found locally, it attempts 

3949 to load the object from the database based on primary key, and if 

3950 none can be located, creates a new instance. The state of each 

3951 attribute on the source instance is then copied to the target 

3952 instance. The resulting target instance is then returned by the 

3953 method; the original source instance is left unmodified, and 

3954 un-associated with the :class:`.Session` if not already. 

3955 

3956 This operation cascades to associated instances if the association is 

3957 mapped with ``cascade="merge"``. 

3958 

3959 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3960 

3961 :param instance: Instance to be merged. 

3962 :param load: Boolean, when False, :meth:`.merge` switches into 

3963 a "high performance" mode which causes it to forego emitting history 

3964 events as well as all database access. This flag is used for 

3965 cases such as transferring graphs of objects into a :class:`.Session` 

3966 from a second level cache, or to transfer just-loaded objects 

3967 into the :class:`.Session` owned by a worker thread or process 

3968 without re-querying the database. 

3969 

3970 The ``load=False`` use case adds the caveat that the given 

3971 object has to be in a "clean" state, that is, has no pending changes 

3972 to be flushed - even if the incoming object is detached from any 

3973 :class:`.Session`. This is so that when 

3974 the merge operation populates local attributes and 

3975 cascades to related objects and 

3976 collections, the values can be "stamped" onto the 

3977 target object as is, without generating any history or attribute 

3978 events, and without the need to reconcile the incoming data with 

3979 any existing related objects or collections that might not 

3980 be loaded. The resulting objects from ``load=False`` are always 

3981 produced as "clean", so it is only appropriate that the given objects 

3982 should be "clean" as well, else this suggests a mis-use of the 

3983 method. 

3984 :param options: optional sequence of loader options which will be 

3985 applied to the :meth:`_orm.Session.get` method when the merge 

3986 operation loads the existing version of the object from the database. 

3987 

3988 .. versionadded:: 1.4.24 

3989 

3990 

3991 .. seealso:: 

3992 

3993 :func:`.make_transient_to_detached` - provides for an alternative 

3994 means of "merging" a single object into the :class:`.Session` 

3995 

3996 :meth:`.Session.merge_all` - multiple instance version 

3997 

3998 """ 

3999 

4000 if self._warn_on_events: 

4001 self._flush_warning("Session.merge()") 

4002 

4003 if load: 

4004 # flush current contents if we expect to load data 

4005 self._autoflush() 

4006 

4007 with self.no_autoflush: 

4008 return self._merge( 

4009 object_state(instance), 

4010 attributes.instance_dict(instance), 

4011 load=load, 

4012 options=options, 

4013 _recursive={}, 

4014 _resolve_conflict_map={}, 

4015 ) 

4016 

4017 def merge_all( 

4018 self, 

4019 instances: Iterable[_O], 

4020 *, 

4021 load: bool = True, 

4022 options: Optional[Sequence[ORMOption]] = None, 

4023 ) -> Sequence[_O]: 

4024 """Calls :meth:`.Session.merge` on multiple instances. 

4025 

4026 .. seealso:: 

4027 

4028 :meth:`.Session.merge` - main documentation on merge 

4029 

4030 .. versionadded:: 2.1 

4031 

4032 """ 

4033 

4034 if self._warn_on_events: 

4035 self._flush_warning("Session.merge_all()") 

4036 

4037 if load: 

4038 # flush current contents if we expect to load data 

4039 self._autoflush() 

4040 

4041 return [ 

4042 self._merge( 

4043 object_state(instance), 

4044 attributes.instance_dict(instance), 

4045 load=load, 

4046 options=options, 

4047 _recursive={}, 

4048 _resolve_conflict_map={}, 

4049 ) 

4050 for instance in instances 

4051 ] 

4052 

4053 def _merge( 

4054 self, 

4055 state: InstanceState[_O], 

4056 state_dict: _InstanceDict, 

4057 *, 

4058 options: Optional[Sequence[ORMOption]] = None, 

4059 load: bool, 

4060 _recursive: Dict[Any, object], 

4061 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4062 ) -> _O: 

4063 mapper: Mapper[_O] = _state_mapper(state) 

4064 if state in _recursive: 

4065 return cast(_O, _recursive[state]) 

4066 

4067 new_instance = False 

4068 key = state.key 

4069 

4070 merged: Optional[_O] 

4071 

4072 if key is None: 

4073 if state in self._new: 

4074 util.warn( 

4075 "Instance %s is already pending in this Session yet is " 

4076 "being merged again; this is probably not what you want " 

4077 "to do" % state_str(state) 

4078 ) 

4079 

4080 if not load: 

4081 raise sa_exc.InvalidRequestError( 

4082 "merge() with load=False option does not support " 

4083 "objects transient (i.e. unpersisted) objects. flush() " 

4084 "all changes on mapped instances before merging with " 

4085 "load=False." 

4086 ) 

4087 key = mapper._identity_key_from_state(state) 

4088 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4089 1 

4090 ] and ( 

4091 not _none_set.intersection(key[1]) 

4092 or ( 

4093 mapper.allow_partial_pks 

4094 and not _none_set.issuperset(key[1]) 

4095 ) 

4096 ) 

4097 else: 

4098 key_is_persistent = True 

4099 

4100 merged = self.identity_map.get(key) 

4101 

4102 if merged is None: 

4103 if key_is_persistent and key in _resolve_conflict_map: 

4104 merged = cast(_O, _resolve_conflict_map[key]) 

4105 

4106 elif not load: 

4107 if state.modified: 

4108 raise sa_exc.InvalidRequestError( 

4109 "merge() with load=False option does not support " 

4110 "objects marked as 'dirty'. flush() all changes on " 

4111 "mapped instances before merging with load=False." 

4112 ) 

4113 merged = mapper.class_manager.new_instance() 

4114 merged_state = attributes.instance_state(merged) 

4115 merged_state.key = key 

4116 self._update_impl(merged_state) 

4117 new_instance = True 

4118 

4119 elif key_is_persistent: 

4120 merged = self.get( 

4121 mapper.class_, 

4122 key[1], 

4123 identity_token=key[2], 

4124 options=options, 

4125 ) 

4126 

4127 if merged is None: 

4128 merged = mapper.class_manager.new_instance() 

4129 merged_state = attributes.instance_state(merged) 

4130 merged_dict = attributes.instance_dict(merged) 

4131 new_instance = True 

4132 self._save_or_update_state(merged_state) 

4133 else: 

4134 merged_state = attributes.instance_state(merged) 

4135 merged_dict = attributes.instance_dict(merged) 

4136 

4137 _recursive[state] = merged 

4138 _resolve_conflict_map[key] = merged 

4139 

4140 # check that we didn't just pull the exact same 

4141 # state out. 

4142 if state is not merged_state: 

4143 # version check if applicable 

4144 if mapper.version_id_col is not None: 

4145 existing_version = mapper._get_state_attr_by_column( 

4146 state, 

4147 state_dict, 

4148 mapper.version_id_col, 

4149 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4150 ) 

4151 

4152 merged_version = mapper._get_state_attr_by_column( 

4153 merged_state, 

4154 merged_dict, 

4155 mapper.version_id_col, 

4156 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4157 ) 

4158 

4159 if ( 

4160 existing_version 

4161 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4162 and merged_version 

4163 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4164 and existing_version != merged_version 

4165 ): 

4166 raise exc.StaleDataError( 

4167 "Version id '%s' on merged state %s " 

4168 "does not match existing version '%s'. " 

4169 "Leave the version attribute unset when " 

4170 "merging to update the most recent version." 

4171 % ( 

4172 existing_version, 

4173 state_str(merged_state), 

4174 merged_version, 

4175 ) 

4176 ) 

4177 

4178 merged_state.load_path = state.load_path 

4179 merged_state.load_options = state.load_options 

4180 

4181 # since we are copying load_options, we need to copy 

4182 # the callables_ that would have been generated by those 

4183 # load_options. 

4184 # assumes that the callables we put in state.callables_ 

4185 # are not instance-specific (which they should not be) 

4186 merged_state._copy_callables(state) 

4187 

4188 for prop in mapper.iterate_properties: 

4189 prop.merge( 

4190 self, 

4191 state, 

4192 state_dict, 

4193 merged_state, 

4194 merged_dict, 

4195 load, 

4196 _recursive, 

4197 _resolve_conflict_map, 

4198 ) 

4199 

4200 if not load: 

4201 # remove any history 

4202 merged_state._commit_all(merged_dict, self.identity_map) 

4203 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4204 merged_state, None 

4205 ) 

4206 

4207 if new_instance: 

4208 merged_state.manager.dispatch.load(merged_state, None) 

4209 

4210 return merged 

4211 

4212 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4213 if not self.identity_map.contains_state(state): 

4214 raise sa_exc.InvalidRequestError( 

4215 "Instance '%s' is not persistent within this Session" 

4216 % state_str(state) 

4217 ) 

4218 

4219 def _save_impl(self, state: InstanceState[Any]) -> None: 

4220 if state.key is not None: 

4221 raise sa_exc.InvalidRequestError( 

4222 "Object '%s' already has an identity - " 

4223 "it can't be registered as pending" % state_str(state) 

4224 ) 

4225 

4226 obj = state.obj() 

4227 to_attach = self._before_attach(state, obj) 

4228 if state not in self._new: 

4229 self._new[state] = obj 

4230 state.insert_order = len(self._new) 

4231 if to_attach: 

4232 self._after_attach(state, obj) 

4233 

4234 def _update_impl( 

4235 self, state: InstanceState[Any], revert_deletion: bool = False 

4236 ) -> None: 

4237 if state.key is None: 

4238 raise sa_exc.InvalidRequestError( 

4239 "Instance '%s' is not persisted" % state_str(state) 

4240 ) 

4241 

4242 if state._deleted: 

4243 if revert_deletion: 

4244 if not state._attached: 

4245 return 

4246 del state._deleted 

4247 else: 

4248 raise sa_exc.InvalidRequestError( 

4249 "Instance '%s' has been deleted. " 

4250 "Use the make_transient() " 

4251 "function to send this object back " 

4252 "to the transient state." % state_str(state) 

4253 ) 

4254 

4255 obj = state.obj() 

4256 

4257 # check for late gc 

4258 if obj is None: 

4259 return 

4260 

4261 to_attach = self._before_attach(state, obj) 

4262 

4263 self._deleted.pop(state, None) 

4264 if revert_deletion: 

4265 self.identity_map.replace(state) 

4266 else: 

4267 self.identity_map.add(state) 

4268 

4269 if to_attach: 

4270 self._after_attach(state, obj) 

4271 elif revert_deletion: 

4272 self.dispatch.deleted_to_persistent(self, state) 

4273 

4274 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4275 if state.key is None: 

4276 self._save_impl(state) 

4277 else: 

4278 self._update_impl(state) 

4279 

4280 def enable_relationship_loading(self, obj: object) -> None: 

4281 """Associate an object with this :class:`.Session` for related 

4282 object loading. 

4283 

4284 .. warning:: 

4285 

4286 :meth:`.enable_relationship_loading` exists to serve special 

4287 use cases and is not recommended for general use. 

4288 

4289 Accesses of attributes mapped with :func:`_orm.relationship` 

4290 will attempt to load a value from the database using this 

4291 :class:`.Session` as the source of connectivity. The values 

4292 will be loaded based on foreign key and primary key values 

4293 present on this object - if not present, then those relationships 

4294 will be unavailable. 

4295 

4296 The object will be attached to this session, but will 

4297 **not** participate in any persistence operations; its state 

4298 for almost all purposes will remain either "transient" or 

4299 "detached", except for the case of relationship loading. 

4300 

4301 Also note that backrefs will often not work as expected. 

4302 Altering a relationship-bound attribute on the target object 

4303 may not fire off a backref event, if the effective value 

4304 is what was already loaded from a foreign-key-holding value. 

4305 

4306 The :meth:`.Session.enable_relationship_loading` method is 

4307 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4308 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4309 an object to remain transient while still being able to load 

4310 related items. 

4311 

4312 To make a transient object associated with a :class:`.Session` 

4313 via :meth:`.Session.enable_relationship_loading` pending, add 

4314 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4315 If the object instead represents an existing identity in the database, 

4316 it should be merged using :meth:`.Session.merge`. 

4317 

4318 :meth:`.Session.enable_relationship_loading` does not improve 

4319 behavior when the ORM is used normally - object references should be 

4320 constructed at the object level, not at the foreign key level, so 

4321 that they are present in an ordinary way before flush() 

4322 proceeds. This method is not intended for general use. 

4323 

4324 .. seealso:: 

4325 

4326 :paramref:`_orm.relationship.load_on_pending` - this flag 

4327 allows per-relationship loading of many-to-ones on items that 

4328 are pending. 

4329 

4330 :func:`.make_transient_to_detached` - allows for an object to 

4331 be added to a :class:`.Session` without SQL emitted, which then 

4332 will unexpire attributes on access. 

4333 

4334 """ 

4335 try: 

4336 state = attributes.instance_state(obj) 

4337 except exc.NO_STATE as err: 

4338 raise exc.UnmappedInstanceError(obj) from err 

4339 

4340 to_attach = self._before_attach(state, obj) 

4341 state._load_pending = True 

4342 if to_attach: 

4343 self._after_attach(state, obj) 

4344 

4345 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4346 self._autobegin_t() 

4347 

4348 if state.session_id == self.hash_key: 

4349 return False 

4350 

4351 if state.session_id and state.session_id in _sessions: 

4352 raise sa_exc.InvalidRequestError( 

4353 "Object '%s' is already attached to session '%s' " 

4354 "(this is '%s')" 

4355 % (state_str(state), state.session_id, self.hash_key) 

4356 ) 

4357 

4358 self.dispatch.before_attach(self, state) 

4359 

4360 return True 

4361 

4362 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4363 state.session_id = self.hash_key 

4364 if state.modified and state._strong_obj is None: 

4365 state._strong_obj = obj 

4366 self.dispatch.after_attach(self, state) 

4367 

4368 if state.key: 

4369 self.dispatch.detached_to_persistent(self, state) 

4370 else: 

4371 self.dispatch.transient_to_pending(self, state) 

4372 

4373 def __contains__(self, instance: object) -> bool: 

4374 """Return True if the instance is associated with this session. 

4375 

4376 The instance may be pending or persistent within the Session for a 

4377 result of True. 

4378 

4379 """ 

4380 try: 

4381 state = attributes.instance_state(instance) 

4382 except exc.NO_STATE as err: 

4383 raise exc.UnmappedInstanceError(instance) from err 

4384 return self._contains_state(state) 

4385 

4386 def __iter__(self) -> Iterator[object]: 

4387 """Iterate over all pending or persistent instances within this 

4388 Session. 

4389 

4390 """ 

4391 return iter( 

4392 list(self._new.values()) + list(self.identity_map.values()) 

4393 ) 

4394 

4395 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4396 return state in self._new or self.identity_map.contains_state(state) 

4397 

4398 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4399 """Flush all the object changes to the database. 

4400 

4401 Writes out all pending object creations, deletions and modifications 

4402 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4403 automatically ordered by the Session's unit of work dependency 

4404 solver. 

4405 

4406 Database operations will be issued in the current transactional 

4407 context and do not affect the state of the transaction, unless an 

4408 error occurs, in which case the entire transaction is rolled back. 

4409 You may flush() as often as you like within a transaction to move 

4410 changes from Python to the database's transaction buffer. 

4411 

4412 :param objects: Optional; restricts the flush operation to operate 

4413 only on elements that are in the given collection. 

4414 

4415 This feature is for an extremely narrow set of use cases where 

4416 particular objects may need to be operated upon before the 

4417 full flush() occurs. It is not intended for general use. 

4418 

4419 .. deprecated:: 2.1 

4420 

4421 """ 

4422 

4423 if self._flushing: 

4424 raise sa_exc.InvalidRequestError("Session is already flushing") 

4425 

4426 if self._is_clean(): 

4427 return 

4428 try: 

4429 self._flushing = True 

4430 self._flush(objects) 

4431 finally: 

4432 self._flushing = False 

4433 

4434 def _flush_warning(self, method: Any) -> None: 

4435 util.warn( 

4436 "Usage of the '%s' operation is not currently supported " 

4437 "within the execution stage of the flush process. " 

4438 "Results may not be consistent. Consider using alternative " 

4439 "event listeners or connection-level operations instead." % method 

4440 ) 

4441 

4442 def _is_clean(self) -> bool: 

4443 return ( 

4444 not self.identity_map.check_modified() 

4445 and not self._deleted 

4446 and not self._new 

4447 ) 

4448 

4449 # have this here since it otherwise causes issues with the proxy 

4450 # method generation 

4451 @deprecated_params( 

4452 objects=( 

4453 "2.1", 

4454 "The `objects` parameter of `Session.flush` is deprecated", 

4455 ) 

4456 ) 

4457 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4458 dirty = self._dirty_states 

4459 if not dirty and not self._deleted and not self._new: 

4460 self.identity_map._modified.clear() 

4461 return 

4462 

4463 flush_context = UOWTransaction(self) 

4464 

4465 if self.dispatch.before_flush: 

4466 self.dispatch.before_flush(self, flush_context, objects) 

4467 # re-establish "dirty states" in case the listeners 

4468 # added 

4469 dirty = self._dirty_states 

4470 

4471 deleted = set(self._deleted) 

4472 new = set(self._new) 

4473 

4474 dirty = set(dirty).difference(deleted) 

4475 

4476 # create the set of all objects we want to operate upon 

4477 if objects: 

4478 # specific list passed in 

4479 objset = set() 

4480 for o in objects: 

4481 try: 

4482 state = attributes.instance_state(o) 

4483 

4484 except exc.NO_STATE as err: 

4485 raise exc.UnmappedInstanceError(o) from err 

4486 objset.add(state) 

4487 else: 

4488 objset = None 

4489 

4490 # store objects whose fate has been decided 

4491 processed = set() 

4492 

4493 # put all saves/updates into the flush context. detect top-level 

4494 # orphans and throw them into deleted. 

4495 if objset: 

4496 proc = new.union(dirty).intersection(objset).difference(deleted) 

4497 else: 

4498 proc = new.union(dirty).difference(deleted) 

4499 

4500 for state in proc: 

4501 is_orphan = _state_mapper(state)._is_orphan(state) 

4502 

4503 is_persistent_orphan = is_orphan and state.has_identity 

4504 

4505 if ( 

4506 is_orphan 

4507 and not is_persistent_orphan 

4508 and state._orphaned_outside_of_session 

4509 ): 

4510 self._expunge_states([state]) 

4511 else: 

4512 _reg = flush_context.register_object( 

4513 state, isdelete=is_persistent_orphan 

4514 ) 

4515 assert _reg, "Failed to add object to the flush context!" 

4516 processed.add(state) 

4517 

4518 # put all remaining deletes into the flush context. 

4519 if objset: 

4520 proc = deleted.intersection(objset).difference(processed) 

4521 else: 

4522 proc = deleted.difference(processed) 

4523 for state in proc: 

4524 _reg = flush_context.register_object(state, isdelete=True) 

4525 assert _reg, "Failed to add object to the flush context!" 

4526 

4527 if not flush_context.has_work: 

4528 return 

4529 

4530 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4531 try: 

4532 self._warn_on_events = True 

4533 try: 

4534 flush_context.execute() 

4535 finally: 

4536 self._warn_on_events = False 

4537 

4538 self.dispatch.after_flush(self, flush_context) 

4539 

4540 flush_context.finalize_flush_changes() 

4541 

4542 if not objects and self.identity_map._modified: 

4543 len_ = len(self.identity_map._modified) 

4544 

4545 statelib.InstanceState._commit_all_states( 

4546 [ 

4547 (state, state.dict) 

4548 for state in self.identity_map._modified 

4549 ], 

4550 instance_dict=self.identity_map, 

4551 ) 

4552 util.warn( 

4553 "Attribute history events accumulated on %d " 

4554 "previously clean instances " 

4555 "within inner-flush event handlers have been " 

4556 "reset, and will not result in database updates. " 

4557 "Consider using set_committed_value() within " 

4558 "inner-flush event handlers to avoid this warning." % len_ 

4559 ) 

4560 

4561 # useful assertions: 

4562 # if not objects: 

4563 # assert not self.identity_map._modified 

4564 # else: 

4565 # assert self.identity_map._modified == \ 

4566 # self.identity_map._modified.difference(objects) 

4567 

4568 self.dispatch.after_flush_postexec(self, flush_context) 

4569 

4570 transaction.commit() 

4571 

4572 except: 

4573 with util.safe_reraise(): 

4574 transaction.rollback(_capture_exception=True) 

4575 

4576 def bulk_save_objects( 

4577 self, 

4578 objects: Iterable[object], 

4579 return_defaults: bool = False, 

4580 update_changed_only: bool = True, 

4581 preserve_order: bool = True, 

4582 ) -> None: 

4583 """Perform a bulk save of the given list of objects. 

4584 

4585 .. legacy:: 

4586 

4587 This method is a legacy feature as of the 2.0 series of 

4588 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4589 the sections :ref:`orm_queryguide_bulk_insert` and 

4590 :ref:`orm_queryguide_bulk_update`. 

4591 

4592 For general INSERT and UPDATE of existing ORM mapped objects, 

4593 prefer standard :term:`unit of work` data management patterns, 

4594 introduced in the :ref:`unified_tutorial` at 

4595 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4596 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4597 which solves previous issues of bulk INSERT slowness. 

4598 

4599 :param objects: a sequence of mapped object instances. The mapped 

4600 objects are persisted as is, and are **not** associated with the 

4601 :class:`.Session` afterwards. 

4602 

4603 For each object, whether the object is sent as an INSERT or an 

4604 UPDATE is dependent on the same rules used by the :class:`.Session` 

4605 in traditional operation; if the object has the 

4606 :attr:`.InstanceState.key` 

4607 attribute set, then the object is assumed to be "detached" and 

4608 will result in an UPDATE. Otherwise, an INSERT is used. 

4609 

4610 In the case of an UPDATE, statements are grouped based on which 

4611 attributes have changed, and are thus to be the subject of each 

4612 SET clause. If ``update_changed_only`` is False, then all 

4613 attributes present within each object are applied to the UPDATE 

4614 statement, which may help in allowing the statements to be grouped 

4615 together into a larger executemany(), and will also reduce the 

4616 overhead of checking history on attributes. 

4617 

4618 :param return_defaults: when True, rows that are missing values which 

4619 generate defaults, namely integer primary key defaults and sequences, 

4620 will be inserted **one at a time**, so that the primary key value 

4621 is available. In particular this will allow joined-inheritance 

4622 and other multi-table mappings to insert correctly without the need 

4623 to provide primary key values ahead of time; however, 

4624 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4625 reduces the performance gains** of the method overall. It is strongly 

4626 advised to please use the standard :meth:`_orm.Session.add_all` 

4627 approach. 

4628 

4629 :param update_changed_only: when True, UPDATE statements are rendered 

4630 based on those attributes in each state that have logged changes. 

4631 When False, all attributes present are rendered into the SET clause 

4632 with the exception of primary key attributes. 

4633 

4634 :param preserve_order: when True, the order of inserts and updates 

4635 matches exactly the order in which the objects are given. When 

4636 False, common types of objects are grouped into inserts 

4637 and updates, to allow for more batching opportunities. 

4638 

4639 .. seealso:: 

4640 

4641 :doc:`queryguide/dml` 

4642 

4643 :meth:`.Session.bulk_insert_mappings` 

4644 

4645 :meth:`.Session.bulk_update_mappings` 

4646 

4647 """ 

4648 

4649 obj_states: Iterable[InstanceState[Any]] 

4650 

4651 obj_states = (attributes.instance_state(obj) for obj in objects) 

4652 

4653 if not preserve_order: 

4654 # the purpose of this sort is just so that common mappers 

4655 # and persistence states are grouped together, so that groupby 

4656 # will return a single group for a particular type of mapper. 

4657 # it's not trying to be deterministic beyond that. 

4658 obj_states = sorted( 

4659 obj_states, 

4660 key=lambda state: (id(state.mapper), state.key is not None), 

4661 ) 

4662 

4663 def grouping_key( 

4664 state: InstanceState[_O], 

4665 ) -> Tuple[Mapper[_O], bool]: 

4666 return (state.mapper, state.key is not None) 

4667 

4668 for (mapper, isupdate), states in itertools.groupby( 

4669 obj_states, grouping_key 

4670 ): 

4671 self._bulk_save_mappings( 

4672 mapper, 

4673 states, 

4674 isupdate=isupdate, 

4675 isstates=True, 

4676 return_defaults=return_defaults, 

4677 update_changed_only=update_changed_only, 

4678 render_nulls=False, 

4679 ) 

4680 

4681 def bulk_insert_mappings( 

4682 self, 

4683 mapper: Mapper[Any], 

4684 mappings: Iterable[Dict[str, Any]], 

4685 return_defaults: bool = False, 

4686 render_nulls: bool = False, 

4687 ) -> None: 

4688 """Perform a bulk insert of the given list of mapping dictionaries. 

4689 

4690 .. legacy:: 

4691 

4692 This method is a legacy feature as of the 2.0 series of 

4693 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4694 the sections :ref:`orm_queryguide_bulk_insert` and 

4695 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4696 implementation details with this method and adds new features 

4697 as well. 

4698 

4699 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4700 object, 

4701 representing the single kind of object represented within the mapping 

4702 list. 

4703 

4704 :param mappings: a sequence of dictionaries, each one containing the 

4705 state of the mapped row to be inserted, in terms of the attribute 

4706 names on the mapped class. If the mapping refers to multiple tables, 

4707 such as a joined-inheritance mapping, each dictionary must contain all 

4708 keys to be populated into all tables. 

4709 

4710 :param return_defaults: when True, the INSERT process will be altered 

4711 to ensure that newly generated primary key values will be fetched. 

4712 The rationale for this parameter is typically to enable 

4713 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4714 be bulk inserted. 

4715 

4716 .. note:: for backends that don't support RETURNING, the 

4717 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4718 parameter can significantly decrease performance as INSERT 

4719 statements can no longer be batched. See 

4720 :ref:`engine_insertmanyvalues` 

4721 for background on which backends are affected. 

4722 

4723 :param render_nulls: When True, a value of ``None`` will result 

4724 in a NULL value being included in the INSERT statement, rather 

4725 than the column being omitted from the INSERT. This allows all 

4726 the rows being INSERTed to have the identical set of columns which 

4727 allows the full set of rows to be batched to the DBAPI. Normally, 

4728 each column-set that contains a different combination of NULL values 

4729 than the previous row must omit a different series of columns from 

4730 the rendered INSERT statement, which means it must be emitted as a 

4731 separate statement. By passing this flag, the full set of rows 

4732 are guaranteed to be batchable into one batch; the cost however is 

4733 that server-side defaults which are invoked by an omitted column will 

4734 be skipped, so care must be taken to ensure that these are not 

4735 necessary. 

4736 

4737 .. warning:: 

4738 

4739 When this flag is set, **server side default SQL values will 

4740 not be invoked** for those columns that are inserted as NULL; 

4741 the NULL value will be sent explicitly. Care must be taken 

4742 to ensure that no server-side default functions need to be 

4743 invoked for the operation as a whole. 

4744 

4745 .. seealso:: 

4746 

4747 :doc:`queryguide/dml` 

4748 

4749 :meth:`.Session.bulk_save_objects` 

4750 

4751 :meth:`.Session.bulk_update_mappings` 

4752 

4753 """ 

4754 self._bulk_save_mappings( 

4755 mapper, 

4756 mappings, 

4757 isupdate=False, 

4758 isstates=False, 

4759 return_defaults=return_defaults, 

4760 update_changed_only=False, 

4761 render_nulls=render_nulls, 

4762 ) 

4763 

4764 def bulk_update_mappings( 

4765 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4766 ) -> None: 

4767 """Perform a bulk update of the given list of mapping dictionaries. 

4768 

4769 .. legacy:: 

4770 

4771 This method is a legacy feature as of the 2.0 series of 

4772 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4773 the sections :ref:`orm_queryguide_bulk_insert` and 

4774 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4775 implementation details with this method and adds new features 

4776 as well. 

4777 

4778 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4779 object, 

4780 representing the single kind of object represented within the mapping 

4781 list. 

4782 

4783 :param mappings: a sequence of dictionaries, each one containing the 

4784 state of the mapped row to be updated, in terms of the attribute names 

4785 on the mapped class. If the mapping refers to multiple tables, such 

4786 as a joined-inheritance mapping, each dictionary may contain keys 

4787 corresponding to all tables. All those keys which are present and 

4788 are not part of the primary key are applied to the SET clause of the 

4789 UPDATE statement; the primary key values, which are required, are 

4790 applied to the WHERE clause. 

4791 

4792 

4793 .. seealso:: 

4794 

4795 :doc:`queryguide/dml` 

4796 

4797 :meth:`.Session.bulk_insert_mappings` 

4798 

4799 :meth:`.Session.bulk_save_objects` 

4800 

4801 """ 

4802 self._bulk_save_mappings( 

4803 mapper, 

4804 mappings, 

4805 isupdate=True, 

4806 isstates=False, 

4807 return_defaults=False, 

4808 update_changed_only=False, 

4809 render_nulls=False, 

4810 ) 

4811 

4812 def _bulk_save_mappings( 

4813 self, 

4814 mapper: Mapper[_O], 

4815 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4816 *, 

4817 isupdate: bool, 

4818 isstates: bool, 

4819 return_defaults: bool, 

4820 update_changed_only: bool, 

4821 render_nulls: bool, 

4822 ) -> None: 

4823 mapper = _class_to_mapper(mapper) 

4824 self._flushing = True 

4825 

4826 transaction = self._autobegin_t()._begin() 

4827 try: 

4828 if isupdate: 

4829 bulk_persistence._bulk_update( 

4830 mapper, 

4831 mappings, 

4832 transaction, 

4833 isstates=isstates, 

4834 update_changed_only=update_changed_only, 

4835 ) 

4836 else: 

4837 bulk_persistence._bulk_insert( 

4838 mapper, 

4839 mappings, 

4840 transaction, 

4841 isstates=isstates, 

4842 return_defaults=return_defaults, 

4843 render_nulls=render_nulls, 

4844 ) 

4845 transaction.commit() 

4846 

4847 except: 

4848 with util.safe_reraise(): 

4849 transaction.rollback(_capture_exception=True) 

4850 finally: 

4851 self._flushing = False 

4852 

4853 def is_modified( 

4854 self, instance: object, include_collections: bool = True 

4855 ) -> bool: 

4856 r"""Return ``True`` if the given instance has locally 

4857 modified attributes. 

4858 

4859 This method retrieves the history for each instrumented 

4860 attribute on the instance and performs a comparison of the current 

4861 value to its previously flushed or committed value, if any. 

4862 

4863 It is in effect a more expensive and accurate 

4864 version of checking for the given instance in the 

4865 :attr:`.Session.dirty` collection; a full test for 

4866 each attribute's net "dirty" status is performed. 

4867 

4868 E.g.:: 

4869 

4870 return session.is_modified(someobject) 

4871 

4872 A few caveats to this method apply: 

4873 

4874 * Instances present in the :attr:`.Session.dirty` collection may 

4875 report ``False`` when tested with this method. This is because 

4876 the object may have received change events via attribute mutation, 

4877 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4878 is the same as that loaded from the database, resulting in no net 

4879 change here. 

4880 * Scalar attributes may not have recorded the previously set 

4881 value when a new value was applied, if the attribute was not loaded, 

4882 or was expired, at the time the new value was received - in these 

4883 cases, the attribute is assumed to have a change, even if there is 

4884 ultimately no net change against its database value. SQLAlchemy in 

4885 most cases does not need the "old" value when a set event occurs, so 

4886 it skips the expense of a SQL call if the old value isn't present, 

4887 based on the assumption that an UPDATE of the scalar value is 

4888 usually needed, and in those few cases where it isn't, is less 

4889 expensive on average than issuing a defensive SELECT. 

4890 

4891 The "old" value is fetched unconditionally upon set only if the 

4892 attribute container has the ``active_history`` flag set to ``True``. 

4893 This flag is set typically for primary key attributes and scalar 

4894 object references that are not a simple many-to-one. To set this 

4895 flag for any arbitrary mapped column, use the ``active_history`` 

4896 argument with :func:`.column_property`. 

4897 

4898 :param instance: mapped instance to be tested for pending changes. 

4899 :param include_collections: Indicates if multivalued collections 

4900 should be included in the operation. Setting this to ``False`` is a 

4901 way to detect only local-column based properties (i.e. scalar columns 

4902 or many-to-one foreign keys) that would result in an UPDATE for this 

4903 instance upon flush. 

4904 

4905 """ 

4906 state = object_state(instance) 

4907 

4908 if not state.modified: 

4909 return False 

4910 

4911 dict_ = state.dict 

4912 

4913 for attr in state.manager.attributes: 

4914 if ( 

4915 not include_collections 

4916 and hasattr(attr.impl, "get_collection") 

4917 ) or not hasattr(attr.impl, "get_history"): 

4918 continue 

4919 

4920 (added, unchanged, deleted) = attr.impl.get_history( 

4921 state, dict_, passive=PassiveFlag.NO_CHANGE 

4922 ) 

4923 

4924 if added or deleted: 

4925 return True 

4926 else: 

4927 return False 

4928 

4929 @property 

4930 def is_active(self) -> bool: 

4931 """True if this :class:`.Session` not in "partial rollback" state. 

4932 

4933 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4934 a new transaction immediately, so this attribute will be False 

4935 when the :class:`_orm.Session` is first instantiated. 

4936 

4937 "partial rollback" state typically indicates that the flush process 

4938 of the :class:`_orm.Session` has failed, and that the 

4939 :meth:`_orm.Session.rollback` method must be emitted in order to 

4940 fully roll back the transaction. 

4941 

4942 If this :class:`_orm.Session` is not in a transaction at all, the 

4943 :class:`_orm.Session` will autobegin when it is first used, so in this 

4944 case :attr:`_orm.Session.is_active` will return True. 

4945 

4946 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4947 and that transaction has not been rolled back internally, the 

4948 :attr:`_orm.Session.is_active` will also return True. 

4949 

4950 .. seealso:: 

4951 

4952 :ref:`faq_session_rollback` 

4953 

4954 :meth:`_orm.Session.in_transaction` 

4955 

4956 """ 

4957 return self._transaction is None or self._transaction.is_active 

4958 

4959 @property 

4960 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4961 """The set of all persistent states considered dirty. 

4962 

4963 This method returns all states that were modified including 

4964 those that were possibly deleted. 

4965 

4966 """ 

4967 return self.identity_map._dirty_states() 

4968 

4969 @property 

4970 def dirty(self) -> IdentitySet: 

4971 """The set of all persistent instances considered dirty. 

4972 

4973 E.g.:: 

4974 

4975 some_mapped_object in session.dirty 

4976 

4977 Instances are considered dirty when they were modified but not 

4978 deleted. 

4979 

4980 Note that this 'dirty' calculation is 'optimistic'; most 

4981 attribute-setting or collection modification operations will 

4982 mark an instance as 'dirty' and place it in this set, even if 

4983 there is no net change to the attribute's value. At flush 

4984 time, the value of each attribute is compared to its 

4985 previously saved value, and if there's no net change, no SQL 

4986 operation will occur (this is a more expensive operation so 

4987 it's only done at flush time). 

4988 

4989 To check if an instance has actionable net changes to its 

4990 attributes, use the :meth:`.Session.is_modified` method. 

4991 

4992 """ 

4993 return IdentitySet( 

4994 [ 

4995 state.obj() 

4996 for state in self._dirty_states 

4997 if state not in self._deleted 

4998 ] 

4999 ) 

5000 

5001 @property 

5002 def deleted(self) -> IdentitySet: 

5003 "The set of all instances marked as 'deleted' within this ``Session``" 

5004 

5005 return util.IdentitySet(list(self._deleted.values())) 

5006 

5007 @property 

5008 def new(self) -> IdentitySet: 

5009 "The set of all instances marked as 'new' within this ``Session``." 

5010 

5011 return util.IdentitySet(list(self._new.values())) 

5012 

5013 

5014_S = TypeVar("_S", bound="Session") 

5015 

5016 

5017class sessionmaker(_SessionClassMethods, Generic[_S]): 

5018 """A configurable :class:`.Session` factory. 

5019 

5020 The :class:`.sessionmaker` factory generates new 

5021 :class:`.Session` objects when called, creating them given 

5022 the configurational arguments established here. 

5023 

5024 e.g.:: 

5025 

5026 from sqlalchemy import create_engine 

5027 from sqlalchemy.orm import sessionmaker 

5028 

5029 # an Engine, which the Session will use for connection 

5030 # resources 

5031 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

5032 

5033 Session = sessionmaker(engine) 

5034 

5035 with Session() as session: 

5036 session.add(some_object) 

5037 session.add(some_other_object) 

5038 session.commit() 

5039 

5040 Context manager use is optional; otherwise, the returned 

5041 :class:`_orm.Session` object may be closed explicitly via the 

5042 :meth:`_orm.Session.close` method. Using a 

5043 ``try:/finally:`` block is optional, however will ensure that the close 

5044 takes place even if there are database errors:: 

5045 

5046 session = Session() 

5047 try: 

5048 session.add(some_object) 

5049 session.add(some_other_object) 

5050 session.commit() 

5051 finally: 

5052 session.close() 

5053 

5054 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5055 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5056 for :class:`_engine.Connection` objects. In this way it also includes 

5057 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5058 manager which both begins and commits a transaction, as well as closes 

5059 out the :class:`_orm.Session` when complete, rolling back the transaction 

5060 if any errors occur:: 

5061 

5062 Session = sessionmaker(engine) 

5063 

5064 with Session.begin() as session: 

5065 session.add(some_object) 

5066 session.add(some_other_object) 

5067 # commits transaction, closes session 

5068 

5069 .. versionadded:: 1.4 

5070 

5071 When calling upon :class:`_orm.sessionmaker` to construct a 

5072 :class:`_orm.Session`, keyword arguments may also be passed to the 

5073 method; these arguments will override that of the globally configured 

5074 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5075 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5076 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5077 

5078 Session = sessionmaker(engine) 

5079 

5080 # bind an individual session to a connection 

5081 

5082 with engine.connect() as connection: 

5083 with Session(bind=connection) as session: 

5084 ... # work with session 

5085 

5086 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5087 can be used to specify additional keyword arguments to the factory, which 

5088 will take effect for subsequent :class:`.Session` objects generated. This 

5089 is usually used to associate one or more :class:`_engine.Engine` objects 

5090 with an existing 

5091 :class:`.sessionmaker` factory before it is first used:: 

5092 

5093 # application starts, sessionmaker does not have 

5094 # an engine bound yet 

5095 Session = sessionmaker() 

5096 

5097 # ... later, when an engine URL is read from a configuration 

5098 # file or other events allow the engine to be created 

5099 engine = create_engine("sqlite:///foo.db") 

5100 Session.configure(bind=engine) 

5101 

5102 sess = Session() 

5103 # work with session 

5104 

5105 .. seealso:: 

5106 

5107 :ref:`session_getting` - introductory text on creating 

5108 sessions using :class:`.sessionmaker`. 

5109 

5110 """ 

5111 

5112 class_: Type[_S] 

5113 

5114 @overload 

5115 def __init__( 

5116 self, 

5117 bind: Optional[_SessionBind] = ..., 

5118 *, 

5119 class_: Type[_S], 

5120 autoflush: bool = ..., 

5121 expire_on_commit: bool = ..., 

5122 info: Optional[_InfoType] = ..., 

5123 **kw: Any, 

5124 ): ... 

5125 

5126 @overload 

5127 def __init__( 

5128 self: "sessionmaker[Session]", 

5129 bind: Optional[_SessionBind] = ..., 

5130 *, 

5131 autoflush: bool = ..., 

5132 expire_on_commit: bool = ..., 

5133 info: Optional[_InfoType] = ..., 

5134 **kw: Any, 

5135 ): ... 

5136 

5137 def __init__( 

5138 self, 

5139 bind: Optional[_SessionBind] = None, 

5140 *, 

5141 class_: Type[_S] = Session, # type: ignore 

5142 autoflush: bool = True, 

5143 expire_on_commit: bool = True, 

5144 info: Optional[_InfoType] = None, 

5145 **kw: Any, 

5146 ): 

5147 r"""Construct a new :class:`.sessionmaker`. 

5148 

5149 All arguments here except for ``class_`` correspond to arguments 

5150 accepted by :class:`.Session` directly. See the 

5151 :meth:`.Session.__init__` docstring for more details on parameters. 

5152 

5153 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5154 with 

5155 which newly created :class:`.Session` objects will be associated. 

5156 :param class\_: class to use in order to create new :class:`.Session` 

5157 objects. Defaults to :class:`.Session`. 

5158 :param autoflush: The autoflush setting to use with newly created 

5159 :class:`.Session` objects. 

5160 

5161 .. seealso:: 

5162 

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

5164 

5165 :param expire_on_commit=True: the 

5166 :paramref:`_orm.Session.expire_on_commit` setting to use 

5167 with newly created :class:`.Session` objects. 

5168 

5169 :param info: optional dictionary of information that will be available 

5170 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5171 replaced, when the ``info`` parameter is specified to the specific 

5172 :class:`.Session` construction operation. 

5173 

5174 :param \**kw: all other keyword arguments are passed to the 

5175 constructor of newly created :class:`.Session` objects. 

5176 

5177 """ 

5178 kw["bind"] = bind 

5179 kw["autoflush"] = autoflush 

5180 kw["expire_on_commit"] = expire_on_commit 

5181 if info is not None: 

5182 kw["info"] = info 

5183 self.kw = kw 

5184 # make our own subclass of the given class, so that 

5185 # events can be associated with it specifically. 

5186 self.class_ = type(class_.__name__, (class_,), {}) 

5187 

5188 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5189 """Produce a context manager that both provides a new 

5190 :class:`_orm.Session` as well as a transaction that commits. 

5191 

5192 

5193 e.g.:: 

5194 

5195 Session = sessionmaker(some_engine) 

5196 

5197 with Session.begin() as session: 

5198 session.add(some_object) 

5199 

5200 # commits transaction, closes session 

5201 

5202 .. versionadded:: 1.4 

5203 

5204 

5205 """ 

5206 

5207 session = self() 

5208 return session._maker_context_manager() 

5209 

5210 def __call__(self, **local_kw: Any) -> _S: 

5211 """Produce a new :class:`.Session` object using the configuration 

5212 established in this :class:`.sessionmaker`. 

5213 

5214 In Python, the ``__call__`` method is invoked on an object when 

5215 it is "called" in the same way as a function:: 

5216 

5217 Session = sessionmaker(some_engine) 

5218 session = Session() # invokes sessionmaker.__call__() 

5219 

5220 """ 

5221 for k, v in self.kw.items(): 

5222 if k == "info" and "info" in local_kw: 

5223 d = v.copy() 

5224 d.update(local_kw["info"]) 

5225 local_kw["info"] = d 

5226 else: 

5227 local_kw.setdefault(k, v) 

5228 return self.class_(**local_kw) 

5229 

5230 def configure(self, **new_kw: Any) -> None: 

5231 """(Re)configure the arguments for this sessionmaker. 

5232 

5233 e.g.:: 

5234 

5235 Session = sessionmaker() 

5236 

5237 Session.configure(bind=create_engine("sqlite://")) 

5238 """ 

5239 self.kw.update(new_kw) 

5240 

5241 def __repr__(self) -> str: 

5242 return "%s(class_=%r, %s)" % ( 

5243 self.__class__.__name__, 

5244 self.class_.__name__, 

5245 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5246 ) 

5247 

5248 

5249def close_all_sessions() -> None: 

5250 """Close all sessions in memory. 

5251 

5252 This function consults a global registry of all :class:`.Session` objects 

5253 and calls :meth:`.Session.close` on them, which resets them to a clean 

5254 state. 

5255 

5256 This function is not for general use but may be useful for test suites 

5257 within the teardown scheme. 

5258 

5259 """ 

5260 

5261 for sess in _sessions.values(): 

5262 sess.close() 

5263 

5264 

5265def make_transient(instance: object) -> None: 

5266 """Alter the state of the given instance so that it is :term:`transient`. 

5267 

5268 .. note:: 

5269 

5270 :func:`.make_transient` is a special-case function for 

5271 advanced use cases only. 

5272 

5273 The given mapped instance is assumed to be in the :term:`persistent` or 

5274 :term:`detached` state. The function will remove its association with any 

5275 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5276 effect is that the object will behave as though it were newly constructed, 

5277 except retaining any attribute / collection values that were loaded at the 

5278 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5279 if this object had been deleted as a result of using 

5280 :meth:`.Session.delete`. 

5281 

5282 .. warning:: 

5283 

5284 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5285 load ORM-mapped attributes that are not currently loaded at the time 

5286 the function is called. This includes attributes which: 

5287 

5288 * were expired via :meth:`.Session.expire` 

5289 

5290 * were expired as the natural effect of committing a session 

5291 transaction, e.g. :meth:`.Session.commit` 

5292 

5293 * are normally :term:`lazy loaded` but are not currently loaded 

5294 

5295 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5296 not yet loaded 

5297 

5298 * were not present in the query which loaded this object, such as that 

5299 which is common in joined table inheritance and other scenarios. 

5300 

5301 After :func:`.make_transient` is called, unloaded attributes such 

5302 as those above will normally resolve to the value ``None`` when 

5303 accessed, or an empty collection for a collection-oriented attribute. 

5304 As the object is transient and un-associated with any database 

5305 identity, it will no longer retrieve these values. 

5306 

5307 .. seealso:: 

5308 

5309 :func:`.make_transient_to_detached` 

5310 

5311 """ 

5312 state = attributes.instance_state(instance) 

5313 s = _state_session(state) 

5314 if s: 

5315 s._expunge_states([state]) 

5316 

5317 # remove expired state 

5318 state.expired_attributes.clear() 

5319 

5320 # remove deferred callables 

5321 if state.callables: 

5322 del state.callables 

5323 

5324 if state.key: 

5325 del state.key 

5326 if state._deleted: 

5327 del state._deleted 

5328 

5329 

5330def make_transient_to_detached(instance: object) -> None: 

5331 """Make the given transient instance :term:`detached`. 

5332 

5333 .. note:: 

5334 

5335 :func:`.make_transient_to_detached` is a special-case function for 

5336 advanced use cases only. 

5337 

5338 All attribute history on the given instance 

5339 will be reset as though the instance were freshly loaded 

5340 from a query. Missing attributes will be marked as expired. 

5341 The primary key attributes of the object, which are required, will be made 

5342 into the "key" of the instance. 

5343 

5344 The object can then be added to a session, or merged 

5345 possibly with the load=False flag, at which point it will look 

5346 as if it were loaded that way, without emitting SQL. 

5347 

5348 This is a special use case function that differs from a normal 

5349 call to :meth:`.Session.merge` in that a given persistent state 

5350 can be manufactured without any SQL calls. 

5351 

5352 .. seealso:: 

5353 

5354 :func:`.make_transient` 

5355 

5356 :meth:`.Session.enable_relationship_loading` 

5357 

5358 """ 

5359 state = attributes.instance_state(instance) 

5360 if state.session_id or state.key: 

5361 raise sa_exc.InvalidRequestError("Given object must be transient") 

5362 state.key = state.mapper._identity_key_from_state(state) 

5363 if state._deleted: 

5364 del state._deleted 

5365 state._commit_all(state.dict) 

5366 state._expire_attributes(state.dict, state.unloaded) 

5367 

5368 

5369def object_session(instance: object) -> Optional[Session]: 

5370 """Return the :class:`.Session` to which the given instance belongs. 

5371 

5372 This is essentially the same as the :attr:`.InstanceState.session` 

5373 accessor. See that attribute for details. 

5374 

5375 """ 

5376 

5377 try: 

5378 state = attributes.instance_state(instance) 

5379 except exc.NO_STATE as err: 

5380 raise exc.UnmappedInstanceError(instance) from err 

5381 else: 

5382 return _state_session(state) 

5383 

5384 

5385_new_sessionid = util.counter()