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

1492 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 FrozenSet 

22from typing import Generic 

23from typing import Iterable 

24from typing import Iterator 

25from typing import List 

26from typing import Literal 

27from typing import Mapping 

28from typing import NoReturn 

29from typing import Optional 

30from typing import overload 

31from typing import Protocol 

32from typing import Sequence 

33from typing import Set 

34from typing import Tuple 

35from typing import Type 

36from typing import TYPE_CHECKING 

37from typing import TypeVar 

38from typing import Union 

39import weakref 

40 

41from . import attributes 

42from . import bulk_persistence 

43from . import context 

44from . import descriptor_props 

45from . import exc 

46from . import identity 

47from . import loading 

48from . import query 

49from . import state as statelib 

50from ._typing import _O 

51from ._typing import insp_is_mapper 

52from ._typing import is_composite_class 

53from ._typing import is_orm_option 

54from ._typing import is_user_defined_option 

55from .base import _class_to_mapper 

56from .base import _none_set 

57from .base import _state_mapper 

58from .base import instance_str 

59from .base import LoaderCallableStatus 

60from .base import object_mapper 

61from .base import object_state 

62from .base import PassiveFlag 

63from .base import state_str 

64from .context import _ORMCompileState 

65from .context import FromStatement 

66from .identity import IdentityMap 

67from .query import Query 

68from .state import InstanceState 

69from .state_changes import _StateChange 

70from .state_changes import _StateChangeState 

71from .state_changes import _StateChangeStates 

72from .unitofwork import UOWTransaction 

73from .. import engine 

74from .. import exc as sa_exc 

75from .. import sql 

76from .. import util 

77from ..engine import Connection 

78from ..engine import Engine 

79from ..engine.util import TransactionalContext 

80from ..event import dispatcher 

81from ..event import EventTarget 

82from ..inspection import inspect 

83from ..inspection import Inspectable 

84from ..sql import coercions 

85from ..sql import dml 

86from ..sql import roles 

87from ..sql import Select 

88from ..sql import TableClause 

89from ..sql import visitors 

90from ..sql.base import _NoArg 

91from ..sql.base import CompileState 

92from ..sql.schema import Table 

93from ..sql.selectable import ForUpdateArg 

94from ..util import deprecated_params 

95from ..util import IdentitySet 

96from ..util.typing import Never 

97from ..util.typing import TupleAny 

98from ..util.typing import TypeVarTuple 

99from ..util.typing import Unpack 

100 

101if typing.TYPE_CHECKING: 

102 from ._typing import _EntityType 

103 from ._typing import _IdentityKeyType 

104 from ._typing import _InstanceDict 

105 from ._typing import OrmExecuteOptionsParameter 

106 from .interfaces import ORMOption 

107 from .interfaces import UserDefinedOption 

108 from .mapper import Mapper 

109 from .path_registry import PathRegistry 

110 from .query import RowReturningQuery 

111 from ..engine import Result 

112 from ..engine import Row 

113 from ..engine import RowMapping 

114 from ..engine.base import Transaction 

115 from ..engine.base import TwoPhaseTransaction 

116 from ..engine.interfaces import _CoreAnyExecuteParams 

117 from ..engine.interfaces import _CoreSingleExecuteParams 

118 from ..engine.interfaces import _ExecuteOptions 

119 from ..engine.interfaces import CoreExecuteOptionsParameter 

120 from ..engine.result import ScalarResult 

121 from ..event import _InstanceLevelDispatch 

122 from ..sql._typing import _ColumnsClauseArgument 

123 from ..sql._typing import _InfoType 

124 from ..sql._typing import _T0 

125 from ..sql._typing import _T1 

126 from ..sql._typing import _T2 

127 from ..sql._typing import _T3 

128 from ..sql._typing import _T4 

129 from ..sql._typing import _T5 

130 from ..sql._typing import _T6 

131 from ..sql._typing import _T7 

132 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

133 from ..sql.base import Executable 

134 from ..sql.base import ExecutableOption 

135 from ..sql.elements import ClauseElement 

136 from ..sql.roles import TypedColumnsClauseRole 

137 from ..sql.selectable import ForUpdateParameter 

138 from ..sql.selectable import TypedReturnsRows 

139 

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

141_Ts = TypeVarTuple("_Ts") 

142 

143__all__ = [ 

144 "Session", 

145 "SessionTransaction", 

146 "sessionmaker", 

147 "ORMExecuteState", 

148 "close_all_sessions", 

149 "make_transient", 

150 "make_transient_to_detached", 

151 "object_session", 

152] 

153 

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

155 weakref.WeakValueDictionary() 

156) 

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

158""" 

159 

160statelib._sessions = _sessions 

161 

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

163 

164_BindArguments = Dict[str, Any] 

165 

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

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

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

169 

170JoinTransactionMode = Literal[ 

171 "conditional_savepoint", 

172 "rollback_only", 

173 "control_fully", 

174 "create_savepoint", 

175] 

176 

177 

178class _ConnectionCallableProto(Protocol): 

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

180 

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

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

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

184 as persistence time. 

185 

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

187 is established when using the horizontal sharding extension. 

188 

189 """ 

190 

191 def __call__( 

192 self, 

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

194 instance: Optional[object] = None, 

195 **kw: Any, 

196 ) -> Connection: ... 

197 

198 

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

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

201 associated, if any. 

202 """ 

203 return state.session 

204 

205 

206class _SessionClassMethods: 

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

208 

209 @classmethod 

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

211 def identity_key( 

212 cls, 

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

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

215 *, 

216 instance: Optional[Any] = None, 

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

218 identity_token: Optional[Any] = None, 

219 ) -> _IdentityKeyType[Any]: 

220 """Return an identity key. 

221 

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

223 

224 """ 

225 return util.preloaded.orm_util.identity_key( 

226 class_, 

227 ident, 

228 instance=instance, 

229 row=row, 

230 identity_token=identity_token, 

231 ) 

232 

233 @classmethod 

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

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

236 

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

238 

239 """ 

240 

241 return object_session(instance) 

242 

243 

244class SessionTransactionState(_StateChangeState): 

245 ACTIVE = 1 

246 PREPARED = 2 

247 COMMITTED = 3 

248 DEACTIVE = 4 

249 CLOSED = 5 

250 PROVISIONING_CONNECTION = 6 

251 

252 

253# backwards compatibility 

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

255 SessionTransactionState 

256) 

257 

258 

259class ORMExecuteState(util.MemoizedSlots): 

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

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

262 

263 .. versionadded:: 1.4 

264 

265 .. seealso:: 

266 

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

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

269 

270 """ 

271 

272 __slots__ = ( 

273 "session", 

274 "statement", 

275 "parameters", 

276 "execution_options", 

277 "local_execution_options", 

278 "bind_arguments", 

279 "identity_token", 

280 "_compile_state_cls", 

281 "_starting_event_idx", 

282 "_events_todo", 

283 "_update_execution_options", 

284 ) 

285 

286 session: Session 

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

288 

289 statement: Executable 

290 """The SQL statement being invoked. 

291 

292 For an ORM selection as would 

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

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

295 """ 

296 

297 parameters: Optional[_CoreAnyExecuteParams] 

298 """Optional mapping or list of mappings of parameters that was passed to 

299 :meth:`_orm.Session.execute`. 

300 

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

302 effective parameters passed to the method. 

303 

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

305 mutated or replaced. 

306 

307 """ 

308 

309 execution_options: _ExecuteOptions 

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

311 

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

313 locally passed execution options. 

314 

315 .. seealso:: 

316 

317 :attr:`_orm.ORMExecuteState.local_execution_options` 

318 

319 :meth:`_sql.Executable.execution_options` 

320 

321 :ref:`orm_queryguide_execution_options` 

322 

323 """ 

324 

325 local_execution_options: _ExecuteOptions 

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

327 :meth:`.Session.execute` method. 

328 

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

330 being invoked. 

331 

332 .. seealso:: 

333 

334 :attr:`_orm.ORMExecuteState.execution_options` 

335 

336 """ 

337 

338 bind_arguments: _BindArguments 

339 """The dictionary passed as the 

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

341 

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

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

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

345 

346 """ 

347 

348 _compile_state_cls: Optional[Type[_ORMCompileState]] 

349 _starting_event_idx: int 

350 _events_todo: List[Any] 

351 _update_execution_options: _ExecuteOptions 

352 

353 def __init__( 

354 self, 

355 session: Session, 

356 statement: Executable, 

357 parameters: Optional[_CoreAnyExecuteParams], 

358 execution_options: _ExecuteOptions, 

359 bind_arguments: _BindArguments, 

360 compile_state_cls: Optional[Type[_ORMCompileState]], 

361 events_todo: List[_InstanceLevelDispatch[Session]], 

362 ): 

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

364 

365 this object is constructed internally. 

366 

367 """ 

368 self.session = session 

369 self.statement = statement 

370 self.parameters = parameters 

371 self.local_execution_options = execution_options 

372 self.execution_options = statement._execution_options.union( 

373 execution_options 

374 ) 

375 self.bind_arguments = bind_arguments 

376 self._compile_state_cls = compile_state_cls 

377 self._events_todo = list(events_todo) 

378 self._update_execution_options = util.EMPTY_DICT 

379 

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

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

382 

