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

1472 statements  

1# orm/session.py 

2# Copyright (C) 2005-2026 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 Mapping 

27from typing import NoReturn 

28from typing import Optional 

29from typing import overload 

30from typing import Protocol 

31from typing import Sequence 

32from typing import Set 

33from typing import Tuple 

34from typing import Type 

35from typing import TYPE_CHECKING 

36from typing import TypeVar 

37from typing import Union 

38import weakref 

39 

40from . import attributes 

41from . import bulk_persistence 

42from . import context 

43from . import descriptor_props 

44from . import exc 

45from . import identity 

46from . import loading 

47from . import query 

48from . import state as statelib 

49from ._typing import _O 

50from ._typing import insp_is_mapper 

51from ._typing import is_composite_class 

52from ._typing import is_orm_option 

53from ._typing import is_user_defined_option 

54from .base import _class_to_mapper 

55from .base import _none_set 

56from .base import _state_mapper 

57from .base import instance_str 

58from .base import LoaderCallableStatus 

59from .base import object_mapper 

60from .base import object_state 

61from .base import PassiveFlag 

62from .base import state_str 

63from .context import _ORMCompileState 

64from .context import FromStatement 

65from .identity import IdentityMap 

66from .query import Query 

67from .state import InstanceState 

68from .state_changes import _StateChange 

69from .state_changes import _StateChangeState 

70from .state_changes import _StateChangeStates 

71from .unitofwork import UOWTransaction 

72from .. import engine 

73from .. import exc as sa_exc 

74from .. import sql 

75from .. import util 

76from ..engine import Connection 

77from ..engine import Engine 

78from ..engine.util import TransactionalContext 

79from ..event import dispatcher 

80from ..event import EventTarget 

81from ..inspection import inspect 

82from ..inspection import Inspectable 

83from ..sql import coercions 

84from ..sql import dml 

85from ..sql import roles 

86from ..sql import Select 

87from ..sql import TableClause 

88from ..sql import visitors 

89from ..sql.base import _NoArg 

90from ..sql.base import CompileState 

91from ..sql.schema import Table 

92from ..sql.selectable import ForUpdateArg 

93from ..util import deprecated_params 

94from ..util import IdentitySet 

95from ..util.typing import Never 

96from ..util.typing import TupleAny 

97from ..util.typing import TypeVarTuple 

98from ..util.typing import Unpack 

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 """Optional mapping or list of mappings of parameters that was passed to 

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

299 

300 May be mutated or re-assigned in place, which will take effect as the 

301 effective parameters passed to the method. 

302 

303 .. versionchanged:: 2.1 :attr:`.ORMExecuteState.parameters` may now be 

304 mutated or replaced. 

305 

306 """ 

307 

308 execution_options: _ExecuteOptions 

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

310 

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

312 locally passed execution options. 

313 

314 .. seealso:: 

315 

316 :attr:`_orm.ORMExecuteState.local_execution_options` 

317 

318 :meth:`_sql.Executable.execution_options` 

319 

320 :ref:`orm_queryguide_execution_options` 

321 

322 """ 

323 

324 local_execution_options: _ExecuteOptions 

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

326 :meth:`.Session.execute` method. 

327 

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

329 being invoked. 

330 

331 .. seealso:: 

332 

333 :attr:`_orm.ORMExecuteState.execution_options` 

334 

335 """ 

336 

337 bind_arguments: _BindArguments 

338 """The dictionary passed as the 

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

340 

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

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

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

344 

345 """ 

346 

347 _compile_state_cls: Optional[Type[_ORMCompileState]] 

348 _starting_event_idx: int 

349 _events_todo: List[Any] 

350 _update_execution_options: _ExecuteOptions 

351 

352 def __init__( 

353 self, 

354 session: Session, 

355 statement: Executable, 

356 parameters: Optional[_CoreAnyExecuteParams], 

357 execution_options: _ExecuteOptions, 

358 bind_arguments: _BindArguments, 

359 compile_state_cls: Optional[Type[_ORMCompileState]], 

360 events_todo: List[_InstanceLevelDispatch[Session]], 

361 ): 

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

363 

364 this object is constructed internally. 

365 

366 """ 

367 self.session = session 

368 self.statement = statement 

369 self.parameters = parameters 

370 self.local_execution_options = execution_options 

371 self.execution_options = statement._execution_options.union( 

372 execution_options 

373 ) 

374 self.bind_arguments = bind_arguments 

375 self._compile_state_cls = compile_state_cls 

376 self._events_todo = list(events_todo) 

377 self._update_execution_options = util.EMPTY_DICT 

378 

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

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

381 

382 def invoke_statement( 

383 self, 

384 statement: Optional[Executable] = None, 

385 params: Optional[_CoreAnyExecuteParams] = None, 

386 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

387 bind_arguments: Optional[_BindArguments] = None, 

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

389 """Execute the statement represented by this 

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

391 already proceeded. 

392 

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

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

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

396 that want to override how the ultimate 

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

398 retrieve results from an offline cache or which concatenate results 

399 from multiple executions. 

400 

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

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

403 is propagated to the calling 

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

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

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

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

408 

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

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

411 

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

413 which will be merged into the existing 

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

415 

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

417 for executemany executions. 

418 

419 :param execution_options: optional dictionary of execution options 

420 will be merged into the existing 

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

422 :class:`.ORMExecuteState`. 

423 

424 :param bind_arguments: optional dictionary of bind_arguments 

425 which will be merged amongst the current 

426 :attr:`.ORMExecuteState.bind_arguments` 

427 of this :class:`.ORMExecuteState`. 

428 

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

430 

431 .. seealso:: 

432 

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

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

435 

436 

437 """ 

438 

439 if statement is None: 

440 statement = self.statement 

441 

442 _bind_arguments = dict(self.bind_arguments) 

443 if bind_arguments: 

444 _bind_arguments.update(bind_arguments) 

445 _bind_arguments["_sa_skip_events"] = True 

446 

447 _params: Optional[_CoreAnyExecuteParams] 

448 if params: 

449 if self.is_executemany: 

450 _params = [] 

451 exec_many_parameters = cast( 

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

453 ) 

454 for _existing_params, _new_params in itertools.zip_longest( 

455 exec_many_parameters, 

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

457 ): 

458 if _existing_params is None or _new_params is None: 

459 raise sa_exc.InvalidRequestError( 

460 f"Can't apply executemany parameters to " 

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

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

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

464 f"to ORMExecuteState.invoke_statement() " 

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

466 ) 

467 _existing_params = dict(_existing_params) 

468 _existing_params.update(_new_params) 

469 _params.append(_existing_params) 

470 else: 

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

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

473 else: 

474 _params = self.parameters 

475 

476 _execution_options = self.local_execution_options 

477 if execution_options: 

478 _execution_options = _execution_options.union(execution_options) 

479 

480 return self.session._execute_internal( 

481 statement, 

482 _params, 

483 execution_options=_execution_options, 

484 bind_arguments=_bind_arguments, 

485 _parent_execute_state=self, 

486 ) 

487 

488 @property 

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

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

491 

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

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

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

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

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

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

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

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

500 would be selected. 

501 

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

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

504 way of getting this mapper. 

505 

506 .. versionadded:: 1.4.0b2 

507 

508 .. seealso:: 

509 

510 :attr:`_orm.ORMExecuteState.all_mappers` 

511 

512 

513 """ 

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

515 return mp 

516 

517 @property 

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

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

520 involved at the top level of this statement. 

521 

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

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

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

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

526 

527 .. versionadded:: 1.4.0b2 

528 

529 .. seealso:: 

530 

531 :attr:`_orm.ORMExecuteState.bind_mapper` 

532 

533 

534 

535 """ 

536 if not self.is_orm_statement: 

537 return [] 

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

539 result = [] 

540 seen = set() 

541 for d in self.statement.column_descriptions: 

542 ent = d["entity"] 

543 if ent: 

544 insp = inspect(ent, raiseerr=False) 

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

546 seen.add(insp.mapper) 

547 result.append(insp.mapper) 

548 return result 

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

550 return [self.bind_mapper] 

551 else: 

552 return [] 

553 

554 @property 

555 def is_orm_statement(self) -> bool: 

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

557 

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

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

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

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

562 and no ORM-level automation takes place. 

563 

564 """ 

565 return self._compile_state_cls is not None 

566 

567 @property 

568 def is_executemany(self) -> bool: 

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

570 dictionaries with more than one dictionary. 

571 

572 .. versionadded:: 2.0 

573 

574 """ 

575 return isinstance(self.parameters, list) 

576 

577 @property 

578 def is_select(self) -> bool: 

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

580 

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

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

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

584 ``select(Entity).from_statement(select(..))`` 

585 

586 """ 

587 return self.statement.is_select 

588 

589 @property 

590 def is_from_statement(self) -> bool: 

591 """return True if this operation is a 

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

593 

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

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

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

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

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

599 :class:`_sql.Select` construct. 

600 

601 .. versionadded:: 2.0.30 

602 

603 """ 

604 return self.statement.is_from_statement 

605 

606 @property 

607 def is_insert(self) -> bool: 

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

609 

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

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

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

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

614 

615 """ 

616 return self.statement.is_dml and self.statement.is_insert 

617 

618 @property 

619 def is_update(self) -> bool: 

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

621 

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

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

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

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

626 

627 """ 

628 return self.statement.is_dml and self.statement.is_update 

629 

630 @property 

631 def is_delete(self) -> bool: 

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

633 

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

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

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

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

638 

639 """ 

640 return self.statement.is_dml and self.statement.is_delete 

641 

642 @property 

643 def _is_crud(self) -> bool: 

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

645 

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

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

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

649 self._update_execution_options = self._update_execution_options.union( 

650 opts 

651 ) 

652 

653 def _orm_compile_options( 

654 self, 

655 ) -> Optional[ 

656 Union[ 

657 context._ORMCompileState.default_compile_options, 

658 Type[context._ORMCompileState.default_compile_options], 

659 ] 

660 ]: 

661 if not self.is_select: 

662 return None 

663 try: 

664 opts = self.statement._compile_options 

665 except AttributeError: 

666 return None 

667 

668 if opts is not None and opts.isinstance( 

669 context._ORMCompileState.default_compile_options 

670 ): 

671 return opts # type: ignore[return-value] 

672 else: 

673 return None 

674 

675 @property 

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

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

678 for a lazy load operation. 

679 

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

681 sharding extension, where it is available within specific query 

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

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

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

685 compilation time. 

686 

687 """ 

688 return self.load_options._lazy_loaded_from 

689 

690 @property 

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

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

693 

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

695 when a particular object or collection is being loaded. 

696 

697 """ 

698 opts = self._orm_compile_options() 

699 if opts is not None: 

700 return opts._current_path 

701 else: 

702 return None 

703 

704 @property 

705 def is_column_load(self) -> bool: 

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

707 attributes on an existing ORM object. 

708 

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

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

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

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

713 loaded. 

714 

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

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

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

718 and loader options travelling with the instance 

719 will have already been added to the query. 

720 

721 .. versionadded:: 1.4.0b2 

722 

723 .. seealso:: 

724 

725 :attr:`_orm.ORMExecuteState.is_relationship_load` 

726 

727 """ 

728 opts = self._orm_compile_options() 

729 return opts is not None and opts._for_refresh_state 

730 

731 @property 

732 def is_relationship_load(self) -> bool: 

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

734 relationship. 

735 

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

737 SelectInLoader, SubqueryLoader, or similar, and the entire 

738 SELECT statement being emitted is on behalf of a relationship 

739 load. 

740 

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

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

743 capable of being propagated to relationship loaders and should 

744 be already present. 

745 

746 .. seealso:: 

747 

748 :attr:`_orm.ORMExecuteState.is_column_load` 

749 

750 """ 

751 opts = self._orm_compile_options() 

752 if opts is None: 

753 return False 

754 path = self.loader_strategy_path 

755 return path is not None and not path.is_root 

756 

757 @property 

758 def load_options( 

759 self, 

760 ) -> Union[ 

761 context.QueryContext.default_load_options, 

762 Type[context.QueryContext.default_load_options], 

763 ]: 

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

765 

766 if not self.is_select: 

767 raise sa_exc.InvalidRequestError( 

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

769 "so there are no load options." 

770 ) 

771 

772 lo: Union[ 

773 context.QueryContext.default_load_options, 

774 Type[context.QueryContext.default_load_options], 

775 ] = self.execution_options.get( 

776 "_sa_orm_load_options", context.QueryContext.default_load_options 

777 ) 

778 return lo 

779 

780 @property 

781 def update_delete_options( 

782 self, 

783 ) -> Union[ 

784 bulk_persistence._BulkUDCompileState.default_update_options, 

785 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

786 ]: 

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

788 execution.""" 

789 

790 if not self._is_crud: 

791 raise sa_exc.InvalidRequestError( 

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

793 "statement so there are no update options." 

794 ) 

795 uo: Union[ 

796 bulk_persistence._BulkUDCompileState.default_update_options, 

797 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

798 ] = self.execution_options.get( 

799 "_sa_orm_update_options", 

800 bulk_persistence._BulkUDCompileState.default_update_options, 

801 ) 

802 return uo 

803 

804 @property 

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

806 return [ 

807 opt 

808 for opt in self.statement._with_options 

809 if is_orm_option(opt) and not opt._is_compile_state 

810 ] 

811 

812 @property 

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

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

815 associated with the statement being invoked. 

816 

817 .. versionchanged:: 2.1 - the returned option take into 

818 consideration any options added before calling 

819 :meth:`_sql.Select.with_only_columns` or 

820 :meth:`_orm.Query.with_entities`. 

821 

822 """ 

823 items = [ 

824 self.statement, 

825 *getattr(self.statement, "_memoized_select_entities", ()), 

826 ] 

827 return [ 

828 opt 

829 for item in items 

830 for opt in item._with_options 

831 if is_user_defined_option(opt) 

832 ] 

833 

834 

835class SessionTransactionOrigin(Enum): 

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

837 

838 This enumeration is present on the 

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

840 :class:`.SessionTransaction` object. 

841 

842 .. versionadded:: 2.0 

843 

844 """ 

845 

846 AUTOBEGIN = 0 

847 """transaction were started by autobegin""" 

848 

849 BEGIN = 1 

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

851 

852 BEGIN_NESTED = 2 

853 """transaction were started by :meth:`_orm.Session.begin_nested`""" 

854 

855 SUBTRANSACTION = 3 

856 """transaction is an internal "subtransaction" """ 

857 

858 

859class SessionTransaction(_StateChange, TransactionalContext): 

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

861 

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

863 :meth:`_orm.Session.begin` 

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

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

866 transactions. 

867 

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

869 at: :ref:`unitofwork_transaction`. 

870 

871 

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

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

874 

875 .. seealso:: 

876 

877 :ref:`unitofwork_transaction` 

878 

879 :meth:`.Session.begin` 

880 

881 :meth:`.Session.begin_nested` 

882 

883 :meth:`.Session.rollback` 

884 

885 :meth:`.Session.commit` 

886 

887 :meth:`.Session.in_transaction` 

888 

889 :meth:`.Session.in_nested_transaction` 

890 

891 :meth:`.Session.get_transaction` 

892 

893 :meth:`.Session.get_nested_transaction` 

894 

895 

896 """ 

897 

898 _rollback_exception: Optional[BaseException] = None 

899 

900 _connections: Dict[ 

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

902 ] 

903 session: Session 

904 _parent: Optional[SessionTransaction] 

905 

906 _state: SessionTransactionState 

907 

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

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

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

911 _key_switches: weakref.WeakKeyDictionary[ 

912 InstanceState[Any], Tuple[Any, Any] 

913 ] 

914 

915 origin: SessionTransactionOrigin 

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

917 

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

919 enumeration indicating the source event that led to constructing 

920 this :class:`_orm.SessionTransaction`. 

921 

922 .. versionadded:: 2.0 

923 

924 """ 

925 

926 nested: bool = False 

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

928 

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

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

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

932 

933 .. seealso:: 

934 

935 :attr:`.SessionTransaction.origin` 

936 

937 """ 

938 

939 def __init__( 

940 self, 

941 session: Session, 

942 origin: SessionTransactionOrigin, 

943 parent: Optional[SessionTransaction] = None, 

944 ): 

945 TransactionalContext._trans_ctx_check(session) 

946 

947 self.session = session 

948 self._connections = {} 

949 self._parent = parent 

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

951 self.origin = origin 

952 

953 if session._close_state is _SessionCloseState.CLOSED: 

954 raise sa_exc.InvalidRequestError( 

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

956 "to handle any more transaction requests." 

957 ) 

958 

959 if nested: 

960 if not parent: 

961 raise sa_exc.InvalidRequestError( 

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

963 "transaction is in progress" 

964 ) 

965 

966 self._previous_nested_transaction = session._nested_transaction 

967 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

968 assert parent is not None 

969 else: 

970 assert parent is None 

971 

972 self._state = SessionTransactionState.ACTIVE 

973 

974 self._take_snapshot() 

975 

976 # make sure transaction is assigned before we call the 

977 # dispatch 

978 self.session._transaction = self 

979 

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

981 

982 def _raise_for_prerequisite_state( 

983 self, operation_name: str, state: _StateChangeState 

984 ) -> NoReturn: 

985 if state is SessionTransactionState.DEACTIVE: 

986 if self._rollback_exception: 

987 raise sa_exc.PendingRollbackError( 

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

989 "due to a previous exception during flush." 

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

991 "first issue Session.rollback()." 

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

993 code="7s2a", 

994 ) 

995 else: 

996 raise sa_exc.InvalidRequestError( 

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

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

999 "can be emitted within this transaction." 

1000 ) 

1001 elif state is SessionTransactionState.CLOSED: 

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

1003 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

1004 raise sa_exc.InvalidRequestError( 

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

1006 "operations are not permitted", 

1007 code="isce", 

1008 ) 

1009 else: 

1010 raise sa_exc.InvalidRequestError( 

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

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

1013 ) 

1014 

1015 @property 

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

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

1018 :class:`.SessionTransaction`. 

1019 

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

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

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

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

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

1025 "nested" / SAVEPOINT transaction. If the 

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

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

1028 

1029 """ 