383 def invoke_statement( 

384 self, 

385 statement: Optional[Executable] = None, 

386 params: Optional[_CoreAnyExecuteParams] = None, 

387 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

388 bind_arguments: Optional[_BindArguments] = None, 

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

390 """Execute the statement represented by this 

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

392 already proceeded. 

393 

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

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

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

397 that want to override how the ultimate 

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

399 retrieve results from an offline cache or which concatenate results 

400 from multiple executions. 

401 

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

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

404 is propagated to the calling 

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

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

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

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

409 

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

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

412 

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

414 which will be merged into the existing 

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

416 

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

418 for executemany executions. 

419 

420 :param execution_options: optional dictionary of execution options 

421 will be merged into the existing 

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

423 :class:`.ORMExecuteState`. 

424 

425 :param bind_arguments: optional dictionary of bind_arguments 

426 which will be merged amongst the current 

427 :attr:`.ORMExecuteState.bind_arguments` 

428 of this :class:`.ORMExecuteState`. 

429 

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

431 

432 .. seealso:: 

433 

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

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

436 

437 

438 """ 

439 

440 if statement is None: 

441 statement = self.statement 

442 

443 _bind_arguments = dict(self.bind_arguments) 

444 if bind_arguments: 

445 _bind_arguments.update(bind_arguments) 

446 _bind_arguments["_sa_skip_events"] = True 

447 

448 _params: Optional[_CoreAnyExecuteParams] 

449 if params: 

450 if self.is_executemany: 

451 _params = [] 

452 exec_many_parameters = cast( 

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

454 ) 

455 for _existing_params, _new_params in itertools.zip_longest( 

456 exec_many_parameters, 

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

458 ): 

459 if _existing_params is None or _new_params is None: 

460 raise sa_exc.InvalidRequestError( 

461 f"Can't apply executemany parameters to " 

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

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

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

465 f"to ORMExecuteState.invoke_statement() " 

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

467 ) 

468 _existing_params = dict(_existing_params) 

469 _existing_params.update(_new_params) 

470 _params.append(_existing_params) 

471 else: 

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

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

474 else: 

475 _params = self.parameters 

476 

477 _execution_options = self.local_execution_options 

478 if execution_options: 

479 _execution_options = _execution_options.union(execution_options) 

480 

481 return self.session._execute_internal( 

482 statement, 

483 _params, 

484 execution_options=_execution_options, 

485 bind_arguments=_bind_arguments, 

486 _parent_execute_state=self, 

487 ) 

488 

489 @property 

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

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

492 

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

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

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

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

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

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

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

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

501 would be selected. 

502 

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

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

505 way of getting this mapper. 

506 

507 .. versionadded:: 1.4.0b2 

508 

509 .. seealso:: 

510 

511 :attr:`_orm.ORMExecuteState.all_mappers` 

512 

513 

514 """ 

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

516 return mp 

517 

518 @property 

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

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

521 involved at the top level of this statement. 

522 

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

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

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

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

527 

528 .. versionadded:: 1.4.0b2 

529 

530 .. seealso:: 

531 

532 :attr:`_orm.ORMExecuteState.bind_mapper` 

533 

534 

535 

536 """ 

537 if not self.is_orm_statement: 

538 return [] 

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

540 result = [] 

541 seen = set() 

542 for d in self.statement.column_descriptions: 

543 ent = d["entity"] 

544 if ent: 

545 insp = inspect(ent, raiseerr=False) 

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

547 seen.add(insp.mapper) 

548 result.append(insp.mapper) 

549 return result 

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

551 return [self.bind_mapper] 

552 else: 

553 return [] 

554 

555 @property 

556 def is_orm_statement(self) -> bool: 

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

558 

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

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

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

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

563 and no ORM-level automation takes place. 

564 

565 """ 

566 return self._compile_state_cls is not None 

567 

568 @property 

569 def is_executemany(self) -> bool: 

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

571 dictionaries with more than one dictionary. 

572 

573 .. versionadded:: 2.0 

574 

575 """ 

576 return isinstance(self.parameters, list) 

577 

578 @property 

579 def is_select(self) -> bool: 

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

581 

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

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

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

585 ``select(Entity).from_statement(select(..))`` 

586 

587 """ 

588 return self.statement.is_select 

589 

590 @property 

591 def is_from_statement(self) -> bool: 

592 """return True if this operation is a 

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

594 

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

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

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

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

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

600 :class:`_sql.Select` construct. 

601 

602 .. versionadded:: 2.0.30 

603 

604 """ 

605 return self.statement.is_from_statement 

606 

607 @property 

608 def is_insert(self) -> bool: 

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

610 

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

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

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

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

615 

616 """ 

617 return self.statement.is_dml and self.statement.is_insert 

618 

619 @property 

620 def is_update(self) -> bool: 

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

622 

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

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

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

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

627 

628 """ 

629 return self.statement.is_dml and self.statement.is_update 

630 

631 @property 

632 def is_delete(self) -> bool: 

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

634 

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

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

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

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

639 

640 """ 

641 return self.statement.is_dml and self.statement.is_delete 

642 

643 @property 

644 def _is_crud(self) -> bool: 

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

646 

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

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

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

650 self._update_execution_options = self._update_execution_options.union( 

651 opts 

652 ) 

653 

654 def _orm_compile_options( 

655 self, 

656 ) -> Optional[ 

657 Union[ 

658 context._ORMCompileState.default_compile_options, 

659 Type[context._ORMCompileState.default_compile_options], 

660 ] 

661 ]: 

662 if not self.is_select: 

663 return None 

664 try: 

665 opts = self.statement._compile_options 

666 except AttributeError: 

667 return None 

668 

669 if opts is not None and opts.isinstance( 

670 context._ORMCompileState.default_compile_options 

671 ): 

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

673 else: 

674 return None 

675 

676 @property 

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

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

679 for a lazy load operation. 

680 

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

682 sharding extension, where it is available within specific query 

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

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

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

686 compilation time. 

687 

688 """ 

689 return self.load_options._lazy_loaded_from 

690 

691 @property 

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

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

694 

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

696 when a particular object or collection is being loaded. 

697 

698 """ 

699 opts = self._orm_compile_options() 

700 if opts is not None: 

701 return opts._current_path 

702 else: 

703 return None 

704 

705 @property 

706 def is_column_load(self) -> bool: 

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

708 attributes on an existing ORM object. 

709 

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

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

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

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

714 loaded. 

715 

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

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

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

719 and loader options travelling with the instance 

720 will have already been added to the query. 

721 

722 .. versionadded:: 1.4.0b2 

723 

724 .. seealso:: 

725 

726 :attr:`_orm.ORMExecuteState.is_relationship_load` 

727 

728 """ 

729 opts = self._orm_compile_options() 

730 return opts is not None and opts._for_refresh_state 

731 

732 @property 

733 def is_relationship_load(self) -> bool: 

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

735 relationship. 

736 

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

738 SelectInLoader, SubqueryLoader, or similar, and the entire 

739 SELECT statement being emitted is on behalf of a relationship 

740 load. 

741 

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

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

744 capable of being propagated to relationship loaders and should 

745 be already present. 

746 

747 .. seealso:: 

748 

749 :attr:`_orm.ORMExecuteState.is_column_load` 

750 

751 """ 

752 opts = self._orm_compile_options() 

753 if opts is None: 

754 return False 

755 path = self.loader_strategy_path 

756 return path is not None and not path.is_root 

757 

758 @property 

759 def load_options( 

760 self, 

761 ) -> Union[ 

762 context.QueryContext.default_load_options, 

763 Type[context.QueryContext.default_load_options], 

764 ]: 

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

766 

767 if not self.is_select: 

768 raise sa_exc.InvalidRequestError( 

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

770 "so there are no load options." 

771 ) 

772 

773 lo: Union[ 

774 context.QueryContext.default_load_options, 

775 Type[context.QueryContext.default_load_options], 

776 ] = self.execution_options.get( 

777 "_sa_orm_load_options", context.QueryContext.default_load_options 

778 ) 

779 return lo 

780 

781 @property 

782 def update_delete_options( 

783 self, 

784 ) -> Union[ 

785 bulk_persistence._BulkUDCompileState.default_update_options, 

786 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

787 ]: 

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

789 execution.""" 

790 

791 if not self._is_crud: 

792 raise sa_exc.InvalidRequestError( 

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

794 "statement so there are no update options." 

795 ) 

796 uo: Union[ 

797 bulk_persistence._BulkUDCompileState.default_update_options, 

798 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

799 ] = self.execution_options.get( 

800 "_sa_orm_update_options", 

801 bulk_persistence._BulkUDCompileState.default_update_options, 

802 ) 

803 return uo 

804 

805 @property 

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

807 return [ 

808 opt 

809 for opt in self.statement._with_options 

810 if is_orm_option(opt) and not opt._is_compile_state 

811 ] 

812 

813 @property 

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

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

816 associated with the statement being invoked. 

817 

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

819 consideration any options added before calling 

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

821 :meth:`_orm.Query.with_entities`. 

822 

823 """ 

824 items = [ 

825 self.statement, 

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

827 ] 

828 return [ 

829 opt 

830 for item in items 

831 for opt in item._with_options 

832 if is_user_defined_option(opt) 

833 ] 

834 

835 

836class SessionTransactionOrigin(Enum): 

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

838 

839 This enumeration is present on the 

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

841 :class:`.SessionTransaction` object. 

842 

843 .. versionadded:: 2.0 

844 

845 """ 

846 

847 AUTOBEGIN = 0 

848 """transaction were started by autobegin""" 

849 

850 BEGIN = 1 

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

852 

853 BEGIN_NESTED = 2 

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

855 

856 SUBTRANSACTION = 3 

857 """transaction is an internal "subtransaction" """ 

858 

859 

860class SessionTransaction(_StateChange, TransactionalContext): 

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

862 

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

864 :meth:`_orm.Session.begin` 

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

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

867 transactions. 

868 

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

870 at: :ref:`unitofwork_transaction`. 

871 

872 

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

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

875 

876 .. seealso:: 

877 

878 :ref:`unitofwork_transaction` 

879 

880 :meth:`.Session.begin` 

881 

882 :meth:`.Session.begin_nested` 

883 

884 :meth:`.Session.rollback` 

885 

886 :meth:`.Session.commit` 

887 

888 :meth:`.Session.in_transaction` 

889 

890 :meth:`.Session.in_nested_transaction` 

891 

892 :meth:`.Session.get_transaction` 

893 

894 :meth:`.Session.get_nested_transaction` 

895 

896 

897 """ 

898 

899 _rollback_exception: Optional[BaseException] = None 

900 

901 _connections: Dict[ 

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

903 ] 

904 session: Session 

905 _parent: Optional[SessionTransaction] 

906 

907 _state: SessionTransactionState 

908 

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

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

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

912 _key_switches: weakref.WeakKeyDictionary[ 

913 InstanceState[Any], Tuple[Any, Any] 

914 ] 

915 

916 origin: SessionTransactionOrigin 

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

918 

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

920 enumeration indicating the source event that led to constructing 

921 this :class:`_orm.SessionTransaction`. 

922 

923 .. versionadded:: 2.0 

924 

925 """ 

926 

927 nested: bool = False 

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

929 

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

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

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

933 

934 .. seealso:: 

935 

936 :attr:`.SessionTransaction.origin` 

937 

938 """ 

939 

940 def __init__( 

941 self, 

942 session: Session, 

943 origin: SessionTransactionOrigin, 

944 parent: Optional[SessionTransaction] = None, 

945 ): 

946 TransactionalContext._trans_ctx_check(session) 

947 

948 self.session = session 

949 self._connections = {} 

950 self._parent = parent 

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

952 self.origin = origin 

953 

954 if session._close_state is _SessionCloseState.CLOSED: 

955 raise sa_exc.InvalidRequestError( 

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

957 "to handle any more transaction requests." 

958 ) 

959 

960 if nested: 

961 if not parent: 

962 raise sa_exc.InvalidRequestError( 

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

964 "transaction is in progress" 

965 ) 

966 

967 self._previous_nested_transaction = session._nested_transaction 

968 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

969 assert parent is not None 

970 else: 

971 assert parent is None 

972 

973 self._state = SessionTransactionState.ACTIVE 

974 

975 self._take_snapshot() 

976 

977 # make sure transaction is assigned before we call the 

978 # dispatch 

979 self.session._transaction = self 

980 

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

982 

983 def _raise_for_prerequisite_state( 

984 self, operation_name: str, state: _StateChangeState 

985 ) -> NoReturn: 

986 if state is SessionTransactionState.DEACTIVE: 

987 if self._rollback_exception: 

988 raise sa_exc.PendingRollbackError( 

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

990 "due to a previous exception during flush." 

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

992 "first issue Session.rollback()." 

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

994 code="7s2a", 

995 ) 

996 else: 

997 raise sa_exc.InvalidRequestError( 

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

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

1000 "can be emitted within this transaction." 

1001 ) 

1002 elif state is SessionTransactionState.CLOSED: 

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

1004 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

1005 raise sa_exc.InvalidRequestError( 

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

1007 "operations are not permitted", 

1008 code="isce", 

1009 ) 

1010 else: 

1011 raise sa_exc.InvalidRequestError( 

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

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

1014 ) 

1015 

1016 @property 

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

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

1019 :class:`.SessionTransaction`. 

1020 

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

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

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

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

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

1026 "nested" / SAVEPOINT transaction. If the 

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

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

1029 

1030 """ 

1031 return self._parent 

1032 

1033 @property 

1034 def is_active(self) -> bool: 

1035 return ( 

1036 self.session is not None 

1037 and self._state is SessionTransactionState.ACTIVE 

1038 ) 

1039 

1040 @property 

1041 def _is_transaction_boundary(self) -> bool: 

1042 return self.nested or not self._parent 

1043 

1044 @_StateChange.declare_states( 

1045 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1046 ) 

1047 def connection( 

1048 self, 

1049 bindkey: Optional[Mapper[Any]], 

1050 execution_options: Optional[_ExecuteOptions] = None, 

1051 **kwargs: Any, 

1052 ) -> Connection: 

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

1054 return self._connection_for_bind(bind, execution_options) 

1055 

1056 @property 

1057 def _connections_boundary(self) -> SessionTransaction: 

1058 trans = self 

1059 while not trans._is_transaction_boundary: 

1060 assert trans._parent is not None 

1061 trans = trans._parent 

1062 return trans 

1063 

1064 @property 

1065 def connections(self) -> FrozenSet[Connection]: 

1066 """The :class:`_engine.Connection` objects this 

1067 :class:`.SessionTransaction` has acquired so far. 

1068 

1069 Reading this attribute never acquires a connection, and works in any 

1070 state of the transaction. After the transaction ends, it still holds 

1071 the connections that were used. 

1072 

1073 .. note:: 

1074 

1075 A savepoint holds only the connections it has used itself. 

1076 To get all connections in the outer transaction, even while a 

1077 savepoint is active, use :meth:`.Session.get_transaction` 

1078 rather than :meth:`.Session.get_nested_transaction`:: 

1079 

1080 root = session.get_transaction() 

1081 connections = root.connections 

1082 

1083 .. versionadded:: 2.1.7 

1084 

1085 .. seealso:: 

1086 

1087 :meth:`.SessionTransaction.bound_connection` 

1088 

1089 """ 

1090 connections = self._connections_boundary._connections 

1091 return frozenset(conn for conn, _, _, _ in connections.values()) 

1092 

1093 def bound_connection( 

1094 self, bindkey: Optional[_EntityBindKey[Any]] = None, **kwargs: Any 

1095 ) -> Connection: 

1096 """Return the :class:`_engine.Connection` this 

1097 :class:`.SessionTransaction` has acquired for the given bind key. 

1098 

1099 The bind key is resolved as in :meth:`_orm.Session.connection`, but 

1100 no connection is acquired; if the transaction holds none for that 

1101 bind, :class:`~sqlalchemy.exc.InvalidRequestError` is raised. 

1102 Additional keyword arguments such as ``clause`` and ``bind`` are 

1103 passed to :meth:`_orm.Session.get_bind`. 

1104 

1105 .. note:: 

1106 

1107 A savepoint can return only a connection it has used itself. 

1108 To look up a connection in the outer transaction, even while a 

1109 savepoint is active, use :meth:`.Session.get_transaction` 

1110 rather than :meth:`.Session.get_nested_transaction`:: 

1111 

1112 root = session.get_transaction() 

1113 connection = root.bound_connection(bindkey) 

1114 

1115 .. versionadded:: 2.1.7 

1116 

1117 .. seealso:: 

1118 

1119 :attr:`.SessionTransaction.connections` 

1120 

1121 """ 

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

1123 entry = self._connections_boundary._connections.get(bind) 

1124 if entry is None: 

1125 raise sa_exc.InvalidRequestError( 

1126 "This transaction has not acquired a connection for " 

1127 f"bind {bind!r}" 

1128 ) 

1129 return entry[0] 

1130 

1131 @_StateChange.declare_states( 

1132 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1133 ) 

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

1135 return SessionTransaction( 

1136 self.session, 

1137 ( 

1138 SessionTransactionOrigin.BEGIN_NESTED 

1139 if nested 

1140 else SessionTransactionOrigin.SUBTRANSACTION 

1141 ), 

1142 self, 

1143 ) 

1144 

1145 def _iterate_self_and_parents( 

1146 self, upto: Optional[SessionTransaction] = None 

1147 ) -> Iterable[SessionTransaction]: 

1148 current = self 

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

1150 while current: 

1151 result += (current,) 

1152 if current._parent is upto: 

1153 break 

1154 elif current._parent is None: 

1155 raise sa_exc.InvalidRequestError( 

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

1157 % (upto) 

1158 ) 

1159 else: 

1160 current = current._parent 

1161 

1162 return result 

1163 

1164 def _take_snapshot(self) -> None: 

1165 if not self._is_transaction_boundary: 

1166 parent = self._parent 

1167 assert parent is not None 

1168 self._new = parent._new 

1169 self._deleted = parent._deleted 

1170 self._dirty = parent._dirty 

1171 self._key_switches = parent._key_switches 

1172 return 

1173 

1174 is_begin = self.origin in ( 

1175 SessionTransactionOrigin.BEGIN, 

1176 SessionTransactionOrigin.AUTOBEGIN, 

1177 ) 

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

1179 self.session.flush() 

1180 

1181 self._new = weakref.WeakKeyDictionary() 

1182 self._deleted = weakref.WeakKeyDictionary() 

1183 self._dirty = weakref.WeakKeyDictionary() 

1184 self._key_switches = weakref.WeakKeyDictionary() 

1185 

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

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

1188 

1189 Corresponds to a rollback. 

1190 

1191 """ 

1192 assert self._is_transaction_boundary 

1193 

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

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

1196 

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

1198 # we probably can do this conditionally based on 

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

1200 self.session.identity_map.safe_discard(s) 

1201 

1202 # restore the old key 

1203 s.key = oldkey 

1204 

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

1206 if s not in to_expunge: 

1207 self.session.identity_map.replace(s) 

1208 

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

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

1211 

1212 assert not self.session._deleted 

1213 

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

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

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

1217 

1218 def _remove_snapshot(self) -> None: 

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

1220 

1221 Corresponds to a commit. 

1222 

1223 """ 

1224 assert self._is_transaction_boundary 

1225 

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

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

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

1229 

1230 statelib.InstanceState._detach_states( 

1231 list(self._deleted), self.session 

1232 ) 

1233 self._deleted.clear() 

1234 elif self.nested: 

1235 parent = self._parent 

1236 assert parent is not None 

1237 parent._new.update(self._new) 

1238 parent._dirty.update(self._dirty) 

1239 parent._deleted.update(self._deleted) 

1240 parent._key_switches.update(self._key_switches) 

1241 

1242 @_StateChange.declare_states( 

1243 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1244 ) 

1245 def _connection_for_bind( 

1246 self, 

1247 bind: _SessionBind, 

1248 execution_options: Optional[CoreExecuteOptionsParameter], 

1249 ) -> Connection: 

1250 if bind in self._connections: 

1251 if execution_options: 

1252 util.warn( 

1253 "Connection is already established for the " 

1254 "given bind; execution_options ignored" 

1255 ) 

1256 return self._connections[bind][0] 

1257 

1258 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1259 

1260 local_connect = False 

1261 should_commit = True 

1262 

1263 try: 

1264 if self._parent: 

1265 # the root transaction applies execution options if it 

1266 # acquires the connection here, else warns that they are 

1267 # ignored 

1268 conn = self._parent._connection_for_bind( 

1269 bind, execution_options 

1270 ) 

1271 if not self.nested: 

1272 return conn 

1273 else: 

1274 if isinstance(bind, engine.Connection): 

1275 conn = bind 

1276 if conn.engine in self._connections: 

1277 raise sa_exc.InvalidRequestError( 

1278 "Session already has a Connection associated " 

1279 "for the given Connection's Engine" 

1280 ) 

1281 else: 

1282 conn = bind.connect() 

1283 local_connect = True 

1284 

1285 try: 

1286 # a savepoint receives the connection from the root 

1287 # transaction with execution options already applied 

1288 if self._parent is None: 

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

1290 if self.session.execution_options: 

1291 conn_exec_opts.update(self.session.execution_options) 

1292 if execution_options: 

1293 conn_exec_opts.update(execution_options) 

1294 if conn_exec_opts: 

1295 conn = conn.execution_options(**conn_exec_opts) 

1296 

1297 transaction: Transaction 

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

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

1300 # conn.in_transaction() ? 

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

1302 # that it is in fact twophase. 

1303 transaction = conn.begin_twophase() 

1304 elif self.nested: 

1305 transaction = conn.begin_nested() 

1306 elif conn.in_transaction(): 

1307 

1308 if local_connect: 

1309 _trans = conn.get_transaction() 

1310 assert _trans is not None 

1311 transaction = _trans 

1312 else: 

1313 join_transaction_mode = ( 

1314 self.session.join_transaction_mode 

1315 ) 

1316 

1317 if join_transaction_mode == "conditional_savepoint": 

1318 if conn.in_nested_transaction(): 

1319 join_transaction_mode = "create_savepoint" 

1320 else: 

1321 join_transaction_mode = "rollback_only" 

1322 

1323 if join_transaction_mode in ( 

1324 "control_fully", 

1325 "rollback_only", 

1326 ): 

1327 if conn.in_nested_transaction(): 

1328 transaction = ( 

1329 conn._get_required_nested_transaction() 

1330 ) 

1331 else: 

1332 transaction = conn._get_required_transaction() 

1333 if join_transaction_mode == "rollback_only": 

1334 should_commit = False 

1335 elif join_transaction_mode == "create_savepoint": 

1336 transaction = conn.begin_nested() 

1337 else: 

1338 assert False, join_transaction_mode 

1339 else: 

1340 transaction = conn.begin() 

1341 except: 

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

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

1344 if local_connect: 

1345 conn.close() 

1346 raise 

1347 else: 

1348 bind_is_connection = isinstance(bind, engine.Connection) 

1349 

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

1351 conn, 

1352 transaction, 

1353 should_commit, 

1354 not bind_is_connection, 

1355 ) 

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

1357 return conn 

1358 finally: 

1359 self._state = SessionTransactionState.ACTIVE 

1360 

1361 def prepare(self) -> None: 

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

1363 raise sa_exc.InvalidRequestError( 

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

1365 "can't prepare." 

1366 ) 

1367 self._prepare_impl() 

1368 

1369 @_StateChange.declare_states( 

1370 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1371 ) 

1372 def _prepare_impl(self) -> None: 

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

1374 self.session.dispatch.before_commit(self.session) 

1375 

1376 stx = self.session._transaction 

1377 assert stx is not None 

1378 if stx is not self: 

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

1380 subtransaction.commit() 

1381 

1382 if not self.session._flushing: 

1383 for _flush_guard in range(100): 

1384 if self.session._is_clean(): 

1385 break 

1386 self.session.flush() 

1387 else: 

1388 raise exc.FlushError( 

1389 "Over 100 subsequent flushes have occurred within " 

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

1391 "creating new objects?" 

1392 ) 

1393 

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

1395 try: 

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

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

1398 except: 

1399 with util.safe_reraise(): 

1400 with self._expect_state(SessionTransactionState.CLOSED): 

1401 self.rollback() 

1402 

1403 self._state = SessionTransactionState.PREPARED 

1404 

1405 @_StateChange.declare_states( 

1406 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1407 SessionTransactionState.CLOSED, 

1408 ) 

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

1410 if self._state is not SessionTransactionState.PREPARED: 

1411 with self._expect_state(SessionTransactionState.PREPARED): 

1412 self._prepare_impl() 

1413 

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

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

1416 self._connections.values() 

1417 ): 

1418 if should_commit: 

1419 trans.commit() 

1420 

1421 self._state = SessionTransactionState.COMMITTED 

1422 self.session.dispatch.after_commit(self.session) 

1423 

1424 self._remove_snapshot() 

1425 

1426 with self._expect_state(SessionTransactionState.CLOSED): 

1427 self.close() 

1428 

1429 if _to_root and self._parent: 

1430 self._parent.commit(_to_root=True) 

1431 

1432 @_StateChange.declare_states( 

1433 ( 

1434 SessionTransactionState.ACTIVE, 

1435 SessionTransactionState.DEACTIVE, 

1436 SessionTransactionState.PREPARED, 

1437 ), 

1438 SessionTransactionState.CLOSED, 

1439 ) 

1440 def rollback( 

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

1442 ) -> None: 

1443 stx = self.session._transaction 

1444 assert stx is not None 

1445 if stx is not self: 

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

1447 subtransaction.close() 

1448 

1449 boundary = self 

1450 rollback_err = None 

1451 if self._state in ( 

1452 SessionTransactionState.ACTIVE, 

1453 SessionTransactionState.PREPARED, 

1454 ): 

1455 for transaction in self._iterate_self_and_parents(): 

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

1457 try: 

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

1459 t[1].rollback() 

1460 

1461 transaction._state = SessionTransactionState.DEACTIVE 

1462 self.session.dispatch.after_rollback(self.session) 

1463 except: 

1464 rollback_err = sys.exc_info() 

1465 finally: 

1466 transaction._state = SessionTransactionState.DEACTIVE 

1467 transaction._restore_snapshot( 

1468 dirty_only=transaction.nested 

1469 ) 

1470 boundary = transaction 

1471 break 

1472 else: 

1473 transaction._state = SessionTransactionState.DEACTIVE 

1474 

1475 sess = self.session 

1476 

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

1478 # if items were added, deleted, or mutated 

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

1480 util.warn( 

1481 "Session's state has been changed on " 

1482 "a non-active transaction - this state " 

1483 "will be discarded." 

1484 ) 

1485 boundary._restore_snapshot(dirty_only=boundary.nested) 

1486 

1487 with self._expect_state(SessionTransactionState.CLOSED): 

1488 self.close() 

1489 

1490 if self._parent and _capture_exception: 

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

1492 

1493 if rollback_err and rollback_err[1]: 

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

1495 

1496 sess.dispatch.after_soft_rollback(sess, self) 

1497 

1498 if _to_root and self._parent: 

1499 self._parent.rollback(_to_root=True) 

1500 

1501 @_StateChange.declare_states( 

1502 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1503 ) 

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

1505 if self.nested: 

1506 self.session._nested_transaction = ( 

1507 self._previous_nested_transaction 

1508 ) 

1509 

1510 self.session._transaction = self._parent 

1511 

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

1513 self._connections.values() 

1514 ): 

1515 if invalidate and self._parent is None: 

1516 connection.invalidate() 

1517 if should_commit and transaction.is_active: 

1518 transaction.close() 

1519 if autoclose and self._parent is None: 

1520 connection.close() 

1521 

1522 self._state = SessionTransactionState.CLOSED 

1523 sess = self.session 

1524 

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

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

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

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

1529 # passes with these commented out. 

1530 # self.session = None # type: ignore 

1531 # self._connections = None # type: ignore 

1532 

1533 sess.dispatch.after_transaction_end(sess, self) 

1534 

1535 def _get_subject(self) -> Session: 

1536 return self.session 

1537 

1538 def _transaction_is_active(self) -> bool: 

1539 return self._state is SessionTransactionState.ACTIVE 

1540 

1541 def _transaction_is_closed(self) -> bool: 

1542 return self._state is SessionTransactionState.CLOSED 

1543 

1544 def _rollback_can_be_called(self) -> bool: 

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

1546 

1547 

1548class _SessionCloseState(Enum): 

1549 ACTIVE = 1 

1550 CLOSED = 2 

1551 CLOSE_IS_RESET = 3 

1552 

1553 

1554class Session(_SessionClassMethods, EventTarget): 

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

1556 

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

1558 See :ref:`session_faq_threadsafe` for background. 

1559 

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

1561 

1562 

1563 """ 

1564 

1565 _is_asyncio = False 

1566 

1567 dispatch: dispatcher[Session] 

1568 

1569 identity_map: IdentityMap 

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

1571 

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

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

1574 that have row identity) currently in the session. 

1575 

1576 .. seealso:: 

1577 

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

1579 in this dictionary. 

1580 

1581 """ 

1582 

1583 binds: Mapping[_SessionBindKey, _SessionBind] 

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

1585 :class:`_engine.Connection` objects. 

1586 

1587 This collection is established from the 

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

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

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

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

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

1593 

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

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

1596 

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

1598 privately. 

1599 

1600 """ 

1601 

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

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

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

1605 _flushing: bool 

1606 _warn_on_events: bool 

1607 _transaction: Optional[SessionTransaction] 

1608 _nested_transaction: Optional[SessionTransaction] 

1609 hash_key: int 

1610 autoflush: bool 

1611 expire_on_commit: bool 

1612 enable_baked_queries: bool 

1613 twophase: bool 

1614 join_transaction_mode: JoinTransactionMode 

1615 execution_options: _ExecuteOptions = util.EMPTY_DICT 

1616 _query_cls: Type[Query[Any]] 

1617 _close_state: _SessionCloseState 

1618 

1619 def __init__( 

1620 self, 

1621 bind: Optional[_SessionBind] = None, 

1622 *, 

1623 autoflush: bool = True, 

1624 future: Literal[True] = True, 

1625 expire_on_commit: bool = True, 

1626 autobegin: bool = True, 

1627 twophase: bool = False, 

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

1629 enable_baked_queries: bool = True, 

1630 info: Optional[_InfoType] = None, 

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

1632 autocommit: Literal[False] = False, 

1633 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1635 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

1636 ): 

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

1638 

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

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

1641 set of arguments. 

1642 

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

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

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

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

1647 results. 

1648 

1649 .. seealso:: 

1650 

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

1652 

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

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

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

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

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

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

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

1660 

1661 .. versionadded:: 2.0 

1662 

1663 .. seealso:: 

1664 

1665 :ref:`session_autobegin_disable` 

1666 

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

1668 :class:`_engine.Connection` to 

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

1670 operations performed by this session will execute via this 

1671 connectable. 

1672 

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

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

1675 objects as the source of 

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

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

1678 arbitrary Python classes that are bases for mapped classes, 

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

1680 The 

1681 values of the dictionary are then instances of 

1682 :class:`_engine.Engine` 

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

1684 Operations which 

1685 proceed relative to a particular mapped class will consult this 

1686 dictionary for the closest matching entity in order to determine 

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

1688 operation. The complete heuristics for resolution are 

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

1690 

1691 Session = sessionmaker( 

1692 binds={ 

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

1694 SomeDeclarativeBase: create_engine( 

1695 "postgresql+psycopg2://engine2" 

1696 ), 

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

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

1699 } 

1700 ) 

1701 

1702 .. seealso:: 

1703 

1704 :ref:`session_partitioning` 

1705 

1706 :meth:`.Session.bind_mapper` 

1707 

1708 :meth:`.Session.bind_table` 

1709 

1710 :meth:`.Session.get_bind` 

1711 

1712 

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

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

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

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

1717 constructor for ``Session``. 

1718 

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

1720 A parameter consumed 

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

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

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

1724 this particular extension is disabled. 

1725 

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

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

1728 flag therefore only affects applications that are making explicit 

1729 use of this extension within their own code. 

1730 

1731 :param execution_options: optional dictionary of execution options 

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

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

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

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

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

1737 event hooks such as 

1738 :meth:`_events.ConnectionEvents.before_cursor_execute`. 

1739 

1740 Execution options present in statements as well as options passed 

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

1742 precedence over the session-wide options. 

1743 

1744 .. versionadded:: 2.1 

1745 

1746 .. versionchanged:: 2.1.0b3 

1747 Session-level execution options are now applied to the 

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

1749 they take effect for flush operations as well as explicit 

1750 query executions. Previously, options were only applied to 

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

1752 

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

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

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

1756 transaction will load from the most recent database state. 

1757 

1758 .. seealso:: 

1759 

1760 :ref:`session_committing` 

1761 

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

1763 

1764 .. seealso:: 

1765 

1766 :ref:`migration_20_toplevel` 

1767 

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

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

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

1771 construction time so that modifications to the per- 

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

1773 :class:`.Session`. 

1774 

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

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

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

1778 

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

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

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

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

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

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

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

1786 transaction, before each transaction is committed. 

1787 

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

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

1790 

1791 :param join_transaction_mode: Describes the transactional behavior to 

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

1793 has already begun a transaction outside the scope of this 

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

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

1796 

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

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

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

1800 etc. are actually invoked: 

1801 

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

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

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

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

1806 a SAVEPOINT, in other words 

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

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

1809 

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

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

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

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

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

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

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

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

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

1819 

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

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

1822 its own transaction. This transaction by its nature rides 

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

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

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

1826 external transaction will remain unaffected throughout the 

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

1828 

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

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

1831 initiated transaction should remain unaffected; however, it relies 

1832 on proper SAVEPOINT support from the underlying driver and 

1833 database. 

1834 

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

1836 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1837 without workarounds. See the sections 

1838 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1839 for details on current workarounds. 

1840 

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

1842 control of the given transaction as its own; 

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

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

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

1846 call ``.rollback`` on the transaction. 

1847 

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

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

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

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

1852 SAVEPOINT. 

1853 

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

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

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

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

1858 given transaction. 

1859 

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

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