1030 return self._parent 

1031 

1032 @property 

1033 def is_active(self) -> bool: 

1034 return ( 

1035 self.session is not None 

1036 and self._state is SessionTransactionState.ACTIVE 

1037 ) 

1038 

1039 @property 

1040 def _is_transaction_boundary(self) -> bool: 

1041 return self.nested or not self._parent 

1042 

1043 @_StateChange.declare_states( 

1044 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1045 ) 

1046 def connection( 

1047 self, 

1048 bindkey: Optional[Mapper[Any]], 

1049 execution_options: Optional[_ExecuteOptions] = None, 

1050 **kwargs: Any, 

1051 ) -> Connection: 

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

1053 return self._connection_for_bind(bind, execution_options) 

1054 

1055 @_StateChange.declare_states( 

1056 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1057 ) 

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

1059 return SessionTransaction( 

1060 self.session, 

1061 ( 

1062 SessionTransactionOrigin.BEGIN_NESTED 

1063 if nested 

1064 else SessionTransactionOrigin.SUBTRANSACTION 

1065 ), 

1066 self, 

1067 ) 

1068 

1069 def _iterate_self_and_parents( 

1070 self, upto: Optional[SessionTransaction] = None 

1071 ) -> Iterable[SessionTransaction]: 

1072 current = self 

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

1074 while current: 

1075 result += (current,) 

1076 if current._parent is upto: 

1077 break 

1078 elif current._parent is None: 

1079 raise sa_exc.InvalidRequestError( 

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

1081 % (upto) 

1082 ) 

1083 else: 

1084 current = current._parent 

1085 

1086 return result 

1087 

1088 def _take_snapshot(self) -> None: 

1089 if not self._is_transaction_boundary: 

1090 parent = self._parent 

1091 assert parent is not None 

1092 self._new = parent._new 

1093 self._deleted = parent._deleted 

1094 self._dirty = parent._dirty 

1095 self._key_switches = parent._key_switches 

1096 return 

1097 

1098 is_begin = self.origin in ( 

1099 SessionTransactionOrigin.BEGIN, 

1100 SessionTransactionOrigin.AUTOBEGIN, 

1101 ) 

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

1103 self.session.flush() 

1104 

1105 self._new = weakref.WeakKeyDictionary() 

1106 self._deleted = weakref.WeakKeyDictionary() 

1107 self._dirty = weakref.WeakKeyDictionary() 

1108 self._key_switches = weakref.WeakKeyDictionary() 

1109 

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

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

1112 

1113 Corresponds to a rollback. 

1114 

1115 """ 

1116 assert self._is_transaction_boundary 

1117 

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

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

1120 

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

1122 # we probably can do this conditionally based on 

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

1124 self.session.identity_map.safe_discard(s) 

1125 

1126 # restore the old key 

1127 s.key = oldkey 

1128 

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

1130 if s not in to_expunge: 

1131 self.session.identity_map.replace(s) 

1132 

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

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

1135 

1136 assert not self.session._deleted 

1137 

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

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

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

1141 

1142 def _remove_snapshot(self) -> None: 

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

1144 

1145 Corresponds to a commit. 

1146 

1147 """ 

1148 assert self._is_transaction_boundary 

1149 

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

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

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

1153 

1154 statelib.InstanceState._detach_states( 

1155 list(self._deleted), self.session 

1156 ) 

1157 self._deleted.clear() 

1158 elif self.nested: 

1159 parent = self._parent 

1160 assert parent is not None 

1161 parent._new.update(self._new) 

1162 parent._dirty.update(self._dirty) 

1163 parent._deleted.update(self._deleted) 

1164 parent._key_switches.update(self._key_switches) 

1165 

1166 @_StateChange.declare_states( 

1167 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1168 ) 

1169 def _connection_for_bind( 

1170 self, 

1171 bind: _SessionBind, 

1172 execution_options: Optional[CoreExecuteOptionsParameter], 

1173 ) -> Connection: 

1174 if bind in self._connections: 

1175 if execution_options: 

1176 util.warn( 

1177 "Connection is already established for the " 

1178 "given bind; execution_options ignored" 

1179 ) 

1180 return self._connections[bind][0] 

1181 

1182 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1183 

1184 local_connect = False 

1185 should_commit = True 

1186 

1187 try: 

1188 if self._parent: 

1189 conn = self._parent._connection_for_bind( 

1190 bind, execution_options 

1191 ) 

1192 if not self.nested: 

1193 return conn 

1194 else: 

1195 if isinstance(bind, engine.Connection): 

1196 conn = bind 

1197 if conn.engine in self._connections: 

1198 raise sa_exc.InvalidRequestError( 

1199 "Session already has a Connection associated " 

1200 "for the given Connection's Engine" 

1201 ) 

1202 else: 

1203 conn = bind.connect() 

1204 local_connect = True 

1205 

1206 try: 

1207 conn_exec_opts: Dict[str, Any] = {} 

1208 if self.session.execution_options: 

1209 conn_exec_opts.update(self.session.execution_options) 

1210 if execution_options: 

1211 conn_exec_opts.update(execution_options) 

1212 if conn_exec_opts: 

1213 conn = conn.execution_options(**conn_exec_opts) 

1214 

1215 transaction: Transaction 

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

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

1218 # conn.in_transaction() ? 

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

1220 # that it is in fact twophase. 

1221 transaction = conn.begin_twophase() 

1222 elif self.nested: 

1223 transaction = conn.begin_nested() 

1224 elif conn.in_transaction(): 

1225 

1226 if local_connect: 

1227 _trans = conn.get_transaction() 

1228 assert _trans is not None 

1229 transaction = _trans 

1230 else: 

1231 join_transaction_mode = ( 

1232 self.session.join_transaction_mode 

1233 ) 

1234 

1235 if join_transaction_mode == "conditional_savepoint": 

1236 if conn.in_nested_transaction(): 

1237 join_transaction_mode = "create_savepoint" 

1238 else: 

1239 join_transaction_mode = "rollback_only" 

1240 

1241 if join_transaction_mode in ( 

1242 "control_fully", 

1243 "rollback_only", 

1244 ): 

1245 if conn.in_nested_transaction(): 

1246 transaction = ( 

1247 conn._get_required_nested_transaction() 

1248 ) 

1249 else: 

1250 transaction = conn._get_required_transaction() 

1251 if join_transaction_mode == "rollback_only": 

1252 should_commit = False 

1253 elif join_transaction_mode == "create_savepoint": 

1254 transaction = conn.begin_nested() 

1255 else: 

1256 assert False, join_transaction_mode 

1257 else: 

1258 transaction = conn.begin() 

1259 except: 

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

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

1262 if local_connect: 

1263 conn.close() 

1264 raise 

1265 else: 

1266 bind_is_connection = isinstance(bind, engine.Connection) 

1267 

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

1269 conn, 

1270 transaction, 

1271 should_commit, 

1272 not bind_is_connection, 

1273 ) 

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

1275 return conn 

1276 finally: 

1277 self._state = SessionTransactionState.ACTIVE 

1278 

1279 def prepare(self) -> None: 

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

1281 raise sa_exc.InvalidRequestError( 

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

1283 "can't prepare." 

1284 ) 

1285 self._prepare_impl() 

1286 

1287 @_StateChange.declare_states( 

1288 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1289 ) 

1290 def _prepare_impl(self) -> None: 

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

1292 self.session.dispatch.before_commit(self.session) 

1293 

1294 stx = self.session._transaction 

1295 assert stx is not None 

1296 if stx is not self: 

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

1298 subtransaction.commit() 

1299 

1300 if not self.session._flushing: 

1301 for _flush_guard in range(100): 

1302 if self.session._is_clean(): 

1303 break 

1304 self.session.flush() 

1305 else: 

1306 raise exc.FlushError( 

1307 "Over 100 subsequent flushes have occurred within " 

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

1309 "creating new objects?" 

1310 ) 

1311 

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

1313 try: 

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

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

1316 except: 

1317 with util.safe_reraise(): 

1318 with self._expect_state(SessionTransactionState.CLOSED): 

1319 self.rollback() 

1320 

1321 self._state = SessionTransactionState.PREPARED 

1322 

1323 @_StateChange.declare_states( 

1324 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1325 SessionTransactionState.CLOSED, 

1326 ) 

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

1328 if self._state is not SessionTransactionState.PREPARED: 

1329 with self._expect_state(SessionTransactionState.PREPARED): 

1330 self._prepare_impl() 

1331 

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

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

1334 self._connections.values() 

1335 ): 

1336 if should_commit: 

1337 trans.commit() 

1338 

1339 self._state = SessionTransactionState.COMMITTED 

1340 self.session.dispatch.after_commit(self.session) 

1341 

1342 self._remove_snapshot() 

1343 

1344 with self._expect_state(SessionTransactionState.CLOSED): 

1345 self.close() 

1346 

1347 if _to_root and self._parent: 

1348 self._parent.commit(_to_root=True) 

1349 

1350 @_StateChange.declare_states( 

1351 ( 

1352 SessionTransactionState.ACTIVE, 

1353 SessionTransactionState.DEACTIVE, 

1354 SessionTransactionState.PREPARED, 

1355 ), 

1356 SessionTransactionState.CLOSED, 

1357 ) 

1358 def rollback( 

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

1360 ) -> None: 

1361 stx = self.session._transaction 

1362 assert stx is not None 

1363 if stx is not self: 

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

1365 subtransaction.close() 

1366 

1367 boundary = self 

1368 rollback_err = None 

1369 if self._state in ( 

1370 SessionTransactionState.ACTIVE, 

1371 SessionTransactionState.PREPARED, 

1372 ): 

1373 for transaction in self._iterate_self_and_parents(): 

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

1375 try: 

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

1377 t[1].rollback() 

1378 

1379 transaction._state = SessionTransactionState.DEACTIVE 

1380 self.session.dispatch.after_rollback(self.session) 

1381 except: 

1382 rollback_err = sys.exc_info() 

1383 finally: 

1384 transaction._state = SessionTransactionState.DEACTIVE 

1385 transaction._restore_snapshot( 

1386 dirty_only=transaction.nested 

1387 ) 

1388 boundary = transaction 

1389 break 

1390 else: 

1391 transaction._state = SessionTransactionState.DEACTIVE 

1392 

1393 sess = self.session 

1394 

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

1396 # if items were added, deleted, or mutated 

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

1398 util.warn( 

1399 "Session's state has been changed on " 

1400 "a non-active transaction - this state " 

1401 "will be discarded." 

1402 ) 

1403 boundary._restore_snapshot(dirty_only=boundary.nested) 

1404 

1405 with self._expect_state(SessionTransactionState.CLOSED): 

1406 self.close() 

1407 

1408 if self._parent and _capture_exception: 

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

1410 

1411 if rollback_err and rollback_err[1]: 

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

1413 

1414 sess.dispatch.after_soft_rollback(sess, self) 

1415 

1416 if _to_root and self._parent: 

1417 self._parent.rollback(_to_root=True) 

1418 

1419 @_StateChange.declare_states( 

1420 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1421 ) 

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

1423 if self.nested: 

1424 self.session._nested_transaction = ( 

1425 self._previous_nested_transaction 

1426 ) 

1427 

1428 self.session._transaction = self._parent 

1429 

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

1431 self._connections.values() 

1432 ): 

1433 if invalidate and self._parent is None: 

1434 connection.invalidate() 

1435 if should_commit and transaction.is_active: 

1436 transaction.close() 

1437 if autoclose and self._parent is None: 

1438 connection.close() 

1439 

1440 self._state = SessionTransactionState.CLOSED 

1441 sess = self.session 

1442 

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

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

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

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

1447 # passes with these commented out. 

1448 # self.session = None # type: ignore 

1449 # self._connections = None # type: ignore 

1450 

1451 sess.dispatch.after_transaction_end(sess, self) 

1452 

1453 def _get_subject(self) -> Session: 

1454 return self.session 

1455 

1456 def _transaction_is_active(self) -> bool: 

1457 return self._state is SessionTransactionState.ACTIVE 

1458 

1459 def _transaction_is_closed(self) -> bool: 

1460 return self._state is SessionTransactionState.CLOSED 

1461 

1462 def _rollback_can_be_called(self) -> bool: 

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

1464 

1465 

1466class _SessionCloseState(Enum): 

1467 ACTIVE = 1 

1468 CLOSED = 2 

1469 CLOSE_IS_RESET = 3 

1470 

1471 

1472class Session(_SessionClassMethods, EventTarget): 

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

1474 

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

1476 See :ref:`session_faq_threadsafe` for background. 

1477 

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

1479 

1480 

1481 """ 

1482 

1483 _is_asyncio = False 

1484 

1485 dispatch: dispatcher[Session] 

1486 

1487 identity_map: IdentityMap 

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

1489 

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

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

1492 that have row identity) currently in the session. 

1493 

1494 .. seealso:: 

1495 

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

1497 in this dictionary. 

1498 

1499 """ 

1500 

1501 binds: Mapping[_SessionBindKey, _SessionBind] 

1502 """An immutable mapping of bind targets to :class:`_engine.Engine` or 

1503 :class:`_engine.Connection` objects. 

1504 

1505 This collection is established from the 

1506 :paramref:`_orm.Session.binds` parameter as well as the 

1507 :meth:`_orm.Session.bind_mapper` and :meth:`_orm.Session.bind_table` 

1508 methods, and is consulted by :meth:`_orm.Session.get_bind`. The keys are 

1509 normalized from what was originally passed; a mapper or mapped class is 

1510 entered both under the mapped class and under each of its selectables. 

1511 

1512 The collection is replaced, rather than mutated, whenever a new bind is 

1513 added, so a reference to it will not observe subsequent changes. 

1514 

1515 .. versionadded:: 2.1 Previously this collection was stored 

1516 privately. 

1517 

1518 """ 

1519 

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

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

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

1523 _flushing: bool 

1524 _warn_on_events: bool 

1525 _transaction: Optional[SessionTransaction] 

1526 _nested_transaction: Optional[SessionTransaction] 

1527 hash_key: int 

1528 autoflush: bool 

1529 expire_on_commit: bool 

1530 enable_baked_queries: bool 

1531 twophase: bool 

1532 join_transaction_mode: JoinTransactionMode 

1533 execution_options: _ExecuteOptions = util.EMPTY_DICT 

1534 _query_cls: Type[Query[Any]] 

1535 _close_state: _SessionCloseState 

1536 

1537 def __init__( 

1538 self, 

1539 bind: Optional[_SessionBind] = None, 

1540 *, 

1541 autoflush: bool = True, 

1542 future: Literal[True] = True, 

1543 expire_on_commit: bool = True, 

1544 autobegin: bool = True, 

1545 twophase: bool = False, 

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

1547 enable_baked_queries: bool = True, 

1548 info: Optional[_InfoType] = None, 

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

1550 autocommit: Literal[False] = False, 

1551 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1553 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

1554 ): 

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

1556 

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

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

1559 set of arguments. 

1560 

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

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

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

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

1565 results. 

1566 

1567 .. seealso:: 

1568 

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

1570 

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

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

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

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

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

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

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

1578 

1579 .. versionadded:: 2.0 

1580 

1581 .. seealso:: 

1582 

1583 :ref:`session_autobegin_disable` 

1584 

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

1586 :class:`_engine.Connection` to 

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

1588 operations performed by this session will execute via this 

1589 connectable. 

1590 

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

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

1593 objects as the source of 

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

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

1596 arbitrary Python classes that are bases for mapped classes, 

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

1598 The 

1599 values of the dictionary are then instances of 

1600 :class:`_engine.Engine` 

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

1602 Operations which 

1603 proceed relative to a particular mapped class will consult this 

1604 dictionary for the closest matching entity in order to determine 

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

1606 operation. The complete heuristics for resolution are 

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

1608 

1609 Session = sessionmaker( 

1610 binds={ 

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

1612 SomeDeclarativeBase: create_engine( 

1613 "postgresql+psycopg2://engine2" 

1614 ), 

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

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

1617 } 

1618 ) 

1619 

1620 .. seealso:: 

1621 

1622 :ref:`session_partitioning` 

1623 

1624 :meth:`.Session.bind_mapper` 

1625 

1626 :meth:`.Session.bind_table` 

1627 

1628 :meth:`.Session.get_bind` 

1629 

1630 

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

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

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

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

1635 constructor for ``Session``. 

1636 

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

1638 A parameter consumed 

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

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

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

1642 this particular extension is disabled. 

1643 

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

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

1646 flag therefore only affects applications that are making explicit 

1647 use of this extension within their own code. 

1648 

1649 :param execution_options: optional dictionary of execution options 

1650 that will be applied to the :class:`_engine.Connection` when first 

1651 procured for a transaction, as well as to all explicit query 

1652 executions such as :meth:`_orm.Session.execute`, 

1653 :meth:`_orm.Session.scalars`, and similar. This includes 

1654 flush (INSERT/UPDATE/DELETE) operations and is visible within 

1655 event hooks such as 

1656 :meth:`_events.ConnectionEvents.before_cursor_execute`. 

1657 

1658 Execution options present in statements as well as options passed 

1659 to methods like :meth:`_orm.Session.execute` explicitly take 

1660 precedence over the session-wide options. 

1661 

1662 .. versionadded:: 2.1 

1663 

1664 .. versionchanged:: 2.1.0b3 

1665 Session-level execution options are now applied to the 

1666 :class:`_engine.Connection` at procurement time, so that 

1667 they take effect for flush operations as well as explicit 

1668 query executions. Previously, options were only applied to 

1669 explicit calls such as :meth:`_orm.Session.execute`. 

1670 

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

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

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

1674 transaction will load from the most recent database state. 

1675 

1676 .. seealso:: 

1677 

1678 :ref:`session_committing` 

1679 

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

1681 

1682 .. seealso:: 

1683 

1684 :ref:`migration_20_toplevel` 

1685 

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

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

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

1689 construction time so that modifications to the per- 

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

1691 :class:`.Session`. 

1692 

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

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

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

1696 

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

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

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

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

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

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

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

1704 transaction, before each transaction is committed. 