1862 regular database transaction (i.e. 

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

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

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

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

1867 

1868 .. versionadded:: 2.0.0rc1 

1869 

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

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

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

1873 

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

1875 A future SQLAlchemy version may change the default value of 

1876 this flag to ``False``. 

1877 

1878 .. seealso:: 

1879 

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

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

1882 

1883 """ # noqa 

1884 

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

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

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

1888 # of cases including in our own test suite 

1889 if autocommit: 

1890 raise sa_exc.ArgumentError( 

1891 "autocommit=True is no longer supported" 

1892 ) 

1893 self.identity_map = identity._WeakInstanceDict() 

1894 

1895 if not future: 

1896 raise sa_exc.ArgumentError( 

1897 "The 'future' parameter passed to " 

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

1899 ) 

1900 

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

1902 self._deleted = {} # same 

1903 self.bind = bind 

1904 self.binds = util.EMPTY_DICT 

1905 self._flushing = False 

1906 self._warn_on_events = False 

1907 self._transaction = None 

1908 self._nested_transaction = None 

1909 self.hash_key = _new_sessionid() 

1910 self.autobegin = autobegin 

1911 self.autoflush = autoflush 

1912 self.expire_on_commit = expire_on_commit 

1913 self.enable_baked_queries = enable_baked_queries 

1914 if execution_options: 

1915 self.execution_options = self.execution_options.union( 

1916 execution_options 

1917 ) 

1918 

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

1920 # the default will switch to close_resets_only=False. 

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

1922 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1923 else: 

1924 self._close_state = _SessionCloseState.ACTIVE 

1925 if ( 

1926 join_transaction_mode 

1927 and join_transaction_mode 

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

1929 ): 

1930 raise sa_exc.ArgumentError( 

1931 f"invalid selection for join_transaction_mode: " 

1932 f'"{join_transaction_mode}"' 

1933 ) 

1934 self.join_transaction_mode = join_transaction_mode 

1935 

1936 self.twophase = twophase 

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

1938 if info: 

1939 self.info.update(info) 

1940 

1941 if binds is not None: 

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

1943 self._add_bind(key, bind) 

1944 

1945 _sessions[self.hash_key] = self 

1946 

1947 # used by sqlalchemy.engine.util.TransactionalContext 

1948 _trans_context_manager: Optional[TransactionalContext] = None 

1949 

1950 connection_callable: Optional[_ConnectionCallableProto] = None 

1951 

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

1953 return self 

1954 

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

1956 self.close() 

1957 

1958 @contextlib.contextmanager 

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

1960 with self: 

1961 with self.begin(): 

1962 yield self 

1963 

1964 def in_transaction(self) -> bool: 

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

1966 

1967 .. versionadded:: 1.4 

1968 

1969 .. seealso:: 

1970 

1971 :attr:`_orm.Session.is_active` 

1972 

1973 

1974 """ 

1975 return self._transaction is not None 

1976 

1977 def in_nested_transaction(self) -> bool: 

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

1979 transaction, e.g. SAVEPOINT. 

1980 

1981 .. versionadded:: 1.4 

1982 

1983 """ 

1984 return self._nested_transaction is not None 

1985 

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

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

1988 

1989 .. versionadded:: 1.4 

1990 

1991 """ 

1992 trans = self._transaction 

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

1994 trans = trans._parent 

1995 return trans 

1996 

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

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

1999 

2000 .. versionadded:: 1.4 

2001 

2002 """ 

2003 

2004 return self._nested_transaction 

2005 

2006 @util.memoized_property 

2007 def info(self) -> _InfoType: 

2008 """A user-modifiable dictionary. 

2009 

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

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

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

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

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

2015 

2016 """ 

2017 return {} 

2018 

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

2020 if self._transaction is None: 

2021 if not begin and not self.autobegin: 

2022 raise sa_exc.InvalidRequestError( 

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

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

2025 ) 

2026 trans = SessionTransaction( 

2027 self, 

2028 ( 

2029 SessionTransactionOrigin.BEGIN 

2030 if begin 

2031 else SessionTransactionOrigin.AUTOBEGIN 

2032 ), 

2033 ) 

2034 assert self._transaction is trans 

2035 return trans 

2036 

2037 return self._transaction 

2038 

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

2040 """Begin a transaction, or nested transaction, 

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

2042 

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

2044 so that normally it is not necessary to call the 

2045 :meth:`_orm.Session.begin` 

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

2047 the scope of when the transactional state is begun. 

2048 

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

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

2051 

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

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

2054 documentation on SAVEPOINT transactions, please see 

2055 :ref:`session_begin_nested`. 

2056 

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

2058 :class:`.SessionTransaction` 

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

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

2061 an example. 

2062 

2063 .. seealso:: 

2064 

2065 :ref:`session_autobegin` 

2066 

2067 :ref:`unitofwork_transaction` 

2068 

2069 :meth:`.Session.begin_nested` 

2070 

2071 

2072 """ 

2073 

2074 trans = self._transaction 

2075 if trans is None: 

2076 trans = self._autobegin_t(begin=True) 

2077 

2078 if not nested: 

2079 return trans 

2080 

2081 assert trans is not None 

2082 

2083 if nested: 

2084 trans = trans._begin(nested=nested) 

2085 assert self._transaction is trans 

2086 self._nested_transaction = trans 

2087 else: 

2088 raise sa_exc.InvalidRequestError( 

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

2090 ) 

2091 

2092 return trans # needed for __enter__/__exit__ hook 

2093 

2094 def begin_nested(self) -> SessionTransaction: 

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

2096 

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

2098 SAVEPOINT for this method to function correctly. 

2099 

2100 For documentation on SAVEPOINT 

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

2102 

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

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

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

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

2107 

2108 .. seealso:: 

2109 

2110 :ref:`session_begin_nested` 

2111 

2112 :ref:`pysqlite_serializable` - special workarounds required 

2113 with the SQLite driver in order for SAVEPOINT to work 

2114 correctly. For asyncio use cases, see the section 

2115 :ref:`aiosqlite_serializable`. 

2116 

2117 """ 

2118 return self.begin(nested=True) 

2119 

2120 def rollback(self) -> None: 

2121 """Rollback the current transaction in progress. 

2122 

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

2124 

2125 The method always rolls back 

2126 the topmost database transaction, discarding any nested 

2127 transactions that may be in progress. 

2128 

2129 .. seealso:: 

2130 

2131 :ref:`session_rollback` 

2132 

2133 :ref:`unitofwork_transaction` 

2134 

2135 """ 

2136 if self._transaction is None: 

2137 pass 

2138 else: 

2139 self._transaction.rollback(_to_root=True) 

2140 

2141 def commit(self) -> None: 

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

2143 

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

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

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

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

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

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

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

2151 to disable this behavior. 

2152 

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

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

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

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

2157 normally affect the database unless pending flush changes were 

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

2159 rules. 

2160 

2161 The outermost database transaction is committed unconditionally, 

2162 automatically releasing any SAVEPOINTs in effect. 

2163 

2164 .. seealso:: 

2165 

2166 :ref:`session_committing` 

2167 

2168 :ref:`unitofwork_transaction` 

2169 

2170 :ref:`asyncio_orm_avoid_lazyloads` 

2171 

2172 """ 

2173 trans = self._transaction 

2174 if trans is None: 

2175 trans = self._autobegin_t() 

2176 

2177 trans.commit(_to_root=True) 

2178 

2179 def prepare(self) -> None: 

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

2181 

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

2183 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2184 

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

2186 current transaction is not such, an 

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

2188 

2189 """ 

2190 trans = self._transaction 

2191 if trans is None: 

2192 trans = self._autobegin_t() 

2193 

2194 trans.prepare() 

2195 

2196 def connection( 

2197 self, 

2198 bind_arguments: Optional[_BindArguments] = None, 

2199 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2200 ) -> Connection: 

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

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

2203 

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

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

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

2207 returned (note that no 

2208 transactional state is established with the DBAPI until the first 

2209 SQL statement is emitted). 

2210 

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

2212 resolved through any of the optional keyword arguments. This 

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

2214 

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

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

2217 to :meth:`.Session.get_bind`. 

2218 

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

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

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

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

2223 the arguments are ignored. 

2224 

2225 .. seealso:: 

2226 

2227 :ref:`session_transaction_isolation` 

2228 

2229 """ 

2230 

2231 if bind_arguments: 

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

2233 

2234 if bind is None: 

2235 bind = self.get_bind(**bind_arguments) 

2236 else: 

2237 bind = self.get_bind() 

2238 

2239 return self._connection_for_bind( 

2240 bind, 

2241 execution_options=execution_options, 

2242 ) 

2243 

2244 def _connection_for_bind( 

2245 self, 

2246 engine: _SessionBind, 

2247 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2248 **kw: Any, 

2249 ) -> Connection: 

2250 TransactionalContext._trans_ctx_check(self) 

2251 

2252 trans = self._transaction 

2253 if trans is None: 

2254 trans = self._autobegin_t() 

2255 return trans._connection_for_bind(engine, execution_options) 

2256 

2257 @overload 

2258 def _execute_internal( 

2259 self, 

2260 statement: Executable, 

2261 params: Optional[_CoreSingleExecuteParams] = None, 

2262 *, 

2263 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2264 bind_arguments: Optional[_BindArguments] = None, 

2265 _parent_execute_state: Optional[Any] = None, 

2266 _add_event: Optional[Any] = None, 

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

2268 ) -> Any: ... 

2269 

2270 @overload 

2271 def _execute_internal( 

2272 self, 

2273 statement: Executable, 

2274 params: Optional[_CoreAnyExecuteParams] = None, 

2275 *, 

2276 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2277 bind_arguments: Optional[_BindArguments] = None, 

2278 _parent_execute_state: Optional[Any] = None, 

2279 _add_event: Optional[Any] = None, 

2280 _scalar_result: bool = ..., 

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

2282 

2283 def _execute_internal( 

2284 self, 

2285 statement: Executable, 

2286 params: Optional[_CoreAnyExecuteParams] = None, 

2287 *, 

2288 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2289 bind_arguments: Optional[_BindArguments] = None, 

2290 _parent_execute_state: Optional[Any] = None, 

2291 _add_event: Optional[Any] = None, 

2292 _scalar_result: bool = False, 

2293 ) -> Any: 

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

2295 

2296 if not bind_arguments: 

2297 bind_arguments = {} 

2298 else: 

2299 bind_arguments = dict(bind_arguments) 

2300 

2301 if ( 

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

2303 == "orm" 

2304 ): 

2305 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2306 statement, "orm" 

2307 ) 

2308 if TYPE_CHECKING: 

2309 assert isinstance( 

2310 compile_state_cls, context._AbstractORMCompileState 

2311 ) 

2312 else: 

2313 compile_state_cls = None 

2314 bind_arguments.setdefault("clause", statement) 

2315 

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

2317 util.coerce_to_immutabledict(execution_options) 

2318 ) 

2319 if self.execution_options: 

2320 # merge given execution options with session-wide execution 

2321 # options. if the statement also has execution_options, 

2322 # maintain priority of session.execution_options -> 

2323 # statement.execution_options -> method passed execution_options 

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

2325 # will come from the statement 

2326 if statement._execution_options: 

2327 combined_execution_options = util.immutabledict( 

2328 { 

2329 k: v 

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

2331 if k not in statement._execution_options 

2332 } 

2333 ).union(combined_execution_options) 

2334 else: 

2335 combined_execution_options = self.execution_options.union( 

2336 combined_execution_options 

2337 ) 

2338 

2339 if _parent_execute_state: 

2340 events_todo = _parent_execute_state._remaining_events() 

2341 else: 

2342 events_todo = self.dispatch.do_orm_execute 

2343 if _add_event: 

2344 events_todo = list(events_todo) + [_add_event] 

2345 

2346 if events_todo: 

2347 # save the original execution options before 

2348 # orm_pre_session_exec processes them, so that we can pass 

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

2350 # hooks) to the second orm_pre_session_exec call. This 

2351 # prevents internal state like _sa_orm_load_options and 

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

2353 # which would otherwise cause issues like yield_per incorrectly 

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

2355 # part of #13301. 

2356 original_execution_options = combined_execution_options 

2357 

2358 if compile_state_cls is not None: 

2359 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2365 ( 

2366 statement, 

2367 combined_execution_options, 

2368 params, 

2369 ) = compile_state_cls.orm_pre_session_exec( 

2370 self, 

2371 statement, 

2372 params, 

2373 combined_execution_options, 

2374 bind_arguments, 

2375 True, 

2376 ) 

2377 

2378 orm_exec_state = ORMExecuteState( 

2379 self, 

2380 statement, 

2381 params, 

2382 combined_execution_options, 

2383 bind_arguments, 

2384 compile_state_cls, 

2385 events_todo, 

2386 ) 

2387 for idx, fn in enumerate(events_todo): 

2388 orm_exec_state._starting_event_idx = idx 

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

2390 orm_exec_state 

2391 ) 

2392 if fn_result: 

2393 if _scalar_result: 

2394 return fn_result.scalar() 

2395 else: 

2396 return fn_result 

2397 

2398 statement = orm_exec_state.statement 

2399 params = orm_exec_state.parameters 

2400 

2401 # use the original execution options plus only the explicit 

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

2403 # the first orm_pre_session_exec call 

2404 combined_execution_options = original_execution_options.union( 

2405 orm_exec_state._update_execution_options 

2406 ) 

2407 

2408 if compile_state_cls is not None: 

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

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

2411 # new execution_options into load_options / update_delete_options, 

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

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

2414 ( 

2415 statement, 

2416 combined_execution_options, 

2417 params, 

2418 ) = compile_state_cls.orm_pre_session_exec( 

2419 self, 

2420 statement, 

2421 params, 

2422 combined_execution_options, 

2423 bind_arguments, 

2424 False, 

2425 ) 

2426 else: 

2427 # Issue #9809: unconditionally autoflush for Core statements 

2428 self._autoflush() 

2429 

2430 bind = self.get_bind(**bind_arguments) 

2431 

2432 conn = self._connection_for_bind(bind) 

2433 

2434 if _scalar_result and not compile_state_cls: 

2435 if TYPE_CHECKING: 

2436 params = cast(_CoreSingleExecuteParams, params) 

2437 return conn.scalar( 

2438 statement, 

2439 params or {}, 

2440 execution_options=combined_execution_options, 

2441 ) 

2442 

2443 if compile_state_cls: 

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

2445 compile_state_cls.orm_execute_statement( 

2446 self, 

2447 statement, 

2448 params or {}, 

2449 combined_execution_options, 

2450 bind_arguments, 

2451 conn, 

2452 ) 

2453 ) 

2454 else: 

2455 result = conn.execute( 

2456 statement, params, execution_options=combined_execution_options 

2457 ) 

2458 

2459 if _scalar_result: 

2460 return result.scalar() 

2461 else: 

2462 return result 

2463 

2464 @overload 

2465 def execute( 

2466 self, 

2467 statement: TypedReturnsRows[Unpack[_Ts]], 

2468 params: Optional[_CoreAnyExecuteParams] = None, 

2469 *, 

2470 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2471 bind_arguments: Optional[_BindArguments] = None, 

2472 _parent_execute_state: Optional[Any] = None, 

2473 _add_event: Optional[Any] = None, 

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

2475 

2476 @overload 

2477 def execute( 

2478 self, 

2479 statement: Executable, 

2480 params: Optional[_CoreAnyExecuteParams] = None, 

2481 *, 

2482 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2483 bind_arguments: Optional[_BindArguments] = None, 

2484 _parent_execute_state: Optional[Any] = None, 

2485 _add_event: Optional[Any] = None, 

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

2487 

2488 def execute( 

2489 self, 

2490 statement: Executable, 

2491 params: Optional[_CoreAnyExecuteParams] = None, 

2492 *, 

2493 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2494 bind_arguments: Optional[_BindArguments] = None, 

2495 _parent_execute_state: Optional[Any] = None, 

2496 _add_event: Optional[Any] = None, 

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

2498 r"""Execute a SQL expression construct. 

2499 

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

2501 results of the statement execution. 

2502 

2503 E.g.:: 

2504 

2505 from sqlalchemy import select 

2506 

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

2508 

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

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

2511 of :class:`_engine.Connection`. 

2512 

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

2514 now the primary point of ORM statement execution when using 

2515 :term:`2.0 style` ORM usage. 

2516 

2517 :param statement: 

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

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

2520 

2521 :param params: 

2522 Optional dictionary, or list of dictionaries, containing 

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

2524 execution occurs; if a list of dictionaries, an 

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

2526 must correspond to parameter names present in the statement. 

2527 

2528 :param execution_options: optional dictionary of execution options, 

2529 which will be associated with the statement execution. This 

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

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

2532 provide additional options understood only in an ORM context. 

2533 

2534 The execution_options are passed along to methods like 

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

2536 highest priority to execution_options that are passed to this 

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

2538 statement object if any, and finally those options present 

2539 session-wide. 

2540 

2541 .. seealso:: 

2542 

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

2544 options 

2545 

2546 :param bind_arguments: dictionary of additional arguments to determine 

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

2548 Contents of this dictionary are passed to the 

2549 :meth:`.Session.get_bind` method. 

2550 

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

2552 

2553 

2554 """ 

2555 return self._execute_internal( 

2556 statement, 

2557 params, 

2558 execution_options=execution_options, 

2559 bind_arguments=bind_arguments, 

2560 _parent_execute_state=_parent_execute_state, 

2561 _add_event=_add_event, 

2562 ) 

2563 

2564 # special case to handle mypy issue: 

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

2566 @overload 

2567 def scalar( 

2568 self, 

2569 statement: TypedReturnsRows[Never], 

2570 params: Optional[_CoreSingleExecuteParams] = None, 

2571 *, 

2572 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2573 bind_arguments: Optional[_BindArguments] = None, 

2574 **kw: Any, 

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

2576 

2577 @overload 

2578 def scalar( 

2579 self, 

2580 statement: TypedReturnsRows[_T], 

2581 params: Optional[_CoreSingleExecuteParams] = None, 

2582 *, 

2583 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2584 bind_arguments: Optional[_BindArguments] = None, 

2585 **kw: Any, 

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

2587 

2588 @overload 

2589 def scalar( 

2590 self, 

2591 statement: Executable, 

2592 params: Optional[_CoreSingleExecuteParams] = None, 

2593 *, 

2594 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2595 bind_arguments: Optional[_BindArguments] = None, 

2596 **kw: Any, 

2597 ) -> Any: ... 

2598 

2599 def scalar( 

2600 self, 

2601 statement: Executable, 

2602 params: Optional[_CoreSingleExecuteParams] = None, 

2603 *, 

2604 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2605 bind_arguments: Optional[_BindArguments] = None, 

2606 **kw: Any, 

2607 ) -> Any: 

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

2609 

2610 Usage and parameters are the same as that of 

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

2612 value. 

2613 

2614 """ 

2615 

2616 return self._execute_internal( 

2617 statement, 

2618 params, 

2619 execution_options=execution_options, 

2620 bind_arguments=bind_arguments, 

2621 _scalar_result=True, 

2622 **kw, 

2623 ) 

2624 

2625 @overload 

2626 def scalars( 

2627 self, 

2628 statement: TypedReturnsRows[_T], 

2629 params: Optional[_CoreAnyExecuteParams] = None, 

2630 *, 

2631 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2632 bind_arguments: Optional[_BindArguments] = None, 

2633 **kw: Any, 

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

2635 

2636 @overload 

2637 def scalars( 

2638 self, 

2639 statement: Executable, 

2640 params: Optional[_CoreAnyExecuteParams] = None, 

2641 *, 

2642 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2643 bind_arguments: Optional[_BindArguments] = None, 

2644 **kw: Any, 

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

2646 

2647 def scalars( 

2648 self, 

2649 statement: Executable, 

2650 params: Optional[_CoreAnyExecuteParams] = None, 

2651 *, 

2652 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2653 bind_arguments: Optional[_BindArguments] = None, 

2654 **kw: Any, 

2655 ) -> ScalarResult[Any]: 

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

2657 

2658 Usage and parameters are the same as that of 

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

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

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

2662 

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

2664 

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

2666 

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

2668 

2669 .. seealso:: 

2670 

2671 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2673 

2674 """ 

2675 

2676 return self._execute_internal( 

2677 statement, 

2678 params=params, 

2679 execution_options=execution_options, 

2680 bind_arguments=bind_arguments, 

2681 _scalar_result=False, # mypy appreciates this 

2682 **kw, 

2683 ).scalars() 

2684 

2685 def close(self) -> None: 

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

2687 :class:`_orm.Session`. 

2688 

2689 This expunges all ORM objects associated with this 

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

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

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

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

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

2695 

2696 .. tip:: 

2697 

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

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

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

2701 distinct "closed" state; it merely means 

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

2703 and ORM objects. 

2704 

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

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

2707 any further action on the session will be forbidden. 

2708 

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

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

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

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

2713 

2714 .. seealso:: 

2715 

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

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

2718 

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

2720 ``close()`` with the parameter 

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

2722 

2723 """ 

2724 self._close_impl(invalidate=False) 

2725 

2726 def reset(self) -> None: 

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

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

2729 

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

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

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

2733 brand new, and ready to be used again. 

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

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

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

2737 

2738 .. versionadded:: 2.0.22 

2739 

2740 .. seealso:: 

2741 

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

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

2744 

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

2746 prevent reuse of the Session when the parameter 

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

2748 """ 

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

2750 

2751 def invalidate(self) -> None: 

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

2753 

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

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

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

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

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

2759 multiple engines). 

2760 

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

2762 the connections are no longer safe to be used. 

2763 

2764 Below illustrates a scenario when using `gevent 

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

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

2767 

2768 import gevent 

2769 

2770 try: 

2771 sess = Session() 

2772 sess.add(User()) 

2773 sess.commit() 

2774 except gevent.Timeout: 

2775 sess.invalidate() 

2776 raise 

2777 except: 

2778 sess.rollback() 

2779 raise 

2780 

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

2782 does, including that all ORM objects are expunged. 

2783 

2784 """ 

2785 self._close_impl(invalidate=True) 

2786 

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

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

2789 self._close_state = _SessionCloseState.CLOSED 

2790 self.expunge_all() 

2791 if self._transaction is not None: 

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

2793 transaction.close(invalidate) 

2794 

2795 def expunge_all(self) -> None: 

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

2797 

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

2799 ``Session``. 

2800 

2801 """ 

2802 

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

2804 self.identity_map._kill() 

2805 self.identity_map = identity._WeakInstanceDict() 

2806 self._new = {} 

2807 self._deleted = {} 

2808 

2809 statelib.InstanceState._detach_states(all_states, self) 

2810 

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

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

2813 

2814 try: 

2815 insp = inspect(key) 

2816 except sa_exc.NoInspectionAvailable as err: 

2817 if not isinstance(key, type): 

2818 raise sa_exc.ArgumentError( 

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

2820 ) from err 

2821 else: 

2822 new_binds[key] = bind 

2823 else: 

2824 if TYPE_CHECKING: 

2825 assert isinstance(insp, Inspectable) 

2826 

2827 if isinstance(insp, TableClause): 

2828 new_binds[insp] = bind 

2829 elif insp_is_mapper(insp): 

2830 new_binds[insp.class_] = bind 

2831 for _selectable in insp._all_tables: 

2832 new_binds[_selectable] = bind 

2833 else: 

2834 raise sa_exc.ArgumentError( 

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

2836 ) 

2837 

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

2839 new_binds 

2840 ) 

2841 

2842 def bind_mapper( 

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

2844 ) -> None: 

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

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

2847 :class:`_engine.Connection`. 

2848 

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

2850 :meth:`.Session.get_bind` method. 

2851 

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

2853 or an instance of a mapped 

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

2855 classes. 

2856 

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

2858 object. 

2859 

2860 .. seealso:: 

2861 

2862 :ref:`session_partitioning` 

2863 

2864 :paramref:`.Session.binds` 

2865 

2866 :meth:`.Session.bind_table` 

2867 

2868 

2869 """ 

2870 self._add_bind(mapper, bind) 

2871 

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

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

2874 :class:`_engine.Engine` 

2875 or :class:`_engine.Connection`. 

2876 

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

2878 :meth:`.Session.get_bind` method. 

2879 

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

2881 which is typically the target 

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

2883 mapped. 

2884 

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

2886 object. 

2887 

2888 .. seealso:: 

2889 

2890 :ref:`session_partitioning` 

2891 

2892 :paramref:`.Session.binds` 

2893 

2894 :meth:`.Session.bind_mapper` 

2895 

2896 

2897 """ 

2898 self._add_bind(table, bind) 

2899 

2900 def get_bind( 

2901 self, 

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

2903 *, 

2904 clause: Optional[ClauseElement] = None, 

2905 bind: Optional[_SessionBind] = None, 

2906 _sa_skip_events: Optional[bool] = None, 

2907 _sa_skip_for_implicit_returning: bool = False, 

2908 **kw: Any, 

2909 ) -> Union[Engine, Connection]: 

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

2911 

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

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

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

2915 

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

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

2918 appropriate bind to return. 

2919 

2920 Note that the "mapper" argument is usually present 

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

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

2923 individual INSERT/UPDATE/DELETE operation within a 

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

2925 

2926 The order of resolution is: 

2927 

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

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

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

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

2932 superclasses to more general. 

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

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

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

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

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

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

2939 associated with the clause. 

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

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

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

2943 selectable to which the mapper is mapped. 

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

2945 is raised. 

2946 

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

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

2949 of bind resolution scheme. See the example at 

2950 :ref:`session_custom_partitioning`. 

2951 

2952 :param mapper: 

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

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

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

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

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

2958 mapped for a bind. 

2959 

2960 :param clause: 

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

2962 :func:`_expression.select`, 

2963 :func:`_expression.text`, 

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

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

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

2967 associated with 

2968 bound :class:`_schema.MetaData`. 

2969 

2970 .. seealso:: 

2971 

2972 :ref:`session_partitioning` 

2973 

2974 :paramref:`.Session.binds` 

2975 

2976 :meth:`.Session.bind_mapper` 

2977 

2978 :meth:`.Session.bind_table` 

2979 

2980 """ 

2981 

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

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

2984 if bind: 

2985 return bind 

2986 elif not self.binds and self.bind: 

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

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

2989 return self.bind 

2990 

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

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

2993 # mapper and the clause 

2994 if mapper is None and clause is None: 

2995 if self.bind: 

2996 return self.bind 

2997 else: 

2998 raise sa_exc.UnboundExecutionError( 

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

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

3001 "a binding." 

3002 ) 

3003 

3004 # look more closely at the mapper. 

3005 if mapper is not None: 

3006 try: 

3007 inspected_mapper = inspect(mapper) 

3008 except sa_exc.NoInspectionAvailable as err: 

3009 if isinstance(mapper, type): 

3010 raise exc.UnmappedClassError(mapper) from err 

3011 else: 

3012 raise 

3013 else: 

3014 inspected_mapper = None 

3015 

3016 # match up the mapper or clause in the binds 

3017 if self.binds: 

3018 # matching mappers and selectables to entries in the 

3019 # binds dictionary; supported use case. 

3020 if inspected_mapper: 

3021 for cls in inspected_mapper.class_.__mro__: 

3022 if cls in self.binds: 

3023 return self.binds[cls] 

3024 if clause is None: 

3025 clause = inspected_mapper.persist_selectable 

3026 

3027 if clause is not None: 

3028 plugin_subject = clause._propagate_attrs.get( 

3029 "plugin_subject", None 

3030 ) 

3031 

3032 if plugin_subject is not None: 

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

3034 if cls in self.binds: 

3035 return self.binds[cls] 

3036 

3037 for obj in visitors.iterate(clause): 

3038 if obj in self.binds: 

3039 if TYPE_CHECKING: 

3040 assert isinstance(obj, Table) 

3041 return self.binds[obj] 

3042 

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

3044 # return that 

3045 if self.bind: 

3046 return self.bind 

3047 

3048 context = [] 

3049 if inspected_mapper is not None: 

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

3051 if clause is not None: 

3052 context.append("SQL expression") 

3053 

3054 raise sa_exc.UnboundExecutionError( 

3055 f"Could not locate a bind configured on " 

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

3057 ) 

3058 

3059 @overload 

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

3061 

3062 @overload 

3063 def query( 

3064 self, _colexpr: TypedColumnsClauseRole[_T] 

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

3066 

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

3068 

3069 # code within this block is **programmatically, 

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

3071 

3072 @overload 

3073 def query( 

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

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

3076 

3077 @overload 

3078 def query( 

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

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

3081 

3082 @overload 

3083 def query( 

3084 self, 

3085 __ent0: _TCCA[_T0], 

3086 __ent1: _TCCA[_T1], 

3087 __ent2: _TCCA[_T2], 

3088 __ent3: _TCCA[_T3], 

3089 /, 

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

3091 

3092 @overload 

3093 def query( 

3094 self, 

3095 __ent0: _TCCA[_T0], 

3096 __ent1: _TCCA[_T1], 

3097 __ent2: _TCCA[_T2], 

3098 __ent3: _TCCA[_T3], 

3099 __ent4: _TCCA[_T4], 

3100 /, 

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

3102 

3103 @overload 

3104 def query( 

3105 self, 

3106 __ent0: _TCCA[_T0], 

3107 __ent1: _TCCA[_T1], 

3108 __ent2: _TCCA[_T2], 

3109 __ent3: _TCCA[_T3], 

3110 __ent4: _TCCA[_T4], 

3111 __ent5: _TCCA[_T5], 

3112 /, 

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

3114 

3115 @overload 

3116 def query( 

3117 self, 

3118 __ent0: _TCCA[_T0], 

3119 __ent1: _TCCA[_T1], 

3120 __ent2: _TCCA[_T2], 

3121 __ent3: _TCCA[_T3], 

3122 __ent4: _TCCA[_T4], 

3123 __ent5: _TCCA[_T5], 

3124 __ent6: _TCCA[_T6], 

3125 /, 

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

3127 

3128 @overload 

3129 def query( 

3130 self, 

3131 __ent0: _TCCA[_T0], 

3132 __ent1: _TCCA[_T1], 

3133 __ent2: _TCCA[_T2], 

3134 __ent3: _TCCA[_T3], 

3135 __ent4: _TCCA[_T4], 

3136 __ent5: _TCCA[_T5], 

3137 __ent6: _TCCA[_T6], 

3138 __ent7: _TCCA[_T7], 

3139 /, 

3140 *entities: _ColumnsClauseArgument[Any], 

3141 ) -> RowReturningQuery[ 

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

3143 ]: ... 

3144 

3145 # END OVERLOADED FUNCTIONS self.query 

3146 

3147 @overload 

3148 def query( 

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

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

3151 

3152 def query( 

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

3154 ) -> Query[Any]: 

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

3156 :class:`_orm.Session`. 

3157 

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

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

3160 to construct ORM queries. 

3161 

3162 .. seealso:: 

3163 

3164 :ref:`unified_tutorial` 

3165 

3166 :ref:`queryguide_toplevel` 

3167 

3168 :ref:`query_api_toplevel` - legacy API doc 

3169 

3170 """ 

3171 

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

3173 

3174 def _identity_lookup( 

3175 self, 

3176 mapper: Mapper[_O], 

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

3178 identity_token: Any = None, 

3179 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

3181 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3182 bind_arguments: Optional[_BindArguments] = None, 

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

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

3185 

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

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

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

3189 check if was deleted). 

3190 

3191 e.g.:: 

3192 

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

3194 

3195 :param mapper: mapper in use 

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

3197 a tuple. 

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

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

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

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

3202 :param passive: passive load flag passed to 

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

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

3205 if the flag allows for SQL to be emitted. 

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

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

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

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

3210 relationship-loaded). 

3211 

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

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

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

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

3216 

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

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

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

3220 :class:`_query.Query` object. 

3221 

3222 

3223 """ 

3224 

3225 key = mapper.identity_key_from_primary_key( 

3226 primary_key_identity, identity_token=identity_token 

3227 ) 

3228 

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

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

3231 return return_value 

3232 

3233 @util.non_memoized_property 

3234 @contextlib.contextmanager 

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

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

3237 

3238 e.g.:: 

3239 

3240 with session.no_autoflush: 

3241 

3242 some_object = SomeClass() 

3243 session.add(some_object) 

3244 # won't autoflush 

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

3246 

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

3248 will not be subject to flushes occurring upon query 

3249 access. This is useful when initializing a series 

3250 of objects which involve existing database queries, 

3251 where the uncompleted object should not yet be flushed. 

3252 

3253 """ 

3254 autoflush = self.autoflush 

3255 self.autoflush = False 

3256 try: 

3257 yield self 

3258 finally: 

3259 self.autoflush = autoflush 

3260 

3261 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3265 "warning happened while initializing objects.", 

3266 sa_exc.SAWarning, 

3267 ) 

3268 def _autoflush(self) -> None: 

3269 if self.autoflush and not self._flushing: 

3270 try: 

3271 self.flush() 

3272 except sa_exc.StatementError as e: 

3273 # note we are reraising StatementError as opposed to 

3274 # raising FlushError with "chaining" to remain compatible 

3275 # with code that catches StatementError, IntegrityError, 

3276 # etc. 

3277 e.add_detail( 

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

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

3280 "flush is occurring prematurely" 

3281 ) 

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

3283 

3284 def refresh( 

3285 self, 

3286 instance: object, 

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

3288 with_for_update: ForUpdateParameter = None, 

3289 ) -> None: 

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

3291 

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

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

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

3295 value available in the current transaction. 

3296 

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

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

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

3300 

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

3302 can also refresh eagerly loaded attributes. 

3303 

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

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

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

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

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

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

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

3311 refreshed. 

3312 

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

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

3315 attributes for those which are named explicitly in the 

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

3317 

3318 .. tip:: 

3319 

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

3321 refreshing both column and relationship oriented attributes, its 

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

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

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

3325 once while having explicit control over relationship loader 

3326 strategies, use the 

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

3328 instead. 

3329 

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

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

3332 in database state outside of that transaction. Refreshing 

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

3334 where database rows have not yet been accessed. 

3335 

3336 :param attribute_names: optional. An iterable collection of 

3337 string attribute names indicating a subset of attributes to 

3338 be refreshed. 

3339 

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

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

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

3343 flags should match the parameters of 

3344 :meth:`_query.Query.with_for_update`. 

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

3346 

3347 .. seealso:: 

3348 

3349 :ref:`session_expire` - introductory material 

3350 

3351 :meth:`.Session.expire` 

3352 

3353 :meth:`.Session.expire_all` 

3354 

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

3356 to refresh objects as they would be loaded normally. 

3357 

3358 """ 

3359 try: 

3360 state = attributes.instance_state(instance) 

3361 except exc.NO_STATE as err: 

3362 raise exc.UnmappedInstanceError(instance) from err 

3363 

3364 self._expire_state(state, attribute_names) 

3365 

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

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

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

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

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

3371 # load_on_ident. 

3372 self._autoflush() 

3373 

3374 if with_for_update == {}: 

3375 raise sa_exc.ArgumentError( 

3376 "with_for_update should be the boolean value " 

3377 "True, or a dictionary with options. " 

3378 "A blank dictionary is ambiguous." 

3379 ) 

3380 

3381 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3382 

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

3384 if ( 

3385 loading._load_on_ident( 

3386 self, 

3387 stmt, 

3388 state.key, 

3389 refresh_state=state, 

3390 with_for_update=with_for_update, 

3391 only_load_props=attribute_names, 

3392 require_pk_cols=True, 

3393 # technically unnecessary as we just did autoflush 

3394 # above, however removes the additional unnecessary 

3395 # call to _autoflush() 

3396 no_autoflush=True, 

3397 is_user_refresh=True, 

3398 ) 

3399 is None 

3400 ): 

3401 raise sa_exc.InvalidRequestError( 

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

3403 ) 

3404 

3405 def expire_all(self) -> None: 

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

3407 

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

3409 a query will be issued using the 

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

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

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

3413 previously read in that same transaction, regardless of changes 

3414 in database state outside of that transaction. 

3415 

3416 To expire individual objects and individual attributes 

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

3418 

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

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

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

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

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

3424 assuming the transaction is isolated. 

3425 

3426 .. seealso:: 

3427 

3428 :ref:`session_expire` - introductory material 

3429 

3430 :meth:`.Session.expire` 

3431 

3432 :meth:`.Session.refresh` 

3433 

3434 :meth:`_orm.Query.populate_existing` 

3435 

3436 """ 

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

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

3439 

3440 def expire( 

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

3442 ) -> None: 

3443 """Expire the attributes on an instance. 

3444 

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

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

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

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

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

3450 previously read in that same transaction, regardless of changes 

3451 in database state outside of that transaction. 

3452 

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

3454 use :meth:`Session.expire_all`. 

3455 

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

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

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

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

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

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

3462 transaction. 

3463 

3464 :param instance: The instance to be refreshed. 

3465 :param attribute_names: optional list of string attribute names 

3466 indicating a subset of attributes to be expired. 

3467 

3468 .. seealso:: 

3469 

3470 :ref:`session_expire` - introductory material 

3471 

3472 :meth:`.Session.expire` 

3473 

3474 :meth:`.Session.refresh` 

3475 

3476 :meth:`_orm.Query.populate_existing` 

3477 

3478 """ 

3479 try: 

3480 state = attributes.instance_state(instance) 

3481 except exc.NO_STATE as err: 

3482 raise exc.UnmappedInstanceError(instance) from err 

3483 self._expire_state(state, attribute_names) 

3484 

3485 def _expire_state( 

3486 self, 

3487 state: InstanceState[Any], 

3488 attribute_names: Optional[Iterable[str]], 

3489 ) -> None: 

3490 self._validate_persistent(state) 

3491 if attribute_names: 

3492 state._expire_attributes(state.dict, attribute_names) 

3493 else: 

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

3495 # remove associations 

3496 cascaded = list( 

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

3498 ) 

3499 self._conditional_expire(state) 

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

3501 self._conditional_expire(st_) 

3502 

3503 def _conditional_expire( 

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

3505 ) -> None: 

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

3507 

3508 if state.key: 

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

3510 elif state in self._new: 

3511 self._new.pop(state) 

3512 state._detach(self) 

3513 

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

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

3516 

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

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

3519 

3520 """ 

3521 try: 

3522 state = attributes.instance_state(instance) 

3523 except exc.NO_STATE as err: 

3524 raise exc.UnmappedInstanceError(instance) from err 

3525 if state.session_id is not self.hash_key: 

3526 raise sa_exc.InvalidRequestError( 

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

3528 ) 

3529 

3530 cascaded = list( 

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

3532 ) 

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

3534 

3535 def _expunge_states( 

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

3537 ) -> None: 

3538 for state in states: 

3539 if state in self._new: 

3540 self._new.pop(state) 

3541 elif self.identity_map.contains_state(state): 

3542 self.identity_map.safe_discard(state) 

3543 self._deleted.pop(state, None) 

3544 elif self._transaction: 

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

3546 # in the transaction snapshot 

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

3548 statelib.InstanceState._detach_states( 

3549 states, self, to_transient=to_transient 

3550 ) 

3551 

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

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

3554 

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

3556 state as well as already persistent objects. 

3557 

3558 """ 

3559 

3560 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3561 for state in states: 

3562 mapper = _state_mapper(state) 

3563 

3564 # prevent against last minute dereferences of the object 

3565 obj = state.obj() 

3566 if obj is not None: 

3567 instance_key = mapper._identity_key_from_state(state) 

3568 

3569 if ( 

3570 _none_set.intersection(instance_key[1]) 

3571 and not mapper.allow_partial_pks 

3572 or _none_set.issuperset(instance_key[1]) 

3573 ): 

3574 raise exc.FlushError( 

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

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

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

3578 "that the mapped Column object is configured to " 

3579 "expect these generated values. Ensure also that " 

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

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

3582 % state_str(state) 

3583 ) 

3584 

3585 if state.key is None: 

3586 state.key = instance_key 

3587 elif state.key != instance_key: 

3588 # primary key switch. use safe_discard() in case another 

3589 # state has already replaced this one in the identity 

3590 # map (see test/orm/test_naturalpks.py ReversePKsTest) 

3591 self.identity_map.safe_discard(state) 

3592 trans = self._transaction 

3593 assert trans is not None 

3594 if state in trans._key_switches: 

3595 orig_key = trans._key_switches[state][0] 

3596 else: 

3597 orig_key = state.key 

3598 trans._key_switches[state] = ( 

3599 orig_key, 

3600 instance_key, 

3601 ) 

3602 state.key = instance_key 

3603 

3604 # there can be an existing state in the identity map 

3605 # that is replaced when the primary keys of two instances 

3606 # are swapped; see test/orm/test_naturalpks.py -> test_reverse 

3607 old = self.identity_map.replace(state) 

3608 

3609 # check the replaced state's identity without emitting 

3610 # any SQL; an expired state such as one left over from 

3611 # a rollback will not match and does not warn 

3612 if ( 

3613 old is not None 

3614 and old.obj() is not None 

3615 and mapper._identity_key_from_state( 

3616 old, PassiveFlag.PASSIVE_NO_FETCH 

3617 ) 

3618 == instance_key 

3619 ): 

3620 util.warn( 

3621 "Identity map already had an identity for %s, " 

3622 "replacing it with newly flushed object. Are there " 

3623 "load operations occurring inside of an event handler " 

3624 "within the flush?" % (instance_key,) 

3625 ) 

3626 state._orphaned_outside_of_session = False 

3627 

3628 statelib.InstanceState._commit_all_states( 

3629 ((state, state.dict) for state in states), self.identity_map 

3630 ) 

3631 

3632 self._register_altered(states) 

3633 

3634 if pending_to_persistent is not None: 

3635 for state in states.intersection(self._new): 

3636 pending_to_persistent(self, state) 

3637 

3638 # remove from new last, might be the last strong ref 

3639 for state in set(states).intersection(self._new): 

3640 self._new.pop(state) 

3641 

3642 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None: 

3643 if self._transaction: 

3644 for state in states: 

3645 if state in self._new: 

3646 self._transaction._new[state] = True 

3647 else: 

3648 self._transaction._dirty[state] = True 

3649 

3650 def _remove_newly_deleted( 

3651 self, states: Iterable[InstanceState[Any]] 

3652 ) -> None: 

3653 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3654 for state in states: 

3655 if self._transaction: 

3656 self._transaction._deleted[state] = True 

3657 

3658 if persistent_to_deleted is not None: 

3659 # get a strong reference before we pop out of 

3660 # self._deleted 

3661 obj = state.obj() # noqa 

3662 

3663 self.identity_map.safe_discard(state) 

3664 self._deleted.pop(state, None) 

3665 state._deleted = True 

3666 # can't call state._detach() here, because this state 

3667 # is still in the transaction snapshot and needs to be 

3668 # tracked as part of that 

3669 if persistent_to_deleted is not None: 

3670 persistent_to_deleted(self, state) 

3671 

3672 def add(self, instance: object, *, _warn: bool = True) -> None: 

3673 """Place an object into this :class:`_orm.Session`. 

3674 

3675 Objects that are in the :term:`transient` state when passed to the 

3676 :meth:`_orm.Session.add` method will move to the 

3677 :term:`pending` state, until the next flush, at which point they 

3678 will move to the :term:`persistent` state. 

3679 

3680 Objects that are in the :term:`detached` state when passed to the 

3681 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3682 state directly. 

3683 

3684 If the transaction used by the :class:`_orm.Session` is rolled back, 

3685 objects which were transient when they were passed to 

3686 :meth:`_orm.Session.add` will be moved back to the 

3687 :term:`transient` state, and will no longer be present within this 

3688 :class:`_orm.Session`. 

3689 

3690 .. seealso:: 

3691 

3692 :meth:`_orm.Session.add_all` 

3693 

3694 :ref:`session_adding` - at :ref:`session_basics` 

3695 

3696 """ 

3697 if _warn and self._warn_on_events: 

3698 self._flush_warning("Session.add()") 

3699 

3700 try: 

3701 state = attributes.instance_state(instance) 

3702 except exc.NO_STATE as err: 

3703 raise exc.UnmappedInstanceError(instance) from err 

3704 

3705 self._save_or_update_state(state) 

3706 

3707 def add_all(self, instances: Iterable[object]) -> None: 

3708 """Add the given collection of instances to this :class:`_orm.Session`. 

3709 

3710 See the documentation for :meth:`_orm.Session.add` for a general 

3711 behavioral description. 

3712 

3713 .. seealso:: 

3714 

3715 :meth:`_orm.Session.add` 

3716 

3717 :ref:`session_adding` - at :ref:`session_basics` 

3718 

3719 """ 

3720 

3721 if self._warn_on_events: 

3722 self._flush_warning("Session.add_all()") 

3723 

3724 for instance in instances: 

3725 self.add(instance, _warn=False) 

3726 

3727 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3728 state._orphaned_outside_of_session = False 

3729 self._save_or_update_impl(state) 

3730 

3731 mapper = _state_mapper(state) 

3732 for o, m, st_, dct_ in mapper.cascade_iterator( 

3733 "save-update", state, halt_on=self._contains_state 

3734 ): 

3735 self._save_or_update_impl(st_) 

3736 

3737 def delete(self, instance: object) -> None: 

3738 """Mark an instance as deleted. 

3739 

3740 The object is assumed to be either :term:`persistent` or 

3741 :term:`detached` when passed; after the method is called, the 

3742 object will remain in the :term:`persistent` state until the next 

3743 flush proceeds. During this time, the object will also be a member 

3744 of the :attr:`_orm.Session.deleted` collection. 

3745 

3746 When the next flush proceeds, the object will move to the 

3747 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3748 for its row within the current transaction. When the transaction 

3749 is successfully committed, 

3750 the deleted object is moved to the :term:`detached` state and is 

3751 no longer present within this :class:`_orm.Session`. 

3752 

3753 .. seealso:: 

3754 

3755 :ref:`session_deleting` - at :ref:`session_basics` 

3756 

3757 :meth:`.Session.delete_all` - multiple instance version 

3758 

3759 """ 

3760 if self._warn_on_events: 

3761 self._flush_warning("Session.delete()") 

3762 

3763 self._delete_impl(object_state(instance), instance, head=True) 

3764 

3765 def delete_all(self, instances: Iterable[object]) -> None: 

3766 """Calls :meth:`.Session.delete` on multiple instances. 

3767 

3768 .. seealso:: 

3769 

3770 :meth:`.Session.delete` - main documentation on delete 

3771 

3772 .. versionadded:: 2.1 

3773 

3774 """ 

3775 

3776 if self._warn_on_events: 

3777 self._flush_warning("Session.delete_all()") 

3778 

3779 for instance in instances: 

3780 self._delete_impl(object_state(instance), instance, head=True) 

3781 

3782 def _delete_impl( 

3783 self, state: InstanceState[Any], obj: object, head: bool 

3784 ) -> None: 

3785 if state.key is None: 

3786 if head: 

3787 raise sa_exc.InvalidRequestError( 

3788 "Instance '%s' is not persisted" % state_str(state) 

3789 ) 

3790 else: 

3791 return 

3792 

3793 to_attach = self._before_attach(state, obj) 

3794 

3795 if state in self._deleted: 

3796 return 

3797 

3798 self.identity_map.add(state) 

3799 

3800 if to_attach: 

3801 self._after_attach(state, obj) 

3802 

3803 if head: 

3804 # grab the cascades before adding the item to the deleted list 

3805 # so that autoflush does not delete the item 

3806 # the strong reference to the instance itself is significant here 

3807 cascade_states = list( 

3808 state.manager.mapper.cascade_iterator("delete", state) 

3809 ) 

3810 else: 

3811 cascade_states = None 

3812 

3813 self._deleted[state] = obj 

3814 

3815 if head: 

3816 if TYPE_CHECKING: 

3817 assert cascade_states is not None 

3818 for o, m, st_, dct_ in cascade_states: 

3819 self._delete_impl(st_, o, False) 

3820 

3821 def get( 

3822 self, 

3823 entity: _EntityBindKey[_O], 

3824 ident: _PKIdentityArgument, 

3825 *, 

3826 options: Optional[Sequence[ORMOption]] = None, 

3827 populate_existing: bool | None = None, 

3828 with_for_update: ForUpdateParameter = None, 

3829 identity_token: Optional[Any] = None, 

3830 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3831 bind_arguments: Optional[_BindArguments] = None, 

3832 ) -> Optional[_O]: 

3833 """Return an instance based on the given primary key identifier, 

3834 or ``None`` if not found. 

3835 

3836 E.g.:: 

3837 

3838 my_user = session.get(User, 5) 

3839 

3840 some_object = session.get(VersionedFoo, (5, 10)) 

3841 

3842 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3843 

3844 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3845 from the now legacy :meth:`_orm.Query.get` method. 

3846 

3847 :meth:`_orm.Session.get` is special in that it provides direct 

3848 access to the identity map of the :class:`.Session`. 

3849 If the given primary key identifier is present 

3850 in the local identity map, the object is returned 

3851 directly from this collection and no SQL is emitted, 

3852 unless the object has been marked fully expired. 

3853 If not present, 

3854 a SELECT is performed in order to locate the object. 

3855 

3856 :meth:`_orm.Session.get` also will perform a check if 

3857 the object is present in the identity map and 

3858 marked as expired - a SELECT 

3859 is emitted to refresh the object as well as to 

3860 ensure that the row is still present. 

3861 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3862 

3863 :param entity: a mapped class or :class:`.Mapper` indicating the 

3864 type of entity to be loaded. 

3865 

3866 :param ident: A scalar, tuple, or dictionary representing the 

3867 primary key. For a composite (e.g. multiple column) primary key, 

3868 a tuple or dictionary should be passed. 

3869 

3870 For a single-column primary key, the scalar calling form is typically 

3871 the most expedient. If the primary key of a row is the value "5", 

3872 the call looks like:: 

3873 

3874 my_object = session.get(SomeClass, 5) 

3875 

3876 The tuple form contains primary key values typically in 

3877 the order in which they correspond to the mapped 

3878 :class:`_schema.Table` 

3879 object's primary key columns, or if the 

3880 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3881 used, in 

3882 the order used for that parameter. For example, if the primary key 

3883 of a row is represented by the integer 

3884 digits "5, 10" the call would look like:: 

3885 

3886 my_object = session.get(SomeClass, (5, 10)) 

3887 

3888 The dictionary form should include as keys the mapped attribute names 

3889 corresponding to each element of the primary key. If the mapped class 

3890 has the attributes ``id``, ``version_id`` as the attributes which 

3891 store the object's primary key value, the call would look like:: 

3892 

3893 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3894 

3895 :param options: optional sequence of loader options which will be 

3896 applied to the query, if one is emitted. 

3897 

3898 :param populate_existing: causes the method to unconditionally emit 

3899 a SQL query and refresh the object with the newly loaded data, 

3900 regardless of whether or not the object is already present. 

3901 Setting this flag takes precedence over passing it as an 

3902 execution option. 

3903 

3904 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE 

3905 should be used, or may be a dictionary containing flags to 

3906 indicate a more specific set of FOR UPDATE flags for the SELECT; 

3907 flags should match the parameters of 

3908 :meth:`_query.Query.with_for_update`. 

3909 Supersedes the :paramref:`.Session.refresh.lockmode` parameter. 

3910 

3911 :param execution_options: optional dictionary of execution options, 

3912 which will be associated with the query execution if one is emitted. 

3913 This dictionary can provide a subset of the options that are 

3914 accepted by :meth:`_engine.Connection.execution_options`, and may 

3915 also provide additional options understood only in an ORM context. 

3916 

3917 .. versionadded:: 1.4.29 

3918 

3919 .. seealso:: 

3920 

3921 :ref:`orm_queryguide_execution_options` - ORM-specific execution 

3922 options 

3923 

3924 :param bind_arguments: dictionary of additional arguments to determine 

3925 the bind. May include "mapper", "bind", or other custom arguments. 

3926 Contents of this dictionary are passed to the 

3927 :meth:`.Session.get_bind` method. 

3928 

3929 .. versionadded:: 2.0.0rc1 

3930 

3931 :return: The object instance, or ``None``. 

3932 

3933 """ # noqa: E501 

3934 return self._get_impl( 

3935 entity, 

3936 ident, 

3937 loading._load_on_pk_identity, 

3938 options=options, 

3939 populate_existing=populate_existing, 

3940 with_for_update=with_for_update, 

3941 identity_token=identity_token, 

3942 execution_options=execution_options, 

3943 bind_arguments=bind_arguments, 

3944 ) 

3945 

3946 def get_one( 

3947 self, 

3948 entity: _EntityBindKey[_O], 

3949 ident: _PKIdentityArgument, 

3950 *, 

3951 options: Optional[Sequence[ORMOption]] = None, 

3952 populate_existing: bool | None = None, 

3953 with_for_update: ForUpdateParameter = None, 

3954 identity_token: Optional[Any] = None, 

3955 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3956 bind_arguments: Optional[_BindArguments] = None, 

3957 ) -> _O: 

3958 """Return exactly one instance based on the given primary key 

3959 identifier, or raise an exception if not found. 

3960 

3961 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3962 

3963 For a detailed documentation of the arguments see the 

3964 method :meth:`.Session.get`. 

3965 

3966 .. versionadded:: 2.0.22 

3967 

3968 :return: The object instance. 

3969 

3970 .. seealso:: 

3971 

3972 :meth:`.Session.get` - equivalent method that instead 

3973 returns ``None`` if no row was found with the provided primary 

3974 key 

3975 

3976 """ 

3977 

3978 instance = self.get( 

3979 entity, 

3980 ident, 

3981 options=options, 

3982 populate_existing=populate_existing, 

3983 with_for_update=with_for_update, 

3984 identity_token=identity_token, 

3985 execution_options=execution_options, 

3986 bind_arguments=bind_arguments, 

3987 ) 

3988 

3989 if instance is None: 

3990 raise sa_exc.NoResultFound( 

3991 "No row was found when one was required" 

3992 ) 

3993 

3994 return instance 

3995 

3996 def _get_impl( 

3997 self, 

3998 entity: _EntityBindKey[_O], 

3999 primary_key_identity: _PKIdentityArgument, 

4000 db_load_fn: Callable[..., _O], 

4001 *, 

4002 options: Optional[Sequence[ExecutableOption]] = None, 

4003 populate_existing: bool | None = None, 

4004 with_for_update: ForUpdateParameter = None, 

4005 identity_token: Optional[Any] = None, 

4006 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

4007 bind_arguments: Optional[_BindArguments] = None, 

4008 ) -> Optional[_O]: 

4009 # set populate_existing value; direct parameter 

4010 # takes precedence over execution_options 

4011 if populate_existing is not None: 

4012 execution_options = { 

4013 **execution_options, 

4014 "populate_existing": populate_existing, 

4015 } 

4016 else: 

4017 populate_existing = execution_options.get( 

4018 "populate_existing", False 

4019 ) 

4020 

4021 # convert composite types to individual args 

4022 if ( 

4023 is_composite_class(primary_key_identity) 

4024 and type(primary_key_identity) 

4025 in descriptor_props._composite_getters 

4026 ): 

4027 getter = descriptor_props._composite_getters[ 

4028 type(primary_key_identity) 

4029 ] 

4030 primary_key_identity = getter(primary_key_identity) 

4031 

4032 mapper: Optional[Mapper[_O]] = inspect(entity) 

4033 

4034 if mapper is None or not mapper.is_mapper: 

4035 raise sa_exc.ArgumentError( 

4036 "Expected mapped class or mapper, got: %r" % entity 

4037 ) 

4038 

4039 is_dict = isinstance(primary_key_identity, dict) 

4040 if not is_dict: 

4041 primary_key_identity = util.to_list( 

4042 primary_key_identity, default=[None] 

4043 ) 

4044 

4045 if len(primary_key_identity) != len(mapper.primary_key): 

4046 raise sa_exc.InvalidRequestError( 

4047 "Incorrect number of values in identifier to formulate " 

4048 "primary key for session.get(); primary key columns " 

4049 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

4050 ) 

4051 

4052 if is_dict: 

4053 pk_synonyms = mapper._pk_synonyms 

4054 

4055 if pk_synonyms: 

4056 correct_keys = set(pk_synonyms).intersection( 

4057 primary_key_identity 

4058 ) 

4059 

4060 if correct_keys: 

4061 primary_key_identity = dict(primary_key_identity) 

4062 for k in correct_keys: 

4063 primary_key_identity[pk_synonyms[k]] = ( 

4064 primary_key_identity[k] 

4065 ) 

4066 

4067 try: 

4068 primary_key_identity = list( 

4069 primary_key_identity[prop.key] 

4070 for prop in mapper._identity_key_props 

4071 ) 

4072 

4073 except KeyError as err: 

4074 raise sa_exc.InvalidRequestError( 

4075 "Incorrect names of values in identifier to formulate " 

4076 "primary key for session.get(); primary key attribute " 

4077 "names are %s (synonym names are also accepted)" 

4078 % ",".join( 

4079 "'%s'" % prop.key 

4080 for prop in mapper._identity_key_props 

4081 ) 

4082 ) from err 

4083 

4084 for_update_arg = ForUpdateArg._from_argument(with_for_update) 

4085 

4086 if ( 

4087 not populate_existing 

4088 and not mapper.always_refresh 

4089 and for_update_arg is None 

4090 ): 

4091 instance = self._identity_lookup( 

4092 mapper, 

4093 primary_key_identity, 

4094 identity_token=identity_token, 

4095 execution_options=execution_options, 

4096 bind_arguments=bind_arguments, 

4097 ) 

4098 

4099 if instance is not None: 

4100 # reject calls for id in identity map but class 

4101 # mismatch. 

4102 if not isinstance(instance, mapper.class_): 

4103 return None 

4104 return instance 

4105 

4106 # TODO: this was being tested before, but this is not possible 

4107 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

4108 

4109 load_options = context.QueryContext.default_load_options 

4110 

4111 if populate_existing: 

4112 load_options += {"_populate_existing": populate_existing} 

4113 statement = sql.select(mapper) 

4114 if for_update_arg is not None: 

4115 statement._for_update_arg = for_update_arg 

4116 

4117 if options: 

4118 statement = statement.options(*options) 

4119 if self.execution_options: 

4120 execution_options = self.execution_options.union(execution_options) 

4121 return db_load_fn( 

4122 self, 

4123 statement, 

4124 primary_key_identity, 

4125 load_options=load_options, 

4126 identity_token=identity_token, 

4127 execution_options=execution_options, 

4128 bind_arguments=bind_arguments, 

4129 ) 

4130 

4131 def merge( 

4132 self, 

4133 instance: _O, 

4134 *, 

4135 load: bool = True, 

4136 options: Optional[Sequence[ORMOption]] = None, 

4137 ) -> _O: 

4138 """Copy the state of a given instance into a corresponding instance 

4139 within this :class:`.Session`. 

4140 

4141 :meth:`.Session.merge` examines the primary key attributes of the 

4142 source instance, and attempts to reconcile it with an instance of the 

4143 same primary key in the session. If not found locally, it attempts 

4144 to load the object from the database based on primary key, and if 

4145 none can be located, creates a new instance. The state of each 

4146 attribute on the source instance is then copied to the target 

4147 instance. The resulting target instance is then returned by the 

4148 method; the original source instance is left unmodified, and 

4149 un-associated with the :class:`.Session` if not already. 

4150 

4151 This operation cascades to associated instances if the association is 

4152 mapped with ``cascade="merge"``. 

4153 

4154 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

4155 

4156 :param instance: Instance to be merged. 

4157 :param load: Boolean, when False, :meth:`.merge` switches into 

4158 a "high performance" mode which causes it to forego emitting history 

4159 events as well as all database access. This flag is used for 

4160 cases such as transferring graphs of objects into a :class:`.Session` 

4161 from a second level cache, or to transfer just-loaded objects 

4162 into the :class:`.Session` owned by a worker thread or process 

4163 without re-querying the database. 

4164 

4165 The ``load=False`` use case adds the caveat that the given 

4166 object has to be in a "clean" state, that is, has no pending changes 

4167 to be flushed - even if the incoming object is detached from any 

4168 :class:`.Session`. This is so that when 

4169 the merge operation populates local attributes and 

4170 cascades to related objects and 

4171 collections, the values can be "stamped" onto the 

4172 target object as is, without generating any history or attribute 

4173 events, and without the need to reconcile the incoming data with 

4174 any existing related objects or collections that might not 

4175 be loaded. The resulting objects from ``load=False`` are always 

4176 produced as "clean", so it is only appropriate that the given objects 

4177 should be "clean" as well, else this suggests a mis-use of the 

4178 method. 

4179 :param options: optional sequence of loader options which will be 

4180 applied to the :meth:`_orm.Session.get` method when the merge 

4181 operation loads the existing version of the object from the database. 

4182 

4183 .. versionadded:: 1.4.24 

4184 

4185 

4186 .. seealso:: 

4187 

4188 :func:`.make_transient_to_detached` - provides for an alternative 

4189 means of "merging" a single object into the :class:`.Session` 

4190 

4191 :meth:`.Session.merge_all` - multiple instance version 

4192 

4193 """ 

4194 

4195 if self._warn_on_events: 

4196 self._flush_warning("Session.merge()") 

4197 

4198 if load: 

4199 # flush current contents if we expect to load data 

4200 self._autoflush() 

4201 

4202 with self.no_autoflush: 

4203 return self._merge( 

4204 object_state(instance), 

4205 attributes.instance_dict(instance), 

4206 load=load, 

4207 options=options, 

4208 _recursive={}, 

4209 _resolve_conflict_map={}, 

4210 ) 

4211 

4212 def merge_all( 

4213 self, 

4214 instances: Iterable[_O], 

4215 *, 

4216 load: bool = True, 

4217 options: Optional[Sequence[ORMOption]] = None, 

4218 ) -> Sequence[_O]: 

4219 """Calls :meth:`.Session.merge` on multiple instances. 

4220 

4221 .. seealso:: 

4222 

4223 :meth:`.Session.merge` - main documentation on merge 

4224 

4225 .. versionadded:: 2.1 

4226 

4227 """ 

4228 

4229 if self._warn_on_events: 

4230 self._flush_warning("Session.merge_all()") 

4231 

4232 if load: 

4233 # flush current contents if we expect to load data 

4234 self._autoflush() 

4235 

4236 with self.no_autoflush: 

4237 return [ 

4238 self._merge( 

4239 object_state(instance), 

4240 attributes.instance_dict(instance), 

4241 load=load, 

4242 options=options, 

4243 _recursive={}, 

4244 _resolve_conflict_map={}, 

4245 ) 

4246 for instance in instances 

4247 ] 

4248 

4249 def _merge( 

4250 self, 

4251 state: InstanceState[_O], 

4252 state_dict: _InstanceDict, 

4253 *, 

4254 options: Optional[Sequence[ORMOption]] = None, 

4255 load: bool, 

4256 _recursive: Dict[Any, object], 

4257 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4258 ) -> _O: 

4259 mapper: Mapper[_O] = _state_mapper(state) 

4260 if state in _recursive: 

4261 return cast(_O, _recursive[state]) 

4262 

4263 new_instance = False 

4264 key = state.key 

4265 

4266 merged: Optional[_O] 

4267 

4268 if key is None: 

4269 if state in self._new: 

4270 util.warn( 

4271 "Instance %s is already pending in this Session yet is " 

4272 "being merged again; this is probably not what you want " 

4273 "to do" % state_str(state) 

4274 ) 

4275 

4276 if not load: 

4277 raise sa_exc.InvalidRequestError( 

4278 "merge() with load=False option does not support " 

4279 "objects transient (i.e. unpersisted) objects. flush() " 

4280 "all changes on mapped instances before merging with " 

4281 "load=False." 

4282 ) 

4283 key = mapper._identity_key_from_state(state) 

4284 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4285 1 

4286 ] and ( 

4287 not _none_set.intersection(key[1]) 

4288 or ( 

4289 mapper.allow_partial_pks 

4290 and not _none_set.issuperset(key[1]) 

4291 ) 

4292 ) 

4293 else: 

4294 key_is_persistent = True 

4295 

4296 merged = self.identity_map.get(key) 

4297 

4298 if merged is None: 

4299 if key_is_persistent and key in _resolve_conflict_map: 

4300 merged = cast(_O, _resolve_conflict_map[key]) 

4301 

4302 elif not load: 

4303 if state.modified: 

4304 raise sa_exc.InvalidRequestError( 

4305 "merge() with load=False option does not support " 

4306 "objects marked as 'dirty'. flush() all changes on " 

4307 "mapped instances before merging with load=False." 

4308 ) 

4309 merged = mapper.class_manager.new_instance() 

4310 merged_state = attributes.instance_state(merged) 

4311 merged_state.key = key 

4312 self._update_impl(merged_state) 

4313 new_instance = True 

4314 

4315 elif key_is_persistent: 

4316 merged = self.get( 

4317 mapper.class_, 

4318 key[1], 

4319 identity_token=key[2], 

4320 options=options, 

4321 ) 

4322 

4323 if merged is None: 

4324 merged = mapper.class_manager.new_instance() 

4325 merged_state = attributes.instance_state(merged) 

4326 merged_dict = attributes.instance_dict(merged) 

4327 new_instance = True 

4328 self._save_or_update_state(merged_state) 

4329 else: 

4330 merged_state = attributes.instance_state(merged) 

4331 merged_dict = attributes.instance_dict(merged) 

4332 

4333 _recursive[state] = merged 

4334 _resolve_conflict_map[key] = merged 

4335 

4336 # check that we didn't just pull the exact same 

4337 # state out. 

4338 if state is not merged_state: 

4339 # version check if applicable 

4340 if mapper.version_id_col is not None: 

4341 existing_version = mapper._get_state_attr_by_column( 

4342 state, 

4343 state_dict, 

4344 mapper.version_id_col, 

4345 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4346 ) 

4347 

4348 merged_version = mapper._get_state_attr_by_column( 

4349 merged_state, 

4350 merged_dict, 

4351 mapper.version_id_col, 

4352 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4353 ) 

4354 

4355 if ( 

4356 existing_version 

4357 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4358 and merged_version 

4359 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4360 and existing_version != merged_version 

4361 ): 

4362 raise exc.StaleDataError( 

4363 "Version id '%s' on merged state %s " 

4364 "does not match existing version '%s'. " 

4365 "Leave the version attribute unset when " 

4366 "merging to update the most recent version." 

4367 % ( 

4368 existing_version, 

4369 state_str(merged_state), 

4370 merged_version, 

4371 ) 

4372 ) 

4373 

4374 merged_state.load_path = state.load_path 

4375 merged_state.load_options = state.load_options 

4376 

4377 # since we are copying load_options, we need to copy 

4378 # the callables_ that would have been generated by those 

4379 # load_options. 

4380 # assumes that the callables we put in state.callables_ 

4381 # are not instance-specific (which they should not be) 

4382 merged_state._copy_callables(state) 

4383 

4384 for prop in mapper.iterate_properties: 

4385 prop.merge( 

4386 self, 

4387 state, 

4388 state_dict, 

4389 merged_state, 

4390 merged_dict, 

4391 load, 

4392 _recursive, 

4393 _resolve_conflict_map, 

4394 ) 

4395 

4396 if not load: 

4397 # remove any history 

4398 merged_state._commit_all(merged_dict, self.identity_map) 

4399 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4400 merged_state, None 

4401 ) 

4402 

4403 if new_instance: 

4404 merged_state.manager.dispatch.load(merged_state, None) 

4405 

4406 return merged 

4407 

4408 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4409 if not self.identity_map.contains_state(state): 

4410 raise sa_exc.InvalidRequestError( 

4411 "Instance '%s' is not persistent within this Session" 

4412 % state_str(state) 

4413 ) 

4414 

4415 def _save_impl(self, state: InstanceState[Any]) -> None: 

4416 if state.key is not None: 

4417 raise sa_exc.InvalidRequestError( 

4418 "Object '%s' already has an identity - " 

4419 "it can't be registered as pending" % state_str(state) 

4420 ) 

4421 

4422 obj = state.obj() 

4423 to_attach = self._before_attach(state, obj) 

4424 if state not in self._new: 

4425 self._new[state] = obj 

4426 state.insert_order = len(self._new) 

4427 if to_attach: 

4428 self._after_attach(state, obj) 

4429 

4430 def _update_impl( 

4431 self, state: InstanceState[Any], revert_deletion: bool = False 

4432 ) -> None: 

4433 if state.key is None: 

4434 raise sa_exc.InvalidRequestError( 

4435 "Instance '%s' is not persisted" % state_str(state) 

4436 ) 

4437 

4438 if state._deleted: 

4439 if revert_deletion: 

4440 if not state._attached: 

4441 return 

4442 del state._deleted 

4443 else: 

4444 raise sa_exc.InvalidRequestError( 

4445 "Instance '%s' has been deleted. " 

4446 "Use the make_transient() " 

4447 "function to send this object back " 

4448 "to the transient state." % state_str(state) 

4449 ) 

4450 

4451 obj = state.obj() 

4452 

4453 # check for late gc 

4454 if obj is None: 

4455 return 

4456 

4457 to_attach = self._before_attach(state, obj) 

4458 

4459 self._deleted.pop(state, None) 

4460 if revert_deletion: 

4461 self.identity_map.replace(state) 

4462 else: 

4463 self.identity_map.add(state) 

4464 

4465 if to_attach: 

4466 self._after_attach(state, obj) 

4467 elif revert_deletion: 

4468 self.dispatch.deleted_to_persistent(self, state) 

4469 

4470 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4471 if state.key is None: 

4472 self._save_impl(state) 

4473 else: 

4474 self._update_impl(state) 

4475 

4476 def enable_relationship_loading(self, obj: object) -> None: 

4477 """Associate an object with this :class:`.Session` for related 

4478 object loading. 

4479 

4480 .. warning:: 

4481 

4482 :meth:`.enable_relationship_loading` exists to serve special 

4483 use cases and is not recommended for general use. 

4484 

4485 Accesses of attributes mapped with :func:`_orm.relationship` 

4486 will attempt to load a value from the database using this 

4487 :class:`.Session` as the source of connectivity. The values 

4488 will be loaded based on foreign key and primary key values 

4489 present on this object - if not present, then those relationships 

4490 will be unavailable. 

4491 

4492 The object will be attached to this session, but will 

4493 **not** participate in any persistence operations; its state 

4494 for almost all purposes will remain either "transient" or 

4495 "detached", except for the case of relationship loading. 

4496 

4497 Also note that backrefs will often not work as expected. 

4498 Altering a relationship-bound attribute on the target object 

4499 may not fire off a backref event, if the effective value 

4500 is what was already loaded from a foreign-key-holding value. 

4501 

4502 The :meth:`.Session.enable_relationship_loading` method is 

4503 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4504 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4505 an object to remain transient while still being able to load 

4506 related items. 

4507 

4508 To make a transient object associated with a :class:`.Session` 

4509 via :meth:`.Session.enable_relationship_loading` pending, add 

4510 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4511 If the object instead represents an existing identity in the database, 

4512 it should be merged using :meth:`.Session.merge`. 

4513 

4514 :meth:`.Session.enable_relationship_loading` does not improve 

4515 behavior when the ORM is used normally - object references should be 

4516 constructed at the object level, not at the foreign key level, so 

4517 that they are present in an ordinary way before flush() 

4518 proceeds. This method is not intended for general use. 

4519 

4520 .. seealso:: 

4521 

4522 :paramref:`_orm.relationship.load_on_pending` - this flag 

4523 allows per-relationship loading of many-to-ones on items that 

4524 are pending. 

4525 

4526 :func:`.make_transient_to_detached` - allows for an object to 

4527 be added to a :class:`.Session` without SQL emitted, which then 

4528 will unexpire attributes on access. 

4529 

4530 """ 

4531 try: 

4532 state = attributes.instance_state(obj) 

4533 except exc.NO_STATE as err: 

4534 raise exc.UnmappedInstanceError(obj) from err 

4535 

4536 to_attach = self._before_attach(state, obj) 

4537 state._load_pending = True 

4538 if to_attach: 

4539 self._after_attach(state, obj) 

4540 

4541 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4542 self._autobegin_t() 

4543 

4544 if state.session_id == self.hash_key: 

4545 return False 

4546 

4547 if state.session_id and state.session_id in _sessions: 

4548 raise sa_exc.InvalidRequestError( 

4549 "Object '%s' is already attached to session '%s' " 

4550 "(this is '%s')" 

4551 % (state_str(state), state.session_id, self.hash_key) 

4552 ) 

4553 

4554 self.dispatch.before_attach(self, state) 

4555 

4556 return True 

4557 

4558 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4559 state.session_id = self.hash_key 

4560 if state.modified and state._strong_obj is None: 

4561 state._strong_obj = obj 

4562 self.dispatch.after_attach(self, state) 

4563 

4564 if state.key: 

4565 self.dispatch.detached_to_persistent(self, state) 

4566 else: 

4567 self.dispatch.transient_to_pending(self, state) 

4568 

4569 def __contains__(self, instance: object) -> bool: 

4570 """Return True if the instance is associated with this session. 

4571 

4572 The instance may be pending or persistent within the Session for a 

4573 result of True. 

4574 

4575 """ 

4576 try: 

4577 state = attributes.instance_state(instance) 

4578 except exc.NO_STATE as err: 

4579 raise exc.UnmappedInstanceError(instance) from err 

4580 return self._contains_state(state) 

4581 

4582 def __iter__(self) -> Iterator[object]: 

4583 """Iterate over all pending or persistent instances within this 

4584 Session. 

4585 

4586 """ 

4587 return iter( 

4588 list(self._new.values()) + list(self.identity_map.values()) 

4589 ) 

4590 

4591 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4592 return state in self._new or self.identity_map.contains_state(state) 

4593 

4594 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4595 """Flush all the object changes to the database. 

4596 

4597 Writes out all pending object creations, deletions and modifications 

4598 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4599 automatically ordered by the Session's unit of work dependency 

4600 solver. 

4601 

4602 Database operations will be issued in the current transactional 

4603 context and do not affect the state of the transaction, unless an 

4604 error occurs, in which case the entire transaction is rolled back. 

4605 You may flush() as often as you like within a transaction to move 

4606 changes from Python to the database's transaction buffer. 

4607 

4608 :param objects: Optional; restricts the flush operation to operate 

4609 only on elements that are in the given collection. 

4610 

4611 This feature is for an extremely narrow set of use cases where 

4612 particular objects may need to be operated upon before the 

4613 full flush() occurs. It is not intended for general use. 

4614 

4615 .. deprecated:: 2.1 

4616 

4617 """ 

4618 

4619 if self._flushing: 

4620 raise sa_exc.InvalidRequestError("Session is already flushing") 

4621 

4622 if self._is_clean(): 

4623 return 

4624 try: 

4625 self._flushing = True 

4626 self._flush(objects) 

4627 finally: 

4628 self._flushing = False 

4629 

4630 def _flush_warning(self, method: Any) -> None: 

4631 util.warn( 

4632 "Usage of the '%s' operation is not currently supported " 

4633 "within the execution stage of the flush process. " 

4634 "Results may not be consistent. Consider using alternative " 

4635 "event listeners or connection-level operations instead." % method 

4636 ) 

4637 

4638 def _is_clean(self) -> bool: 

4639 return ( 

4640 not self.identity_map.check_modified() 

4641 and not self._deleted 

4642 and not self._new 

4643 ) 

4644 

4645 # have this here since it otherwise causes issues with the proxy 

4646 # method generation 

4647 @deprecated_params( 

4648 objects=( 

4649 "2.1", 

4650 "The `objects` parameter of `Session.flush` is deprecated", 

4651 ) 

4652 ) 

4653 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4654 dirty = self._dirty_states 

4655 if not dirty and not self._deleted and not self._new: 

4656 self.identity_map._modified.clear() 

4657 return 

4658 

4659 flush_context = UOWTransaction(self) 

4660 

4661 if self.dispatch.before_flush: 

4662 self.dispatch.before_flush(self, flush_context, objects) 

4663 # re-establish "dirty states" in case the listeners 

4664 # added 

4665 dirty = self._dirty_states 

4666 

4667 deleted = set(self._deleted) 

4668 new = set(self._new) 

4669 

4670 dirty = set(dirty).difference(deleted) 

4671 

4672 # create the set of all objects we want to operate upon 

4673 if objects: 

4674 # specific list passed in 

4675 objset = set() 

4676 for o in objects: 

4677 try: 

4678 state = attributes.instance_state(o) 

4679 

4680 except exc.NO_STATE as err: 

4681 raise exc.UnmappedInstanceError(o) from err 

4682 objset.add(state) 

4683 else: 

4684 objset = None 

4685 

4686 # store objects whose fate has been decided 

4687 processed = set() 

4688 

4689 # put all saves/updates into the flush context. detect top-level 

4690 # orphans and throw them into deleted. 

4691 if objset: 

4692 proc = new.union(dirty).intersection(objset).difference(deleted) 

4693 else: 

4694 proc = new.union(dirty).difference(deleted) 

4695 

4696 for state in proc: 

4697 is_orphan = _state_mapper(state)._is_orphan(state) 

4698 

4699 is_persistent_orphan = is_orphan and state.has_identity 

4700 

4701 if ( 

4702 is_orphan 

4703 and not is_persistent_orphan 

4704 and state._orphaned_outside_of_session 

4705 ): 

4706 self._expunge_states([state]) 

4707 else: 

4708 _reg = flush_context.register_object( 

4709 state, isdelete=is_persistent_orphan 

4710 ) 

4711 assert _reg, "Failed to add object to the flush context!" 

4712 processed.add(state) 

4713 

4714 # put all remaining deletes into the flush context. 

4715 if objset: 

4716 proc = deleted.intersection(objset).difference(processed) 

4717 else: 

4718 proc = deleted.difference(processed) 

4719 for state in proc: 

4720 _reg = flush_context.register_object(state, isdelete=True) 

4721 assert _reg, "Failed to add object to the flush context!" 

4722 

4723 if not flush_context.has_work: 

4724 return 

4725 

4726 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4727 try: 

4728 self._warn_on_events = True 

4729 try: 

4730 flush_context.execute() 

4731 finally: 

4732 self._warn_on_events = False 

4733 

4734 self.dispatch.after_flush(self, flush_context) 

4735 

4736 flush_context.finalize_flush_changes() 

4737 

4738 if not objects and self.identity_map._modified: 

4739 len_ = len(self.identity_map._modified) 

4740 

4741 statelib.InstanceState._commit_all_states( 

4742 [ 

4743 (state, state.dict) 

4744 for state in self.identity_map._modified 

4745 ], 

4746 instance_dict=self.identity_map, 

4747 ) 

4748 util.warn( 

4749 "Attribute history events accumulated on %d " 

4750 "previously clean instances " 

4751 "within inner-flush event handlers have been " 

4752 "reset, and will not result in database updates. " 

4753 "Consider using set_committed_value() within " 

4754 "inner-flush event handlers to avoid this warning." % len_ 

4755 ) 

4756 

4757 # useful assertions: 

4758 # if not objects: 

4759 # assert not self.identity_map._modified 

4760 # else: 

4761 # assert self.identity_map._modified == \ 

4762 # self.identity_map._modified.difference(objects) 

4763 

4764 self.dispatch.after_flush_postexec(self, flush_context) 

4765 

4766 transaction.commit() 

4767 

4768 except: 

4769 with util.safe_reraise(): 

4770 transaction.rollback(_capture_exception=True) 

4771 

4772 def bulk_save_objects( 

4773 self, 

4774 objects: Iterable[object], 

4775 return_defaults: bool = False, 

4776 update_changed_only: bool = True, 

4777 preserve_order: bool = True, 

4778 ) -> None: 

4779 """Perform a bulk save of the given list of objects. 

4780 

4781 .. legacy:: 

4782 

4783 This method is a legacy feature as of the 2.0 series of 

4784 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4785 the sections :ref:`orm_queryguide_bulk_insert` and 

4786 :ref:`orm_queryguide_bulk_update`. 

4787 

4788 For general INSERT and UPDATE of existing ORM mapped objects, 

4789 prefer standard :term:`unit of work` data management patterns, 

4790 introduced in the :ref:`unified_tutorial` at 

4791 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4792 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4793 which solves previous issues of bulk INSERT slowness. 

4794 

4795 :param objects: a sequence of mapped object instances. The mapped 

4796 objects are persisted as is, and are **not** associated with the 

4797 :class:`.Session` afterwards. 

4798 

4799 For each object, whether the object is sent as an INSERT or an 

4800 UPDATE is dependent on the same rules used by the :class:`.Session` 

4801 in traditional operation; if the object has the 

4802 :attr:`.InstanceState.key` 

4803 attribute set, then the object is assumed to be "detached" and 

4804 will result in an UPDATE. Otherwise, an INSERT is used. 

4805 

4806 In the case of an UPDATE, statements are grouped based on which 

4807 attributes have changed, and are thus to be the subject of each 

4808 SET clause. If ``update_changed_only`` is False, then all 

4809 attributes present within each object are applied to the UPDATE 

4810 statement, which may help in allowing the statements to be grouped 

4811 together into a larger executemany(), and will also reduce the 

4812 overhead of checking history on attributes. 

4813 

4814 :param return_defaults: when True, rows that are missing values which 

4815 generate defaults, namely integer primary key defaults and sequences, 

4816 will be inserted **one at a time**, so that the primary key value 

4817 is available. In particular this will allow joined-inheritance 

4818 and other multi-table mappings to insert correctly without the need 

4819 to provide primary key values ahead of time; however, 

4820 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4821 reduces the performance gains** of the method overall. It is strongly 

4822 advised to please use the standard :meth:`_orm.Session.add_all` 

4823 approach. 

4824 

4825 :param update_changed_only: when True, UPDATE statements are rendered 

4826 based on those attributes in each state that have logged changes. 

4827 When False, all attributes present are rendered into the SET clause 

4828 with the exception of primary key attributes. 

4829 

4830 :param preserve_order: when True, the order of inserts and updates 

4831 matches exactly the order in which the objects are given. When 

4832 False, common types of objects are grouped into inserts 

4833 and updates, to allow for more batching opportunities. 

4834 

4835 .. seealso:: 

4836 

4837 :doc:`queryguide/dml` 

4838 

4839 :meth:`.Session.bulk_insert_mappings` 

4840 

4841 :meth:`.Session.bulk_update_mappings` 

4842 

4843 """ 

4844 

4845 obj_states: Iterable[InstanceState[Any]] 

4846 

4847 obj_states = (attributes.instance_state(obj) for obj in objects) 

4848 

4849 if not preserve_order: 

4850 # the purpose of this sort is just so that common mappers 

4851 # and persistence states are grouped together, so that groupby 

4852 # will return a single group for a particular type of mapper. 

4853 # it's not trying to be deterministic beyond that. 

4854 obj_states = sorted( 

4855 obj_states, 

4856 key=lambda state: (id(state.mapper), state.key is not None), 

4857 ) 

4858 

4859 def grouping_key( 

4860 state: InstanceState[_O], 

4861 ) -> Tuple[Mapper[_O], bool]: 

4862 return (state.mapper, state.key is not None) 

4863 

4864 for (mapper, isupdate), states in itertools.groupby( 

4865 obj_states, grouping_key 

4866 ): 

4867 self._bulk_save_mappings( 

4868 mapper, 

4869 states, 

4870 isupdate=isupdate, 

4871 isstates=True, 

4872 return_defaults=return_defaults, 

4873 update_changed_only=update_changed_only, 

4874 render_nulls=False, 

4875 ) 

4876 

4877 def bulk_insert_mappings( 

4878 self, 

4879 mapper: _EntityBindKey[Any], 

4880 mappings: Iterable[Dict[str, Any]], 

4881 return_defaults: bool = False, 

4882 render_nulls: bool = False, 

4883 ) -> None: 

4884 """Perform a bulk insert of the given list of mapping dictionaries. 

4885 

4886 .. legacy:: 

4887 

4888 This method is a legacy feature as of the 2.0 series of 

4889 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4890 the sections :ref:`orm_queryguide_bulk_insert` and 

4891 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4892 implementation details with this method and adds new features 

4893 as well. 

4894 

4895 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4896 object, 

4897 representing the single kind of object represented within the mapping 

4898 list. 

4899 

4900 :param mappings: a sequence of dictionaries, each one containing the 

4901 state of the mapped row to be inserted, in terms of the attribute 

4902 names on the mapped class. If the mapping refers to multiple tables, 

4903 such as a joined-inheritance mapping, each dictionary must contain all 

4904 keys to be populated into all tables. 

4905 

4906 :param return_defaults: when True, the INSERT process will be altered 

4907 to ensure that newly generated primary key values will be fetched. 

4908 The rationale for this parameter is typically to enable 

4909 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4910 be bulk inserted. 

4911 

4912 .. note:: for backends that don't support RETURNING, the 

4913 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4914 parameter can significantly decrease performance as INSERT 

4915 statements can no longer be batched. See 

4916 :ref:`engine_insertmanyvalues` 

4917 for background on which backends are affected. 

4918 

4919 :param render_nulls: When True, a value of ``None`` will result 

4920 in a NULL value being included in the INSERT statement, rather 

4921 than the column being omitted from the INSERT. This allows all 

4922 the rows being INSERTed to have the identical set of columns which 

4923 allows the full set of rows to be batched to the DBAPI. Normally, 

4924 each column-set that contains a different combination of NULL values 

4925 than the previous row must omit a different series of columns from 

4926 the rendered INSERT statement, which means it must be emitted as a 

4927 separate statement. By passing this flag, the full set of rows 

4928 are guaranteed to be batchable into one batch; the cost however is 

4929 that server-side defaults which are invoked by an omitted column will 

4930 be skipped, so care must be taken to ensure that these are not 

4931 necessary. 

4932 

4933 .. warning:: 

4934 

4935 When this flag is set, **server side default SQL values will 

4936 not be invoked** for those columns that are inserted as NULL; 

4937 the NULL value will be sent explicitly. Care must be taken 

4938 to ensure that no server-side default functions need to be 

4939 invoked for the operation as a whole. 

4940 

4941 .. seealso:: 

4942 

4943 :doc:`queryguide/dml` 

4944 

4945 :meth:`.Session.bulk_save_objects` 

4946 

4947 :meth:`.Session.bulk_update_mappings` 

4948 

4949 """ 

4950 self._bulk_save_mappings( 

4951 mapper, 

4952 mappings, 

4953 isupdate=False, 

4954 isstates=False, 

4955 return_defaults=return_defaults, 

4956 update_changed_only=False, 

4957 render_nulls=render_nulls, 

4958 ) 

4959 

4960 def bulk_update_mappings( 

4961 self, mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]] 

4962 ) -> None: 

4963 """Perform a bulk update of the given list of mapping dictionaries. 

4964 

4965 .. legacy:: 

4966 

4967 This method is a legacy feature as of the 2.0 series of 

4968 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4969 the sections :ref:`orm_queryguide_bulk_insert` and 

4970 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4971 implementation details with this method and adds new features 

4972 as well. 

4973 

4974 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4975 object, 

4976 representing the single kind of object represented within the mapping 

4977 list. 

4978 

4979 :param mappings: a sequence of dictionaries, each one containing the 

4980 state of the mapped row to be updated, in terms of the attribute names 

4981 on the mapped class. If the mapping refers to multiple tables, such 

4982 as a joined-inheritance mapping, each dictionary may contain keys 

4983 corresponding to all tables. All those keys which are present and 

4984 are not part of the primary key are applied to the SET clause of the 

4985 UPDATE statement; the primary key values, which are required, are 

4986 applied to the WHERE clause. 

4987 

4988 

4989 .. seealso:: 

4990 

4991 :doc:`queryguide/dml` 

4992 

4993 :meth:`.Session.bulk_insert_mappings` 

4994 

4995 :meth:`.Session.bulk_save_objects` 

4996 

4997 """ 

4998 self._bulk_save_mappings( 

4999 mapper, 

5000 mappings, 

5001 isupdate=True, 

5002 isstates=False, 

5003 return_defaults=False, 

5004 update_changed_only=False, 

5005 render_nulls=False, 

5006 ) 

5007 

5008 def _bulk_save_mappings( 

5009 self, 

5010 mapper: _EntityBindKey[_O], 

5011 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

5012 *, 

5013 isupdate: bool, 

5014 isstates: bool, 

5015 return_defaults: bool, 

5016 update_changed_only: bool, 

5017 render_nulls: bool, 

5018 ) -> None: 

5019 mapper = _class_to_mapper(mapper) 

5020 

5021 try: 

5022 self._flushing = True 

5023 

5024 transaction = self._autobegin_t()._begin() 

5025 try: 

5026 if isupdate: 

5027 bulk_persistence._bulk_update( 

5028 mapper, 

5029 mappings, 

5030 transaction, 

5031 isstates=isstates, 

5032 update_changed_only=update_changed_only, 

5033 ) 

5034 else: 

5035 bulk_persistence._bulk_insert( 

5036 mapper, 

5037 mappings, 

5038 transaction, 

5039 isstates=isstates, 

5040 return_defaults=return_defaults, 

5041 render_nulls=render_nulls, 

5042 ) 

5043 transaction.commit() 

5044 

5045 except: 

5046 with util.safe_reraise(): 

5047 transaction.rollback(_capture_exception=True) 

5048 finally: 

5049 self._flushing = False 

5050 

5051 def is_modified( 

5052 self, instance: object, include_collections: bool = True 

5053 ) -> bool: 

5054 r"""Return ``True`` if the given instance has locally 

5055 modified attributes. 

5056 

5057 This method retrieves the history for each instrumented 

5058 attribute on the instance and performs a comparison of the current 

5059 value to its previously flushed or committed value, if any. 

5060 

5061 It is in effect a more expensive and accurate 

5062 version of checking for the given instance in the 

5063 :attr:`.Session.dirty` collection; a full test for 

5064 each attribute's net "dirty" status is performed. 

5065 

5066 E.g.:: 

5067 

5068 return session.is_modified(someobject) 

5069 

5070 A few caveats to this method apply: 

5071 

5072 * Instances present in the :attr:`.Session.dirty` collection may 

5073 report ``False`` when tested with this method. This is because 

5074 the object may have received change events via attribute mutation, 

5075 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

5076 is the same as that loaded from the database, resulting in no net 

5077 change here. 

5078 * Scalar attributes may not have recorded the previously set 

5079 value when a new value was applied, if the attribute was not loaded, 

5080 or was expired, at the time the new value was received - in these 

5081 cases, the attribute is assumed to have a change, even if there is 

5082 ultimately no net change against its database value. SQLAlchemy in 

5083 most cases does not need the "old" value when a set event occurs, so 

5084 it skips the expense of a SQL call if the old value isn't present, 

5085 based on the assumption that an UPDATE of the scalar value is 

5086 usually needed, and in those few cases where it isn't, is less 

5087 expensive on average than issuing a defensive SELECT. 

5088 

5089 The "old" value is fetched unconditionally upon set only if the 

5090 attribute container has the ``active_history`` flag set to ``True``. 

5091 This flag is set typically for primary key attributes and scalar 

5092 object references that are not a simple many-to-one. To set this 

5093 flag for any arbitrary mapped column, use the ``active_history`` 

5094 argument with :func:`.column_property`. 

5095 

5096 :param instance: mapped instance to be tested for pending changes. 

5097 :param include_collections: Indicates if multivalued collections 

5098 should be included in the operation. Setting this to ``False`` is a 

5099 way to detect only local-column based properties (i.e. scalar columns 

5100 or many-to-one foreign keys) that would result in an UPDATE for this 

5101 instance upon flush. 

5102 

5103 """ 

5104 state = object_state(instance) 

5105 

5106 if not state.modified: 

5107 return False 

5108 

5109 dict_ = state.dict 

5110 

5111 for attr in state.manager.attributes: 

5112 if ( 

5113 not include_collections 

5114 and hasattr(attr.impl, "get_collection") 

5115 ) or not hasattr(attr.impl, "get_history"): 

5116 continue 

5117 

5118 added, unchanged, deleted = attr.impl.get_history( 

5119 state, dict_, passive=PassiveFlag.NO_CHANGE 

5120 ) 

5121 

5122 if added or deleted: 

5123 return True 

5124 else: 

5125 return False 

5126 

5127 @property 

5128 def is_active(self) -> bool: 

5129 """True if this :class:`.Session` not in "partial rollback" state. 

5130 

5131 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

5132 a new transaction immediately, so this attribute will be False 

5133 when the :class:`_orm.Session` is first instantiated. 

5134 

5135 "partial rollback" state typically indicates that the flush process 

5136 of the :class:`_orm.Session` has failed, and that the 

5137 :meth:`_orm.Session.rollback` method must be emitted in order to 

5138 fully roll back the transaction. 

5139 

5140 If this :class:`_orm.Session` is not in a transaction at all, the 

5141 :class:`_orm.Session` will autobegin when it is first used, so in this 

5142 case :attr:`_orm.Session.is_active` will return True. 

5143 

5144 Otherwise, if this :class:`_orm.Session` is within a transaction, 

5145 and that transaction has not been rolled back internally, the 

5146 :attr:`_orm.Session.is_active` will also return True. 

5147 

5148 .. seealso:: 

5149 

5150 :ref:`faq_session_rollback` 

5151 

5152 :meth:`_orm.Session.in_transaction` 

5153 

5154 """ 

5155 return self._transaction is None or self._transaction.is_active 

5156 

5157 @property 

5158 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

5159 """The set of all persistent states considered dirty. 

5160 

5161 This method returns all states that were modified including 

5162 those that were possibly deleted. 

5163 

5164 """ 

5165 return self.identity_map._dirty_states() 

5166 

5167 @property 

5168 def dirty(self) -> IdentitySet: 

5169 """The set of all persistent instances considered dirty. 

5170 

5171 E.g.:: 

5172 

5173 some_mapped_object in session.dirty 

5174 

5175 Instances are considered dirty when they were modified but not 

5176 deleted. 

5177 

5178 Note that this 'dirty' calculation is 'optimistic'; most 

5179 attribute-setting or collection modification operations will 

5180 mark an instance as 'dirty' and place it in this set, even if 

5181 there is no net change to the attribute's value. At flush 

5182 time, the value of each attribute is compared to its 

5183 previously saved value, and if there's no net change, no SQL 

5184 operation will occur (this is a more expensive operation so 

5185 it's only done at flush time). 

5186 

5187 To check if an instance has actionable net changes to its 

5188 attributes, use the :meth:`.Session.is_modified` method. 

5189 

5190 """ 

5191 return IdentitySet( 

5192 [ 

5193 state.obj() 

5194 for state in self._dirty_states 

5195 if state not in self._deleted 

5196 ] 

5197 ) 

5198 

5199 @property 

5200 def deleted(self) -> IdentitySet: 

5201 "The set of all instances marked as 'deleted' within this ``Session``" 

5202 

5203 return util.IdentitySet(list(self._deleted.values())) 

5204 

5205 @property 

5206 def new(self) -> IdentitySet: 

5207 "The set of all instances marked as 'new' within this ``Session``." 

5208 

5209 return util.IdentitySet(list(self._new.values())) 

5210 

5211 

5212_S = TypeVar("_S", bound="Session") 

5213 

5214 

5215class sessionmaker(_SessionClassMethods, Generic[_S]): 

5216 """A configurable :class:`.Session` factory. 

5217 

5218 The :class:`.sessionmaker` factory generates new 

5219 :class:`.Session` objects when called, creating them given 

5220 the configurational arguments established here. 

5221 

5222 e.g.:: 

5223 

5224 from sqlalchemy import create_engine 

5225 from sqlalchemy.orm import sessionmaker 

5226 

5227 # an Engine, which the Session will use for connection 

5228 # resources 

5229 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

5230 

5231 Session = sessionmaker(engine) 

5232 

5233 with Session() as session: 

5234 session.add(some_object) 

5235 session.add(some_other_object) 

5236 session.commit() 

5237 

5238 Context manager use is optional; otherwise, the returned 

5239 :class:`_orm.Session` object may be closed explicitly via the 

5240 :meth:`_orm.Session.close` method. Using a 

5241 ``try:/finally:`` block is optional, however will ensure that the close 

5242 takes place even if there are database errors:: 

5243 

5244 session = Session() 

5245 try: 

5246 session.add(some_object) 

5247 session.add(some_other_object) 

5248 session.commit() 

5249 finally: 

5250 session.close() 

5251 

5252 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5253 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5254 for :class:`_engine.Connection` objects. In this way it also includes 

5255 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5256 manager which both begins and commits a transaction, as well as closes 

5257 out the :class:`_orm.Session` when complete, rolling back the transaction 

5258 if any errors occur:: 

5259 

5260 Session = sessionmaker(engine) 

5261 

5262 with Session.begin() as session: 

5263 session.add(some_object) 

5264 session.add(some_other_object) 

5265 # commits transaction, closes session 

5266 

5267 .. versionadded:: 1.4 

5268 

5269 When calling upon :class:`_orm.sessionmaker` to construct a 

5270 :class:`_orm.Session`, keyword arguments may also be passed to the 

5271 method; these arguments will override that of the globally configured 

5272 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5273 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5274 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5275 

5276 Session = sessionmaker(engine) 

5277 

5278 # bind an individual session to a connection 

5279 

5280 with engine.connect() as connection: 

5281 with Session(bind=connection) as session: 

5282 ... # work with session 

5283 

5284 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5285 can be used to specify additional keyword arguments to the factory, which 

5286 will take effect for subsequent :class:`.Session` objects generated. This 

5287 is usually used to associate one or more :class:`_engine.Engine` objects 

5288 with an existing 

5289 :class:`.sessionmaker` factory before it is first used:: 

5290 

5291 # application starts, sessionmaker does not have 

5292 # an engine bound yet 

5293 Session = sessionmaker() 

5294 

5295 # ... later, when an engine URL is read from a configuration 

5296 # file or other events allow the engine to be created 

5297 engine = create_engine("sqlite:///foo.db") 

5298 Session.configure(bind=engine) 

5299 

5300 sess = Session() 

5301 # work with session 

5302 

5303 .. seealso:: 

5304 

5305 :ref:`session_getting` - introductory text on creating 

5306 sessions using :class:`.sessionmaker`. 

5307 

5308 """ 

5309 

5310 class_: Type[_S] 

5311 

5312 @overload 

5313 def __init__( 

5314 self, 

5315 bind: Optional[_SessionBind] = ..., 

5316 *, 

5317 class_: Type[_S], 

5318 autoflush: bool = ..., 

5319 expire_on_commit: bool = ..., 

5320 info: Optional[_InfoType] = ..., 

5321 **kw: Any, 

5322 ): ... 

5323 

5324 @overload 

5325 def __init__( 

5326 self: "sessionmaker[Session]", 

5327 bind: Optional[_SessionBind] = ..., 

5328 *, 

5329 autoflush: bool = ..., 

5330 expire_on_commit: bool = ..., 

5331 info: Optional[_InfoType] = ..., 

5332 **kw: Any, 

5333 ): ... 

5334 

5335 def __init__( 

5336 self, 

5337 bind: Optional[_SessionBind] = None, 

5338 *, 

5339 class_: Type[_S] = Session, # type: ignore[assignment] 

5340 autoflush: bool = True, 

5341 expire_on_commit: bool = True, 

5342 info: Optional[_InfoType] = None, 

5343 **kw: Any, 

5344 ): 

5345 r"""Construct a new :class:`.sessionmaker`. 

5346 

5347 All arguments here except for ``class_`` correspond to arguments 

5348 accepted by :class:`.Session` directly. See the 

5349 :meth:`.Session.__init__` docstring for more details on parameters. 

5350 

5351 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5352 with 

5353 which newly created :class:`.Session` objects will be associated. 

5354 :param class\_: class to use in order to create new :class:`.Session` 

5355 objects. Defaults to :class:`.Session`. 

5356 :param autoflush: The autoflush setting to use with newly created 

5357 :class:`.Session` objects. 

5358 

5359 .. seealso:: 

5360 

5361 :ref:`session_flushing` - additional background on autoflush 

5362 

5363 :param expire_on_commit=True: the 

5364 :paramref:`_orm.Session.expire_on_commit` setting to use 

5365 with newly created :class:`.Session` objects. 

5366 

5367 :param info: optional dictionary of information that will be available 

5368 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5369 replaced, when the ``info`` parameter is specified to the specific 

5370 :class:`.Session` construction operation. 

5371 

5372 :param \**kw: all other keyword arguments are passed to the 

5373 constructor of newly created :class:`.Session` objects. 

5374 

5375 """ 

5376 kw["bind"] = bind 

5377 kw["autoflush"] = autoflush 

5378 kw["expire_on_commit"] = expire_on_commit 

5379 if info is not None: 

5380 kw["info"] = info 

5381 self.kw = kw 

5382 # make our own subclass of the given class, so that 

5383 # events can be associated with it specifically. 

5384 self.class_ = type(class_.__name__, (class_,), {}) 

5385 

5386 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5387 """Produce a context manager that both provides a new 

5388 :class:`_orm.Session` as well as a transaction that commits. 

5389 

5390 

5391 e.g.:: 

5392 

5393 Session = sessionmaker(some_engine) 

5394 

5395 with Session.begin() as session: 

5396 session.add(some_object) 

5397 

5398 # commits transaction, closes session 

5399 

5400 .. versionadded:: 1.4 

5401 

5402 

5403 """ 

5404 

5405 session = self() 

5406 return session._maker_context_manager() 

5407 

5408 def __call__(self, **local_kw: Any) -> _S: 

5409 """Produce a new :class:`.Session` object using the configuration 

5410 established in this :class:`.sessionmaker`. 

5411 

5412 In Python, the ``__call__`` method is invoked on an object when 

5413 it is "called" in the same way as a function:: 

5414 

5415 Session = sessionmaker(some_engine) 

5416 session = Session() # invokes sessionmaker.__call__() 

5417 

5418 """ 

5419 for k, v in self.kw.items(): 

5420 if k == "info" and "info" in local_kw: 

5421 d = v.copy() 

5422 d.update(local_kw["info"]) 

5423 local_kw["info"] = d 

5424 else: 

5425 local_kw.setdefault(k, v) 

5426 return self.class_(**local_kw) 

5427 

5428 def configure(self, **new_kw: Any) -> None: 

5429 """(Re)configure the arguments for this sessionmaker. 

5430 

5431 e.g.:: 

5432 

5433 Session = sessionmaker() 

5434 

5435 Session.configure(bind=create_engine("sqlite://")) 

5436 """ 

5437 self.kw.update(new_kw) 

5438 

5439 def __repr__(self) -> str: 

5440 return "%s(class_=%r, %s)" % ( 

5441 self.__class__.__name__, 

5442 self.class_.__name__, 

5443 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5444 ) 

5445 

5446 

5447def close_all_sessions() -> None: 

5448 """Close all sessions in memory. 

5449 

5450 This function consults a global registry of all :class:`.Session` objects 

5451 and calls :meth:`.Session.close` on them, which resets them to a clean 

5452 state. 

5453 

5454 This function is not for general use but may be useful for test suites 

5455 within the teardown scheme. 

5456 

5457 """ 

5458 

5459 for sess in _sessions.values(): 

5460 sess.close() 

5461 

5462 

5463def make_transient(instance: object) -> None: 

5464 """Alter the state of the given instance so that it is :term:`transient`. 

5465 

5466 .. note:: 

5467 

5468 :func:`.make_transient` is a special-case function for 

5469 advanced use cases only. 

5470 

5471 The given mapped instance is assumed to be in the :term:`persistent` or 

5472 :term:`detached` state. The function will remove its association with any 

5473 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5474 effect is that the object will behave as though it were newly constructed, 

5475 except retaining any attribute / collection values that were loaded at the 

5476 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5477 if this object had been deleted as a result of using 

5478 :meth:`.Session.delete`. 

5479 

5480 .. warning:: 

5481 

5482 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5483 load ORM-mapped attributes that are not currently loaded at the time 

5484 the function is called. This includes attributes which: 

5485 

5486 * were expired via :meth:`.Session.expire` 

5487 

5488 * were expired as the natural effect of committing a session 

5489 transaction, e.g. :meth:`.Session.commit` 

5490 

5491 * are normally :term:`lazy loaded` but are not currently loaded 

5492 

5493 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5494 not yet loaded 

5495 

5496 * were not present in the query which loaded this object, such as that 

5497 which is common in joined table inheritance and other scenarios. 

5498 

5499 After :func:`.make_transient` is called, unloaded attributes such 

5500 as those above will normally resolve to the value ``None`` when 

5501 accessed, or an empty collection for a collection-oriented attribute. 

5502 As the object is transient and un-associated with any database 

5503 identity, it will no longer retrieve these values. 

5504 

5505 .. seealso:: 

5506 

5507 :func:`.make_transient_to_detached` 

5508 

5509 """ 

5510 state = attributes.instance_state(instance) 

5511 s = _state_session(state) 

5512 if s: 

5513 s._expunge_states([state]) 

5514 

5515 # remove expired state 

5516 state.expired_attributes.clear() 

5517 

5518 # remove deferred callables 

5519 if state.callables: 

5520 del state.callables 

5521 

5522 if state.key: 

5523 del state.key 

5524 if state._deleted: 

5525 del state._deleted 

5526 

5527 

5528def make_transient_to_detached(instance: object) -> None: 

5529 """Make the given transient instance :term:`detached`. 

5530 

5531 .. note:: 

5532 

5533 :func:`.make_transient_to_detached` is a special-case function for 

5534 advanced use cases only. 

5535 

5536 All attribute history on the given instance 

5537 will be reset as though the instance were freshly loaded 

5538 from a query. Missing attributes will be marked as expired. 

5539 The primary key attributes of the object, which are required, will be made 

5540 into the "key" of the instance. 

5541 

5542 The object can then be added to a session, or merged 

5543 possibly with the load=False flag, at which point it will look 

5544 as if it were loaded that way, without emitting SQL. 

5545 

5546 This is a special use case function that differs from a normal 

5547 call to :meth:`.Session.merge` in that a given persistent state 

5548 can be manufactured without any SQL calls. 

5549 

5550 .. seealso:: 

5551 

5552 :func:`.make_transient` 

5553 

5554 :meth:`.Session.enable_relationship_loading` 

5555 

5556 """ 

5557 state = attributes.instance_state(instance) 

5558 if state.session_id or state.key: 

5559 raise sa_exc.InvalidRequestError("Given object must be transient") 

5560 state.key = state.mapper._identity_key_from_state(state) 

5561 if state._deleted: 

5562 del state._deleted 

5563 state._commit_all(state.dict) 

5564 state._expire_attributes(state.dict, state.unloaded) 

5565 

5566 

5567def object_session(instance: object) -> Optional[Session]: 

5568 """Return the :class:`.Session` to which the given instance belongs. 

5569 

5570 This is essentially the same as the :attr:`.InstanceState.session` 

5571 accessor. See that attribute for details. 

5572 

5573 """ 

5574 

5575 try: 

5576 state = attributes.instance_state(instance) 

5577 except exc.NO_STATE as err: 

5578 raise exc.UnmappedInstanceError(instance) from err 

5579 else: 

5580 return _state_session(state) 

5581 

5582 

5583_new_sessionid = util.counter()