1705 

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

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

1708 

1709 :param join_transaction_mode: Describes the transactional behavior to 

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

1711 has already begun a transaction outside the scope of this 

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

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

1714 

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

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

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

1718 etc. are actually invoked: 

1719 

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

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

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

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

1724 a SAVEPOINT, in other words 

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

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

1727 

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

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

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

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

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

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

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

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

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

1737 

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

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

1740 its own transaction. This transaction by its nature rides 

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

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

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

1744 external transaction will remain unaffected throughout the 

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

1746 

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

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

1749 initiated transaction should remain unaffected; however, it relies 

1750 on proper SAVEPOINT support from the underlying driver and 

1751 database. 

1752 

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

1754 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1755 without workarounds. See the sections 

1756 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1757 for details on current workarounds. 

1758 

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

1760 control of the given transaction as its own; 

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

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

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

1764 call ``.rollback`` on the transaction. 

1765 

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

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

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

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

1770 SAVEPOINT. 

1771 

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

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

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

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

1776 given transaction. 

1777 

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

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

1780 regular database transaction (i.e. 

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

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

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

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

1785 

1786 .. versionadded:: 2.0.0rc1 

1787 

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

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

1790 or should pass in a no longer usable state, disabling reuse. 

1791 

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

1793 A future SQLAlchemy version may change the default value of 

1794 this flag to ``False``. 

1795 

1796 .. seealso:: 

1797 

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

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

1800 

1801 """ # noqa 

1802 

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

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

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

1806 # of cases including in our own test suite 

1807 if autocommit: 

1808 raise sa_exc.ArgumentError( 

1809 "autocommit=True is no longer supported" 

1810 ) 

1811 self.identity_map = identity._WeakInstanceDict() 

1812 

1813 if not future: 

1814 raise sa_exc.ArgumentError( 

1815 "The 'future' parameter passed to " 

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

1817 ) 

1818 

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

1820 self._deleted = {} # same 

1821 self.bind = bind 

1822 self.binds = util.EMPTY_DICT 

1823 self._flushing = False 

1824 self._warn_on_events = False 

1825 self._transaction = None 

1826 self._nested_transaction = None 

1827 self.hash_key = _new_sessionid() 

1828 self.autobegin = autobegin 

1829 self.autoflush = autoflush 

1830 self.expire_on_commit = expire_on_commit 

1831 self.enable_baked_queries = enable_baked_queries 

1832 if execution_options: 

1833 self.execution_options = self.execution_options.union( 

1834 execution_options 

1835 ) 

1836 

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

1838 # the default will switch to close_resets_only=False. 

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

1840 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1841 else: 

1842 self._close_state = _SessionCloseState.ACTIVE 

1843 if ( 

1844 join_transaction_mode 

1845 and join_transaction_mode 

1846 not in JoinTransactionMode.__args__ # type: ignore[attr-defined] 

1847 ): 

1848 raise sa_exc.ArgumentError( 

1849 f"invalid selection for join_transaction_mode: " 

1850 f'"{join_transaction_mode}"' 

1851 ) 

1852 self.join_transaction_mode = join_transaction_mode 

1853 

1854 self.twophase = twophase 

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

1856 if info: 

1857 self.info.update(info) 

1858 

1859 if binds is not None: 

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

1861 self._add_bind(key, bind) 

1862 

1863 _sessions[self.hash_key] = self 

1864 

1865 # used by sqlalchemy.engine.util.TransactionalContext 

1866 _trans_context_manager: Optional[TransactionalContext] = None 

1867 

1868 connection_callable: Optional[_ConnectionCallableProto] = None 

1869 

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

1871 return self 

1872 

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

1874 self.close() 

1875 

1876 @contextlib.contextmanager 

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

1878 with self: 

1879 with self.begin(): 

1880 yield self 

1881 

1882 def in_transaction(self) -> bool: 

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

1884 

1885 .. versionadded:: 1.4 

1886 

1887 .. seealso:: 

1888 

1889 :attr:`_orm.Session.is_active` 

1890 

1891 

1892 """ 

1893 return self._transaction is not None 

1894 

1895 def in_nested_transaction(self) -> bool: 

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

1897 transaction, e.g. SAVEPOINT. 

1898 

1899 .. versionadded:: 1.4 

1900 

1901 """ 

1902 return self._nested_transaction is not None 

1903 

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

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

1906 

1907 .. versionadded:: 1.4 

1908 

1909 """ 

1910 trans = self._transaction 

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

1912 trans = trans._parent 

1913 return trans 

1914 

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

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

1917 

1918 .. versionadded:: 1.4 

1919 

1920 """ 

1921 

1922 return self._nested_transaction 

1923 

1924 @util.memoized_property 

1925 def info(self) -> _InfoType: 

1926 """A user-modifiable dictionary. 

1927 

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

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

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

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

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

1933 

1934 """ 

1935 return {} 

1936 

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

1938 if self._transaction is None: 

1939 if not begin and not self.autobegin: 

1940 raise sa_exc.InvalidRequestError( 

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

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

1943 ) 

1944 trans = SessionTransaction( 

1945 self, 

1946 ( 

1947 SessionTransactionOrigin.BEGIN 

1948 if begin 

1949 else SessionTransactionOrigin.AUTOBEGIN 

1950 ), 

1951 ) 

1952 assert self._transaction is trans 

1953 return trans 

1954 

1955 return self._transaction 

1956 

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

1958 """Begin a transaction, or nested transaction, 

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

1960 

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

1962 so that normally it is not necessary to call the 

1963 :meth:`_orm.Session.begin` 

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

1965 the scope of when the transactional state is begun. 

1966 

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

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

1969 

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

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

1972 documentation on SAVEPOINT transactions, please see 

1973 :ref:`session_begin_nested`. 

1974 

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

1976 :class:`.SessionTransaction` 

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

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

1979 an example. 

1980 

1981 .. seealso:: 

1982 

1983 :ref:`session_autobegin` 

1984 

1985 :ref:`unitofwork_transaction` 

1986 

1987 :meth:`.Session.begin_nested` 

1988 

1989 

1990 """ 

1991 

1992 trans = self._transaction 

1993 if trans is None: 

1994 trans = self._autobegin_t(begin=True) 

1995 

1996 if not nested: 

1997 return trans 

1998 

1999 assert trans is not None 

2000 

2001 if nested: 

2002 trans = trans._begin(nested=nested) 

2003 assert self._transaction is trans 

2004 self._nested_transaction = trans 

2005 else: 

2006 raise sa_exc.InvalidRequestError( 

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

2008 ) 

2009 

2010 return trans # needed for __enter__/__exit__ hook 

2011 

2012 def begin_nested(self) -> SessionTransaction: 

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

2014 

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

2016 SAVEPOINT for this method to function correctly. 

2017 

2018 For documentation on SAVEPOINT 

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

2020 

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

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

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

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

2025 

2026 .. seealso:: 

2027 

2028 :ref:`session_begin_nested` 

2029 

2030 :ref:`pysqlite_serializable` - special workarounds required 

2031 with the SQLite driver in order for SAVEPOINT to work 

2032 correctly. For asyncio use cases, see the section 

2033 :ref:`aiosqlite_serializable`. 

2034 

2035 """ 

2036 return self.begin(nested=True) 

2037 

2038 def rollback(self) -> None: 

2039 """Rollback the current transaction in progress. 

2040 

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

2042 

2043 The method always rolls back 

2044 the topmost database transaction, discarding any nested 

2045 transactions that may be in progress. 

2046 

2047 .. seealso:: 

2048 

2049 :ref:`session_rollback` 

2050 

2051 :ref:`unitofwork_transaction` 

2052 

2053 """ 

2054 if self._transaction is None: 

2055 pass 

2056 else: 

2057 self._transaction.rollback(_to_root=True) 

2058 

2059 def commit(self) -> None: 

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

2061 

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

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

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

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

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

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

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

2069 to disable this behavior. 

2070 

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

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

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

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

2075 normally affect the database unless pending flush changes were 

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

2077 rules. 

2078 

2079 The outermost database transaction is committed unconditionally, 

2080 automatically releasing any SAVEPOINTs in effect. 

2081 

2082 .. seealso:: 

2083 

2084 :ref:`session_committing` 

2085 

2086 :ref:`unitofwork_transaction` 

2087 

2088 :ref:`asyncio_orm_avoid_lazyloads` 

2089 

2090 """ 

2091 trans = self._transaction 

2092 if trans is None: 

2093 trans = self._autobegin_t() 

2094 

2095 trans.commit(_to_root=True) 

2096 

2097 def prepare(self) -> None: 

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

2099 

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

2101 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2102 

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

2104 current transaction is not such, an 

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

2106 

2107 """ 

2108 trans = self._transaction 

2109 if trans is None: 

2110 trans = self._autobegin_t() 

2111 

2112 trans.prepare() 

2113 

2114 def connection( 

2115 self, 

2116 bind_arguments: Optional[_BindArguments] = None, 

2117 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2118 ) -> Connection: 

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

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

2121 

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

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

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

2125 returned (note that no 

2126 transactional state is established with the DBAPI until the first 

2127 SQL statement is emitted). 

2128 

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

2130 resolved through any of the optional keyword arguments. This 

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

2132 

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

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

2135 to :meth:`.Session.get_bind`. 

2136 

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

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

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

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

2141 the arguments are ignored. 

2142 

2143 .. seealso:: 

2144 

2145 :ref:`session_transaction_isolation` 

2146 

2147 """ 

2148 

2149 if bind_arguments: 

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

2151 

2152 if bind is None: 

2153 bind = self.get_bind(**bind_arguments) 

2154 else: 

2155 bind = self.get_bind() 

2156 

2157 return self._connection_for_bind( 

2158 bind, 

2159 execution_options=execution_options, 

2160 ) 

2161 

2162 def _connection_for_bind( 

2163 self, 

2164 engine: _SessionBind, 

2165 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2166 **kw: Any, 

2167 ) -> Connection: 

2168 TransactionalContext._trans_ctx_check(self) 

2169 

2170 trans = self._transaction 

2171 if trans is None: 

2172 trans = self._autobegin_t() 

2173 return trans._connection_for_bind(engine, execution_options) 

2174 

2175 @overload 

2176 def _execute_internal( 

2177 self, 

2178 statement: Executable, 

2179 params: Optional[_CoreSingleExecuteParams] = None, 

2180 *, 

2181 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2182 bind_arguments: Optional[_BindArguments] = None, 

2183 _parent_execute_state: Optional[Any] = None, 

2184 _add_event: Optional[Any] = None, 

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

2186 ) -> Any: ... 

2187 

2188 @overload 

2189 def _execute_internal( 

2190 self, 

2191 statement: Executable, 

2192 params: Optional[_CoreAnyExecuteParams] = None, 

2193 *, 

2194 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2195 bind_arguments: Optional[_BindArguments] = None, 

2196 _parent_execute_state: Optional[Any] = None, 

2197 _add_event: Optional[Any] = None, 

2198 _scalar_result: bool = ..., 

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

2200 

2201 def _execute_internal( 

2202 self, 

2203 statement: Executable, 

2204 params: Optional[_CoreAnyExecuteParams] = None, 

2205 *, 

2206 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2207 bind_arguments: Optional[_BindArguments] = None, 

2208 _parent_execute_state: Optional[Any] = None, 

2209 _add_event: Optional[Any] = None, 

2210 _scalar_result: bool = False, 

2211 ) -> Any: 

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

2213 

2214 if not bind_arguments: 

2215 bind_arguments = {} 

2216 else: 

2217 bind_arguments = dict(bind_arguments) 

2218 

2219 if ( 

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

2221 == "orm" 

2222 ): 

2223 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2224 statement, "orm" 

2225 ) 

2226 if TYPE_CHECKING: 

2227 assert isinstance( 

2228 compile_state_cls, context._AbstractORMCompileState 

2229 ) 

2230 else: 

2231 compile_state_cls = None 

2232 bind_arguments.setdefault("clause", statement) 

2233 

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

2235 util.coerce_to_immutabledict(execution_options) 

2236 ) 

2237 if self.execution_options: 

2238 # merge given execution options with session-wide execution 

2239 # options. if the statement also has execution_options, 

2240 # maintain priority of session.execution_options -> 

2241 # statement.execution_options -> method passed execution_options 

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

2243 # will come from the statement 

2244 if statement._execution_options: 

2245 combined_execution_options = util.immutabledict( 

2246 { 

2247 k: v 

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

2249 if k not in statement._execution_options 

2250 } 

2251 ).union(combined_execution_options) 

2252 else: 

2253 combined_execution_options = self.execution_options.union( 

2254 combined_execution_options 

2255 ) 

2256 

2257 if _parent_execute_state: 

2258 events_todo = _parent_execute_state._remaining_events() 

2259 else: 

2260 events_todo = self.dispatch.do_orm_execute 

2261 if _add_event: 

2262 events_todo = list(events_todo) + [_add_event] 

2263 

2264 if events_todo: 

2265 # save the original execution options before 

2266 # orm_pre_session_exec processes them, so that we can pass 

2267 # the unprocessed options (plus any explicit updates from event 

2268 # hooks) to the second orm_pre_session_exec call. This 

2269 # prevents internal state like _sa_orm_load_options and 

2270 # yield_per from the first call leaking into the second call, 

2271 # which would otherwise cause issues like yield_per incorrectly 

2272 # propagating into post-load (selectinload etc.) queries. 

2273 # part of #13301. 

2274 original_execution_options = combined_execution_options 

2275 

2276 if compile_state_cls is not None: 

2277 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2283 ( 

2284 statement, 

2285 combined_execution_options, 

2286 params, 

2287 ) = compile_state_cls.orm_pre_session_exec( 

2288 self, 

2289 statement, 

2290 params, 

2291 combined_execution_options, 

2292 bind_arguments, 

2293 True, 

2294 ) 

2295 

2296 orm_exec_state = ORMExecuteState( 

2297 self, 

2298 statement, 

2299 params, 

2300 combined_execution_options, 

2301 bind_arguments, 

2302 compile_state_cls, 

2303 events_todo, 

2304 ) 

2305 for idx, fn in enumerate(events_todo): 

2306 orm_exec_state._starting_event_idx = idx 

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

2308 orm_exec_state 

2309 ) 

2310 if fn_result: 

2311 if _scalar_result: 

2312 return fn_result.scalar() 

2313 else: 

2314 return fn_result 

2315 

2316 statement = orm_exec_state.statement 

2317 params = orm_exec_state.parameters 

2318 

2319 # use the original execution options plus only the explicit 

2320 # updates from event hooks, not the processed options from 

2321 # the first orm_pre_session_exec call 

2322 combined_execution_options = original_execution_options.union( 

2323 orm_exec_state._update_execution_options 

2324 ) 

2325 

2326 if compile_state_cls is not None: 

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

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

2329 # new execution_options into load_options / update_delete_options, 

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

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

2332 ( 

2333 statement, 

2334 combined_execution_options, 

2335 params, 

2336 ) = compile_state_cls.orm_pre_session_exec( 

2337 self, 

2338 statement, 

2339 params, 

2340 combined_execution_options, 

2341 bind_arguments, 

2342 False, 

2343 ) 

2344 else: 

2345 # Issue #9809: unconditionally autoflush for Core statements 

2346 self._autoflush() 

2347 

2348 bind = self.get_bind(**bind_arguments) 

2349 

2350 conn = self._connection_for_bind(bind) 

2351 

2352 if _scalar_result and not compile_state_cls: 

2353 if TYPE_CHECKING: 

2354 params = cast(_CoreSingleExecuteParams, params) 

2355 return conn.scalar( 

2356 statement, 

2357 params or {}, 

2358 execution_options=combined_execution_options, 

2359 ) 

2360 

2361 if compile_state_cls: 

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

2363 compile_state_cls.orm_execute_statement( 

2364 self, 

2365 statement, 

2366 params or {}, 

2367 combined_execution_options, 

2368 bind_arguments, 

2369 conn, 

2370 ) 

2371 ) 

2372 else: 

2373 result = conn.execute( 

2374 statement, params, execution_options=combined_execution_options 

2375 ) 

2376 

2377 if _scalar_result: 

2378 return result.scalar() 

2379 else: 

2380 return result 

2381 

2382 @overload 

2383 def execute( 

2384 self, 

2385 statement: TypedReturnsRows[Unpack[_Ts]], 

2386 params: Optional[_CoreAnyExecuteParams] = None, 

2387 *, 

2388 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2389 bind_arguments: Optional[_BindArguments] = None, 

2390 _parent_execute_state: Optional[Any] = None, 

2391 _add_event: Optional[Any] = None, 

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

2393 

2394 @overload 

2395 def execute( 

2396 self, 

2397 statement: Executable, 

2398 params: Optional[_CoreAnyExecuteParams] = None, 

2399 *, 

2400 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2401 bind_arguments: Optional[_BindArguments] = None, 

2402 _parent_execute_state: Optional[Any] = None, 

2403 _add_event: Optional[Any] = None, 

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

2405 

2406 def execute( 

2407 self, 

2408 statement: Executable, 

2409 params: Optional[_CoreAnyExecuteParams] = None, 

2410 *, 

2411 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2412 bind_arguments: Optional[_BindArguments] = None, 

2413 _parent_execute_state: Optional[Any] = None, 

2414 _add_event: Optional[Any] = None, 

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

2416 r"""Execute a SQL expression construct. 

2417 

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

2419 results of the statement execution. 

2420 

2421 E.g.:: 

2422 

2423 from sqlalchemy import select 

2424 

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

2426 

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

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

2429 of :class:`_engine.Connection`. 

2430 

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

2432 now the primary point of ORM statement execution when using 

2433 :term:`2.0 style` ORM usage. 

2434 

2435 :param statement: 

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

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

2438 

2439 :param params: 

2440 Optional dictionary, or list of dictionaries, containing 

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

2442 execution occurs; if a list of dictionaries, an 

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

2444 must correspond to parameter names present in the statement. 

2445 

2446 :param execution_options: optional dictionary of execution options, 

2447 which will be associated with the statement execution. This 

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

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

2450 provide additional options understood only in an ORM context. 

2451 

2452 The execution_options are passed along to methods like 

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

2454 highest priority to execution_options that are passed to this 

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

2456 statement object if any, and finally those options present 

2457 session-wide. 

2458 

2459 .. seealso:: 

2460 

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

2462 options 

2463 

2464 :param bind_arguments: dictionary of additional arguments to determine 

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

2466 Contents of this dictionary are passed to the 

2467 :meth:`.Session.get_bind` method. 

2468 

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

2470 

2471 

2472 """ 

2473 return self._execute_internal( 

2474 statement, 

2475 params, 

2476 execution_options=execution_options, 

2477 bind_arguments=bind_arguments, 

2478 _parent_execute_state=_parent_execute_state, 

2479 _add_event=_add_event, 

2480 ) 

2481 

2482 # special case to handle mypy issue: 

2483 # https://github.com/python/mypy/issues/20651 

2484 @overload 

2485 def scalar( 

2486 self, 

2487 statement: TypedReturnsRows[Never], 

2488 params: Optional[_CoreSingleExecuteParams] = None, 

2489 *, 

2490 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2491 bind_arguments: Optional[_BindArguments] = None, 

2492 **kw: Any, 

2493 ) -> Optional[Any]: ... 

2494 

2495 @overload 

2496 def scalar( 

2497 self, 

2498 statement: TypedReturnsRows[_T], 

2499 params: Optional[_CoreSingleExecuteParams] = None, 

2500 *, 

2501 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2502 bind_arguments: Optional[_BindArguments] = None, 

2503 **kw: Any, 

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

2505 

2506 @overload 

2507 def scalar( 

2508 self, 

2509 statement: Executable, 

2510 params: Optional[_CoreSingleExecuteParams] = None, 

2511 *, 

2512 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2513 bind_arguments: Optional[_BindArguments] = None, 

2514 **kw: Any, 

2515 ) -> Any: ... 

2516 

2517 def scalar( 

2518 self, 

2519 statement: Executable, 

2520 params: Optional[_CoreSingleExecuteParams] = None, 

2521 *, 

2522 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2523 bind_arguments: Optional[_BindArguments] = None, 

2524 **kw: Any, 

2525 ) -> Any: 

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

2527 

2528 Usage and parameters are the same as that of 

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

2530 value. 

2531 

2532 """ 

2533 

2534 return self._execute_internal( 

2535 statement, 

2536 params, 

2537 execution_options=execution_options, 

2538 bind_arguments=bind_arguments, 

2539 _scalar_result=True, 

2540 **kw, 

2541 ) 

2542 

2543 @overload 

2544 def scalars( 

2545 self, 

2546 statement: TypedReturnsRows[_T], 

2547 params: Optional[_CoreAnyExecuteParams] = None, 

2548 *, 

2549 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2550 bind_arguments: Optional[_BindArguments] = None, 

2551 **kw: Any, 

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

2553 

2554 @overload 

2555 def scalars( 

2556 self, 

2557 statement: Executable, 

2558 params: Optional[_CoreAnyExecuteParams] = None, 

2559 *, 

2560 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2561 bind_arguments: Optional[_BindArguments] = None, 

2562 **kw: Any, 

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

2564 

2565 def scalars( 

2566 self, 

2567 statement: Executable, 

2568 params: Optional[_CoreAnyExecuteParams] = None, 

2569 *, 

2570 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2571 bind_arguments: Optional[_BindArguments] = None, 

2572 **kw: Any, 

2573 ) -> ScalarResult[Any]: 

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

2575 

2576 Usage and parameters are the same as that of 

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

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

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

2580 

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

2582 

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

2584 

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

2586 

2587 .. seealso:: 

2588 

2589 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2591 

2592 """ 

2593 

2594 return self._execute_internal( 

2595 statement, 

2596 params=params, 

2597 execution_options=execution_options, 

2598 bind_arguments=bind_arguments, 

2599 _scalar_result=False, # mypy appreciates this 

2600 **kw, 

2601 ).scalars() 

2602 

2603 def close(self) -> None: 

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

2605 :class:`_orm.Session`. 

2606 

2607 This expunges all ORM objects associated with this 

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

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

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

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

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

2613 

2614 .. tip:: 

2615 

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

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

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

2619 distinct "closed" state; it merely means 

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

2621 and ORM objects. 

2622 

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

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

2625 any further action on the session will be forbidden. 

2626 

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

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

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

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

2631 

2632 .. seealso:: 

2633 

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

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

2636 

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

2638 ``close()`` with the parameter 

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

2640 

2641 """ 

2642 self._close_impl(invalidate=False) 

2643 

2644 def reset(self) -> None: 

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

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

2647 

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

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

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

2651 brand new, and ready to be used again. 

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

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

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

2655 

2656 .. versionadded:: 2.0.22 

2657 

2658 .. seealso:: 

2659 

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

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

2662 

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

2664 prevent reuse of the Session when the parameter 

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

2666 """ 

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

2668 

2669 def invalidate(self) -> None: 

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

2671 

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

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

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

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

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

2677 multiple engines). 

2678 

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

2680 the connections are no longer safe to be used. 

2681 

2682 Below illustrates a scenario when using `gevent 

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

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

2685 

2686 import gevent 

2687 

2688 try: 

2689 sess = Session() 

2690 sess.add(User()) 

2691 sess.commit() 

2692 except gevent.Timeout: 

2693 sess.invalidate() 

2694 raise 

2695 except: 

2696 sess.rollback() 

2697 raise 

2698 

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

2700 does, including that all ORM objects are expunged. 

2701 

2702 """ 

2703 self._close_impl(invalidate=True) 

2704 

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

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

2707 self._close_state = _SessionCloseState.CLOSED 

2708 self.expunge_all() 

2709 if self._transaction is not None: 

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

2711 transaction.close(invalidate) 

2712 

2713 def expunge_all(self) -> None: 

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

2715 

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

2717 ``Session``. 

2718 

2719 """ 

2720 

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

2722 self.identity_map._kill() 

2723 self.identity_map = identity._WeakInstanceDict() 

2724 self._new = {} 

2725 self._deleted = {} 

2726 

2727 statelib.InstanceState._detach_states(all_states, self) 

2728 

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

2730 new_binds: Dict[_SessionBindKey, _SessionBind] = {} 

2731 

2732 try: 

2733 insp = inspect(key) 

2734 except sa_exc.NoInspectionAvailable as err: 

2735 if not isinstance(key, type): 

2736 raise sa_exc.ArgumentError( 

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

2738 ) from err 

2739 else: 

2740 new_binds[key] = bind 

2741 else: 

2742 if TYPE_CHECKING: 

2743 assert isinstance(insp, Inspectable) 

2744 

2745 if isinstance(insp, TableClause): 

2746 new_binds[insp] = bind 

2747 elif insp_is_mapper(insp): 

2748 new_binds[insp.class_] = bind 

2749 for _selectable in insp._all_tables: 

2750 new_binds[_selectable] = bind 

2751 else: 

2752 raise sa_exc.ArgumentError( 

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

2754 ) 

2755 

2756 self.binds = self.binds.merge_with( # type: ignore[attr-defined] 

2757 new_binds 

2758 ) 

2759 

2760 def bind_mapper( 

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

2762 ) -> None: 

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

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

2765 :class:`_engine.Connection`. 

2766 

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

2768 :meth:`.Session.get_bind` method. 

2769 

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

2771 or an instance of a mapped 

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

2773 classes. 

2774 

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

2776 object. 

2777 

2778 .. seealso:: 

2779 

2780 :ref:`session_partitioning` 

2781 

2782 :paramref:`.Session.binds` 

2783 

2784 :meth:`.Session.bind_table` 

2785 

2786 

2787 """ 

2788 self._add_bind(mapper, bind) 

2789 

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

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

2792 :class:`_engine.Engine` 

2793 or :class:`_engine.Connection`. 

2794 

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

2796 :meth:`.Session.get_bind` method. 

2797 

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

2799 which is typically the target 

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

2801 mapped. 

2802 

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

2804 object. 

2805 

2806 .. seealso:: 

2807 

2808 :ref:`session_partitioning` 

2809 

2810 :paramref:`.Session.binds` 

2811 

2812 :meth:`.Session.bind_mapper` 

2813 

2814 

2815 """ 

2816 self._add_bind(table, bind) 

2817 

2818 def get_bind( 

2819 self, 

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

2821 *, 

2822 clause: Optional[ClauseElement] = None, 

2823 bind: Optional[_SessionBind] = None, 

2824 _sa_skip_events: Optional[bool] = None, 

2825 _sa_skip_for_implicit_returning: bool = False, 

2826 **kw: Any, 

2827 ) -> Union[Engine, Connection]: 

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

2829 

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

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

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

2833 

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

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

2836 appropriate bind to return. 

2837 

2838 Note that the "mapper" argument is usually present 

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

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

2841 individual INSERT/UPDATE/DELETE operation within a 

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

2843 

2844 The order of resolution is: 

2845 

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

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

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

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

2850 superclasses to more general. 

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

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

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

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

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

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

2857 associated with the clause. 

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

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

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

2861 selectable to which the mapper is mapped. 

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

2863 is raised. 

2864 

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

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

2867 of bind resolution scheme. See the example at 

2868 :ref:`session_custom_partitioning`. 

2869 

2870 :param mapper: 

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

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

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

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

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

2876 mapped for a bind. 

2877 

2878 :param clause: 

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

2880 :func:`_expression.select`, 

2881 :func:`_expression.text`, 

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

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

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

2885 associated with 

2886 bound :class:`_schema.MetaData`. 

2887 

2888 .. seealso:: 

2889 

2890 :ref:`session_partitioning` 

2891 

2892 :paramref:`.Session.binds` 

2893 

2894 :meth:`.Session.bind_mapper` 

2895 

2896 :meth:`.Session.bind_table` 

2897 

2898 """ 

2899 

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

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

2902 if bind: 

2903 return bind 

2904 elif not self.binds and self.bind: 

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

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

2907 return self.bind 

2908 

2909 # we don't have self.bind and either have self.binds 

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

2911 # mapper and the clause 

2912 if mapper is None and clause is None: 

2913 if self.bind: 

2914 return self.bind 

2915 else: 

2916 raise sa_exc.UnboundExecutionError( 

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

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

2919 "a binding." 

2920 ) 

2921 

2922 # look more closely at the mapper. 

2923 if mapper is not None: 

2924 try: 

2925 inspected_mapper = inspect(mapper) 

2926 except sa_exc.NoInspectionAvailable as err: 

2927 if isinstance(mapper, type): 

2928 raise exc.UnmappedClassError(mapper) from err 

2929 else: 

2930 raise 

2931 else: 

2932 inspected_mapper = None 

2933 

2934 # match up the mapper or clause in the binds 

2935 if self.binds: 

2936 # matching mappers and selectables to entries in the 

2937 # binds dictionary; supported use case. 

2938 if inspected_mapper: 

2939 for cls in inspected_mapper.class_.__mro__: 

2940 if cls in self.binds: 

2941 return self.binds[cls] 

2942 if clause is None: 

2943 clause = inspected_mapper.persist_selectable 

2944 

2945 if clause is not None: 

2946 plugin_subject = clause._propagate_attrs.get( 

2947 "plugin_subject", None 

2948 ) 

2949 

2950 if plugin_subject is not None: 

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

2952 if cls in self.binds: 

2953 return self.binds[cls] 

2954 

2955 for obj in visitors.iterate(clause): 

2956 if obj in self.binds: 

2957 if TYPE_CHECKING: 

2958 assert isinstance(obj, Table) 

2959 return self.binds[obj] 

2960 

2961 # none of the binds matched, but we have a fallback bind. 

2962 # return that 

2963 if self.bind: 

2964 return self.bind 

2965 

2966 context = [] 

2967 if inspected_mapper is not None: 

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

2969 if clause is not None: 

2970 context.append("SQL expression") 

2971 

2972 raise sa_exc.UnboundExecutionError( 

2973 f"Could not locate a bind configured on " 

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

2975 ) 

2976 

2977 @overload 

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

2979 

2980 @overload 

2981 def query( 

2982 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2984 

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

2986 

2987 # code within this block is **programmatically, 

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

2989 

2990 @overload 

2991 def query( 

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

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

2994 

2995 @overload 

2996 def query( 

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

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

2999 

3000 @overload 

3001 def query( 

3002 self, 

3003 __ent0: _TCCA[_T0], 

3004 __ent1: _TCCA[_T1], 

3005 __ent2: _TCCA[_T2], 

3006 __ent3: _TCCA[_T3], 

3007 /, 

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

3009 

3010 @overload 

3011 def query( 

3012 self, 

3013 __ent0: _TCCA[_T0], 

3014 __ent1: _TCCA[_T1], 

3015 __ent2: _TCCA[_T2], 

3016 __ent3: _TCCA[_T3], 

3017 __ent4: _TCCA[_T4], 

3018 /, 

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

3020 

3021 @overload 

3022 def query( 

3023 self, 

3024 __ent0: _TCCA[_T0], 

3025 __ent1: _TCCA[_T1], 

3026 __ent2: _TCCA[_T2], 

3027 __ent3: _TCCA[_T3], 

3028 __ent4: _TCCA[_T4], 

3029 __ent5: _TCCA[_T5], 

3030 /, 

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

3032 

3033 @overload 

3034 def query( 

3035 self, 

3036 __ent0: _TCCA[_T0], 

3037 __ent1: _TCCA[_T1], 

3038 __ent2: _TCCA[_T2], 

3039 __ent3: _TCCA[_T3], 

3040 __ent4: _TCCA[_T4], 

3041 __ent5: _TCCA[_T5], 

3042 __ent6: _TCCA[_T6], 

3043 /, 

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

3045 

3046 @overload 

3047 def query( 

3048 self, 

3049 __ent0: _TCCA[_T0], 

3050 __ent1: _TCCA[_T1], 

3051 __ent2: _TCCA[_T2], 

3052 __ent3: _TCCA[_T3], 

3053 __ent4: _TCCA[_T4], 

3054 __ent5: _TCCA[_T5], 

3055 __ent6: _TCCA[_T6], 

3056 __ent7: _TCCA[_T7], 

3057 /, 

3058 *entities: _ColumnsClauseArgument[Any], 

3059 ) -> RowReturningQuery[ 

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

3061 ]: ... 

3062 

3063 # END OVERLOADED FUNCTIONS self.query 

3064 

3065 @overload 

3066 def query( 

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

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

3069 

3070 def query( 

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

3072 ) -> Query[Any]: 

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

3074 :class:`_orm.Session`. 

3075 

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

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

3078 to construct ORM queries. 

3079 

3080 .. seealso:: 

3081 

3082 :ref:`unified_tutorial` 

3083 

3084 :ref:`queryguide_toplevel` 

3085 

3086 :ref:`query_api_toplevel` - legacy API doc 

3087 

3088 """ 

3089 

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

3091 

3092 def _identity_lookup( 

3093 self, 

3094 mapper: Mapper[_O], 

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

3096 identity_token: Any = None, 

3097 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

3099 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3100 bind_arguments: Optional[_BindArguments] = None, 

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

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

3103 

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

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

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

3107 check if was deleted). 

3108 

3109 e.g.:: 

3110 

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

3112 

3113 :param mapper: mapper in use 

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

3115 a tuple. 

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

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

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

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

3120 :param passive: passive load flag passed to 

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

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

3123 if the flag allows for SQL to be emitted. 

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

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

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

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

3128 relationship-loaded). 

3129 

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

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

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

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

3134 

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

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

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

3138 :class:`_query.Query` object. 

3139 

3140 

3141 """ 

3142 

3143 key = mapper.identity_key_from_primary_key( 

3144 primary_key_identity, identity_token=identity_token 

3145 ) 

3146 

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

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

3149 return return_value 

3150 

3151 @util.non_memoized_property 

3152 @contextlib.contextmanager 

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

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

3155 

3156 e.g.:: 

3157 

3158 with session.no_autoflush: 

3159 

3160 some_object = SomeClass() 

3161 session.add(some_object) 

3162 # won't autoflush 

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

3164 

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

3166 will not be subject to flushes occurring upon query 

3167 access. This is useful when initializing a series 

3168 of objects which involve existing database queries, 

3169 where the uncompleted object should not yet be flushed. 

3170 

3171 """ 

3172 autoflush = self.autoflush 

3173 self.autoflush = False 

3174 try: 

3175 yield self 

3176 finally: 

3177 self.autoflush = autoflush 

3178 

3179 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3183 "warning happened while initializing objects.", 

3184 sa_exc.SAWarning, 

3185 ) 

3186 def _autoflush(self) -> None: 

3187 if self.autoflush and not self._flushing: 

3188 try: 

3189 self.flush() 

3190 except sa_exc.StatementError as e: 

3191 # note we are reraising StatementError as opposed to 

3192 # raising FlushError with "chaining" to remain compatible 

3193 # with code that catches StatementError, IntegrityError, 

3194 # etc. 

3195 e.add_detail( 

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

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

3198 "flush is occurring prematurely" 

3199 ) 

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

3201 

3202 def refresh( 

3203 self, 

3204 instance: object, 

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

3206 with_for_update: ForUpdateParameter = None, 

3207 ) -> None: 

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

3209 

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

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

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

3213 value available in the current transaction. 

3214 

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

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

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

3218 

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

3220 can also refresh eagerly loaded attributes. 

3221 

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

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

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

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

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

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

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

3229 refreshed. 

3230 

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

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

3233 attributes for those which are named explicitly in the 

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

3235 

3236 .. tip:: 

3237 

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

3239 refreshing both column and relationship oriented attributes, its 

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

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

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

3243 once while having explicit control over relationship loader 

3244 strategies, use the 

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

3246 instead. 

3247 

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

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

3250 in database state outside of that transaction. Refreshing 

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

3252 where database rows have not yet been accessed. 

3253 

3254 :param attribute_names: optional. An iterable collection of 

3255 string attribute names indicating a subset of attributes to 

3256 be refreshed. 

3257 

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

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

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

3261 flags should match the parameters of 

3262 :meth:`_query.Query.with_for_update`. 

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

3264 

3265 .. seealso:: 

3266 

3267 :ref:`session_expire` - introductory material 

3268 

3269 :meth:`.Session.expire` 

3270 

3271 :meth:`.Session.expire_all` 

3272 

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

3274 to refresh objects as they would be loaded normally. 

3275 

3276 """ 

3277 try: 

3278 state = attributes.instance_state(instance) 

3279 except exc.NO_STATE as err: 

3280 raise exc.UnmappedInstanceError(instance) from err 

3281 

3282 self._expire_state(state, attribute_names) 

3283 

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

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

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

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

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

3289 # load_on_ident. 

3290 self._autoflush() 

3291 

3292 if with_for_update == {}: 

3293 raise sa_exc.ArgumentError( 

3294 "with_for_update should be the boolean value " 

3295 "True, or a dictionary with options. " 

3296 "A blank dictionary is ambiguous." 

3297 ) 

3298 

3299 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3300 

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

3302 if ( 

3303 loading._load_on_ident( 

3304 self, 

3305 stmt, 

3306 state.key, 

3307 refresh_state=state, 

3308 with_for_update=with_for_update, 

3309 only_load_props=attribute_names, 

3310 require_pk_cols=True, 

3311 # technically unnecessary as we just did autoflush 

3312 # above, however removes the additional unnecessary 

3313 # call to _autoflush() 

3314 no_autoflush=True, 

3315 is_user_refresh=True, 

3316 ) 

3317 is None 

3318 ): 

3319 raise sa_exc.InvalidRequestError( 

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

3321 ) 

3322 

3323 def expire_all(self) -> None: 

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

3325 

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

3327 a query will be issued using the 

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

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

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

3331 previously read in that same transaction, regardless of changes 

3332 in database state outside of that transaction. 

3333 

3334 To expire individual objects and individual attributes 

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

3336 

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

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

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

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

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

3342 assuming the transaction is isolated. 

3343 

3344 .. seealso:: 

3345 

3346 :ref:`session_expire` - introductory material 

3347 

3348 :meth:`.Session.expire` 

3349 

3350 :meth:`.Session.refresh` 

3351 

3352 :meth:`_orm.Query.populate_existing` 

3353 

3354 """ 

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

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

3357 

3358 def expire( 

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

3360 ) -> None: 

3361 """Expire the attributes on an instance. 

3362 

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

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

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

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

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

3368 previously read in that same transaction, regardless of changes 

3369 in database state outside of that transaction. 

3370 

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

3372 use :meth:`Session.expire_all`. 

3373 

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

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

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

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

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

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

3380 transaction. 

3381 

3382 :param instance: The instance to be refreshed. 

3383 :param attribute_names: optional list of string attribute names 

3384 indicating a subset of attributes to be expired. 

3385 

3386 .. seealso:: 

3387 

3388 :ref:`session_expire` - introductory material 

3389 

3390 :meth:`.Session.expire` 

3391 

3392 :meth:`.Session.refresh` 

3393 

3394 :meth:`_orm.Query.populate_existing` 

3395 

3396 """ 

3397 try: 

3398 state = attributes.instance_state(instance) 

3399 except exc.NO_STATE as err: 

3400 raise exc.UnmappedInstanceError(instance) from err 

3401 self._expire_state(state, attribute_names) 

3402 

3403 def _expire_state( 

3404 self, 

3405 state: InstanceState[Any], 

3406 attribute_names: Optional[Iterable[str]], 

3407 ) -> None: 

3408 self._validate_persistent(state) 

3409 if attribute_names: 

3410 state._expire_attributes(state.dict, attribute_names) 

3411 else: 

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

3413 # remove associations 

3414 cascaded = list( 

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

3416 ) 

3417 self._conditional_expire(state) 

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

3419 self._conditional_expire(st_) 

3420 

3421 def _conditional_expire( 

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

3423 ) -> None: 

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

3425 

3426 if state.key: 

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

3428 elif state in self._new: 

3429 self._new.pop(state) 

3430 state._detach(self) 

3431 

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

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

3434 

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

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

3437 

3438 """ 

3439 try: 

3440 state = attributes.instance_state(instance) 

3441 except exc.NO_STATE as err: 

3442 raise exc.UnmappedInstanceError(instance) from err 

3443 if state.session_id is not self.hash_key: 

3444 raise sa_exc.InvalidRequestError( 

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

3446 ) 

3447 

3448 cascaded = list( 

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

3450 ) 

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

3452 

3453 def _expunge_states( 

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

3455 ) -> None: 

3456 for state in states: 

3457 if state in self._new: 

3458 self._new.pop(state) 

3459 elif self.identity_map.contains_state(state): 

3460 self.identity_map.safe_discard(state) 

3461 self._deleted.pop(state, None) 

3462 elif self._transaction: 

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

3464 # in the transaction snapshot 

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

3466 statelib.InstanceState._detach_states( 

3467 states, self, to_transient=to_transient 

3468 ) 

3469 

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

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

3472 

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

3474 state as well as already persistent objects. 

3475 

3476 """ 

3477 

3478 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3479 for state in states: 

3480 mapper = _state_mapper(state) 

3481 

3482 # prevent against last minute dereferences of the object 

3483 obj = state.obj() 

3484 if obj is not None: 

3485 instance_key = mapper._identity_key_from_state(state) 

3486 

3487 if ( 

3488 _none_set.intersection(instance_key[1]) 

3489 and not mapper.allow_partial_pks 

3490 or _none_set.issuperset(instance_key[1]) 

3491 ): 

3492 raise exc.FlushError( 

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

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

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

3496 "that the mapped Column object is configured to " 

3497 "expect these generated values. Ensure also that " 

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

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

3500 % state_str(state) 

3501 ) 

3502 

3503 if state.key is None: 

3504 state.key = instance_key 

3505 elif state.key != instance_key: 

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

3507 # state has already replaced this one in the identity 

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

3509 self.identity_map.safe_discard(state) 

3510 trans = self._transaction 

3511 assert trans is not None 

3512 if state in trans._key_switches: 

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

3514 else: 

3515 orig_key = state.key 

3516 trans._key_switches[state] = ( 

3517 orig_key, 

3518 instance_key, 

3519 ) 

3520 state.key = instance_key 

3521 

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

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

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

3525 old = self.identity_map.replace(state) 

3526 if ( 

3527 old is not None 

3528 and mapper._identity_key_from_state(old) == instance_key 

3529 and old.obj() is not None 

3530 ): 

3531 util.warn( 

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

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

3534 "load operations occurring inside of an event handler " 

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

3536 ) 

3537 state._orphaned_outside_of_session = False 

3538 

3539 statelib.InstanceState._commit_all_states( 

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

3541 ) 

3542 

3543 self._register_altered(states) 

3544 

3545 if pending_to_persistent is not None: 

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

3547 pending_to_persistent(self, state) 

3548 

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

3550 for state in set(states).intersection(self._new): 

3551 self._new.pop(state) 

3552 

3553 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None: 

3554 if self._transaction: 

3555 for state in states: 

3556 if state in self._new: 

3557 self._transaction._new[state] = True 

3558 else: 

3559 self._transaction._dirty[state] = True 

3560 

3561 def _remove_newly_deleted( 

3562 self, states: Iterable[InstanceState[Any]] 

3563 ) -> None: 

3564 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3565 for state in states: 

3566 if self._transaction: 

3567 self._transaction._deleted[state] = True 

3568 

3569 if persistent_to_deleted is not None: 

3570 # get a strong reference before we pop out of 

3571 # self._deleted 

3572 obj = state.obj() # noqa 

3573 

3574 self.identity_map.safe_discard(state) 

3575 self._deleted.pop(state, None) 

3576 state._deleted = True 

3577 # can't call state._detach() here, because this state 

3578 # is still in the transaction snapshot and needs to be 

3579 # tracked as part of that 

3580 if persistent_to_deleted is not None: 

3581 persistent_to_deleted(self, state) 

3582 

3583 def add(self, instance: object, *, _warn: bool = True) -> None: 

3584 """Place an object into this :class:`_orm.Session`. 

3585 

3586 Objects that are in the :term:`transient` state when passed to the 

3587 :meth:`_orm.Session.add` method will move to the 

3588 :term:`pending` state, until the next flush, at which point they 

3589 will move to the :term:`persistent` state. 

3590 

3591 Objects that are in the :term:`detached` state when passed to the 

3592 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3593 state directly. 

3594 

3595 If the transaction used by the :class:`_orm.Session` is rolled back, 

3596 objects which were transient when they were passed to 

3597 :meth:`_orm.Session.add` will be moved back to the 

3598 :term:`transient` state, and will no longer be present within this 

3599 :class:`_orm.Session`. 

3600 

3601 .. seealso:: 

3602 

3603 :meth:`_orm.Session.add_all` 

3604 

3605 :ref:`session_adding` - at :ref:`session_basics` 

3606 

3607 """ 

3608 if _warn and self._warn_on_events: 

3609 self._flush_warning("Session.add()") 

3610 

3611 try: 

3612 state = attributes.instance_state(instance) 

3613 except exc.NO_STATE as err: 

3614 raise exc.UnmappedInstanceError(instance) from err 

3615 

3616 self._save_or_update_state(state) 

3617 

3618 def add_all(self, instances: Iterable[object]) -> None: 

3619 """Add the given collection of instances to this :class:`_orm.Session`. 

3620 

3621 See the documentation for :meth:`_orm.Session.add` for a general 

3622 behavioral description. 

3623 

3624 .. seealso:: 

3625 

3626 :meth:`_orm.Session.add` 

3627 

3628 :ref:`session_adding` - at :ref:`session_basics` 

3629 

3630 """ 

3631 

3632 if self._warn_on_events: 

3633 self._flush_warning("Session.add_all()") 

3634 

3635 for instance in instances: 

3636 self.add(instance, _warn=False) 

3637 

3638 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3639 state._orphaned_outside_of_session = False 

3640 self._save_or_update_impl(state) 

3641 

3642 mapper = _state_mapper(state) 

3643 for o, m, st_, dct_ in mapper.cascade_iterator( 

3644 "save-update", state, halt_on=self._contains_state 

3645 ): 

3646 self._save_or_update_impl(st_) 

3647 

3648 def delete(self, instance: object) -> None: 

3649 """Mark an instance as deleted. 

3650 

3651 The object is assumed to be either :term:`persistent` or 

3652 :term:`detached` when passed; after the method is called, the 

3653 object will remain in the :term:`persistent` state until the next 

3654 flush proceeds. During this time, the object will also be a member 

3655 of the :attr:`_orm.Session.deleted` collection. 

3656 

3657 When the next flush proceeds, the object will move to the 

3658 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3659 for its row within the current transaction. When the transaction 

3660 is successfully committed, 

3661 the deleted object is moved to the :term:`detached` state and is 

3662 no longer present within this :class:`_orm.Session`. 

3663 

3664 .. seealso:: 

3665 

3666 :ref:`session_deleting` - at :ref:`session_basics` 

3667 

3668 :meth:`.Session.delete_all` - multiple instance version 

3669 

3670 """ 

3671 if self._warn_on_events: 

3672 self._flush_warning("Session.delete()") 

3673 

3674 self._delete_impl(object_state(instance), instance, head=True) 

3675 

3676 def delete_all(self, instances: Iterable[object]) -> None: 

3677 """Calls :meth:`.Session.delete` on multiple instances. 

3678 

3679 .. seealso:: 

3680 

3681 :meth:`.Session.delete` - main documentation on delete 

3682 

3683 .. versionadded:: 2.1 

3684 

3685 """ 

3686 

3687 if self._warn_on_events: 

3688 self._flush_warning("Session.delete_all()") 

3689 

3690 for instance in instances: 

3691 self._delete_impl(object_state(instance), instance, head=True) 

3692 

3693 def _delete_impl( 

3694 self, state: InstanceState[Any], obj: object, head: bool 

3695 ) -> None: 

3696 if state.key is None: 

3697 if head: 

3698 raise sa_exc.InvalidRequestError( 

3699 "Instance '%s' is not persisted" % state_str(state) 

3700 ) 

3701 else: 

3702 return 

3703 

3704 to_attach = self._before_attach(state, obj) 

3705 

3706 if state in self._deleted: 

3707 return 

3708 

3709 self.identity_map.add(state) 

3710 

3711 if to_attach: 

3712 self._after_attach(state, obj) 

3713 

3714 if head: 

3715 # grab the cascades before adding the item to the deleted list 

3716 # so that autoflush does not delete the item 

3717 # the strong reference to the instance itself is significant here 

3718 cascade_states = list( 

3719 state.manager.mapper.cascade_iterator("delete", state) 

3720 ) 

3721 else: 

3722 cascade_states = None 

3723 

3724 self._deleted[state] = obj 

3725 

3726 if head: 

3727 if TYPE_CHECKING: 

3728 assert cascade_states is not None 

3729 for o, m, st_, dct_ in cascade_states: 

3730 self._delete_impl(st_, o, False) 

3731 

3732 def get( 

3733 self, 

3734 entity: _EntityBindKey[_O], 

3735 ident: _PKIdentityArgument, 

3736 *, 

3737 options: Optional[Sequence[ORMOption]] = None, 

3738 populate_existing: bool | None = None, 

3739 with_for_update: ForUpdateParameter = None, 

3740 identity_token: Optional[Any] = None, 

3741 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3742 bind_arguments: Optional[_BindArguments] = None, 

3743 ) -> Optional[_O]: 

3744 """Return an instance based on the given primary key identifier, 

3745 or ``None`` if not found. 

3746 

3747 E.g.:: 

3748 

3749 my_user = session.get(User, 5) 

3750 

3751 some_object = session.get(VersionedFoo, (5, 10)) 

3752 

3753 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3754 

3755 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3756 from the now legacy :meth:`_orm.Query.get` method. 

3757 

3758 :meth:`_orm.Session.get` is special in that it provides direct 

3759 access to the identity map of the :class:`.Session`. 

3760 If the given primary key identifier is present 

3761 in the local identity map, the object is returned 

3762 directly from this collection and no SQL is emitted, 

3763 unless the object has been marked fully expired. 

3764 If not present, 

3765 a SELECT is performed in order to locate the object. 

3766 

3767 :meth:`_orm.Session.get` also will perform a check if 

3768 the object is present in the identity map and 

3769 marked as expired - a SELECT 

3770 is emitted to refresh the object as well as to 

3771 ensure that the row is still present. 

3772 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3773 

3774 :param entity: a mapped class or :class:`.Mapper` indicating the 

3775 type of entity to be loaded. 

3776 

3777 :param ident: A scalar, tuple, or dictionary representing the 

3778 primary key. For a composite (e.g. multiple column) primary key, 

3779 a tuple or dictionary should be passed. 

3780 

3781 For a single-column primary key, the scalar calling form is typically 

3782 the most expedient. If the primary key of a row is the value "5", 

3783 the call looks like:: 

3784 

3785 my_object = session.get(SomeClass, 5) 

3786 

3787 The tuple form contains primary key values typically in 

3788 the order in which they correspond to the mapped 

3789 :class:`_schema.Table` 

3790 object's primary key columns, or if the 

3791 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3792 used, in 

3793 the order used for that parameter. For example, if the primary key 

3794 of a row is represented by the integer 

3795 digits "5, 10" the call would look like:: 

3796 

3797 my_object = session.get(SomeClass, (5, 10)) 

3798 

3799 The dictionary form should include as keys the mapped attribute names 

3800 corresponding to each element of the primary key. If the mapped class 

3801 has the attributes ``id``, ``version_id`` as the attributes which 

3802 store the object's primary key value, the call would look like:: 

3803 

3804 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3805 

3806 :param options: optional sequence of loader options which will be 

3807 applied to the query, if one is emitted. 

3808 

3809 :param populate_existing: causes the method to unconditionally emit 

3810 a SQL query and refresh the object with the newly loaded data, 

3811 regardless of whether or not the object is already present. 

3812 Setting this flag takes precedence over passing it as an 

3813 execution option. 

3814 

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

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

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

3818 flags should match the parameters of 

3819 :meth:`_query.Query.with_for_update`. 

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

3821 

3822 :param execution_options: optional dictionary of execution options, 

3823 which will be associated with the query execution if one is emitted. 

3824 This dictionary can provide a subset of the options that are 

3825 accepted by :meth:`_engine.Connection.execution_options`, and may 

3826 also provide additional options understood only in an ORM context. 

3827 

3828 .. versionadded:: 1.4.29 

3829 

3830 .. seealso:: 

3831 

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

3833 options 

3834 

3835 :param bind_arguments: dictionary of additional arguments to determine 

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

3837 Contents of this dictionary are passed to the 

3838 :meth:`.Session.get_bind` method. 

3839 

3840 .. versionadded:: 2.0.0rc1 

3841 

3842 :return: The object instance, or ``None``. 

3843 

3844 """ # noqa: E501 

3845 return self._get_impl( 

3846 entity, 

3847 ident, 

3848 loading._load_on_pk_identity, 

3849 options=options, 

3850 populate_existing=populate_existing, 

3851 with_for_update=with_for_update, 

3852 identity_token=identity_token, 

3853 execution_options=execution_options, 

3854 bind_arguments=bind_arguments, 

3855 ) 

3856 

3857 def get_one( 

3858 self, 

3859 entity: _EntityBindKey[_O], 

3860 ident: _PKIdentityArgument, 

3861 *, 

3862 options: Optional[Sequence[ORMOption]] = None, 

3863 populate_existing: bool | None = None, 

3864 with_for_update: ForUpdateParameter = None, 

3865 identity_token: Optional[Any] = None, 

3866 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3867 bind_arguments: Optional[_BindArguments] = None, 

3868 ) -> _O: 

3869 """Return exactly one instance based on the given primary key 

3870 identifier, or raise an exception if not found. 

3871 

3872 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3873 

3874 For a detailed documentation of the arguments see the 

3875 method :meth:`.Session.get`. 

3876 

3877 .. versionadded:: 2.0.22 

3878 

3879 :return: The object instance. 

3880 

3881 .. seealso:: 

3882 

3883 :meth:`.Session.get` - equivalent method that instead 

3884 returns ``None`` if no row was found with the provided primary 

3885 key 

3886 

3887 """ 

3888 

3889 instance = self.get( 

3890 entity, 

3891 ident, 

3892 options=options, 

3893 populate_existing=populate_existing, 

3894 with_for_update=with_for_update, 

3895 identity_token=identity_token, 

3896 execution_options=execution_options, 

3897 bind_arguments=bind_arguments, 

3898 ) 

3899 

3900 if instance is None: 

3901 raise sa_exc.NoResultFound( 

3902 "No row was found when one was required" 

3903 ) 

3904 

3905 return instance 

3906 

3907 def _get_impl( 

3908 self, 

3909 entity: _EntityBindKey[_O], 

3910 primary_key_identity: _PKIdentityArgument, 

3911 db_load_fn: Callable[..., _O], 

3912 *, 

3913 options: Optional[Sequence[ExecutableOption]] = None, 

3914 populate_existing: bool | None = None, 

3915 with_for_update: ForUpdateParameter = None, 

3916 identity_token: Optional[Any] = None, 

3917 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3918 bind_arguments: Optional[_BindArguments] = None, 

3919 ) -> Optional[_O]: 

3920 # set populate_existing value; direct parameter 

3921 # takes precedence over execution_options 

3922 if populate_existing is not None: 

3923 execution_options = { 

3924 **execution_options, 

3925 "populate_existing": populate_existing, 

3926 } 

3927 else: 

3928 populate_existing = execution_options.get( 

3929 "populate_existing", False 

3930 ) 

3931 

3932 # convert composite types to individual args 

3933 if ( 

3934 is_composite_class(primary_key_identity) 

3935 and type(primary_key_identity) 

3936 in descriptor_props._composite_getters 

3937 ): 

3938 getter = descriptor_props._composite_getters[ 

3939 type(primary_key_identity) 

3940 ] 

3941 primary_key_identity = getter(primary_key_identity) 

3942 

3943 mapper: Optional[Mapper[_O]] = inspect(entity) 

3944 

3945 if mapper is None or not mapper.is_mapper: 

3946 raise sa_exc.ArgumentError( 

3947 "Expected mapped class or mapper, got: %r" % entity 

3948 ) 

3949 

3950 is_dict = isinstance(primary_key_identity, dict) 

3951 if not is_dict: 

3952 primary_key_identity = util.to_list( 

3953 primary_key_identity, default=[None] 

3954 ) 

3955 

3956 if len(primary_key_identity) != len(mapper.primary_key): 

3957 raise sa_exc.InvalidRequestError( 

3958 "Incorrect number of values in identifier to formulate " 

3959 "primary key for session.get(); primary key columns " 

3960 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3961 ) 

3962 

3963 if is_dict: 

3964 pk_synonyms = mapper._pk_synonyms 

3965 

3966 if pk_synonyms: 

3967 correct_keys = set(pk_synonyms).intersection( 

3968 primary_key_identity 

3969 ) 

3970 

3971 if correct_keys: 

3972 primary_key_identity = dict(primary_key_identity) 

3973 for k in correct_keys: 

3974 primary_key_identity[pk_synonyms[k]] = ( 

3975 primary_key_identity[k] 

3976 ) 

3977 

3978 try: 

3979 primary_key_identity = list( 

3980 primary_key_identity[prop.key] 

3981 for prop in mapper._identity_key_props 

3982 ) 

3983 

3984 except KeyError as err: 

3985 raise sa_exc.InvalidRequestError( 

3986 "Incorrect names of values in identifier to formulate " 

3987 "primary key for session.get(); primary key attribute " 

3988 "names are %s (synonym names are also accepted)" 

3989 % ",".join( 

3990 "'%s'" % prop.key 

3991 for prop in mapper._identity_key_props 

3992 ) 

3993 ) from err 

3994 

3995 for_update_arg = ForUpdateArg._from_argument(with_for_update) 

3996 

3997 if ( 

3998 not populate_existing 

3999 and not mapper.always_refresh 

4000 and for_update_arg is None 

4001 ): 

4002 instance = self._identity_lookup( 

4003 mapper, 

4004 primary_key_identity, 

4005 identity_token=identity_token, 

4006 execution_options=execution_options, 

4007 bind_arguments=bind_arguments, 

4008 ) 

4009 

4010 if instance is not None: 

4011 # reject calls for id in identity map but class 

4012 # mismatch. 

4013 if not isinstance(instance, mapper.class_): 

4014 return None 

4015 return instance 

4016 

4017 # TODO: this was being tested before, but this is not possible 

4018 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

4019 

4020 load_options = context.QueryContext.default_load_options 

4021 

4022 if populate_existing: 

4023 load_options += {"_populate_existing": populate_existing} 

4024 statement = sql.select(mapper) 

4025 if for_update_arg is not None: 

4026 statement._for_update_arg = for_update_arg 

4027 

4028 if options: 

4029 statement = statement.options(*options) 

4030 if self.execution_options: 

4031 execution_options = self.execution_options.union(execution_options) 

4032 return db_load_fn( 

4033 self, 

4034 statement, 

4035 primary_key_identity, 

4036 load_options=load_options, 

4037 identity_token=identity_token, 

4038 execution_options=execution_options, 

4039 bind_arguments=bind_arguments, 

4040 ) 

4041 

4042 def merge( 

4043 self, 

4044 instance: _O, 

4045 *, 

4046 load: bool = True, 

4047 options: Optional[Sequence[ORMOption]] = None, 

4048 ) -> _O: 

4049 """Copy the state of a given instance into a corresponding instance 

4050 within this :class:`.Session`. 

4051 

4052 :meth:`.Session.merge` examines the primary key attributes of the 

4053 source instance, and attempts to reconcile it with an instance of the 

4054 same primary key in the session. If not found locally, it attempts 

4055 to load the object from the database based on primary key, and if 

4056 none can be located, creates a new instance. The state of each 

4057 attribute on the source instance is then copied to the target 

4058 instance. The resulting target instance is then returned by the 

4059 method; the original source instance is left unmodified, and 

4060 un-associated with the :class:`.Session` if not already. 

4061 

4062 This operation cascades to associated instances if the association is 

4063 mapped with ``cascade="merge"``. 

4064 

4065 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

4066 

4067 :param instance: Instance to be merged. 

4068 :param load: Boolean, when False, :meth:`.merge` switches into 

4069 a "high performance" mode which causes it to forego emitting history 

4070 events as well as all database access. This flag is used for 

4071 cases such as transferring graphs of objects into a :class:`.Session` 

4072 from a second level cache, or to transfer just-loaded objects 

4073 into the :class:`.Session` owned by a worker thread or process 

4074 without re-querying the database. 

4075 

4076 The ``load=False`` use case adds the caveat that the given 

4077 object has to be in a "clean" state, that is, has no pending changes 

4078 to be flushed - even if the incoming object is detached from any 

4079 :class:`.Session`. This is so that when 

4080 the merge operation populates local attributes and 

4081 cascades to related objects and 

4082 collections, the values can be "stamped" onto the 

4083 target object as is, without generating any history or attribute 

4084 events, and without the need to reconcile the incoming data with 

4085 any existing related objects or collections that might not 

4086 be loaded. The resulting objects from ``load=False`` are always 

4087 produced as "clean", so it is only appropriate that the given objects 

4088 should be "clean" as well, else this suggests a mis-use of the 

4089 method. 

4090 :param options: optional sequence of loader options which will be 

4091 applied to the :meth:`_orm.Session.get` method when the merge 

4092 operation loads the existing version of the object from the database. 

4093 

4094 .. versionadded:: 1.4.24 

4095 

4096 

4097 .. seealso:: 

4098 

4099 :func:`.make_transient_to_detached` - provides for an alternative 

4100 means of "merging" a single object into the :class:`.Session` 

4101 

4102 :meth:`.Session.merge_all` - multiple instance version 

4103 

4104 """ 

4105 

4106 if self._warn_on_events: 

4107 self._flush_warning("Session.merge()") 

4108 

4109 if load: 

4110 # flush current contents if we expect to load data 

4111 self._autoflush() 

4112 

4113 with self.no_autoflush: 

4114 return self._merge( 

4115 object_state(instance), 

4116 attributes.instance_dict(instance), 

4117 load=load, 

4118 options=options, 

4119 _recursive={}, 

4120 _resolve_conflict_map={}, 

4121 ) 

4122 

4123 def merge_all( 

4124 self, 

4125 instances: Iterable[_O], 

4126 *, 

4127 load: bool = True, 

4128 options: Optional[Sequence[ORMOption]] = None, 

4129 ) -> Sequence[_O]: 

4130 """Calls :meth:`.Session.merge` on multiple instances. 

4131 

4132 .. seealso:: 

4133 

4134 :meth:`.Session.merge` - main documentation on merge 

4135 

4136 .. versionadded:: 2.1 

4137 

4138 """ 

4139 

4140 if self._warn_on_events: 

4141 self._flush_warning("Session.merge_all()") 

4142 

4143 if load: 

4144 # flush current contents if we expect to load data 

4145 self._autoflush() 

4146 

4147 return [ 

4148 self._merge( 

4149 object_state(instance), 

4150 attributes.instance_dict(instance), 

4151 load=load, 

4152 options=options, 

4153 _recursive={}, 

4154 _resolve_conflict_map={}, 

4155 ) 

4156 for instance in instances 

4157 ] 

4158 

4159 def _merge( 

4160 self, 

4161 state: InstanceState[_O], 

4162 state_dict: _InstanceDict, 

4163 *, 

4164 options: Optional[Sequence[ORMOption]] = None, 

4165 load: bool, 

4166 _recursive: Dict[Any, object], 

4167 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4168 ) -> _O: 

4169 mapper: Mapper[_O] = _state_mapper(state) 

4170 if state in _recursive: 

4171 return cast(_O, _recursive[state]) 

4172 

4173 new_instance = False 

4174 key = state.key 

4175 

4176 merged: Optional[_O] 

4177 

4178 if key is None: 

4179 if state in self._new: 

4180 util.warn( 

4181 "Instance %s is already pending in this Session yet is " 

4182 "being merged again; this is probably not what you want " 

4183 "to do" % state_str(state) 

4184 ) 

4185 

4186 if not load: 

4187 raise sa_exc.InvalidRequestError( 

4188 "merge() with load=False option does not support " 

4189 "objects transient (i.e. unpersisted) objects. flush() " 

4190 "all changes on mapped instances before merging with " 

4191 "load=False." 

4192 ) 

4193 key = mapper._identity_key_from_state(state) 

4194 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4195 1 

4196 ] and ( 

4197 not _none_set.intersection(key[1]) 

4198 or ( 

4199 mapper.allow_partial_pks 

4200 and not _none_set.issuperset(key[1]) 

4201 ) 

4202 ) 

4203 else: 

4204 key_is_persistent = True 

4205 

4206 merged = self.identity_map.get(key) 

4207 

4208 if merged is None: 

4209 if key_is_persistent and key in _resolve_conflict_map: 

4210 merged = cast(_O, _resolve_conflict_map[key]) 

4211 

4212 elif not load: 

4213 if state.modified: 

4214 raise sa_exc.InvalidRequestError( 

4215 "merge() with load=False option does not support " 

4216 "objects marked as 'dirty'. flush() all changes on " 

4217 "mapped instances before merging with load=False." 

4218 ) 

4219 merged = mapper.class_manager.new_instance() 

4220 merged_state = attributes.instance_state(merged) 

4221 merged_state.key = key 

4222 self._update_impl(merged_state) 

4223 new_instance = True 

4224 

4225 elif key_is_persistent: 

4226 merged = self.get( 

4227 mapper.class_, 

4228 key[1], 

4229 identity_token=key[2], 

4230 options=options, 

4231 ) 

4232 

4233 if merged is None: 

4234 merged = mapper.class_manager.new_instance() 

4235 merged_state = attributes.instance_state(merged) 

4236 merged_dict = attributes.instance_dict(merged) 

4237 new_instance = True 

4238 self._save_or_update_state(merged_state) 

4239 else: 

4240 merged_state = attributes.instance_state(merged) 

4241 merged_dict = attributes.instance_dict(merged) 

4242 

4243 _recursive[state] = merged 

4244 _resolve_conflict_map[key] = merged 

4245 

4246 # check that we didn't just pull the exact same 

4247 # state out. 

4248 if state is not merged_state: 

4249 # version check if applicable 

4250 if mapper.version_id_col is not None: 

4251 existing_version = mapper._get_state_attr_by_column( 

4252 state, 

4253 state_dict, 

4254 mapper.version_id_col, 

4255 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4256 ) 

4257 

4258 merged_version = mapper._get_state_attr_by_column( 

4259 merged_state, 

4260 merged_dict, 

4261 mapper.version_id_col, 

4262 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4263 ) 

4264 

4265 if ( 

4266 existing_version 

4267 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4268 and merged_version 

4269 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4270 and existing_version != merged_version 

4271 ): 

4272 raise exc.StaleDataError( 

4273 "Version id '%s' on merged state %s " 

4274 "does not match existing version '%s'. " 

4275 "Leave the version attribute unset when " 

4276 "merging to update the most recent version." 

4277 % ( 

4278 existing_version, 

4279 state_str(merged_state), 

4280 merged_version, 

4281 ) 

4282 ) 

4283 

4284 merged_state.load_path = state.load_path 

4285 merged_state.load_options = state.load_options 

4286 

4287 # since we are copying load_options, we need to copy 

4288 # the callables_ that would have been generated by those 

4289 # load_options. 

4290 # assumes that the callables we put in state.callables_ 

4291 # are not instance-specific (which they should not be) 

4292 merged_state._copy_callables(state) 

4293 

4294 for prop in mapper.iterate_properties: 

4295 prop.merge( 

4296 self, 

4297 state, 

4298 state_dict, 

4299 merged_state, 

4300 merged_dict, 

4301 load, 

4302 _recursive, 

4303 _resolve_conflict_map, 

4304 ) 

4305 

4306 if not load: 

4307 # remove any history 

4308 merged_state._commit_all(merged_dict, self.identity_map) 

4309 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4310 merged_state, None 

4311 ) 

4312 

4313 if new_instance: 

4314 merged_state.manager.dispatch.load(merged_state, None) 

4315 

4316 return merged 

4317 

4318 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4319 if not self.identity_map.contains_state(state): 

4320 raise sa_exc.InvalidRequestError( 

4321 "Instance '%s' is not persistent within this Session" 

4322 % state_str(state) 

4323 ) 

4324 

4325 def _save_impl(self, state: InstanceState[Any]) -> None: 

4326 if state.key is not None: 

4327 raise sa_exc.InvalidRequestError( 

4328 "Object '%s' already has an identity - " 

4329 "it can't be registered as pending" % state_str(state) 

4330 ) 

4331 

4332 obj = state.obj() 

4333 to_attach = self._before_attach(state, obj) 

4334 if state not in self._new: 

4335 self._new[state] = obj 

4336 state.insert_order = len(self._new) 

4337 if to_attach: 

4338 self._after_attach(state, obj) 

4339 

4340 def _update_impl( 

4341 self, state: InstanceState[Any], revert_deletion: bool = False 

4342 ) -> None: 

4343 if state.key is None: 

4344 raise sa_exc.InvalidRequestError( 

4345 "Instance '%s' is not persisted" % state_str(state) 

4346 ) 

4347 

4348 if state._deleted: 

4349 if revert_deletion: 

4350 if not state._attached: 

4351 return 

4352 del state._deleted 

4353 else: 

4354 raise sa_exc.InvalidRequestError( 

4355 "Instance '%s' has been deleted. " 

4356 "Use the make_transient() " 

4357 "function to send this object back " 

4358 "to the transient state." % state_str(state) 

4359 ) 

4360 

4361 obj = state.obj() 

4362 

4363 # check for late gc 

4364 if obj is None: 

4365 return 

4366 

4367 to_attach = self._before_attach(state, obj) 

4368 

4369 self._deleted.pop(state, None) 

4370 if revert_deletion: 

4371 self.identity_map.replace(state) 

4372 else: 

4373 self.identity_map.add(state) 

4374 

4375 if to_attach: 

4376 self._after_attach(state, obj) 

4377 elif revert_deletion: 

4378 self.dispatch.deleted_to_persistent(self, state) 

4379 

4380 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4381 if state.key is None: 

4382 self._save_impl(state) 

4383 else: 

4384 self._update_impl(state) 

4385 

4386 def enable_relationship_loading(self, obj: object) -> None: 

4387 """Associate an object with this :class:`.Session` for related 

4388 object loading. 

4389 

4390 .. warning:: 

4391 

4392 :meth:`.enable_relationship_loading` exists to serve special 

4393 use cases and is not recommended for general use. 

4394 

4395 Accesses of attributes mapped with :func:`_orm.relationship` 

4396 will attempt to load a value from the database using this 

4397 :class:`.Session` as the source of connectivity. The values 

4398 will be loaded based on foreign key and primary key values 

4399 present on this object - if not present, then those relationships 

4400 will be unavailable. 

4401 

4402 The object will be attached to this session, but will 

4403 **not** participate in any persistence operations; its state 

4404 for almost all purposes will remain either "transient" or 

4405 "detached", except for the case of relationship loading. 

4406 

4407 Also note that backrefs will often not work as expected. 

4408 Altering a relationship-bound attribute on the target object 

4409 may not fire off a backref event, if the effective value 

4410 is what was already loaded from a foreign-key-holding value. 

4411 

4412 The :meth:`.Session.enable_relationship_loading` method is 

4413 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4414 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4415 an object to remain transient while still being able to load 

4416 related items. 

4417 

4418 To make a transient object associated with a :class:`.Session` 

4419 via :meth:`.Session.enable_relationship_loading` pending, add 

4420 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4421 If the object instead represents an existing identity in the database, 

4422 it should be merged using :meth:`.Session.merge`. 

4423 

4424 :meth:`.Session.enable_relationship_loading` does not improve 

4425 behavior when the ORM is used normally - object references should be 

4426 constructed at the object level, not at the foreign key level, so 

4427 that they are present in an ordinary way before flush() 

4428 proceeds. This method is not intended for general use. 

4429 

4430 .. seealso:: 

4431 

4432 :paramref:`_orm.relationship.load_on_pending` - this flag 

4433 allows per-relationship loading of many-to-ones on items that 

4434 are pending. 

4435 

4436 :func:`.make_transient_to_detached` - allows for an object to 

4437 be added to a :class:`.Session` without SQL emitted, which then 

4438 will unexpire attributes on access. 

4439 

4440 """ 

4441 try: 

4442 state = attributes.instance_state(obj) 

4443 except exc.NO_STATE as err: 

4444 raise exc.UnmappedInstanceError(obj) from err 

4445 

4446 to_attach = self._before_attach(state, obj) 

4447 state._load_pending = True 

4448 if to_attach: 

4449 self._after_attach(state, obj) 

4450 

4451 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4452 self._autobegin_t() 

4453 

4454 if state.session_id == self.hash_key: 

4455 return False 

4456 

4457 if state.session_id and state.session_id in _sessions: 

4458 raise sa_exc.InvalidRequestError( 

4459 "Object '%s' is already attached to session '%s' " 

4460 "(this is '%s')" 

4461 % (state_str(state), state.session_id, self.hash_key) 

4462 ) 

4463 

4464 self.dispatch.before_attach(self, state) 

4465 

4466 return True 

4467 

4468 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4469 state.session_id = self.hash_key 

4470 if state.modified and state._strong_obj is None: 

4471 state._strong_obj = obj 

4472 self.dispatch.after_attach(self, state) 

4473 

4474 if state.key: 

4475 self.dispatch.detached_to_persistent(self, state) 

4476 else: 

4477 self.dispatch.transient_to_pending(self, state) 

4478 

4479 def __contains__(self, instance: object) -> bool: 

4480 """Return True if the instance is associated with this session. 

4481 

4482 The instance may be pending or persistent within the Session for a 

4483 result of True. 

4484 

4485 """ 

4486 try: 

4487 state = attributes.instance_state(instance) 

4488 except exc.NO_STATE as err: 

4489 raise exc.UnmappedInstanceError(instance) from err 

4490 return self._contains_state(state) 

4491 

4492 def __iter__(self) -> Iterator[object]: 

4493 """Iterate over all pending or persistent instances within this 

4494 Session. 

4495 

4496 """ 

4497 return iter( 

4498 list(self._new.values()) + list(self.identity_map.values()) 

4499 ) 

4500 

4501 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4502 return state in self._new or self.identity_map.contains_state(state) 

4503 

4504 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4505 """Flush all the object changes to the database. 

4506 

4507 Writes out all pending object creations, deletions and modifications 

4508 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4509 automatically ordered by the Session's unit of work dependency 

4510 solver. 

4511 

4512 Database operations will be issued in the current transactional 

4513 context and do not affect the state of the transaction, unless an 

4514 error occurs, in which case the entire transaction is rolled back. 

4515 You may flush() as often as you like within a transaction to move 

4516 changes from Python to the database's transaction buffer. 

4517 

4518 :param objects: Optional; restricts the flush operation to operate 

4519 only on elements that are in the given collection. 

4520 

4521 This feature is for an extremely narrow set of use cases where 

4522 particular objects may need to be operated upon before the 

4523 full flush() occurs. It is not intended for general use. 

4524 

4525 .. deprecated:: 2.1 

4526 

4527 """ 

4528 

4529 if self._flushing: 

4530 raise sa_exc.InvalidRequestError("Session is already flushing") 

4531 

4532 if self._is_clean(): 

4533 return 

4534 try: 

4535 self._flushing = True 

4536 self._flush(objects) 

4537 finally: 

4538 self._flushing = False 

4539 

4540 def _flush_warning(self, method: Any) -> None: 

4541 util.warn( 

4542 "Usage of the '%s' operation is not currently supported " 

4543 "within the execution stage of the flush process. " 

4544 "Results may not be consistent. Consider using alternative " 

4545 "event listeners or connection-level operations instead." % method 

4546 ) 

4547 

4548 def _is_clean(self) -> bool: 

4549 return ( 

4550 not self.identity_map.check_modified() 

4551 and not self._deleted 

4552 and not self._new 

4553 ) 

4554 

4555 # have this here since it otherwise causes issues with the proxy 

4556 # method generation 

4557 @deprecated_params( 

4558 objects=( 

4559 "2.1", 

4560 "The `objects` parameter of `Session.flush` is deprecated", 

4561 ) 

4562 ) 

4563 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4564 dirty = self._dirty_states 

4565 if not dirty and not self._deleted and not self._new: 

4566 self.identity_map._modified.clear() 

4567 return 

4568 

4569 flush_context = UOWTransaction(self) 

4570 

4571 if self.dispatch.before_flush: 

4572 self.dispatch.before_flush(self, flush_context, objects) 

4573 # re-establish "dirty states" in case the listeners 

4574 # added 

4575 dirty = self._dirty_states 

4576 

4577 deleted = set(self._deleted) 

4578 new = set(self._new) 

4579 

4580 dirty = set(dirty).difference(deleted) 

4581 

4582 # create the set of all objects we want to operate upon 

4583 if objects: 

4584 # specific list passed in 

4585 objset = set() 

4586 for o in objects: 

4587 try: 

4588 state = attributes.instance_state(o) 

4589 

4590 except exc.NO_STATE as err: 

4591 raise exc.UnmappedInstanceError(o) from err 

4592 objset.add(state) 

4593 else: 

4594 objset = None 

4595 

4596 # store objects whose fate has been decided 

4597 processed = set() 

4598 

4599 # put all saves/updates into the flush context. detect top-level 

4600 # orphans and throw them into deleted. 

4601 if objset: 

4602 proc = new.union(dirty).intersection(objset).difference(deleted) 

4603 else: 

4604 proc = new.union(dirty).difference(deleted) 

4605 

4606 for state in proc: 

4607 is_orphan = _state_mapper(state)._is_orphan(state) 

4608 

4609 is_persistent_orphan = is_orphan and state.has_identity 

4610 

4611 if ( 

4612 is_orphan 

4613 and not is_persistent_orphan 

4614 and state._orphaned_outside_of_session 

4615 ): 

4616 self._expunge_states([state]) 

4617 else: 

4618 _reg = flush_context.register_object( 

4619 state, isdelete=is_persistent_orphan 

4620 ) 

4621 assert _reg, "Failed to add object to the flush context!" 

4622 processed.add(state) 

4623 

4624 # put all remaining deletes into the flush context. 

4625 if objset: 

4626 proc = deleted.intersection(objset).difference(processed) 

4627 else: 

4628 proc = deleted.difference(processed) 

4629 for state in proc: 

4630 _reg = flush_context.register_object(state, isdelete=True) 

4631 assert _reg, "Failed to add object to the flush context!" 

4632 

4633 if not flush_context.has_work: 

4634 return 

4635 

4636 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4637 try: 

4638 self._warn_on_events = True 

4639 try: 

4640 flush_context.execute() 

4641 finally: 

4642 self._warn_on_events = False 

4643 

4644 self.dispatch.after_flush(self, flush_context) 

4645 

4646 flush_context.finalize_flush_changes() 

4647 

4648 if not objects and self.identity_map._modified: 

4649 len_ = len(self.identity_map._modified) 

4650 

4651 statelib.InstanceState._commit_all_states( 

4652 [ 

4653 (state, state.dict) 

4654 for state in self.identity_map._modified 

4655 ], 

4656 instance_dict=self.identity_map, 

4657 ) 

4658 util.warn( 

4659 "Attribute history events accumulated on %d " 

4660 "previously clean instances " 

4661 "within inner-flush event handlers have been " 

4662 "reset, and will not result in database updates. " 

4663 "Consider using set_committed_value() within " 

4664 "inner-flush event handlers to avoid this warning." % len_ 

4665 ) 

4666 

4667 # useful assertions: 

4668 # if not objects: 

4669 # assert not self.identity_map._modified 

4670 # else: 

4671 # assert self.identity_map._modified == \ 

4672 # self.identity_map._modified.difference(objects) 

4673 

4674 self.dispatch.after_flush_postexec(self, flush_context) 

4675 

4676 transaction.commit() 

4677 

4678 except: 

4679 with util.safe_reraise(): 

4680 transaction.rollback(_capture_exception=True) 

4681 

4682 def bulk_save_objects( 

4683 self, 

4684 objects: Iterable[object], 

4685 return_defaults: bool = False, 

4686 update_changed_only: bool = True, 

4687 preserve_order: bool = True, 

4688 ) -> None: 

4689 """Perform a bulk save of the given list of objects. 

4690 

4691 .. legacy:: 

4692 

4693 This method is a legacy feature as of the 2.0 series of 

4694 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4695 the sections :ref:`orm_queryguide_bulk_insert` and 

4696 :ref:`orm_queryguide_bulk_update`. 

4697 

4698 For general INSERT and UPDATE of existing ORM mapped objects, 

4699 prefer standard :term:`unit of work` data management patterns, 

4700 introduced in the :ref:`unified_tutorial` at 

4701 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4702 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4703 which solves previous issues of bulk INSERT slowness. 

4704 

4705 :param objects: a sequence of mapped object instances. The mapped 

4706 objects are persisted as is, and are **not** associated with the 

4707 :class:`.Session` afterwards. 

4708 

4709 For each object, whether the object is sent as an INSERT or an 

4710 UPDATE is dependent on the same rules used by the :class:`.Session` 

4711 in traditional operation; if the object has the 

4712 :attr:`.InstanceState.key` 

4713 attribute set, then the object is assumed to be "detached" and 

4714 will result in an UPDATE. Otherwise, an INSERT is used. 

4715 

4716 In the case of an UPDATE, statements are grouped based on which 

4717 attributes have changed, and are thus to be the subject of each 

4718 SET clause. If ``update_changed_only`` is False, then all 

4719 attributes present within each object are applied to the UPDATE 

4720 statement, which may help in allowing the statements to be grouped 

4721 together into a larger executemany(), and will also reduce the 

4722 overhead of checking history on attributes. 

4723 

4724 :param return_defaults: when True, rows that are missing values which 

4725 generate defaults, namely integer primary key defaults and sequences, 

4726 will be inserted **one at a time**, so that the primary key value 

4727 is available. In particular this will allow joined-inheritance 

4728 and other multi-table mappings to insert correctly without the need 

4729 to provide primary key values ahead of time; however, 

4730 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4731 reduces the performance gains** of the method overall. It is strongly 

4732 advised to please use the standard :meth:`_orm.Session.add_all` 

4733 approach. 

4734 

4735 :param update_changed_only: when True, UPDATE statements are rendered 

4736 based on those attributes in each state that have logged changes. 

4737 When False, all attributes present are rendered into the SET clause 

4738 with the exception of primary key attributes. 

4739 

4740 :param preserve_order: when True, the order of inserts and updates 

4741 matches exactly the order in which the objects are given. When 

4742 False, common types of objects are grouped into inserts 

4743 and updates, to allow for more batching opportunities. 

4744 

4745 .. seealso:: 

4746 

4747 :doc:`queryguide/dml` 

4748 

4749 :meth:`.Session.bulk_insert_mappings` 

4750 

4751 :meth:`.Session.bulk_update_mappings` 

4752 

4753 """ 

4754 

4755 obj_states: Iterable[InstanceState[Any]] 

4756 

4757 obj_states = (attributes.instance_state(obj) for obj in objects) 

4758 

4759 if not preserve_order: 

4760 # the purpose of this sort is just so that common mappers 

4761 # and persistence states are grouped together, so that groupby 

4762 # will return a single group for a particular type of mapper. 

4763 # it's not trying to be deterministic beyond that. 

4764 obj_states = sorted( 

4765 obj_states, 

4766 key=lambda state: (id(state.mapper), state.key is not None), 

4767 ) 

4768 

4769 def grouping_key( 

4770 state: InstanceState[_O], 

4771 ) -> Tuple[Mapper[_O], bool]: 

4772 return (state.mapper, state.key is not None) 

4773 

4774 for (mapper, isupdate), states in itertools.groupby( 

4775 obj_states, grouping_key 

4776 ): 

4777 self._bulk_save_mappings( 

4778 mapper, 

4779 states, 

4780 isupdate=isupdate, 

4781 isstates=True, 

4782 return_defaults=return_defaults, 

4783 update_changed_only=update_changed_only, 

4784 render_nulls=False, 

4785 ) 

4786 

4787 def bulk_insert_mappings( 

4788 self, 

4789 mapper: _EntityBindKey[Any], 

4790 mappings: Iterable[Dict[str, Any]], 

4791 return_defaults: bool = False, 

4792 render_nulls: bool = False, 

4793 ) -> None: 

4794 """Perform a bulk insert of the given list of mapping dictionaries. 

4795 

4796 .. legacy:: 

4797 

4798 This method is a legacy feature as of the 2.0 series of 

4799 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4800 the sections :ref:`orm_queryguide_bulk_insert` and 

4801 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4802 implementation details with this method and adds new features 

4803 as well. 

4804 

4805 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4806 object, 

4807 representing the single kind of object represented within the mapping 

4808 list. 

4809 

4810 :param mappings: a sequence of dictionaries, each one containing the 

4811 state of the mapped row to be inserted, in terms of the attribute 

4812 names on the mapped class. If the mapping refers to multiple tables, 

4813 such as a joined-inheritance mapping, each dictionary must contain all 

4814 keys to be populated into all tables. 

4815 

4816 :param return_defaults: when True, the INSERT process will be altered 

4817 to ensure that newly generated primary key values will be fetched. 

4818 The rationale for this parameter is typically to enable 

4819 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4820 be bulk inserted. 

4821 

4822 .. note:: for backends that don't support RETURNING, the 

4823 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4824 parameter can significantly decrease performance as INSERT 

4825 statements can no longer be batched. See 

4826 :ref:`engine_insertmanyvalues` 

4827 for background on which backends are affected. 

4828 

4829 :param render_nulls: When True, a value of ``None`` will result 

4830 in a NULL value being included in the INSERT statement, rather 

4831 than the column being omitted from the INSERT. This allows all 

4832 the rows being INSERTed to have the identical set of columns which 

4833 allows the full set of rows to be batched to the DBAPI. Normally, 

4834 each column-set that contains a different combination of NULL values 

4835 than the previous row must omit a different series of columns from 

4836 the rendered INSERT statement, which means it must be emitted as a 

4837 separate statement. By passing this flag, the full set of rows 

4838 are guaranteed to be batchable into one batch; the cost however is 

4839 that server-side defaults which are invoked by an omitted column will 

4840 be skipped, so care must be taken to ensure that these are not 

4841 necessary. 

4842 

4843 .. warning:: 

4844 

4845 When this flag is set, **server side default SQL values will 

4846 not be invoked** for those columns that are inserted as NULL; 

4847 the NULL value will be sent explicitly. Care must be taken 

4848 to ensure that no server-side default functions need to be 

4849 invoked for the operation as a whole. 

4850 

4851 .. seealso:: 

4852 

4853 :doc:`queryguide/dml` 

4854 

4855 :meth:`.Session.bulk_save_objects` 

4856 

4857 :meth:`.Session.bulk_update_mappings` 

4858 

4859 """ 

4860 self._bulk_save_mappings( 

4861 mapper, 

4862 mappings, 

4863 isupdate=False, 

4864 isstates=False, 

4865 return_defaults=return_defaults, 

4866 update_changed_only=False, 

4867 render_nulls=render_nulls, 

4868 ) 

4869 

4870 def bulk_update_mappings( 

4871 self, mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]] 

4872 ) -> None: 

4873 """Perform a bulk update of the given list of mapping dictionaries. 

4874 

4875 .. legacy:: 

4876 

4877 This method is a legacy feature as of the 2.0 series of 

4878 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4879 the sections :ref:`orm_queryguide_bulk_insert` and 

4880 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4881 implementation details with this method and adds new features 

4882 as well. 

4883 

4884 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4885 object, 

4886 representing the single kind of object represented within the mapping 

4887 list. 

4888 

4889 :param mappings: a sequence of dictionaries, each one containing the 

4890 state of the mapped row to be updated, in terms of the attribute names 

4891 on the mapped class. If the mapping refers to multiple tables, such 

4892 as a joined-inheritance mapping, each dictionary may contain keys 

4893 corresponding to all tables. All those keys which are present and 

4894 are not part of the primary key are applied to the SET clause of the 

4895 UPDATE statement; the primary key values, which are required, are 

4896 applied to the WHERE clause. 

4897 

4898 

4899 .. seealso:: 

4900 

4901 :doc:`queryguide/dml` 

4902 

4903 :meth:`.Session.bulk_insert_mappings` 

4904 

4905 :meth:`.Session.bulk_save_objects` 

4906 

4907 """ 

4908 self._bulk_save_mappings( 

4909 mapper, 

4910 mappings, 

4911 isupdate=True, 

4912 isstates=False, 

4913 return_defaults=False, 

4914 update_changed_only=False, 

4915 render_nulls=False, 

4916 ) 

4917 

4918 def _bulk_save_mappings( 

4919 self, 

4920 mapper: _EntityBindKey[_O], 

4921 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4922 *, 

4923 isupdate: bool, 

4924 isstates: bool, 

4925 return_defaults: bool, 

4926 update_changed_only: bool, 

4927 render_nulls: bool, 

4928 ) -> None: 

4929 mapper = _class_to_mapper(mapper) 

4930 

4931 try: 

4932 self._flushing = True 

4933 

4934 transaction = self._autobegin_t()._begin() 

4935 try: 

4936 if isupdate: 

4937 bulk_persistence._bulk_update( 

4938 mapper, 

4939 mappings, 

4940 transaction, 

4941 isstates=isstates, 

4942 update_changed_only=update_changed_only, 

4943 ) 

4944 else: 

4945 bulk_persistence._bulk_insert( 

4946 mapper, 

4947 mappings, 

4948 transaction, 

4949 isstates=isstates, 

4950 return_defaults=return_defaults, 

4951 render_nulls=render_nulls, 

4952 ) 

4953 transaction.commit() 

4954 

4955 except: 

4956 with util.safe_reraise(): 

4957 transaction.rollback(_capture_exception=True) 

4958 finally: 

4959 self._flushing = False 

4960 

4961 def is_modified( 

4962 self, instance: object, include_collections: bool = True 

4963 ) -> bool: 

4964 r"""Return ``True`` if the given instance has locally 

4965 modified attributes. 

4966 

4967 This method retrieves the history for each instrumented 

4968 attribute on the instance and performs a comparison of the current 

4969 value to its previously flushed or committed value, if any. 

4970 

4971 It is in effect a more expensive and accurate 

4972 version of checking for the given instance in the 

4973 :attr:`.Session.dirty` collection; a full test for 

4974 each attribute's net "dirty" status is performed. 

4975 

4976 E.g.:: 

4977 

4978 return session.is_modified(someobject) 

4979 

4980 A few caveats to this method apply: 

4981 

4982 * Instances present in the :attr:`.Session.dirty` collection may 

4983 report ``False`` when tested with this method. This is because 

4984 the object may have received change events via attribute mutation, 

4985 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4986 is the same as that loaded from the database, resulting in no net 

4987 change here. 

4988 * Scalar attributes may not have recorded the previously set 

4989 value when a new value was applied, if the attribute was not loaded, 

4990 or was expired, at the time the new value was received - in these 

4991 cases, the attribute is assumed to have a change, even if there is 

4992 ultimately no net change against its database value. SQLAlchemy in 

4993 most cases does not need the "old" value when a set event occurs, so 

4994 it skips the expense of a SQL call if the old value isn't present, 

4995 based on the assumption that an UPDATE of the scalar value is 

4996 usually needed, and in those few cases where it isn't, is less 

4997 expensive on average than issuing a defensive SELECT. 

4998 

4999 The "old" value is fetched unconditionally upon set only if the 

5000 attribute container has the ``active_history`` flag set to ``True``. 

5001 This flag is set typically for primary key attributes and scalar 

5002 object references that are not a simple many-to-one. To set this 

5003 flag for any arbitrary mapped column, use the ``active_history`` 

5004 argument with :func:`.column_property`. 

5005 

5006 :param instance: mapped instance to be tested for pending changes. 

5007 :param include_collections: Indicates if multivalued collections 

5008 should be included in the operation. Setting this to ``False`` is a 

5009 way to detect only local-column based properties (i.e. scalar columns 

5010 or many-to-one foreign keys) that would result in an UPDATE for this 

5011 instance upon flush. 

5012 

5013 """ 

5014 state = object_state(instance) 

5015 

5016 if not state.modified: 

5017 return False 

5018 

5019 dict_ = state.dict 

5020 

5021 for attr in state.manager.attributes: 

5022 if ( 

5023 not include_collections 

5024 and hasattr(attr.impl, "get_collection") 

5025 ) or not hasattr(attr.impl, "get_history"): 

5026 continue 

5027 

5028 added, unchanged, deleted = attr.impl.get_history( 

5029 state, dict_, passive=PassiveFlag.NO_CHANGE 

5030 ) 

5031 

5032 if added or deleted: 

5033 return True 

5034 else: 

5035 return False 

5036 

5037 @property 

5038 def is_active(self) -> bool: 

5039 """True if this :class:`.Session` not in "partial rollback" state. 

5040 

5041 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

5042 a new transaction immediately, so this attribute will be False 

5043 when the :class:`_orm.Session` is first instantiated. 

5044 

5045 "partial rollback" state typically indicates that the flush process 

5046 of the :class:`_orm.Session` has failed, and that the 

5047 :meth:`_orm.Session.rollback` method must be emitted in order to 

5048 fully roll back the transaction. 

5049 

5050 If this :class:`_orm.Session` is not in a transaction at all, the 

5051 :class:`_orm.Session` will autobegin when it is first used, so in this 

5052 case :attr:`_orm.Session.is_active` will return True. 

5053 

5054 Otherwise, if this :class:`_orm.Session` is within a transaction, 

5055 and that transaction has not been rolled back internally, the 

5056 :attr:`_orm.Session.is_active` will also return True. 

5057 

5058 .. seealso:: 

5059 

5060 :ref:`faq_session_rollback` 

5061 

5062 :meth:`_orm.Session.in_transaction` 

5063 

5064 """ 

5065 return self._transaction is None or self._transaction.is_active 

5066 

5067 @property 

5068 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

5069 """The set of all persistent states considered dirty. 

5070 

5071 This method returns all states that were modified including 

5072 those that were possibly deleted. 

5073 

5074 """ 

5075 return self.identity_map._dirty_states() 

5076 

5077 @property 

5078 def dirty(self) -> IdentitySet: 

5079 """The set of all persistent instances considered dirty. 

5080 

5081 E.g.:: 

5082 

5083 some_mapped_object in session.dirty 

5084 

5085 Instances are considered dirty when they were modified but not 

5086 deleted. 

5087 

5088 Note that this 'dirty' calculation is 'optimistic'; most 

5089 attribute-setting or collection modification operations will 

5090 mark an instance as 'dirty' and place it in this set, even if 

5091 there is no net change to the attribute's value. At flush 

5092 time, the value of each attribute is compared to its 

5093 previously saved value, and if there's no net change, no SQL 

5094 operation will occur (this is a more expensive operation so 

5095 it's only done at flush time). 

5096 

5097 To check if an instance has actionable net changes to its 

5098 attributes, use the :meth:`.Session.is_modified` method. 

5099 

5100 """ 

5101 return IdentitySet( 

5102 [ 

5103 state.obj() 

5104 for state in self._dirty_states 

5105 if state not in self._deleted 

5106 ] 

5107 ) 

5108 

5109 @property 

5110 def deleted(self) -> IdentitySet: 

5111 "The set of all instances marked as 'deleted' within this ``Session``" 

5112 

5113 return util.IdentitySet(list(self._deleted.values())) 

5114 

5115 @property 

5116 def new(self) -> IdentitySet: 

5117 "The set of all instances marked as 'new' within this ``Session``." 

5118 

5119 return util.IdentitySet(list(self._new.values())) 

5120 

5121 

5122_S = TypeVar("_S", bound="Session") 

5123 

5124 

5125class sessionmaker(_SessionClassMethods, Generic[_S]): 

5126 """A configurable :class:`.Session` factory. 

5127 

5128 The :class:`.sessionmaker` factory generates new 

5129 :class:`.Session` objects when called, creating them given 

5130 the configurational arguments established here. 

5131 

5132 e.g.:: 

5133 

5134 from sqlalchemy import create_engine 

5135 from sqlalchemy.orm import sessionmaker 

5136 

5137 # an Engine, which the Session will use for connection 

5138 # resources 

5139 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

5140 

5141 Session = sessionmaker(engine) 

5142 

5143 with Session() as session: 

5144 session.add(some_object) 

5145 session.add(some_other_object) 

5146 session.commit() 

5147 

5148 Context manager use is optional; otherwise, the returned 

5149 :class:`_orm.Session` object may be closed explicitly via the 

5150 :meth:`_orm.Session.close` method. Using a 

5151 ``try:/finally:`` block is optional, however will ensure that the close 

5152 takes place even if there are database errors:: 

5153 

5154 session = Session() 

5155 try: 

5156 session.add(some_object) 

5157 session.add(some_other_object) 

5158 session.commit() 

5159 finally: 

5160 session.close() 

5161 

5162 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5163 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5164 for :class:`_engine.Connection` objects. In this way it also includes 

5165 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5166 manager which both begins and commits a transaction, as well as closes 

5167 out the :class:`_orm.Session` when complete, rolling back the transaction 

5168 if any errors occur:: 

5169 

5170 Session = sessionmaker(engine) 

5171 

5172 with Session.begin() as session: 

5173 session.add(some_object) 

5174 session.add(some_other_object) 

5175 # commits transaction, closes session 

5176 

5177 .. versionadded:: 1.4 

5178 

5179 When calling upon :class:`_orm.sessionmaker` to construct a 

5180 :class:`_orm.Session`, keyword arguments may also be passed to the 

5181 method; these arguments will override that of the globally configured 

5182 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5183 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5184 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5185 

5186 Session = sessionmaker(engine) 

5187 

5188 # bind an individual session to a connection 

5189 

5190 with engine.connect() as connection: 

5191 with Session(bind=connection) as session: 

5192 ... # work with session 

5193 

5194 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5195 can be used to specify additional keyword arguments to the factory, which 

5196 will take effect for subsequent :class:`.Session` objects generated. This 

5197 is usually used to associate one or more :class:`_engine.Engine` objects 

5198 with an existing 

5199 :class:`.sessionmaker` factory before it is first used:: 

5200 

5201 # application starts, sessionmaker does not have 

5202 # an engine bound yet 

5203 Session = sessionmaker() 

5204 

5205 # ... later, when an engine URL is read from a configuration 

5206 # file or other events allow the engine to be created 

5207 engine = create_engine("sqlite:///foo.db") 

5208 Session.configure(bind=engine) 

5209 

5210 sess = Session() 

5211 # work with session 

5212 

5213 .. seealso:: 

5214 

5215 :ref:`session_getting` - introductory text on creating 

5216 sessions using :class:`.sessionmaker`. 

5217 

5218 """ 

5219 

5220 class_: Type[_S] 

5221 

5222 @overload 

5223 def __init__( 

5224 self, 

5225 bind: Optional[_SessionBind] = ..., 

5226 *, 

5227 class_: Type[_S], 

5228 autoflush: bool = ..., 

5229 expire_on_commit: bool = ..., 

5230 info: Optional[_InfoType] = ..., 

5231 **kw: Any, 

5232 ): ... 

5233 

5234 @overload 

5235 def __init__( 

5236 self: "sessionmaker[Session]", 

5237 bind: Optional[_SessionBind] = ..., 

5238 *, 

5239 autoflush: bool = ..., 

5240 expire_on_commit: bool = ..., 

5241 info: Optional[_InfoType] = ..., 

5242 **kw: Any, 

5243 ): ... 

5244 

5245 def __init__( 

5246 self, 

5247 bind: Optional[_SessionBind] = None, 

5248 *, 

5249 class_: Type[_S] = Session, # type: ignore[assignment] 

5250 autoflush: bool = True, 

5251 expire_on_commit: bool = True, 

5252 info: Optional[_InfoType] = None, 

5253 **kw: Any, 

5254 ): 

5255 r"""Construct a new :class:`.sessionmaker`. 

5256 

5257 All arguments here except for ``class_`` correspond to arguments 

5258 accepted by :class:`.Session` directly. See the 

5259 :meth:`.Session.__init__` docstring for more details on parameters. 

5260 

5261 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5262 with 

5263 which newly created :class:`.Session` objects will be associated. 

5264 :param class\_: class to use in order to create new :class:`.Session` 

5265 objects. Defaults to :class:`.Session`. 

5266 :param autoflush: The autoflush setting to use with newly created 

5267 :class:`.Session` objects. 

5268 

5269 .. seealso:: 

5270 

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

5272 

5273 :param expire_on_commit=True: the 

5274 :paramref:`_orm.Session.expire_on_commit` setting to use 

5275 with newly created :class:`.Session` objects. 

5276 

5277 :param info: optional dictionary of information that will be available 

5278 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5279 replaced, when the ``info`` parameter is specified to the specific 

5280 :class:`.Session` construction operation. 

5281 

5282 :param \**kw: all other keyword arguments are passed to the 

5283 constructor of newly created :class:`.Session` objects. 

5284 

5285 """ 

5286 kw["bind"] = bind 

5287 kw["autoflush"] = autoflush 

5288 kw["expire_on_commit"] = expire_on_commit 

5289 if info is not None: 

5290 kw["info"] = info 

5291 self.kw = kw 

5292 # make our own subclass of the given class, so that 

5293 # events can be associated with it specifically. 

5294 self.class_ = type(class_.__name__, (class_,), {}) 

5295 

5296 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5297 """Produce a context manager that both provides a new 

5298 :class:`_orm.Session` as well as a transaction that commits. 

5299 

5300 

5301 e.g.:: 

5302 

5303 Session = sessionmaker(some_engine) 

5304 

5305 with Session.begin() as session: 

5306 session.add(some_object) 

5307 

5308 # commits transaction, closes session 

5309 

5310 .. versionadded:: 1.4 

5311 

5312 

5313 """ 

5314 

5315 session = self() 

5316 return session._maker_context_manager() 

5317 

5318 def __call__(self, **local_kw: Any) -> _S: 

5319 """Produce a new :class:`.Session` object using the configuration 

5320 established in this :class:`.sessionmaker`. 

5321 

5322 In Python, the ``__call__`` method is invoked on an object when 

5323 it is "called" in the same way as a function:: 

5324 

5325 Session = sessionmaker(some_engine) 

5326 session = Session() # invokes sessionmaker.__call__() 

5327 

5328 """ 

5329 for k, v in self.kw.items(): 

5330 if k == "info" and "info" in local_kw: 

5331 d = v.copy() 

5332 d.update(local_kw["info"]) 

5333 local_kw["info"] = d 

5334 else: 

5335 local_kw.setdefault(k, v) 

5336 return self.class_(**local_kw) 

5337 

5338 def configure(self, **new_kw: Any) -> None: 

5339 """(Re)configure the arguments for this sessionmaker. 

5340 

5341 e.g.:: 

5342 

5343 Session = sessionmaker() 

5344 

5345 Session.configure(bind=create_engine("sqlite://")) 

5346 """ 

5347 self.kw.update(new_kw) 

5348 

5349 def __repr__(self) -> str: 

5350 return "%s(class_=%r, %s)" % ( 

5351 self.__class__.__name__, 

5352 self.class_.__name__, 

5353 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5354 ) 

5355 

5356 

5357def close_all_sessions() -> None: 

5358 """Close all sessions in memory. 

5359 

5360 This function consults a global registry of all :class:`.Session` objects 

5361 and calls :meth:`.Session.close` on them, which resets them to a clean 

5362 state. 

5363 

5364 This function is not for general use but may be useful for test suites 

5365 within the teardown scheme. 

5366 

5367 """ 

5368 

5369 for sess in _sessions.values(): 

5370 sess.close() 

5371 

5372 

5373def make_transient(instance: object) -> None: 

5374 """Alter the state of the given instance so that it is :term:`transient`. 

5375 

5376 .. note:: 

5377 

5378 :func:`.make_transient` is a special-case function for 

5379 advanced use cases only. 

5380 

5381 The given mapped instance is assumed to be in the :term:`persistent` or 

5382 :term:`detached` state. The function will remove its association with any 

5383 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5384 effect is that the object will behave as though it were newly constructed, 

5385 except retaining any attribute / collection values that were loaded at the 

5386 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5387 if this object had been deleted as a result of using 

5388 :meth:`.Session.delete`. 

5389 

5390 .. warning:: 

5391 

5392 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5393 load ORM-mapped attributes that are not currently loaded at the time 

5394 the function is called. This includes attributes which: 

5395 

5396 * were expired via :meth:`.Session.expire` 

5397 

5398 * were expired as the natural effect of committing a session 

5399 transaction, e.g. :meth:`.Session.commit` 

5400 

5401 * are normally :term:`lazy loaded` but are not currently loaded 

5402 

5403 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5404 not yet loaded 

5405 

5406 * were not present in the query which loaded this object, such as that 

5407 which is common in joined table inheritance and other scenarios. 

5408 

5409 After :func:`.make_transient` is called, unloaded attributes such 

5410 as those above will normally resolve to the value ``None`` when 

5411 accessed, or an empty collection for a collection-oriented attribute. 

5412 As the object is transient and un-associated with any database 

5413 identity, it will no longer retrieve these values. 

5414 

5415 .. seealso:: 

5416 

5417 :func:`.make_transient_to_detached` 

5418 

5419 """ 

5420 state = attributes.instance_state(instance) 

5421 s = _state_session(state) 

5422 if s: 

5423 s._expunge_states([state]) 

5424 

5425 # remove expired state 

5426 state.expired_attributes.clear() 

5427 

5428 # remove deferred callables 

5429 if state.callables: 

5430 del state.callables 

5431 

5432 if state.key: 

5433 del state.key 

5434 if state._deleted: 

5435 del state._deleted 

5436 

5437 

5438def make_transient_to_detached(instance: object) -> None: 

5439 """Make the given transient instance :term:`detached`. 

5440 

5441 .. note:: 

5442 

5443 :func:`.make_transient_to_detached` is a special-case function for 

5444 advanced use cases only. 

5445 

5446 All attribute history on the given instance 

5447 will be reset as though the instance were freshly loaded 

5448 from a query. Missing attributes will be marked as expired. 

5449 The primary key attributes of the object, which are required, will be made 

5450 into the "key" of the instance. 

5451 

5452 The object can then be added to a session, or merged 

5453 possibly with the load=False flag, at which point it will look 

5454 as if it were loaded that way, without emitting SQL. 

5455 

5456 This is a special use case function that differs from a normal 

5457 call to :meth:`.Session.merge` in that a given persistent state 

5458 can be manufactured without any SQL calls. 

5459 

5460 .. seealso:: 

5461 

5462 :func:`.make_transient` 

5463 

5464 :meth:`.Session.enable_relationship_loading` 

5465 

5466 """ 

5467 state = attributes.instance_state(instance) 

5468 if state.session_id or state.key: 

5469 raise sa_exc.InvalidRequestError("Given object must be transient") 

5470 state.key = state.mapper._identity_key_from_state(state) 

5471 if state._deleted: 

5472 del state._deleted 

5473 state._commit_all(state.dict) 

5474 state._expire_attributes(state.dict, state.unloaded) 

5475 

5476 

5477def object_session(instance: object) -> Optional[Session]: 

5478 """Return the :class:`.Session` to which the given instance belongs. 

5479 

5480 This is essentially the same as the :attr:`.InstanceState.session` 

5481 accessor. See that attribute for details. 

5482 

5483 """ 

5484 

5485 try: 

5486 state = attributes.instance_state(instance) 

5487 except exc.NO_STATE as err: 

5488 raise exc.UnmappedInstanceError(instance) from err 

5489 else: 

5490 return _state_session(state) 

5491 

5492 

5493_new_sessionid = util.counter()