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

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

1440 statements  

1# orm/session.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

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

9 

10from __future__ import annotations 

11 

12import contextlib 

13from enum import Enum 

14import itertools 

15import sys 

16import typing 

17from typing import Any 

18from typing import Callable 

19from typing import cast 

20from typing import Dict 

21from typing import Generic 

22from typing import Iterable 

23from typing import Iterator 

24from typing import List 

25from typing import NoReturn 

26from typing import Optional 

27from typing import overload 

28from typing import Sequence 

29from typing import Set 

30from typing import Tuple 

31from typing import Type 

32from typing import TYPE_CHECKING 

33from typing import TypeVar 

34from typing import Union 

35import weakref 

36 

37from . import attributes 

38from . import bulk_persistence 

39from . import context 

40from . import descriptor_props 

41from . import exc 

42from . import identity 

43from . import loading 

44from . import query 

45from . import state as statelib 

46from ._typing import _O 

47from ._typing import insp_is_mapper 

48from ._typing import is_composite_class 

49from ._typing import is_orm_option 

50from ._typing import is_user_defined_option 

51from .base import _class_to_mapper 

52from .base import _none_set 

53from .base import _state_mapper 

54from .base import instance_str 

55from .base import LoaderCallableStatus 

56from .base import object_mapper 

57from .base import object_state 

58from .base import PassiveFlag 

59from .base import state_str 

60from .context import FromStatement 

61from .context import ORMCompileState 

62from .identity import IdentityMap 

63from .query import Query 

64from .state import InstanceState 

65from .state_changes import _StateChange 

66from .state_changes import _StateChangeState 

67from .state_changes import _StateChangeStates 

68from .unitofwork import UOWTransaction 

69from .. import engine 

70from .. import exc as sa_exc 

71from .. import sql 

72from .. import util 

73from ..engine import Connection 

74from ..engine import Engine 

75from ..engine.util import TransactionalContext 

76from ..event import dispatcher 

77from ..event import EventTarget 

78from ..inspection import inspect 

79from ..inspection import Inspectable 

80from ..sql import coercions 

81from ..sql import dml 

82from ..sql import roles 

83from ..sql import Select 

84from ..sql import TableClause 

85from ..sql import visitors 

86from ..sql.base import _NoArg 

87from ..sql.base import CompileState 

88from ..sql.schema import Table 

89from ..sql.selectable import ForUpdateArg 

90from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

91from ..util import IdentitySet 

92from ..util.typing import Literal 

93from ..util.typing import Protocol 

94 

95if typing.TYPE_CHECKING: 

96 from ._typing import _EntityType 

97 from ._typing import _IdentityKeyType 

98 from ._typing import _InstanceDict 

99 from ._typing import OrmExecuteOptionsParameter 

100 from .interfaces import ORMOption 

101 from .interfaces import UserDefinedOption 

102 from .mapper import Mapper 

103 from .path_registry import PathRegistry 

104 from .query import RowReturningQuery 

105 from ..engine import Result 

106 from ..engine import Row 

107 from ..engine import RowMapping 

108 from ..engine.base import Transaction 

109 from ..engine.base import TwoPhaseTransaction 

110 from ..engine.interfaces import _CoreAnyExecuteParams 

111 from ..engine.interfaces import _CoreSingleExecuteParams 

112 from ..engine.interfaces import _ExecuteOptions 

113 from ..engine.interfaces import CoreExecuteOptionsParameter 

114 from ..engine.result import ScalarResult 

115 from ..event import _InstanceLevelDispatch 

116 from ..sql._typing import _ColumnsClauseArgument 

117 from ..sql._typing import _InfoType 

118 from ..sql._typing import _T0 

119 from ..sql._typing import _T1 

120 from ..sql._typing import _T2 

121 from ..sql._typing import _T3 

122 from ..sql._typing import _T4 

123 from ..sql._typing import _T5 

124 from ..sql._typing import _T6 

125 from ..sql._typing import _T7 

126 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

127 from ..sql.base import Executable 

128 from ..sql.base import ExecutableOption 

129 from ..sql.elements import ClauseElement 

130 from ..sql.roles import TypedColumnsClauseRole 

131 from ..sql.selectable import ForUpdateParameter 

132 from ..sql.selectable import TypedReturnsRows 

133 

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

135 

136__all__ = [ 

137 "Session", 

138 "SessionTransaction", 

139 "sessionmaker", 

140 "ORMExecuteState", 

141 "close_all_sessions", 

142 "make_transient", 

143 "make_transient_to_detached", 

144 "object_session", 

145] 

146 

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

148 weakref.WeakValueDictionary() 

149) 

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

151""" 

152 

153statelib._sessions = _sessions 

154 

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

156 

157_BindArguments = Dict[str, Any] 

158 

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

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

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

162 

163JoinTransactionMode = Literal[ 

164 "conditional_savepoint", 

165 "rollback_only", 

166 "control_fully", 

167 "create_savepoint", 

168] 

169 

170 

171class _ConnectionCallableProto(Protocol): 

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

173 

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

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

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

177 as persistence time. 

178 

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

180 is established when using the horizontal sharding extension. 

181 

182 """ 

183 

184 def __call__( 

185 self, 

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

187 instance: Optional[object] = None, 

188 **kw: Any, 

189 ) -> Connection: ... 

190 

191 

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

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

194 associated, if any. 

195 """ 

196 return state.session 

197 

198 

199class _SessionClassMethods: 

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

201 

202 @classmethod 

203 @util.deprecated( 

204 "1.3", 

205 "The :meth:`.Session.close_all` method is deprecated and will be " 

206 "removed in a future release. Please refer to " 

207 ":func:`.session.close_all_sessions`.", 

208 ) 

209 def close_all(cls) -> None: 

210 """Close *all* sessions in memory.""" 

211 

212 close_all_sessions() 

213 

214 @classmethod 

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

216 def identity_key( 

217 cls, 

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

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

220 *, 

221 instance: Optional[Any] = None, 

222 row: Optional[Union[Row[Any], RowMapping]] = None, 

223 identity_token: Optional[Any] = None, 

224 ) -> _IdentityKeyType[Any]: 

225 """Return an identity key. 

226 

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

228 

229 """ 

230 return util.preloaded.orm_util.identity_key( 

231 class_, 

232 ident, 

233 instance=instance, 

234 row=row, 

235 identity_token=identity_token, 

236 ) 

237 

238 @classmethod 

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

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

241 

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

243 

244 """ 

245 

246 return object_session(instance) 

247 

248 

249class SessionTransactionState(_StateChangeState): 

250 ACTIVE = 1 

251 PREPARED = 2 

252 COMMITTED = 3 

253 DEACTIVE = 4 

254 CLOSED = 5 

255 PROVISIONING_CONNECTION = 6 

256 

257 

258# backwards compatibility 

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

260 SessionTransactionState 

261) 

262 

263 

264class ORMExecuteState(util.MemoizedSlots): 

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

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

267 

268 .. versionadded:: 1.4 

269 

270 .. seealso:: 

271 

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

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

274 

275 """ 

276 

277 __slots__ = ( 

278 "session", 

279 "statement", 

280 "parameters", 

281 "execution_options", 

282 "local_execution_options", 

283 "bind_arguments", 

284 "identity_token", 

285 "_compile_state_cls", 

286 "_starting_event_idx", 

287 "_events_todo", 

288 "_update_execution_options", 

289 ) 

290 

291 session: Session 

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

293 

294 statement: Executable 

295 """The SQL statement being invoked. 

296 

297 For an ORM selection as would 

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

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

300 """ 

301 

302 parameters: Optional[_CoreAnyExecuteParams] 

303 """Dictionary of parameters that was passed to 

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

305 

306 execution_options: _ExecuteOptions 

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

308 

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

310 locally passed execution options. 

311 

312 .. seealso:: 

313 

314 :attr:`_orm.ORMExecuteState.local_execution_options` 

315 

316 :meth:`_sql.Executable.execution_options` 

317 

318 :ref:`orm_queryguide_execution_options` 

319 

320 """ 

321 

322 local_execution_options: _ExecuteOptions 

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

324 :meth:`.Session.execute` method. 

325 

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

327 being invoked. 

328 

329 .. seealso:: 

330 

331 :attr:`_orm.ORMExecuteState.execution_options` 

332 

333 """ 

334 

335 bind_arguments: _BindArguments 

336 """The dictionary passed as the 

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

338 

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

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

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

342 

343 """ 

344 

345 _compile_state_cls: Optional[Type[ORMCompileState]] 

346 _starting_event_idx: int 

347 _events_todo: List[Any] 

348 _update_execution_options: _ExecuteOptions 

349 

350 def __init__( 

351 self, 

352 session: Session, 

353 statement: Executable, 

354 parameters: Optional[_CoreAnyExecuteParams], 

355 execution_options: _ExecuteOptions, 

356 bind_arguments: _BindArguments, 

357 compile_state_cls: Optional[Type[ORMCompileState]], 

358 events_todo: List[_InstanceLevelDispatch[Session]], 

359 ): 

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

361 

362 this object is constructed internally. 

363 

364 """ 

365 self.session = session 

366 self.statement = statement 

367 self.parameters = parameters 

368 self.local_execution_options = execution_options 

369 self.execution_options = statement._execution_options.union( 

370 execution_options 

371 ) 

372 self.bind_arguments = bind_arguments 

373 self._compile_state_cls = compile_state_cls 

374 self._events_todo = list(events_todo) 

375 self._update_execution_options = util.EMPTY_DICT 

376 

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

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

379 

380 def invoke_statement( 

381 self, 

382 statement: Optional[Executable] = None, 

383 params: Optional[_CoreAnyExecuteParams] = None, 

384 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

385 bind_arguments: Optional[_BindArguments] = None, 

386 ) -> Result[Any]: 

387 """Execute the statement represented by this 

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

389 already proceeded. 

390 

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

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

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

394 that want to override how the ultimate 

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

396 retrieve results from an offline cache or which concatenate results 

397 from multiple executions. 

398 

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

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

401 is propagated to the calling 

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

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

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

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

406 

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

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

409 

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

411 which will be merged into the existing 

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

413 

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

415 for executemany executions. 

416 

417 :param execution_options: optional dictionary of execution options 

418 will be merged into the existing 

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

420 :class:`.ORMExecuteState`. 

421 

422 :param bind_arguments: optional dictionary of bind_arguments 

423 which will be merged amongst the current 

424 :attr:`.ORMExecuteState.bind_arguments` 

425 of this :class:`.ORMExecuteState`. 

426 

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

428 

429 .. seealso:: 

430 

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

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

433 

434 

435 """ 

436 

437 if statement is None: 

438 statement = self.statement 

439 

440 _bind_arguments = dict(self.bind_arguments) 

441 if bind_arguments: 

442 _bind_arguments.update(bind_arguments) 

443 _bind_arguments["_sa_skip_events"] = True 

444 

445 _params: Optional[_CoreAnyExecuteParams] 

446 if params: 

447 if self.is_executemany: 

448 _params = [] 

449 exec_many_parameters = cast( 

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

451 ) 

452 for _existing_params, _new_params in itertools.zip_longest( 

453 exec_many_parameters, 

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

455 ): 

456 if _existing_params is None or _new_params is None: 

457 raise sa_exc.InvalidRequestError( 

458 f"Can't apply executemany parameters to " 

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

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

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

462 f"to ORMExecuteState.invoke_statement() " 

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

464 ) 

465 _existing_params = dict(_existing_params) 

466 _existing_params.update(_new_params) 

467 _params.append(_existing_params) 

468 else: 

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

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

471 else: 

472 _params = self.parameters 

473 

474 _execution_options = self.local_execution_options 

475 if execution_options: 

476 _execution_options = _execution_options.union(execution_options) 

477 

478 return self.session._execute_internal( 

479 statement, 

480 _params, 

481 execution_options=_execution_options, 

482 bind_arguments=_bind_arguments, 

483 _parent_execute_state=self, 

484 ) 

485 

486 @property 

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

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

489 

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

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

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

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

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

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

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

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

498 would be selected. 

499 

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

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

502 way of getting this mapper. 

503 

504 .. versionadded:: 1.4.0b2 

505 

506 .. seealso:: 

507 

508 :attr:`_orm.ORMExecuteState.all_mappers` 

509 

510 

511 """ 

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

513 return mp 

514 

515 @property 

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

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

518 involved at the top level of this statement. 

519 

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

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

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

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

524 

525 .. versionadded:: 1.4.0b2 

526 

527 .. seealso:: 

528 

529 :attr:`_orm.ORMExecuteState.bind_mapper` 

530 

531 

532 

533 """ 

534 if not self.is_orm_statement: 

535 return [] 

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

537 result = [] 

538 seen = set() 

539 for d in self.statement.column_descriptions: 

540 ent = d["entity"] 

541 if ent: 

542 insp = inspect(ent, raiseerr=False) 

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

544 seen.add(insp.mapper) 

545 result.append(insp.mapper) 

546 return result 

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

548 return [self.bind_mapper] 

549 else: 

550 return [] 

551 

552 @property 

553 def is_orm_statement(self) -> bool: 

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

555 

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

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

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

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

560 and no ORM-level automation takes place. 

561 

562 """ 

563 return self._compile_state_cls is not None 

564 

565 @property 

566 def is_executemany(self) -> bool: 

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

568 dictionaries with more than one dictionary. 

569 

570 .. versionadded:: 2.0 

571 

572 """ 

573 return isinstance(self.parameters, list) 

574 

575 @property 

576 def is_select(self) -> bool: 

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

578 

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

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

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

582 ``select(Entity).from_statement(select(..))`` 

583 

584 """ 

585 return self.statement.is_select 

586 

587 @property 

588 def is_from_statement(self) -> bool: 

589 """return True if this operation is a 

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

591 

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

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

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

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

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

597 :class:`_sql.Select` construct. 

598 

599 .. versionadded:: 2.0.30 

600 

601 """ 

602 return self.statement.is_from_statement 

603 

604 @property 

605 def is_insert(self) -> bool: 

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

607 

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

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

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

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

612 

613 """ 

614 return self.statement.is_dml and self.statement.is_insert 

615 

616 @property 

617 def is_update(self) -> bool: 

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

619 

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

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

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

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

624 

625 """ 

626 return self.statement.is_dml and self.statement.is_update 

627 

628 @property 

629 def is_delete(self) -> bool: 

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

631 

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

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

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

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

636 

637 """ 

638 return self.statement.is_dml and self.statement.is_delete 

639 

640 @property 

641 def _is_crud(self) -> bool: 

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

643 

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

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

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

647 self._update_execution_options = self._update_execution_options.union( 

648 opts 

649 ) 

650 

651 def _orm_compile_options( 

652 self, 

653 ) -> Optional[ 

654 Union[ 

655 context.ORMCompileState.default_compile_options, 

656 Type[context.ORMCompileState.default_compile_options], 

657 ] 

658 ]: 

659 if not self.is_select: 

660 return None 

661 try: 

662 opts = self.statement._compile_options 

663 except AttributeError: 

664 return None 

665 

666 if opts is not None and opts.isinstance( 

667 context.ORMCompileState.default_compile_options 

668 ): 

669 return opts # type: ignore 

670 else: 

671 return None 

672 

673 @property 

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

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

676 for a lazy load operation. 

677 

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

679 sharding extension, where it is available within specific query 

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

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

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

683 compilation time. 

684 

685 """ 

686 return self.load_options._lazy_loaded_from 

687 

688 @property 

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

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

691 

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

693 when a particular object or collection is being loaded. 

694 

695 """ 

696 opts = self._orm_compile_options() 

697 if opts is not None: 

698 return opts._current_path 

699 else: 

700 return None 

701 

702 @property 

703 def is_column_load(self) -> bool: 

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

705 attributes on an existing ORM object. 

706 

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

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

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

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

711 loaded. 

712 

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

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

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

716 and loader options travelling with the instance 

717 will have already been added to the query. 

718 

719 .. versionadded:: 1.4.0b2 

720 

721 .. seealso:: 

722 

723 :attr:`_orm.ORMExecuteState.is_relationship_load` 

724 

725 """ 

726 opts = self._orm_compile_options() 

727 return opts is not None and opts._for_refresh_state 

728 

729 @property 

730 def is_relationship_load(self) -> bool: 

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

732 relationship. 

733 

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

735 SelectInLoader, SubqueryLoader, or similar, and the entire 

736 SELECT statement being emitted is on behalf of a relationship 

737 load. 

738 

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

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

741 capable of being propagated to relationship loaders and should 

742 be already present. 

743 

744 .. seealso:: 

745 

746 :attr:`_orm.ORMExecuteState.is_column_load` 

747 

748 """ 

749 opts = self._orm_compile_options() 

750 if opts is None: 

751 return False 

752 path = self.loader_strategy_path 

753 return path is not None and not path.is_root 

754 

755 @property 

756 def load_options( 

757 self, 

758 ) -> Union[ 

759 context.QueryContext.default_load_options, 

760 Type[context.QueryContext.default_load_options], 

761 ]: 

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

763 

764 if not self.is_select: 

765 raise sa_exc.InvalidRequestError( 

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

767 "so there are no load options." 

768 ) 

769 

770 lo: Union[ 

771 context.QueryContext.default_load_options, 

772 Type[context.QueryContext.default_load_options], 

773 ] = self.execution_options.get( 

774 "_sa_orm_load_options", context.QueryContext.default_load_options 

775 ) 

776 return lo 

777 

778 @property 

779 def update_delete_options( 

780 self, 

781 ) -> Union[ 

782 bulk_persistence.BulkUDCompileState.default_update_options, 

783 Type[bulk_persistence.BulkUDCompileState.default_update_options], 

784 ]: 

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

786 execution.""" 

787 

788 if not self._is_crud: 

789 raise sa_exc.InvalidRequestError( 

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

791 "statement so there are no update options." 

792 ) 

793 uo: Union[ 

794 bulk_persistence.BulkUDCompileState.default_update_options, 

795 Type[bulk_persistence.BulkUDCompileState.default_update_options], 

796 ] = self.execution_options.get( 

797 "_sa_orm_update_options", 

798 bulk_persistence.BulkUDCompileState.default_update_options, 

799 ) 

800 return uo 

801 

802 @property 

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

804 return [ 

805 opt 

806 for opt in self.statement._with_options 

807 if is_orm_option(opt) and not opt._is_compile_state 

808 ] 

809 

810 @property 

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

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

813 associated with the statement being invoked. 

814 

815 """ 

816 return [ 

817 opt 

818 for opt in self.statement._with_options 

819 if is_user_defined_option(opt) 

820 ] 

821 

822 

823class SessionTransactionOrigin(Enum): 

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

825 

826 This enumeration is present on the 

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

828 :class:`.SessionTransaction` object. 

829 

830 .. versionadded:: 2.0 

831 

832 """ 

833 

834 AUTOBEGIN = 0 

835 """transaction were started by autobegin""" 

836 

837 BEGIN = 1 

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

839 

840 BEGIN_NESTED = 2 

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

842 

843 SUBTRANSACTION = 3 

844 """transaction is an internal "subtransaction" """ 

845 

846 

847class SessionTransaction(_StateChange, TransactionalContext): 

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

849 

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

851 :meth:`_orm.Session.begin` 

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

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

854 transactions. 

855 

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

857 at: :ref:`unitofwork_transaction`. 

858 

859 

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

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

862 

863 .. seealso:: 

864 

865 :ref:`unitofwork_transaction` 

866 

867 :meth:`.Session.begin` 

868 

869 :meth:`.Session.begin_nested` 

870 

871 :meth:`.Session.rollback` 

872 

873 :meth:`.Session.commit` 

874 

875 :meth:`.Session.in_transaction` 

876 

877 :meth:`.Session.in_nested_transaction` 

878 

879 :meth:`.Session.get_transaction` 

880 

881 :meth:`.Session.get_nested_transaction` 

882 

883 

884 """ 

885 

886 _rollback_exception: Optional[BaseException] = None 

887 

888 _connections: Dict[ 

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

890 ] 

891 session: Session 

892 _parent: Optional[SessionTransaction] 

893 

894 _state: SessionTransactionState 

895 

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

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

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

899 _key_switches: weakref.WeakKeyDictionary[ 

900 InstanceState[Any], Tuple[Any, Any] 

901 ] 

902 

903 origin: SessionTransactionOrigin 

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

905 

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

907 enumeration indicating the source event that led to constructing 

908 this :class:`_orm.SessionTransaction`. 

909 

910 .. versionadded:: 2.0 

911 

912 """ 

913 

914 nested: bool = False 

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

916 

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

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

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

920 

921 .. seealso:: 

922 

923 :attr:`.SessionTransaction.origin` 

924 

925 """ 

926 

927 def __init__( 

928 self, 

929 session: Session, 

930 origin: SessionTransactionOrigin, 

931 parent: Optional[SessionTransaction] = None, 

932 ): 

933 TransactionalContext._trans_ctx_check(session) 

934 

935 self.session = session 

936 self._connections = {} 

937 self._parent = parent 

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

939 self.origin = origin 

940 

941 if session._close_state is _SessionCloseState.CLOSED: 

942 raise sa_exc.InvalidRequestError( 

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

944 "to handle any more transaction requests." 

945 ) 

946 

947 if nested: 

948 if not parent: 

949 raise sa_exc.InvalidRequestError( 

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

951 "transaction is in progress" 

952 ) 

953 

954 self._previous_nested_transaction = session._nested_transaction 

955 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

956 assert parent is not None 

957 else: 

958 assert parent is None 

959 

960 self._state = SessionTransactionState.ACTIVE 

961 

962 self._take_snapshot() 

963 

964 # make sure transaction is assigned before we call the 

965 # dispatch 

966 self.session._transaction = self 

967 

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

969 

970 def _raise_for_prerequisite_state( 

971 self, operation_name: str, state: _StateChangeState 

972 ) -> NoReturn: 

973 if state is SessionTransactionState.DEACTIVE: 

974 if self._rollback_exception: 

975 raise sa_exc.PendingRollbackError( 

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

977 "due to a previous exception during flush." 

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

979 "first issue Session.rollback()." 

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

981 code="7s2a", 

982 ) 

983 else: 

984 raise sa_exc.InvalidRequestError( 

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

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

987 "can be emitted within this transaction." 

988 ) 

989 elif state is SessionTransactionState.CLOSED: 

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

991 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

992 raise sa_exc.InvalidRequestError( 

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

994 "operations are not permitted", 

995 code="isce", 

996 ) 

997 else: 

998 raise sa_exc.InvalidRequestError( 

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

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

1001 ) 

1002 

1003 @property 

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

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

1006 :class:`.SessionTransaction`. 

1007 

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

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

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

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

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

1013 "nested" / SAVEPOINT transaction. If the 

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

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

1016 

1017 """ 

1018 return self._parent 

1019 

1020 @property 

1021 def is_active(self) -> bool: 

1022 return ( 

1023 self.session is not None 

1024 and self._state is SessionTransactionState.ACTIVE 

1025 ) 

1026 

1027 @property 

1028 def _is_transaction_boundary(self) -> bool: 

1029 return self.nested or not self._parent 

1030 

1031 @_StateChange.declare_states( 

1032 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1033 ) 

1034 def connection( 

1035 self, 

1036 bindkey: Optional[Mapper[Any]], 

1037 execution_options: Optional[_ExecuteOptions] = None, 

1038 **kwargs: Any, 

1039 ) -> Connection: 

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

1041 return self._connection_for_bind(bind, execution_options) 

1042 

1043 @_StateChange.declare_states( 

1044 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1045 ) 

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

1047 return SessionTransaction( 

1048 self.session, 

1049 ( 

1050 SessionTransactionOrigin.BEGIN_NESTED 

1051 if nested 

1052 else SessionTransactionOrigin.SUBTRANSACTION 

1053 ), 

1054 self, 

1055 ) 

1056 

1057 def _iterate_self_and_parents( 

1058 self, upto: Optional[SessionTransaction] = None 

1059 ) -> Iterable[SessionTransaction]: 

1060 current = self 

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

1062 while current: 

1063 result += (current,) 

1064 if current._parent is upto: 

1065 break 

1066 elif current._parent is None: 

1067 raise sa_exc.InvalidRequestError( 

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

1069 % (upto) 

1070 ) 

1071 else: 

1072 current = current._parent 

1073 

1074 return result 

1075 

1076 def _take_snapshot(self) -> None: 

1077 if not self._is_transaction_boundary: 

1078 parent = self._parent 

1079 assert parent is not None 

1080 self._new = parent._new 

1081 self._deleted = parent._deleted 

1082 self._dirty = parent._dirty 

1083 self._key_switches = parent._key_switches 

1084 return 

1085 

1086 is_begin = self.origin in ( 

1087 SessionTransactionOrigin.BEGIN, 

1088 SessionTransactionOrigin.AUTOBEGIN, 

1089 ) 

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

1091 self.session.flush() 

1092 

1093 self._new = weakref.WeakKeyDictionary() 

1094 self._deleted = weakref.WeakKeyDictionary() 

1095 self._dirty = weakref.WeakKeyDictionary() 

1096 self._key_switches = weakref.WeakKeyDictionary() 

1097 

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

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

1100 

1101 Corresponds to a rollback. 

1102 

1103 """ 

1104 assert self._is_transaction_boundary 

1105 

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

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

1108 

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

1110 # we probably can do this conditionally based on 

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

1112 self.session.identity_map.safe_discard(s) 

1113 

1114 # restore the old key 

1115 s.key = oldkey 

1116 

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

1118 if s not in to_expunge: 

1119 self.session.identity_map.replace(s) 

1120 

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

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

1123 

1124 assert not self.session._deleted 

1125 

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

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

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

1129 

1130 def _remove_snapshot(self) -> None: 

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

1132 

1133 Corresponds to a commit. 

1134 

1135 """ 

1136 assert self._is_transaction_boundary 

1137 

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

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

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

1141 

1142 statelib.InstanceState._detach_states( 

1143 list(self._deleted), self.session 

1144 ) 

1145 self._deleted.clear() 

1146 elif self.nested: 

1147 parent = self._parent 

1148 assert parent is not None 

1149 parent._new.update(self._new) 

1150 parent._dirty.update(self._dirty) 

1151 parent._deleted.update(self._deleted) 

1152 parent._key_switches.update(self._key_switches) 

1153 

1154 @_StateChange.declare_states( 

1155 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1156 ) 

1157 def _connection_for_bind( 

1158 self, 

1159 bind: _SessionBind, 

1160 execution_options: Optional[CoreExecuteOptionsParameter], 

1161 ) -> Connection: 

1162 if bind in self._connections: 

1163 if execution_options: 

1164 util.warn( 

1165 "Connection is already established for the " 

1166 "given bind; execution_options ignored" 

1167 ) 

1168 return self._connections[bind][0] 

1169 

1170 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1171 

1172 local_connect = False 

1173 should_commit = True 

1174 

1175 try: 

1176 if self._parent: 

1177 conn = self._parent._connection_for_bind( 

1178 bind, execution_options 

1179 ) 

1180 if not self.nested: 

1181 return conn 

1182 else: 

1183 if isinstance(bind, engine.Connection): 

1184 conn = bind 

1185 if conn.engine in self._connections: 

1186 raise sa_exc.InvalidRequestError( 

1187 "Session already has a Connection associated " 

1188 "for the given Connection's Engine" 

1189 ) 

1190 else: 

1191 conn = bind.connect() 

1192 local_connect = True 

1193 

1194 try: 

1195 if execution_options: 

1196 conn = conn.execution_options(**execution_options) 

1197 

1198 transaction: Transaction 

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

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

1201 # conn.in_transaction() ? 

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

1203 # that it is in fact twophase. 

1204 transaction = conn.begin_twophase() 

1205 elif self.nested: 

1206 transaction = conn.begin_nested() 

1207 elif conn.in_transaction(): 

1208 join_transaction_mode = self.session.join_transaction_mode 

1209 

1210 if join_transaction_mode == "conditional_savepoint": 

1211 if conn.in_nested_transaction(): 

1212 join_transaction_mode = "create_savepoint" 

1213 else: 

1214 join_transaction_mode = "rollback_only" 

1215 

1216 if local_connect: 

1217 util.warn( 

1218 "The engine provided as bind produced a " 

1219 "connection that is already in a transaction. " 

1220 "This is usually caused by a core event, " 

1221 "such as 'engine_connect', that has left a " 

1222 "transaction open. The effective join " 

1223 "transaction mode used by this session is " 

1224 f"{join_transaction_mode!r}. To silence this " 

1225 "warning, do not leave transactions open" 

1226 ) 

1227 if join_transaction_mode in ( 

1228 "control_fully", 

1229 "rollback_only", 

1230 ): 

1231 if conn.in_nested_transaction(): 

1232 transaction = ( 

1233 conn._get_required_nested_transaction() 

1234 ) 

1235 else: 

1236 transaction = conn._get_required_transaction() 

1237 if join_transaction_mode == "rollback_only": 

1238 should_commit = False 

1239 elif join_transaction_mode == "create_savepoint": 

1240 transaction = conn.begin_nested() 

1241 else: 

1242 assert False, join_transaction_mode 

1243 else: 

1244 transaction = conn.begin() 

1245 except: 

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

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

1248 if local_connect: 

1249 conn.close() 

1250 raise 

1251 else: 

1252 bind_is_connection = isinstance(bind, engine.Connection) 

1253 

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

1255 conn, 

1256 transaction, 

1257 should_commit, 

1258 not bind_is_connection, 

1259 ) 

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

1261 return conn 

1262 finally: 

1263 self._state = SessionTransactionState.ACTIVE 

1264 

1265 def prepare(self) -> None: 

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

1267 raise sa_exc.InvalidRequestError( 

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

1269 "can't prepare." 

1270 ) 

1271 self._prepare_impl() 

1272 

1273 @_StateChange.declare_states( 

1274 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1275 ) 

1276 def _prepare_impl(self) -> None: 

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

1278 self.session.dispatch.before_commit(self.session) 

1279 

1280 stx = self.session._transaction 

1281 assert stx is not None 

1282 if stx is not self: 

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

1284 subtransaction.commit() 

1285 

1286 if not self.session._flushing: 

1287 for _flush_guard in range(100): 

1288 if self.session._is_clean(): 

1289 break 

1290 self.session.flush() 

1291 else: 

1292 raise exc.FlushError( 

1293 "Over 100 subsequent flushes have occurred within " 

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

1295 "creating new objects?" 

1296 ) 

1297 

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

1299 try: 

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

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

1302 except: 

1303 with util.safe_reraise(): 

1304 with self._expect_state(SessionTransactionState.CLOSED): 

1305 self.rollback() 

1306 

1307 self._state = SessionTransactionState.PREPARED 

1308 

1309 @_StateChange.declare_states( 

1310 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1311 SessionTransactionState.CLOSED, 

1312 ) 

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

1314 if self._state is not SessionTransactionState.PREPARED: 

1315 with self._expect_state(SessionTransactionState.PREPARED): 

1316 self._prepare_impl() 

1317 

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

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

1320 self._connections.values() 

1321 ): 

1322 if should_commit: 

1323 trans.commit() 

1324 

1325 self._state = SessionTransactionState.COMMITTED 

1326 self.session.dispatch.after_commit(self.session) 

1327 

1328 self._remove_snapshot() 

1329 

1330 with self._expect_state(SessionTransactionState.CLOSED): 

1331 self.close() 

1332 

1333 if _to_root and self._parent: 

1334 self._parent.commit(_to_root=True) 

1335 

1336 @_StateChange.declare_states( 

1337 ( 

1338 SessionTransactionState.ACTIVE, 

1339 SessionTransactionState.DEACTIVE, 

1340 SessionTransactionState.PREPARED, 

1341 ), 

1342 SessionTransactionState.CLOSED, 

1343 ) 

1344 def rollback( 

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

1346 ) -> None: 

1347 stx = self.session._transaction 

1348 assert stx is not None 

1349 if stx is not self: 

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

1351 subtransaction.close() 

1352 

1353 boundary = self 

1354 rollback_err = None 

1355 if self._state in ( 

1356 SessionTransactionState.ACTIVE, 

1357 SessionTransactionState.PREPARED, 

1358 ): 

1359 for transaction in self._iterate_self_and_parents(): 

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

1361 try: 

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

1363 t[1].rollback() 

1364 

1365 transaction._state = SessionTransactionState.DEACTIVE 

1366 self.session.dispatch.after_rollback(self.session) 

1367 except: 

1368 rollback_err = sys.exc_info() 

1369 finally: 

1370 transaction._state = SessionTransactionState.DEACTIVE 

1371 transaction._restore_snapshot( 

1372 dirty_only=transaction.nested 

1373 ) 

1374 boundary = transaction 

1375 break 

1376 else: 

1377 transaction._state = SessionTransactionState.DEACTIVE 

1378 

1379 sess = self.session 

1380 

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

1382 # if items were added, deleted, or mutated 

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

1384 util.warn( 

1385 "Session's state has been changed on " 

1386 "a non-active transaction - this state " 

1387 "will be discarded." 

1388 ) 

1389 boundary._restore_snapshot(dirty_only=boundary.nested) 

1390 

1391 with self._expect_state(SessionTransactionState.CLOSED): 

1392 self.close() 

1393 

1394 if self._parent and _capture_exception: 

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

1396 

1397 if rollback_err and rollback_err[1]: 

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

1399 

1400 sess.dispatch.after_soft_rollback(sess, self) 

1401 

1402 if _to_root and self._parent: 

1403 self._parent.rollback(_to_root=True) 

1404 

1405 @_StateChange.declare_states( 

1406 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1407 ) 

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

1409 if self.nested: 

1410 self.session._nested_transaction = ( 

1411 self._previous_nested_transaction 

1412 ) 

1413 

1414 self.session._transaction = self._parent 

1415 

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

1417 self._connections.values() 

1418 ): 

1419 if invalidate and self._parent is None: 

1420 connection.invalidate() 

1421 if should_commit and transaction.is_active: 

1422 transaction.close() 

1423 if autoclose and self._parent is None: 

1424 connection.close() 

1425 

1426 self._state = SessionTransactionState.CLOSED 

1427 sess = self.session 

1428 

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

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

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

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

1433 # passes with these commented out. 

1434 # self.session = None # type: ignore 

1435 # self._connections = None # type: ignore 

1436 

1437 sess.dispatch.after_transaction_end(sess, self) 

1438 

1439 def _get_subject(self) -> Session: 

1440 return self.session 

1441 

1442 def _transaction_is_active(self) -> bool: 

1443 return self._state is SessionTransactionState.ACTIVE 

1444 

1445 def _transaction_is_closed(self) -> bool: 

1446 return self._state is SessionTransactionState.CLOSED 

1447 

1448 def _rollback_can_be_called(self) -> bool: 

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

1450 

1451 

1452class _SessionCloseState(Enum): 

1453 ACTIVE = 1 

1454 CLOSED = 2 

1455 CLOSE_IS_RESET = 3 

1456 

1457 

1458class Session(_SessionClassMethods, EventTarget): 

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

1460 

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

1462 See :ref:`session_faq_threadsafe` for background. 

1463 

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

1465 

1466 

1467 """ 

1468 

1469 _is_asyncio = False 

1470 

1471 dispatch: dispatcher[Session] 

1472 

1473 identity_map: IdentityMap 

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

1475 

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

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

1478 that have row identity) currently in the session. 

1479 

1480 .. seealso:: 

1481 

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

1483 in this dictionary. 

1484 

1485 """ 

1486 

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

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

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

1490 __binds: Dict[_SessionBindKey, _SessionBind] 

1491 _flushing: bool 

1492 _warn_on_events: bool 

1493 _transaction: Optional[SessionTransaction] 

1494 _nested_transaction: Optional[SessionTransaction] 

1495 hash_key: int 

1496 autoflush: bool 

1497 expire_on_commit: bool 

1498 enable_baked_queries: bool 

1499 twophase: bool 

1500 join_transaction_mode: JoinTransactionMode 

1501 _query_cls: Type[Query[Any]] 

1502 _close_state: _SessionCloseState 

1503 

1504 def __init__( 

1505 self, 

1506 bind: Optional[_SessionBind] = None, 

1507 *, 

1508 autoflush: bool = True, 

1509 future: Literal[True] = True, 

1510 expire_on_commit: bool = True, 

1511 autobegin: bool = True, 

1512 twophase: bool = False, 

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

1514 enable_baked_queries: bool = True, 

1515 info: Optional[_InfoType] = None, 

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

1517 autocommit: Literal[False] = False, 

1518 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1520 ): 

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

1522 

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

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

1525 set of arguments. 

1526 

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

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

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

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

1531 results. 

1532 

1533 .. seealso:: 

1534 

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

1536 

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

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

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

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

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

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

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

1544 

1545 .. versionadded:: 2.0 

1546 

1547 .. seealso:: 

1548 

1549 :ref:`session_autobegin_disable` 

1550 

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

1552 :class:`_engine.Connection` to 

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

1554 operations performed by this session will execute via this 

1555 connectable. 

1556 

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

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

1559 objects as the source of 

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

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

1562 arbitrary Python classes that are bases for mapped classes, 

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

1564 The 

1565 values of the dictionary are then instances of 

1566 :class:`_engine.Engine` 

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

1568 Operations which 

1569 proceed relative to a particular mapped class will consult this 

1570 dictionary for the closest matching entity in order to determine 

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

1572 operation. The complete heuristics for resolution are 

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

1574 

1575 Session = sessionmaker( 

1576 binds={ 

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

1578 SomeDeclarativeBase: create_engine( 

1579 "postgresql+psycopg2://engine2" 

1580 ), 

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

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

1583 } 

1584 ) 

1585 

1586 .. seealso:: 

1587 

1588 :ref:`session_partitioning` 

1589 

1590 :meth:`.Session.bind_mapper` 

1591 

1592 :meth:`.Session.bind_table` 

1593 

1594 :meth:`.Session.get_bind` 

1595 

1596 

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

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

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

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

1601 constructor for ``Session``. 

1602 

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

1604 A parameter consumed 

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

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

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

1608 this particular extension is disabled. 

1609 

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

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

1612 flag therefore only affects applications that are making explicit 

1613 use of this extension within their own code. 

1614 

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

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

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

1618 transaction will load from the most recent database state. 

1619 

1620 .. seealso:: 

1621 

1622 :ref:`session_committing` 

1623 

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

1625 

1626 .. seealso:: 

1627 

1628 :ref:`migration_20_toplevel` 

1629 

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

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

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

1633 construction time so that modifications to the per- 

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

1635 :class:`.Session`. 

1636 

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

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

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

1640 

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

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

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

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

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

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

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

1648 transaction, before each transaction is committed. 

1649 

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

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

1652 

1653 :param join_transaction_mode: Describes the transactional behavior to 

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

1655 has already begun a transaction outside the scope of this 

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

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

1658 

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

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

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

1662 etc. are actually invoked: 

1663 

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

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

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

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

1668 a SAVEPOINT, in other words 

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

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

1671 

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

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

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

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

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

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

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

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

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

1681 

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

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

1684 its own transaction. This transaction by its nature rides 

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

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

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

1688 external transaction will remain unaffected throughout the 

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

1690 

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

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

1693 initiated transaction should remain unaffected; however, it relies 

1694 on proper SAVEPOINT support from the underlying driver and 

1695 database. 

1696 

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

1698 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1699 without workarounds. See the sections 

1700 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1701 for details on current workarounds. 

1702 

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

1704 control of the given transaction as its own; 

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

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

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

1708 call ``.rollback`` on the transaction. 

1709 

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

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

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

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

1714 SAVEPOINT. 

1715 

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

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

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

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

1720 given transaction. 

1721 

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

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

1724 regular database transaction (i.e. 

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

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

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

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

1729 

1730 .. versionadded:: 2.0.0rc1 

1731 

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

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

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

1735 

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

1737 A future SQLAlchemy version may change the default value of 

1738 this flag to ``False``. 

1739 

1740 .. seealso:: 

1741 

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

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

1744 

1745 """ # noqa 

1746 

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

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

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

1750 # of cases including in our own test suite 

1751 if autocommit: 

1752 raise sa_exc.ArgumentError( 

1753 "autocommit=True is no longer supported" 

1754 ) 

1755 self.identity_map = identity.WeakInstanceDict() 

1756 

1757 if not future: 

1758 raise sa_exc.ArgumentError( 

1759 "The 'future' parameter passed to " 

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

1761 ) 

1762 

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

1764 self._deleted = {} # same 

1765 self.bind = bind 

1766 self.__binds = {} 

1767 self._flushing = False 

1768 self._warn_on_events = False 

1769 self._transaction = None 

1770 self._nested_transaction = None 

1771 self.hash_key = _new_sessionid() 

1772 self.autobegin = autobegin 

1773 self.autoflush = autoflush 

1774 self.expire_on_commit = expire_on_commit 

1775 self.enable_baked_queries = enable_baked_queries 

1776 

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

1778 # the default will switch to close_resets_only=False. 

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

1780 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1781 else: 

1782 self._close_state = _SessionCloseState.ACTIVE 

1783 if ( 

1784 join_transaction_mode 

1785 and join_transaction_mode 

1786 not in JoinTransactionMode.__args__ # type: ignore 

1787 ): 

1788 raise sa_exc.ArgumentError( 

1789 f"invalid selection for join_transaction_mode: " 

1790 f'"{join_transaction_mode}"' 

1791 ) 

1792 self.join_transaction_mode = join_transaction_mode 

1793 

1794 self.twophase = twophase 

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

1796 if info: 

1797 self.info.update(info) 

1798 

1799 if binds is not None: 

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

1801 self._add_bind(key, bind) 

1802 

1803 _sessions[self.hash_key] = self 

1804 

1805 # used by sqlalchemy.engine.util.TransactionalContext 

1806 _trans_context_manager: Optional[TransactionalContext] = None 

1807 

1808 connection_callable: Optional[_ConnectionCallableProto] = None 

1809 

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

1811 return self 

1812 

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

1814 self.close() 

1815 

1816 @contextlib.contextmanager 

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

1818 with self: 

1819 with self.begin(): 

1820 yield self 

1821 

1822 def in_transaction(self) -> bool: 

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

1824 

1825 .. versionadded:: 1.4 

1826 

1827 .. seealso:: 

1828 

1829 :attr:`_orm.Session.is_active` 

1830 

1831 

1832 """ 

1833 return self._transaction is not None 

1834 

1835 def in_nested_transaction(self) -> bool: 

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

1837 transaction, e.g. SAVEPOINT. 

1838 

1839 .. versionadded:: 1.4 

1840 

1841 """ 

1842 return self._nested_transaction is not None 

1843 

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

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

1846 

1847 .. versionadded:: 1.4 

1848 

1849 """ 

1850 trans = self._transaction 

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

1852 trans = trans._parent 

1853 return trans 

1854 

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

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

1857 

1858 .. versionadded:: 1.4 

1859 

1860 """ 

1861 

1862 return self._nested_transaction 

1863 

1864 @util.memoized_property 

1865 def info(self) -> _InfoType: 

1866 """A user-modifiable dictionary. 

1867 

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

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

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

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

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

1873 

1874 """ 

1875 return {} 

1876 

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

1878 if self._transaction is None: 

1879 if not begin and not self.autobegin: 

1880 raise sa_exc.InvalidRequestError( 

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

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

1883 ) 

1884 trans = SessionTransaction( 

1885 self, 

1886 ( 

1887 SessionTransactionOrigin.BEGIN 

1888 if begin 

1889 else SessionTransactionOrigin.AUTOBEGIN 

1890 ), 

1891 ) 

1892 assert self._transaction is trans 

1893 return trans 

1894 

1895 return self._transaction 

1896 

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

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

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

1900 

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

1902 so that normally it is not necessary to call the 

1903 :meth:`_orm.Session.begin` 

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

1905 the scope of when the transactional state is begun. 

1906 

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

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

1909 

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

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

1912 documentation on SAVEPOINT transactions, please see 

1913 :ref:`session_begin_nested`. 

1914 

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

1916 :class:`.SessionTransaction` 

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

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

1919 an example. 

1920 

1921 .. seealso:: 

1922 

1923 :ref:`session_autobegin` 

1924 

1925 :ref:`unitofwork_transaction` 

1926 

1927 :meth:`.Session.begin_nested` 

1928 

1929 

1930 """ 

1931 

1932 trans = self._transaction 

1933 if trans is None: 

1934 trans = self._autobegin_t(begin=True) 

1935 

1936 if not nested: 

1937 return trans 

1938 

1939 assert trans is not None 

1940 

1941 if nested: 

1942 trans = trans._begin(nested=nested) 

1943 assert self._transaction is trans 

1944 self._nested_transaction = trans 

1945 else: 

1946 raise sa_exc.InvalidRequestError( 

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

1948 ) 

1949 

1950 return trans # needed for __enter__/__exit__ hook 

1951 

1952 def begin_nested(self) -> SessionTransaction: 

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

1954 

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

1956 SAVEPOINT for this method to function correctly. 

1957 

1958 For documentation on SAVEPOINT 

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

1960 

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

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

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

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

1965 

1966 .. seealso:: 

1967 

1968 :ref:`session_begin_nested` 

1969 

1970 :ref:`pysqlite_serializable` - special workarounds required 

1971 with the SQLite driver in order for SAVEPOINT to work 

1972 correctly. For asyncio use cases, see the section 

1973 :ref:`aiosqlite_serializable`. 

1974 

1975 """ 

1976 return self.begin(nested=True) 

1977 

1978 def rollback(self) -> None: 

1979 """Rollback the current transaction in progress. 

1980 

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

1982 

1983 The method always rolls back 

1984 the topmost database transaction, discarding any nested 

1985 transactions that may be in progress. 

1986 

1987 .. seealso:: 

1988 

1989 :ref:`session_rollback` 

1990 

1991 :ref:`unitofwork_transaction` 

1992 

1993 """ 

1994 if self._transaction is None: 

1995 pass 

1996 else: 

1997 self._transaction.rollback(_to_root=True) 

1998 

1999 def commit(self) -> None: 

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

2001 

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

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

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

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

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

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

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

2009 to disable this behavior. 

2010 

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

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

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

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

2015 normally affect the database unless pending flush changes were 

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

2017 rules. 

2018 

2019 The outermost database transaction is committed unconditionally, 

2020 automatically releasing any SAVEPOINTs in effect. 

2021 

2022 .. seealso:: 

2023 

2024 :ref:`session_committing` 

2025 

2026 :ref:`unitofwork_transaction` 

2027 

2028 :ref:`asyncio_orm_avoid_lazyloads` 

2029 

2030 """ 

2031 trans = self._transaction 

2032 if trans is None: 

2033 trans = self._autobegin_t() 

2034 

2035 trans.commit(_to_root=True) 

2036 

2037 def prepare(self) -> None: 

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

2039 

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

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

2042 

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

2044 current transaction is not such, an 

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

2046 

2047 """ 

2048 trans = self._transaction 

2049 if trans is None: 

2050 trans = self._autobegin_t() 

2051 

2052 trans.prepare() 

2053 

2054 def connection( 

2055 self, 

2056 bind_arguments: Optional[_BindArguments] = None, 

2057 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2058 ) -> Connection: 

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

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

2061 

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

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

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

2065 returned (note that no 

2066 transactional state is established with the DBAPI until the first 

2067 SQL statement is emitted). 

2068 

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

2070 resolved through any of the optional keyword arguments. This 

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

2072 

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

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

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

2076 

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

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

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

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

2081 the arguments are ignored. 

2082 

2083 .. seealso:: 

2084 

2085 :ref:`session_transaction_isolation` 

2086 

2087 """ 

2088 

2089 if bind_arguments: 

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

2091 

2092 if bind is None: 

2093 bind = self.get_bind(**bind_arguments) 

2094 else: 

2095 bind = self.get_bind() 

2096 

2097 return self._connection_for_bind( 

2098 bind, 

2099 execution_options=execution_options, 

2100 ) 

2101 

2102 def _connection_for_bind( 

2103 self, 

2104 engine: _SessionBind, 

2105 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2106 **kw: Any, 

2107 ) -> Connection: 

2108 TransactionalContext._trans_ctx_check(self) 

2109 

2110 trans = self._transaction 

2111 if trans is None: 

2112 trans = self._autobegin_t() 

2113 return trans._connection_for_bind(engine, execution_options) 

2114 

2115 @overload 

2116 def _execute_internal( 

2117 self, 

2118 statement: Executable, 

2119 params: Optional[_CoreSingleExecuteParams] = None, 

2120 *, 

2121 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2122 bind_arguments: Optional[_BindArguments] = None, 

2123 _parent_execute_state: Optional[Any] = None, 

2124 _add_event: Optional[Any] = None, 

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

2126 ) -> Any: ... 

2127 

2128 @overload 

2129 def _execute_internal( 

2130 self, 

2131 statement: Executable, 

2132 params: Optional[_CoreAnyExecuteParams] = None, 

2133 *, 

2134 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2135 bind_arguments: Optional[_BindArguments] = None, 

2136 _parent_execute_state: Optional[Any] = None, 

2137 _add_event: Optional[Any] = None, 

2138 _scalar_result: bool = ..., 

2139 ) -> Result[Any]: ... 

2140 

2141 def _execute_internal( 

2142 self, 

2143 statement: Executable, 

2144 params: Optional[_CoreAnyExecuteParams] = None, 

2145 *, 

2146 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2147 bind_arguments: Optional[_BindArguments] = None, 

2148 _parent_execute_state: Optional[Any] = None, 

2149 _add_event: Optional[Any] = None, 

2150 _scalar_result: bool = False, 

2151 ) -> Any: 

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

2153 

2154 if not bind_arguments: 

2155 bind_arguments = {} 

2156 else: 

2157 bind_arguments = dict(bind_arguments) 

2158 

2159 if ( 

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

2161 == "orm" 

2162 ): 

2163 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2164 statement, "orm" 

2165 ) 

2166 if TYPE_CHECKING: 

2167 assert isinstance( 

2168 compile_state_cls, context.AbstractORMCompileState 

2169 ) 

2170 else: 

2171 compile_state_cls = None 

2172 bind_arguments.setdefault("clause", statement) 

2173 

2174 execution_options = util.coerce_to_immutabledict(execution_options) 

2175 

2176 if _parent_execute_state: 

2177 events_todo = _parent_execute_state._remaining_events() 

2178 else: 

2179 events_todo = self.dispatch.do_orm_execute 

2180 if _add_event: 

2181 events_todo = list(events_todo) + [_add_event] 

2182 

2183 if events_todo: 

2184 # save the original execution options before 

2185 # orm_pre_session_exec processes them, so that we can pass 

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

2187 # hooks) to the second orm_pre_session_exec call. This 

2188 # prevents internal state like _sa_orm_load_options and 

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

2190 # which would otherwise cause issues like yield_per incorrectly 

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

2192 # part of #13301. 

2193 original_execution_options = execution_options 

2194 

2195 if compile_state_cls is not None: 

2196 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2202 ( 

2203 statement, 

2204 execution_options, 

2205 ) = compile_state_cls.orm_pre_session_exec( 

2206 self, 

2207 statement, 

2208 params, 

2209 execution_options, 

2210 bind_arguments, 

2211 True, 

2212 ) 

2213 

2214 orm_exec_state = ORMExecuteState( 

2215 self, 

2216 statement, 

2217 params, 

2218 execution_options, 

2219 bind_arguments, 

2220 compile_state_cls, 

2221 events_todo, 

2222 ) 

2223 for idx, fn in enumerate(events_todo): 

2224 orm_exec_state._starting_event_idx = idx 

2225 fn_result: Optional[Result[Any]] = fn(orm_exec_state) 

2226 if fn_result: 

2227 if _scalar_result: 

2228 return fn_result.scalar() 

2229 else: 

2230 return fn_result 

2231 

2232 statement = orm_exec_state.statement 

2233 

2234 # use the original execution options plus only the explicit 

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

2236 # the first orm_pre_session_exec call 

2237 execution_options = original_execution_options.union( 

2238 orm_exec_state._update_execution_options 

2239 ) 

2240 

2241 if compile_state_cls is not None: 

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

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

2244 # new execution_options into load_options / update_delete_options, 

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

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

2247 ( 

2248 statement, 

2249 execution_options, 

2250 ) = compile_state_cls.orm_pre_session_exec( 

2251 self, 

2252 statement, 

2253 params, 

2254 execution_options, 

2255 bind_arguments, 

2256 False, 

2257 ) 

2258 

2259 bind = self.get_bind(**bind_arguments) 

2260 

2261 conn = self._connection_for_bind(bind) 

2262 

2263 if _scalar_result and not compile_state_cls: 

2264 if TYPE_CHECKING: 

2265 params = cast(_CoreSingleExecuteParams, params) 

2266 return conn.scalar( 

2267 statement, params or {}, execution_options=execution_options 

2268 ) 

2269 

2270 if compile_state_cls: 

2271 result: Result[Any] = compile_state_cls.orm_execute_statement( 

2272 self, 

2273 statement, 

2274 params or {}, 

2275 execution_options, 

2276 bind_arguments, 

2277 conn, 

2278 ) 

2279 else: 

2280 result = conn.execute( 

2281 statement, params or {}, execution_options=execution_options 

2282 ) 

2283 

2284 if _scalar_result: 

2285 return result.scalar() 

2286 else: 

2287 return result 

2288 

2289 @overload 

2290 def execute( 

2291 self, 

2292 statement: TypedReturnsRows[_T], 

2293 params: Optional[_CoreAnyExecuteParams] = None, 

2294 *, 

2295 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2296 bind_arguments: Optional[_BindArguments] = None, 

2297 _parent_execute_state: Optional[Any] = None, 

2298 _add_event: Optional[Any] = None, 

2299 ) -> Result[_T]: ... 

2300 

2301 @overload 

2302 def execute( 

2303 self, 

2304 statement: Executable, 

2305 params: Optional[_CoreAnyExecuteParams] = None, 

2306 *, 

2307 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2308 bind_arguments: Optional[_BindArguments] = None, 

2309 _parent_execute_state: Optional[Any] = None, 

2310 _add_event: Optional[Any] = None, 

2311 ) -> Result[Any]: ... 

2312 

2313 def execute( 

2314 self, 

2315 statement: Executable, 

2316 params: Optional[_CoreAnyExecuteParams] = None, 

2317 *, 

2318 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2319 bind_arguments: Optional[_BindArguments] = None, 

2320 _parent_execute_state: Optional[Any] = None, 

2321 _add_event: Optional[Any] = None, 

2322 ) -> Result[Any]: 

2323 r"""Execute a SQL expression construct. 

2324 

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

2326 results of the statement execution. 

2327 

2328 E.g.:: 

2329 

2330 from sqlalchemy import select 

2331 

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

2333 

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

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

2336 of :class:`_engine.Connection`. 

2337 

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

2339 now the primary point of ORM statement execution when using 

2340 :term:`2.0 style` ORM usage. 

2341 

2342 :param statement: 

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

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

2345 

2346 :param params: 

2347 Optional dictionary, or list of dictionaries, containing 

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

2349 execution occurs; if a list of dictionaries, an 

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

2351 must correspond to parameter names present in the statement. 

2352 

2353 :param execution_options: optional dictionary of execution options, 

2354 which will be associated with the statement execution. This 

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

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

2357 provide additional options understood only in an ORM context. 

2358 

2359 .. seealso:: 

2360 

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

2362 options 

2363 

2364 :param bind_arguments: dictionary of additional arguments to determine 

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

2366 Contents of this dictionary are passed to the 

2367 :meth:`.Session.get_bind` method. 

2368 

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

2370 

2371 

2372 """ 

2373 return self._execute_internal( 

2374 statement, 

2375 params, 

2376 execution_options=execution_options, 

2377 bind_arguments=bind_arguments, 

2378 _parent_execute_state=_parent_execute_state, 

2379 _add_event=_add_event, 

2380 ) 

2381 

2382 @overload 

2383 def scalar( 

2384 self, 

2385 statement: TypedReturnsRows[Tuple[_T]], 

2386 params: Optional[_CoreSingleExecuteParams] = None, 

2387 *, 

2388 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2389 bind_arguments: Optional[_BindArguments] = None, 

2390 **kw: Any, 

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

2392 

2393 @overload 

2394 def scalar( 

2395 self, 

2396 statement: Executable, 

2397 params: Optional[_CoreSingleExecuteParams] = None, 

2398 *, 

2399 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2400 bind_arguments: Optional[_BindArguments] = None, 

2401 **kw: Any, 

2402 ) -> Any: ... 

2403 

2404 def scalar( 

2405 self, 

2406 statement: Executable, 

2407 params: Optional[_CoreSingleExecuteParams] = None, 

2408 *, 

2409 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2410 bind_arguments: Optional[_BindArguments] = None, 

2411 **kw: Any, 

2412 ) -> Any: 

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

2414 

2415 Usage and parameters are the same as that of 

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

2417 value. 

2418 

2419 """ 

2420 

2421 return self._execute_internal( 

2422 statement, 

2423 params, 

2424 execution_options=execution_options, 

2425 bind_arguments=bind_arguments, 

2426 _scalar_result=True, 

2427 **kw, 

2428 ) 

2429 

2430 @overload 

2431 def scalars( 

2432 self, 

2433 statement: TypedReturnsRows[Tuple[_T]], 

2434 params: Optional[_CoreAnyExecuteParams] = None, 

2435 *, 

2436 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2437 bind_arguments: Optional[_BindArguments] = None, 

2438 **kw: Any, 

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

2440 

2441 @overload 

2442 def scalars( 

2443 self, 

2444 statement: Executable, 

2445 params: Optional[_CoreAnyExecuteParams] = None, 

2446 *, 

2447 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2448 bind_arguments: Optional[_BindArguments] = None, 

2449 **kw: Any, 

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

2451 

2452 def scalars( 

2453 self, 

2454 statement: Executable, 

2455 params: Optional[_CoreAnyExecuteParams] = None, 

2456 *, 

2457 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2458 bind_arguments: Optional[_BindArguments] = None, 

2459 **kw: Any, 

2460 ) -> ScalarResult[Any]: 

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

2462 

2463 Usage and parameters are the same as that of 

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

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

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

2467 

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

2469 

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

2471 

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

2473 

2474 .. seealso:: 

2475 

2476 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2478 

2479 """ 

2480 

2481 return self._execute_internal( 

2482 statement, 

2483 params=params, 

2484 execution_options=execution_options, 

2485 bind_arguments=bind_arguments, 

2486 _scalar_result=False, # mypy appreciates this 

2487 **kw, 

2488 ).scalars() 

2489 

2490 def close(self) -> None: 

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

2492 :class:`_orm.Session`. 

2493 

2494 This expunges all ORM objects associated with this 

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

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

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

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

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

2500 

2501 .. tip:: 

2502 

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

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

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

2506 distinct "closed" state; it merely means 

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

2508 and ORM objects. 

2509 

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

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

2512 any further action on the session will be forbidden. 

2513 

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

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

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

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

2518 

2519 .. seealso:: 

2520 

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

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

2523 

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

2525 ``close()`` with the parameter 

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

2527 

2528 """ 

2529 self._close_impl(invalidate=False) 

2530 

2531 def reset(self) -> None: 

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

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

2534 

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

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

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

2538 brand new, and ready to be used again. 

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

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

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

2542 

2543 .. versionadded:: 2.0.22 

2544 

2545 .. seealso:: 

2546 

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

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

2549 

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

2551 prevent reuse of the Session when the parameter 

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

2553 """ 

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

2555 

2556 def invalidate(self) -> None: 

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

2558 

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

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

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

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

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

2564 multiple engines). 

2565 

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

2567 the connections are no longer safe to be used. 

2568 

2569 Below illustrates a scenario when using `gevent 

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

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

2572 

2573 import gevent 

2574 

2575 try: 

2576 sess = Session() 

2577 sess.add(User()) 

2578 sess.commit() 

2579 except gevent.Timeout: 

2580 sess.invalidate() 

2581 raise 

2582 except: 

2583 sess.rollback() 

2584 raise 

2585 

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

2587 does, including that all ORM objects are expunged. 

2588 

2589 """ 

2590 self._close_impl(invalidate=True) 

2591 

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

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

2594 self._close_state = _SessionCloseState.CLOSED 

2595 self.expunge_all() 

2596 if self._transaction is not None: 

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

2598 transaction.close(invalidate) 

2599 

2600 def expunge_all(self) -> None: 

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

2602 

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

2604 ``Session``. 

2605 

2606 """ 

2607 

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

2609 self.identity_map._kill() 

2610 self.identity_map = identity.WeakInstanceDict() 

2611 self._new = {} 

2612 self._deleted = {} 

2613 

2614 statelib.InstanceState._detach_states(all_states, self) 

2615 

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

2617 try: 

2618 insp = inspect(key) 

2619 except sa_exc.NoInspectionAvailable as err: 

2620 if not isinstance(key, type): 

2621 raise sa_exc.ArgumentError( 

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

2623 ) from err 

2624 else: 

2625 self.__binds[key] = bind 

2626 else: 

2627 if TYPE_CHECKING: 

2628 assert isinstance(insp, Inspectable) 

2629 

2630 if isinstance(insp, TableClause): 

2631 self.__binds[insp] = bind 

2632 elif insp_is_mapper(insp): 

2633 self.__binds[insp.class_] = bind 

2634 for _selectable in insp._all_tables: 

2635 self.__binds[_selectable] = bind 

2636 else: 

2637 raise sa_exc.ArgumentError( 

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

2639 ) 

2640 

2641 def bind_mapper( 

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

2643 ) -> None: 

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

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

2646 :class:`_engine.Connection`. 

2647 

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

2649 :meth:`.Session.get_bind` method. 

2650 

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

2652 or an instance of a mapped 

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

2654 classes. 

2655 

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

2657 object. 

2658 

2659 .. seealso:: 

2660 

2661 :ref:`session_partitioning` 

2662 

2663 :paramref:`.Session.binds` 

2664 

2665 :meth:`.Session.bind_table` 

2666 

2667 

2668 """ 

2669 self._add_bind(mapper, bind) 

2670 

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

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

2673 :class:`_engine.Engine` 

2674 or :class:`_engine.Connection`. 

2675 

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

2677 :meth:`.Session.get_bind` method. 

2678 

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

2680 which is typically the target 

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

2682 mapped. 

2683 

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

2685 object. 

2686 

2687 .. seealso:: 

2688 

2689 :ref:`session_partitioning` 

2690 

2691 :paramref:`.Session.binds` 

2692 

2693 :meth:`.Session.bind_mapper` 

2694 

2695 

2696 """ 

2697 self._add_bind(table, bind) 

2698 

2699 def get_bind( 

2700 self, 

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

2702 *, 

2703 clause: Optional[ClauseElement] = None, 

2704 bind: Optional[_SessionBind] = None, 

2705 _sa_skip_events: Optional[bool] = None, 

2706 _sa_skip_for_implicit_returning: bool = False, 

2707 **kw: Any, 

2708 ) -> Union[Engine, Connection]: 

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

2710 

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

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

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

2714 

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

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

2717 appropriate bind to return. 

2718 

2719 Note that the "mapper" argument is usually present 

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

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

2722 individual INSERT/UPDATE/DELETE operation within a 

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

2724 

2725 The order of resolution is: 

2726 

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

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

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

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

2731 superclasses to more general. 

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

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

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

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

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

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

2738 associated with the clause. 

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

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

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

2742 selectable to which the mapper is mapped. 

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

2744 is raised. 

2745 

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

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

2748 of bind resolution scheme. See the example at 

2749 :ref:`session_custom_partitioning`. 

2750 

2751 :param mapper: 

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

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

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

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

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

2757 mapped for a bind. 

2758 

2759 :param clause: 

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

2761 :func:`_expression.select`, 

2762 :func:`_expression.text`, 

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

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

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

2766 associated with 

2767 bound :class:`_schema.MetaData`. 

2768 

2769 .. seealso:: 

2770 

2771 :ref:`session_partitioning` 

2772 

2773 :paramref:`.Session.binds` 

2774 

2775 :meth:`.Session.bind_mapper` 

2776 

2777 :meth:`.Session.bind_table` 

2778 

2779 """ 

2780 

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

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

2783 if bind: 

2784 return bind 

2785 elif not self.__binds and self.bind: 

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

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

2788 return self.bind 

2789 

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

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

2792 # mapper and the clause 

2793 if mapper is None and clause is None: 

2794 if self.bind: 

2795 return self.bind 

2796 else: 

2797 raise sa_exc.UnboundExecutionError( 

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

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

2800 "a binding." 

2801 ) 

2802 

2803 # look more closely at the mapper. 

2804 if mapper is not None: 

2805 try: 

2806 inspected_mapper = inspect(mapper) 

2807 except sa_exc.NoInspectionAvailable as err: 

2808 if isinstance(mapper, type): 

2809 raise exc.UnmappedClassError(mapper) from err 

2810 else: 

2811 raise 

2812 else: 

2813 inspected_mapper = None 

2814 

2815 # match up the mapper or clause in the __binds 

2816 if self.__binds: 

2817 # matching mappers and selectables to entries in the 

2818 # binds dictionary; supported use case. 

2819 if inspected_mapper: 

2820 for cls in inspected_mapper.class_.__mro__: 

2821 if cls in self.__binds: 

2822 return self.__binds[cls] 

2823 if clause is None: 

2824 clause = inspected_mapper.persist_selectable 

2825 

2826 if clause is not None: 

2827 plugin_subject = clause._propagate_attrs.get( 

2828 "plugin_subject", None 

2829 ) 

2830 

2831 if plugin_subject is not None: 

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

2833 if cls in self.__binds: 

2834 return self.__binds[cls] 

2835 

2836 for obj in visitors.iterate(clause): 

2837 if obj in self.__binds: 

2838 if TYPE_CHECKING: 

2839 assert isinstance(obj, Table) 

2840 return self.__binds[obj] 

2841 

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

2843 # return that 

2844 if self.bind: 

2845 return self.bind 

2846 

2847 context = [] 

2848 if inspected_mapper is not None: 

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

2850 if clause is not None: 

2851 context.append("SQL expression") 

2852 

2853 raise sa_exc.UnboundExecutionError( 

2854 f"Could not locate a bind configured on " 

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

2856 ) 

2857 

2858 @overload 

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

2860 

2861 @overload 

2862 def query( 

2863 self, _colexpr: TypedColumnsClauseRole[_T] 

2864 ) -> RowReturningQuery[Tuple[_T]]: ... 

2865 

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

2867 

2868 # code within this block is **programmatically, 

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

2870 

2871 @overload 

2872 def query( 

2873 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1] 

2874 ) -> RowReturningQuery[Tuple[_T0, _T1]]: ... 

2875 

2876 @overload 

2877 def query( 

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

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

2880 

2881 @overload 

2882 def query( 

2883 self, 

2884 __ent0: _TCCA[_T0], 

2885 __ent1: _TCCA[_T1], 

2886 __ent2: _TCCA[_T2], 

2887 __ent3: _TCCA[_T3], 

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

2889 

2890 @overload 

2891 def query( 

2892 self, 

2893 __ent0: _TCCA[_T0], 

2894 __ent1: _TCCA[_T1], 

2895 __ent2: _TCCA[_T2], 

2896 __ent3: _TCCA[_T3], 

2897 __ent4: _TCCA[_T4], 

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

2899 

2900 @overload 

2901 def query( 

2902 self, 

2903 __ent0: _TCCA[_T0], 

2904 __ent1: _TCCA[_T1], 

2905 __ent2: _TCCA[_T2], 

2906 __ent3: _TCCA[_T3], 

2907 __ent4: _TCCA[_T4], 

2908 __ent5: _TCCA[_T5], 

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

2910 

2911 @overload 

2912 def query( 

2913 self, 

2914 __ent0: _TCCA[_T0], 

2915 __ent1: _TCCA[_T1], 

2916 __ent2: _TCCA[_T2], 

2917 __ent3: _TCCA[_T3], 

2918 __ent4: _TCCA[_T4], 

2919 __ent5: _TCCA[_T5], 

2920 __ent6: _TCCA[_T6], 

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

2922 

2923 @overload 

2924 def query( 

2925 self, 

2926 __ent0: _TCCA[_T0], 

2927 __ent1: _TCCA[_T1], 

2928 __ent2: _TCCA[_T2], 

2929 __ent3: _TCCA[_T3], 

2930 __ent4: _TCCA[_T4], 

2931 __ent5: _TCCA[_T5], 

2932 __ent6: _TCCA[_T6], 

2933 __ent7: _TCCA[_T7], 

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

2935 

2936 # END OVERLOADED FUNCTIONS self.query 

2937 

2938 @overload 

2939 def query( 

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

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

2942 

2943 def query( 

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

2945 ) -> Query[Any]: 

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

2947 :class:`_orm.Session`. 

2948 

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

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

2951 to construct ORM queries. 

2952 

2953 .. seealso:: 

2954 

2955 :ref:`unified_tutorial` 

2956 

2957 :ref:`queryguide_toplevel` 

2958 

2959 :ref:`query_api_toplevel` - legacy API doc 

2960 

2961 """ 

2962 

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

2964 

2965 def _identity_lookup( 

2966 self, 

2967 mapper: Mapper[_O], 

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

2969 identity_token: Any = None, 

2970 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

2972 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2973 bind_arguments: Optional[_BindArguments] = None, 

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

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

2976 

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

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

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

2980 check if was deleted). 

2981 

2982 e.g.:: 

2983 

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

2985 

2986 :param mapper: mapper in use 

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

2988 a tuple. 

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

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

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

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

2993 :param passive: passive load flag passed to 

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

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

2996 if the flag allows for SQL to be emitted. 

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

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

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

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

3001 relationship-loaded). 

3002 

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

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

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

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

3007 

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

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

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

3011 :class:`_query.Query` object. 

3012 

3013 

3014 """ 

3015 

3016 key = mapper.identity_key_from_primary_key( 

3017 primary_key_identity, identity_token=identity_token 

3018 ) 

3019 

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

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

3022 return return_value 

3023 

3024 @util.non_memoized_property 

3025 @contextlib.contextmanager 

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

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

3028 

3029 e.g.:: 

3030 

3031 with session.no_autoflush: 

3032 

3033 some_object = SomeClass() 

3034 session.add(some_object) 

3035 # won't autoflush 

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

3037 

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

3039 will not be subject to flushes occurring upon query 

3040 access. This is useful when initializing a series 

3041 of objects which involve existing database queries, 

3042 where the uncompleted object should not yet be flushed. 

3043 

3044 """ 

3045 autoflush = self.autoflush 

3046 self.autoflush = False 

3047 try: 

3048 yield self 

3049 finally: 

3050 self.autoflush = autoflush 

3051 

3052 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3056 "warning happened while initializing objects.", 

3057 sa_exc.SAWarning, 

3058 ) 

3059 def _autoflush(self) -> None: 

3060 if self.autoflush and not self._flushing: 

3061 try: 

3062 self.flush() 

3063 except sa_exc.StatementError as e: 

3064 # note we are reraising StatementError as opposed to 

3065 # raising FlushError with "chaining" to remain compatible 

3066 # with code that catches StatementError, IntegrityError, 

3067 # etc. 

3068 e.add_detail( 

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

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

3071 "flush is occurring prematurely" 

3072 ) 

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

3074 

3075 def refresh( 

3076 self, 

3077 instance: object, 

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

3079 with_for_update: ForUpdateParameter = None, 

3080 ) -> None: 

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

3082 

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

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

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

3086 value available in the current transaction. 

3087 

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

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

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

3091 

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

3093 can also refresh eagerly loaded attributes. 

3094 

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

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

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

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

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

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

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

3102 refreshed. 

3103 

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

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

3106 attributes for those which are named explicitly in the 

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

3108 

3109 .. tip:: 

3110 

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

3112 refreshing both column and relationship oriented attributes, its 

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

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

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

3116 once while having explicit control over relationship loader 

3117 strategies, use the 

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

3119 instead. 

3120 

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

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

3123 in database state outside of that transaction. Refreshing 

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

3125 where database rows have not yet been accessed. 

3126 

3127 :param attribute_names: optional. An iterable collection of 

3128 string attribute names indicating a subset of attributes to 

3129 be refreshed. 

3130 

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

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

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

3134 flags should match the parameters of 

3135 :meth:`_query.Query.with_for_update`. 

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

3137 

3138 .. seealso:: 

3139 

3140 :ref:`session_expire` - introductory material 

3141 

3142 :meth:`.Session.expire` 

3143 

3144 :meth:`.Session.expire_all` 

3145 

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

3147 to refresh objects as they would be loaded normally. 

3148 

3149 """ 

3150 try: 

3151 state = attributes.instance_state(instance) 

3152 except exc.NO_STATE as err: 

3153 raise exc.UnmappedInstanceError(instance) from err 

3154 

3155 self._expire_state(state, attribute_names) 

3156 

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

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

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

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

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

3162 # load_on_ident. 

3163 self._autoflush() 

3164 

3165 if with_for_update == {}: 

3166 raise sa_exc.ArgumentError( 

3167 "with_for_update should be the boolean value " 

3168 "True, or a dictionary with options. " 

3169 "A blank dictionary is ambiguous." 

3170 ) 

3171 

3172 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3173 

3174 stmt: Select[Any] = sql.select(object_mapper(instance)) 

3175 if ( 

3176 loading.load_on_ident( 

3177 self, 

3178 stmt, 

3179 state.key, 

3180 refresh_state=state, 

3181 with_for_update=with_for_update, 

3182 only_load_props=attribute_names, 

3183 require_pk_cols=True, 

3184 # technically unnecessary as we just did autoflush 

3185 # above, however removes the additional unnecessary 

3186 # call to _autoflush() 

3187 no_autoflush=True, 

3188 is_user_refresh=True, 

3189 ) 

3190 is None 

3191 ): 

3192 raise sa_exc.InvalidRequestError( 

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

3194 ) 

3195 

3196 def expire_all(self) -> None: 

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

3198 

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

3200 a query will be issued using the 

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

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

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

3204 previously read in that same transaction, regardless of changes 

3205 in database state outside of that transaction. 

3206 

3207 To expire individual objects and individual attributes 

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

3209 

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

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

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

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

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

3215 assuming the transaction is isolated. 

3216 

3217 .. seealso:: 

3218 

3219 :ref:`session_expire` - introductory material 

3220 

3221 :meth:`.Session.expire` 

3222 

3223 :meth:`.Session.refresh` 

3224 

3225 :meth:`_orm.Query.populate_existing` 

3226 

3227 """ 

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

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

3230 

3231 def expire( 

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

3233 ) -> None: 

3234 """Expire the attributes on an instance. 

3235 

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

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

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

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

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

3241 previously read in that same transaction, regardless of changes 

3242 in database state outside of that transaction. 

3243 

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

3245 use :meth:`Session.expire_all`. 

3246 

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

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

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

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

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

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

3253 transaction. 

3254 

3255 :param instance: The instance to be refreshed. 

3256 :param attribute_names: optional list of string attribute names 

3257 indicating a subset of attributes to be expired. 

3258 

3259 .. seealso:: 

3260 

3261 :ref:`session_expire` - introductory material 

3262 

3263 :meth:`.Session.expire` 

3264 

3265 :meth:`.Session.refresh` 

3266 

3267 :meth:`_orm.Query.populate_existing` 

3268 

3269 """ 

3270 try: 

3271 state = attributes.instance_state(instance) 

3272 except exc.NO_STATE as err: 

3273 raise exc.UnmappedInstanceError(instance) from err 

3274 self._expire_state(state, attribute_names) 

3275 

3276 def _expire_state( 

3277 self, 

3278 state: InstanceState[Any], 

3279 attribute_names: Optional[Iterable[str]], 

3280 ) -> None: 

3281 self._validate_persistent(state) 

3282 if attribute_names: 

3283 state._expire_attributes(state.dict, attribute_names) 

3284 else: 

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

3286 # remove associations 

3287 cascaded = list( 

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

3289 ) 

3290 self._conditional_expire(state) 

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

3292 self._conditional_expire(st_) 

3293 

3294 def _conditional_expire( 

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

3296 ) -> None: 

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

3298 

3299 if state.key: 

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

3301 elif state in self._new: 

3302 self._new.pop(state) 

3303 state._detach(self) 

3304 

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

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

3307 

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

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

3310 

3311 """ 

3312 try: 

3313 state = attributes.instance_state(instance) 

3314 except exc.NO_STATE as err: 

3315 raise exc.UnmappedInstanceError(instance) from err 

3316 if state.session_id is not self.hash_key: 

3317 raise sa_exc.InvalidRequestError( 

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

3319 ) 

3320 

3321 cascaded = list( 

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

3323 ) 

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

3325 

3326 def _expunge_states( 

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

3328 ) -> None: 

3329 for state in states: 

3330 if state in self._new: 

3331 self._new.pop(state) 

3332 elif self.identity_map.contains_state(state): 

3333 self.identity_map.safe_discard(state) 

3334 self._deleted.pop(state, None) 

3335 elif self._transaction: 

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

3337 # in the transaction snapshot 

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

3339 statelib.InstanceState._detach_states( 

3340 states, self, to_transient=to_transient 

3341 ) 

3342 

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

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

3345 

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

3347 state as well as already persistent objects. 

3348 

3349 """ 

3350 

3351 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3352 for state in states: 

3353 mapper = _state_mapper(state) 

3354 

3355 # prevent against last minute dereferences of the object 

3356 obj = state.obj() 

3357 if obj is not None: 

3358 instance_key = mapper._identity_key_from_state(state) 

3359 

3360 if ( 

3361 _none_set.intersection(instance_key[1]) 

3362 and not mapper.allow_partial_pks 

3363 or _none_set.issuperset(instance_key[1]) 

3364 ): 

3365 raise exc.FlushError( 

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

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

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

3369 "that the mapped Column object is configured to " 

3370 "expect these generated values. Ensure also that " 

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

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

3373 % state_str(state) 

3374 ) 

3375 

3376 if state.key is None: 

3377 state.key = instance_key 

3378 elif state.key != instance_key: 

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

3380 # state has already replaced this one in the identity 

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

3382 self.identity_map.safe_discard(state) 

3383 trans = self._transaction 

3384 assert trans is not None 

3385 if state in trans._key_switches: 

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

3387 else: 

3388 orig_key = state.key 

3389 trans._key_switches[state] = ( 

3390 orig_key, 

3391 instance_key, 

3392 ) 

3393 state.key = instance_key 

3394 

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

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

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

3398 old = self.identity_map.replace(state) 

3399 if ( 

3400 old is not None 

3401 and mapper._identity_key_from_state(old) == instance_key 

3402 and old.obj() is not None 

3403 ): 

3404 util.warn( 

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

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

3407 "load operations occurring inside of an event handler " 

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

3409 ) 

3410 state._orphaned_outside_of_session = False 

3411 

3412 statelib.InstanceState._commit_all_states( 

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

3414 ) 

3415 

3416 self._register_altered(states) 

3417 

3418 if pending_to_persistent is not None: 

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

3420 pending_to_persistent(self, state) 

3421 

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

3423 for state in set(states).intersection(self._new): 

3424 self._new.pop(state) 

3425 

3426 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None: 

3427 if self._transaction: 

3428 for state in states: 

3429 if state in self._new: 

3430 self._transaction._new[state] = True 

3431 else: 

3432 self._transaction._dirty[state] = True 

3433 

3434 def _remove_newly_deleted( 

3435 self, states: Iterable[InstanceState[Any]] 

3436 ) -> None: 

3437 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3438 for state in states: 

3439 if self._transaction: 

3440 self._transaction._deleted[state] = True 

3441 

3442 if persistent_to_deleted is not None: 

3443 # get a strong reference before we pop out of 

3444 # self._deleted 

3445 obj = state.obj() # noqa 

3446 

3447 self.identity_map.safe_discard(state) 

3448 self._deleted.pop(state, None) 

3449 state._deleted = True 

3450 # can't call state._detach() here, because this state 

3451 # is still in the transaction snapshot and needs to be 

3452 # tracked as part of that 

3453 if persistent_to_deleted is not None: 

3454 persistent_to_deleted(self, state) 

3455 

3456 def add(self, instance: object, _warn: bool = True) -> None: 

3457 """Place an object into this :class:`_orm.Session`. 

3458 

3459 Objects that are in the :term:`transient` state when passed to the 

3460 :meth:`_orm.Session.add` method will move to the 

3461 :term:`pending` state, until the next flush, at which point they 

3462 will move to the :term:`persistent` state. 

3463 

3464 Objects that are in the :term:`detached` state when passed to the 

3465 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3466 state directly. 

3467 

3468 If the transaction used by the :class:`_orm.Session` is rolled back, 

3469 objects which were transient when they were passed to 

3470 :meth:`_orm.Session.add` will be moved back to the 

3471 :term:`transient` state, and will no longer be present within this 

3472 :class:`_orm.Session`. 

3473 

3474 .. seealso:: 

3475 

3476 :meth:`_orm.Session.add_all` 

3477 

3478 :ref:`session_adding` - at :ref:`session_basics` 

3479 

3480 """ 

3481 if _warn and self._warn_on_events: 

3482 self._flush_warning("Session.add()") 

3483 

3484 try: 

3485 state = attributes.instance_state(instance) 

3486 except exc.NO_STATE as err: 

3487 raise exc.UnmappedInstanceError(instance) from err 

3488 

3489 self._save_or_update_state(state) 

3490 

3491 def add_all(self, instances: Iterable[object]) -> None: 

3492 """Add the given collection of instances to this :class:`_orm.Session`. 

3493 

3494 See the documentation for :meth:`_orm.Session.add` for a general 

3495 behavioral description. 

3496 

3497 .. seealso:: 

3498 

3499 :meth:`_orm.Session.add` 

3500 

3501 :ref:`session_adding` - at :ref:`session_basics` 

3502 

3503 """ 

3504 

3505 if self._warn_on_events: 

3506 self._flush_warning("Session.add_all()") 

3507 

3508 for instance in instances: 

3509 self.add(instance, _warn=False) 

3510 

3511 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3512 state._orphaned_outside_of_session = False 

3513 self._save_or_update_impl(state) 

3514 

3515 mapper = _state_mapper(state) 

3516 for o, m, st_, dct_ in mapper.cascade_iterator( 

3517 "save-update", state, halt_on=self._contains_state 

3518 ): 

3519 self._save_or_update_impl(st_) 

3520 

3521 def delete(self, instance: object) -> None: 

3522 """Mark an instance as deleted. 

3523 

3524 The object is assumed to be either :term:`persistent` or 

3525 :term:`detached` when passed; after the method is called, the 

3526 object will remain in the :term:`persistent` state until the next 

3527 flush proceeds. During this time, the object will also be a member 

3528 of the :attr:`_orm.Session.deleted` collection. 

3529 

3530 When the next flush proceeds, the object will move to the 

3531 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3532 for its row within the current transaction. When the transaction 

3533 is successfully committed, 

3534 the deleted object is moved to the :term:`detached` state and is 

3535 no longer present within this :class:`_orm.Session`. 

3536 

3537 .. seealso:: 

3538 

3539 :ref:`session_deleting` - at :ref:`session_basics` 

3540 

3541 """ 

3542 if self._warn_on_events: 

3543 self._flush_warning("Session.delete()") 

3544 

3545 try: 

3546 state = attributes.instance_state(instance) 

3547 except exc.NO_STATE as err: 

3548 raise exc.UnmappedInstanceError(instance) from err 

3549 

3550 self._delete_impl(state, instance, head=True) 

3551 

3552 def _delete_impl( 

3553 self, state: InstanceState[Any], obj: object, head: bool 

3554 ) -> None: 

3555 if state.key is None: 

3556 if head: 

3557 raise sa_exc.InvalidRequestError( 

3558 "Instance '%s' is not persisted" % state_str(state) 

3559 ) 

3560 else: 

3561 return 

3562 

3563 to_attach = self._before_attach(state, obj) 

3564 

3565 if state in self._deleted: 

3566 return 

3567 

3568 self.identity_map.add(state) 

3569 

3570 if to_attach: 

3571 self._after_attach(state, obj) 

3572 

3573 if head: 

3574 # grab the cascades before adding the item to the deleted list 

3575 # so that autoflush does not delete the item 

3576 # the strong reference to the instance itself is significant here 

3577 cascade_states = list( 

3578 state.manager.mapper.cascade_iterator("delete", state) 

3579 ) 

3580 else: 

3581 cascade_states = None 

3582 

3583 self._deleted[state] = obj 

3584 

3585 if head: 

3586 if TYPE_CHECKING: 

3587 assert cascade_states is not None 

3588 for o, m, st_, dct_ in cascade_states: 

3589 self._delete_impl(st_, o, False) 

3590 

3591 def get( 

3592 self, 

3593 entity: _EntityBindKey[_O], 

3594 ident: _PKIdentityArgument, 

3595 *, 

3596 options: Optional[Sequence[ORMOption]] = None, 

3597 populate_existing: bool = False, 

3598 with_for_update: ForUpdateParameter = None, 

3599 identity_token: Optional[Any] = None, 

3600 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3601 bind_arguments: Optional[_BindArguments] = None, 

3602 ) -> Optional[_O]: 

3603 """Return an instance based on the given primary key identifier, 

3604 or ``None`` if not found. 

3605 

3606 E.g.:: 

3607 

3608 my_user = session.get(User, 5) 

3609 

3610 some_object = session.get(VersionedFoo, (5, 10)) 

3611 

3612 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3613 

3614 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3615 from the now legacy :meth:`_orm.Query.get` method. 

3616 

3617 :meth:`_orm.Session.get` is special in that it provides direct 

3618 access to the identity map of the :class:`.Session`. 

3619 If the given primary key identifier is present 

3620 in the local identity map, the object is returned 

3621 directly from this collection and no SQL is emitted, 

3622 unless the object has been marked fully expired. 

3623 If not present, 

3624 a SELECT is performed in order to locate the object. 

3625 

3626 :meth:`_orm.Session.get` also will perform a check if 

3627 the object is present in the identity map and 

3628 marked as expired - a SELECT 

3629 is emitted to refresh the object as well as to 

3630 ensure that the row is still present. 

3631 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3632 

3633 :param entity: a mapped class or :class:`.Mapper` indicating the 

3634 type of entity to be loaded. 

3635 

3636 :param ident: A scalar, tuple, or dictionary representing the 

3637 primary key. For a composite (e.g. multiple column) primary key, 

3638 a tuple or dictionary should be passed. 

3639 

3640 For a single-column primary key, the scalar calling form is typically 

3641 the most expedient. If the primary key of a row is the value "5", 

3642 the call looks like:: 

3643 

3644 my_object = session.get(SomeClass, 5) 

3645 

3646 The tuple form contains primary key values typically in 

3647 the order in which they correspond to the mapped 

3648 :class:`_schema.Table` 

3649 object's primary key columns, or if the 

3650 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3651 used, in 

3652 the order used for that parameter. For example, if the primary key 

3653 of a row is represented by the integer 

3654 digits "5, 10" the call would look like:: 

3655 

3656 my_object = session.get(SomeClass, (5, 10)) 

3657 

3658 The dictionary form should include as keys the mapped attribute names 

3659 corresponding to each element of the primary key. If the mapped class 

3660 has the attributes ``id``, ``version_id`` as the attributes which 

3661 store the object's primary key value, the call would look like:: 

3662 

3663 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3664 

3665 :param options: optional sequence of loader options which will be 

3666 applied to the query, if one is emitted. 

3667 

3668 :param populate_existing: causes the method to unconditionally emit 

3669 a SQL query and refresh the object with the newly loaded data, 

3670 regardless of whether or not the object is already present. 

3671 

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

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

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

3675 flags should match the parameters of 

3676 :meth:`_query.Query.with_for_update`. 

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

3678 

3679 :param execution_options: optional dictionary of execution options, 

3680 which will be associated with the query execution if one is emitted. 

3681 This dictionary can provide a subset of the options that are 

3682 accepted by :meth:`_engine.Connection.execution_options`, and may 

3683 also provide additional options understood only in an ORM context. 

3684 

3685 .. versionadded:: 1.4.29 

3686 

3687 .. seealso:: 

3688 

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

3690 options 

3691 

3692 :param bind_arguments: dictionary of additional arguments to determine 

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

3694 Contents of this dictionary are passed to the 

3695 :meth:`.Session.get_bind` method. 

3696 

3697 .. versionadded: 2.0.0rc1 

3698 

3699 :return: The object instance, or ``None``. 

3700 

3701 """ # noqa: E501 

3702 return self._get_impl( 

3703 entity, 

3704 ident, 

3705 loading.load_on_pk_identity, 

3706 options=options, 

3707 populate_existing=populate_existing, 

3708 with_for_update=with_for_update, 

3709 identity_token=identity_token, 

3710 execution_options=execution_options, 

3711 bind_arguments=bind_arguments, 

3712 ) 

3713 

3714 def get_one( 

3715 self, 

3716 entity: _EntityBindKey[_O], 

3717 ident: _PKIdentityArgument, 

3718 *, 

3719 options: Optional[Sequence[ORMOption]] = None, 

3720 populate_existing: bool = False, 

3721 with_for_update: ForUpdateParameter = None, 

3722 identity_token: Optional[Any] = None, 

3723 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3724 bind_arguments: Optional[_BindArguments] = None, 

3725 ) -> _O: 

3726 """Return exactly one instance based on the given primary key 

3727 identifier, or raise an exception if not found. 

3728 

3729 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3730 

3731 For a detailed documentation of the arguments see the 

3732 method :meth:`.Session.get`. 

3733 

3734 .. versionadded:: 2.0.22 

3735 

3736 :return: The object instance. 

3737 

3738 .. seealso:: 

3739 

3740 :meth:`.Session.get` - equivalent method that instead 

3741 returns ``None`` if no row was found with the provided primary 

3742 key 

3743 

3744 """ 

3745 

3746 instance = self.get( 

3747 entity, 

3748 ident, 

3749 options=options, 

3750 populate_existing=populate_existing, 

3751 with_for_update=with_for_update, 

3752 identity_token=identity_token, 

3753 execution_options=execution_options, 

3754 bind_arguments=bind_arguments, 

3755 ) 

3756 

3757 if instance is None: 

3758 raise sa_exc.NoResultFound( 

3759 "No row was found when one was required" 

3760 ) 

3761 

3762 return instance 

3763 

3764 def _get_impl( 

3765 self, 

3766 entity: _EntityBindKey[_O], 

3767 primary_key_identity: _PKIdentityArgument, 

3768 db_load_fn: Callable[..., _O], 

3769 *, 

3770 options: Optional[Sequence[ExecutableOption]] = None, 

3771 populate_existing: bool = False, 

3772 with_for_update: ForUpdateParameter = None, 

3773 identity_token: Optional[Any] = None, 

3774 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3775 bind_arguments: Optional[_BindArguments] = None, 

3776 ) -> Optional[_O]: 

3777 # convert composite types to individual args 

3778 if ( 

3779 is_composite_class(primary_key_identity) 

3780 and type(primary_key_identity) 

3781 in descriptor_props._composite_getters 

3782 ): 

3783 getter = descriptor_props._composite_getters[ 

3784 type(primary_key_identity) 

3785 ] 

3786 primary_key_identity = getter(primary_key_identity) 

3787 

3788 mapper: Optional[Mapper[_O]] = inspect(entity) 

3789 

3790 if mapper is None or not mapper.is_mapper: 

3791 raise sa_exc.ArgumentError( 

3792 "Expected mapped class or mapper, got: %r" % entity 

3793 ) 

3794 

3795 is_dict = isinstance(primary_key_identity, dict) 

3796 if not is_dict: 

3797 primary_key_identity = util.to_list( 

3798 primary_key_identity, default=[None] 

3799 ) 

3800 

3801 if len(primary_key_identity) != len(mapper.primary_key): 

3802 raise sa_exc.InvalidRequestError( 

3803 "Incorrect number of values in identifier to formulate " 

3804 "primary key for session.get(); primary key columns " 

3805 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3806 ) 

3807 

3808 if is_dict: 

3809 pk_synonyms = mapper._pk_synonyms 

3810 

3811 if pk_synonyms: 

3812 correct_keys = set(pk_synonyms).intersection( 

3813 primary_key_identity 

3814 ) 

3815 

3816 if correct_keys: 

3817 primary_key_identity = dict(primary_key_identity) 

3818 for k in correct_keys: 

3819 primary_key_identity[pk_synonyms[k]] = ( 

3820 primary_key_identity[k] 

3821 ) 

3822 

3823 try: 

3824 primary_key_identity = list( 

3825 primary_key_identity[prop.key] 

3826 for prop in mapper._identity_key_props 

3827 ) 

3828 

3829 except KeyError as err: 

3830 raise sa_exc.InvalidRequestError( 

3831 "Incorrect names of values in identifier to formulate " 

3832 "primary key for session.get(); primary key attribute " 

3833 "names are %s (synonym names are also accepted)" 

3834 % ",".join( 

3835 "'%s'" % prop.key 

3836 for prop in mapper._identity_key_props 

3837 ) 

3838 ) from err 

3839 

3840 for_update_arg = ForUpdateArg._from_argument(with_for_update) 

3841 

3842 if ( 

3843 not populate_existing 

3844 and not mapper.always_refresh 

3845 and for_update_arg is None 

3846 ): 

3847 instance = self._identity_lookup( 

3848 mapper, 

3849 primary_key_identity, 

3850 identity_token=identity_token, 

3851 execution_options=execution_options, 

3852 bind_arguments=bind_arguments, 

3853 ) 

3854 

3855 if instance is not None: 

3856 # reject calls for id in identity map but class 

3857 # mismatch. 

3858 if not isinstance(instance, mapper.class_): 

3859 return None 

3860 return instance 

3861 

3862 # TODO: this was being tested before, but this is not possible 

3863 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3864 

3865 # set_label_style() not strictly necessary, however this will ensure 

3866 # that tablename_colname style is used which at the moment is 

3867 # asserted in a lot of unit tests :) 

3868 

3869 load_options = context.QueryContext.default_load_options 

3870 

3871 if populate_existing: 

3872 load_options += {"_populate_existing": populate_existing} 

3873 statement = sql.select(mapper).set_label_style( 

3874 LABEL_STYLE_TABLENAME_PLUS_COL 

3875 ) 

3876 if for_update_arg is not None: 

3877 statement._for_update_arg = for_update_arg 

3878 

3879 if options: 

3880 statement = statement.options(*options) 

3881 return db_load_fn( 

3882 self, 

3883 statement, 

3884 primary_key_identity, 

3885 load_options=load_options, 

3886 identity_token=identity_token, 

3887 execution_options=execution_options, 

3888 bind_arguments=bind_arguments, 

3889 ) 

3890 

3891 def merge( 

3892 self, 

3893 instance: _O, 

3894 *, 

3895 load: bool = True, 

3896 options: Optional[Sequence[ORMOption]] = None, 

3897 ) -> _O: 

3898 """Copy the state of a given instance into a corresponding instance 

3899 within this :class:`.Session`. 

3900 

3901 :meth:`.Session.merge` examines the primary key attributes of the 

3902 source instance, and attempts to reconcile it with an instance of the 

3903 same primary key in the session. If not found locally, it attempts 

3904 to load the object from the database based on primary key, and if 

3905 none can be located, creates a new instance. The state of each 

3906 attribute on the source instance is then copied to the target 

3907 instance. The resulting target instance is then returned by the 

3908 method; the original source instance is left unmodified, and 

3909 un-associated with the :class:`.Session` if not already. 

3910 

3911 This operation cascades to associated instances if the association is 

3912 mapped with ``cascade="merge"``. 

3913 

3914 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3915 

3916 :param instance: Instance to be merged. 

3917 :param load: Boolean, when False, :meth:`.merge` switches into 

3918 a "high performance" mode which causes it to forego emitting history 

3919 events as well as all database access. This flag is used for 

3920 cases such as transferring graphs of objects into a :class:`.Session` 

3921 from a second level cache, or to transfer just-loaded objects 

3922 into the :class:`.Session` owned by a worker thread or process 

3923 without re-querying the database. 

3924 

3925 The ``load=False`` use case adds the caveat that the given 

3926 object has to be in a "clean" state, that is, has no pending changes 

3927 to be flushed - even if the incoming object is detached from any 

3928 :class:`.Session`. This is so that when 

3929 the merge operation populates local attributes and 

3930 cascades to related objects and 

3931 collections, the values can be "stamped" onto the 

3932 target object as is, without generating any history or attribute 

3933 events, and without the need to reconcile the incoming data with 

3934 any existing related objects or collections that might not 

3935 be loaded. The resulting objects from ``load=False`` are always 

3936 produced as "clean", so it is only appropriate that the given objects 

3937 should be "clean" as well, else this suggests a mis-use of the 

3938 method. 

3939 :param options: optional sequence of loader options which will be 

3940 applied to the :meth:`_orm.Session.get` method when the merge 

3941 operation loads the existing version of the object from the database. 

3942 

3943 .. versionadded:: 1.4.24 

3944 

3945 

3946 .. seealso:: 

3947 

3948 :func:`.make_transient_to_detached` - provides for an alternative 

3949 means of "merging" a single object into the :class:`.Session` 

3950 

3951 """ 

3952 

3953 if self._warn_on_events: 

3954 self._flush_warning("Session.merge()") 

3955 

3956 _recursive: Dict[InstanceState[Any], object] = {} 

3957 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object] = {} 

3958 

3959 if load: 

3960 # flush current contents if we expect to load data 

3961 self._autoflush() 

3962 

3963 object_mapper(instance) # verify mapped 

3964 autoflush = self.autoflush 

3965 try: 

3966 self.autoflush = False 

3967 return self._merge( 

3968 attributes.instance_state(instance), 

3969 attributes.instance_dict(instance), 

3970 load=load, 

3971 options=options, 

3972 _recursive=_recursive, 

3973 _resolve_conflict_map=_resolve_conflict_map, 

3974 ) 

3975 finally: 

3976 self.autoflush = autoflush 

3977 

3978 def _merge( 

3979 self, 

3980 state: InstanceState[_O], 

3981 state_dict: _InstanceDict, 

3982 *, 

3983 options: Optional[Sequence[ORMOption]] = None, 

3984 load: bool, 

3985 _recursive: Dict[Any, object], 

3986 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

3987 ) -> _O: 

3988 mapper: Mapper[_O] = _state_mapper(state) 

3989 if state in _recursive: 

3990 return cast(_O, _recursive[state]) 

3991 

3992 new_instance = False 

3993 key = state.key 

3994 

3995 merged: Optional[_O] 

3996 

3997 if key is None: 

3998 if state in self._new: 

3999 util.warn( 

4000 "Instance %s is already pending in this Session yet is " 

4001 "being merged again; this is probably not what you want " 

4002 "to do" % state_str(state) 

4003 ) 

4004 

4005 if not load: 

4006 raise sa_exc.InvalidRequestError( 

4007 "merge() with load=False option does not support " 

4008 "objects transient (i.e. unpersisted) objects. flush() " 

4009 "all changes on mapped instances before merging with " 

4010 "load=False." 

4011 ) 

4012 key = mapper._identity_key_from_state(state) 

4013 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4014 1 

4015 ] and ( 

4016 not _none_set.intersection(key[1]) 

4017 or ( 

4018 mapper.allow_partial_pks 

4019 and not _none_set.issuperset(key[1]) 

4020 ) 

4021 ) 

4022 else: 

4023 key_is_persistent = True 

4024 

4025 merged = self.identity_map.get(key) 

4026 

4027 if merged is None: 

4028 if key_is_persistent and key in _resolve_conflict_map: 

4029 merged = cast(_O, _resolve_conflict_map[key]) 

4030 

4031 elif not load: 

4032 if state.modified: 

4033 raise sa_exc.InvalidRequestError( 

4034 "merge() with load=False option does not support " 

4035 "objects marked as 'dirty'. flush() all changes on " 

4036 "mapped instances before merging with load=False." 

4037 ) 

4038 merged = mapper.class_manager.new_instance() 

4039 merged_state = attributes.instance_state(merged) 

4040 merged_state.key = key 

4041 self._update_impl(merged_state) 

4042 new_instance = True 

4043 

4044 elif key_is_persistent: 

4045 merged = self.get( 

4046 mapper.class_, 

4047 key[1], 

4048 identity_token=key[2], 

4049 options=options, 

4050 ) 

4051 

4052 if merged is None: 

4053 merged = mapper.class_manager.new_instance() 

4054 merged_state = attributes.instance_state(merged) 

4055 merged_dict = attributes.instance_dict(merged) 

4056 new_instance = True 

4057 self._save_or_update_state(merged_state) 

4058 else: 

4059 merged_state = attributes.instance_state(merged) 

4060 merged_dict = attributes.instance_dict(merged) 

4061 

4062 _recursive[state] = merged 

4063 _resolve_conflict_map[key] = merged 

4064 

4065 # check that we didn't just pull the exact same 

4066 # state out. 

4067 if state is not merged_state: 

4068 # version check if applicable 

4069 if mapper.version_id_col is not None: 

4070 existing_version = mapper._get_state_attr_by_column( 

4071 state, 

4072 state_dict, 

4073 mapper.version_id_col, 

4074 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4075 ) 

4076 

4077 merged_version = mapper._get_state_attr_by_column( 

4078 merged_state, 

4079 merged_dict, 

4080 mapper.version_id_col, 

4081 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4082 ) 

4083 

4084 if ( 

4085 existing_version 

4086 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4087 and merged_version 

4088 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4089 and existing_version != merged_version 

4090 ): 

4091 raise exc.StaleDataError( 

4092 "Version id '%s' on merged state %s " 

4093 "does not match existing version '%s'. " 

4094 "Leave the version attribute unset when " 

4095 "merging to update the most recent version." 

4096 % ( 

4097 existing_version, 

4098 state_str(merged_state), 

4099 merged_version, 

4100 ) 

4101 ) 

4102 

4103 merged_state.load_path = state.load_path 

4104 merged_state.load_options = state.load_options 

4105 

4106 # since we are copying load_options, we need to copy 

4107 # the callables_ that would have been generated by those 

4108 # load_options. 

4109 # assumes that the callables we put in state.callables_ 

4110 # are not instance-specific (which they should not be) 

4111 merged_state._copy_callables(state) 

4112 

4113 for prop in mapper.iterate_properties: 

4114 prop.merge( 

4115 self, 

4116 state, 

4117 state_dict, 

4118 merged_state, 

4119 merged_dict, 

4120 load, 

4121 _recursive, 

4122 _resolve_conflict_map, 

4123 ) 

4124 

4125 if not load: 

4126 # remove any history 

4127 merged_state._commit_all(merged_dict, self.identity_map) 

4128 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4129 merged_state, None 

4130 ) 

4131 

4132 if new_instance: 

4133 merged_state.manager.dispatch.load(merged_state, None) 

4134 

4135 return merged 

4136 

4137 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4138 if not self.identity_map.contains_state(state): 

4139 raise sa_exc.InvalidRequestError( 

4140 "Instance '%s' is not persistent within this Session" 

4141 % state_str(state) 

4142 ) 

4143 

4144 def _save_impl(self, state: InstanceState[Any]) -> None: 

4145 if state.key is not None: 

4146 raise sa_exc.InvalidRequestError( 

4147 "Object '%s' already has an identity - " 

4148 "it can't be registered as pending" % state_str(state) 

4149 ) 

4150 

4151 obj = state.obj() 

4152 to_attach = self._before_attach(state, obj) 

4153 if state not in self._new: 

4154 self._new[state] = obj 

4155 state.insert_order = len(self._new) 

4156 if to_attach: 

4157 self._after_attach(state, obj) 

4158 

4159 def _update_impl( 

4160 self, state: InstanceState[Any], revert_deletion: bool = False 

4161 ) -> None: 

4162 if state.key is None: 

4163 raise sa_exc.InvalidRequestError( 

4164 "Instance '%s' is not persisted" % state_str(state) 

4165 ) 

4166 

4167 if state._deleted: 

4168 if revert_deletion: 

4169 if not state._attached: 

4170 return 

4171 del state._deleted 

4172 else: 

4173 raise sa_exc.InvalidRequestError( 

4174 "Instance '%s' has been deleted. " 

4175 "Use the make_transient() " 

4176 "function to send this object back " 

4177 "to the transient state." % state_str(state) 

4178 ) 

4179 

4180 obj = state.obj() 

4181 

4182 # check for late gc 

4183 if obj is None: 

4184 return 

4185 

4186 to_attach = self._before_attach(state, obj) 

4187 

4188 self._deleted.pop(state, None) 

4189 if revert_deletion: 

4190 self.identity_map.replace(state) 

4191 else: 

4192 self.identity_map.add(state) 

4193 

4194 if to_attach: 

4195 self._after_attach(state, obj) 

4196 elif revert_deletion: 

4197 self.dispatch.deleted_to_persistent(self, state) 

4198 

4199 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4200 if state.key is None: 

4201 self._save_impl(state) 

4202 else: 

4203 self._update_impl(state) 

4204 

4205 def enable_relationship_loading(self, obj: object) -> None: 

4206 """Associate an object with this :class:`.Session` for related 

4207 object loading. 

4208 

4209 .. warning:: 

4210 

4211 :meth:`.enable_relationship_loading` exists to serve special 

4212 use cases and is not recommended for general use. 

4213 

4214 Accesses of attributes mapped with :func:`_orm.relationship` 

4215 will attempt to load a value from the database using this 

4216 :class:`.Session` as the source of connectivity. The values 

4217 will be loaded based on foreign key and primary key values 

4218 present on this object - if not present, then those relationships 

4219 will be unavailable. 

4220 

4221 The object will be attached to this session, but will 

4222 **not** participate in any persistence operations; its state 

4223 for almost all purposes will remain either "transient" or 

4224 "detached", except for the case of relationship loading. 

4225 

4226 Also note that backrefs will often not work as expected. 

4227 Altering a relationship-bound attribute on the target object 

4228 may not fire off a backref event, if the effective value 

4229 is what was already loaded from a foreign-key-holding value. 

4230 

4231 The :meth:`.Session.enable_relationship_loading` method is 

4232 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4233 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4234 an object to remain transient while still being able to load 

4235 related items. 

4236 

4237 To make a transient object associated with a :class:`.Session` 

4238 via :meth:`.Session.enable_relationship_loading` pending, add 

4239 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4240 If the object instead represents an existing identity in the database, 

4241 it should be merged using :meth:`.Session.merge`. 

4242 

4243 :meth:`.Session.enable_relationship_loading` does not improve 

4244 behavior when the ORM is used normally - object references should be 

4245 constructed at the object level, not at the foreign key level, so 

4246 that they are present in an ordinary way before flush() 

4247 proceeds. This method is not intended for general use. 

4248 

4249 .. seealso:: 

4250 

4251 :paramref:`_orm.relationship.load_on_pending` - this flag 

4252 allows per-relationship loading of many-to-ones on items that 

4253 are pending. 

4254 

4255 :func:`.make_transient_to_detached` - allows for an object to 

4256 be added to a :class:`.Session` without SQL emitted, which then 

4257 will unexpire attributes on access. 

4258 

4259 """ 

4260 try: 

4261 state = attributes.instance_state(obj) 

4262 except exc.NO_STATE as err: 

4263 raise exc.UnmappedInstanceError(obj) from err 

4264 

4265 to_attach = self._before_attach(state, obj) 

4266 state._load_pending = True 

4267 if to_attach: 

4268 self._after_attach(state, obj) 

4269 

4270 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4271 self._autobegin_t() 

4272 

4273 if state.session_id == self.hash_key: 

4274 return False 

4275 

4276 if state.session_id and state.session_id in _sessions: 

4277 raise sa_exc.InvalidRequestError( 

4278 "Object '%s' is already attached to session '%s' " 

4279 "(this is '%s')" 

4280 % (state_str(state), state.session_id, self.hash_key) 

4281 ) 

4282 

4283 self.dispatch.before_attach(self, state) 

4284 

4285 return True 

4286 

4287 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4288 state.session_id = self.hash_key 

4289 if state.modified and state._strong_obj is None: 

4290 state._strong_obj = obj 

4291 self.dispatch.after_attach(self, state) 

4292 

4293 if state.key: 

4294 self.dispatch.detached_to_persistent(self, state) 

4295 else: 

4296 self.dispatch.transient_to_pending(self, state) 

4297 

4298 def __contains__(self, instance: object) -> bool: 

4299 """Return True if the instance is associated with this session. 

4300 

4301 The instance may be pending or persistent within the Session for a 

4302 result of True. 

4303 

4304 """ 

4305 try: 

4306 state = attributes.instance_state(instance) 

4307 except exc.NO_STATE as err: 

4308 raise exc.UnmappedInstanceError(instance) from err 

4309 return self._contains_state(state) 

4310 

4311 def __iter__(self) -> Iterator[object]: 

4312 """Iterate over all pending or persistent instances within this 

4313 Session. 

4314 

4315 """ 

4316 return iter( 

4317 list(self._new.values()) + list(self.identity_map.values()) 

4318 ) 

4319 

4320 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4321 return state in self._new or self.identity_map.contains_state(state) 

4322 

4323 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4324 """Flush all the object changes to the database. 

4325 

4326 Writes out all pending object creations, deletions and modifications 

4327 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4328 automatically ordered by the Session's unit of work dependency 

4329 solver. 

4330 

4331 Database operations will be issued in the current transactional 

4332 context and do not affect the state of the transaction, unless an 

4333 error occurs, in which case the entire transaction is rolled back. 

4334 You may flush() as often as you like within a transaction to move 

4335 changes from Python to the database's transaction buffer. 

4336 

4337 :param objects: Optional; restricts the flush operation to operate 

4338 only on elements that are in the given collection. 

4339 

4340 This feature is for an extremely narrow set of use cases where 

4341 particular objects may need to be operated upon before the 

4342 full flush() occurs. It is not intended for general use. 

4343 

4344 """ 

4345 

4346 if self._flushing: 

4347 raise sa_exc.InvalidRequestError("Session is already flushing") 

4348 

4349 if self._is_clean(): 

4350 return 

4351 try: 

4352 self._flushing = True 

4353 self._flush(objects) 

4354 finally: 

4355 self._flushing = False 

4356 

4357 def _flush_warning(self, method: Any) -> None: 

4358 util.warn( 

4359 "Usage of the '%s' operation is not currently supported " 

4360 "within the execution stage of the flush process. " 

4361 "Results may not be consistent. Consider using alternative " 

4362 "event listeners or connection-level operations instead." % method 

4363 ) 

4364 

4365 def _is_clean(self) -> bool: 

4366 return ( 

4367 not self.identity_map.check_modified() 

4368 and not self._deleted 

4369 and not self._new 

4370 ) 

4371 

4372 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4373 dirty = self._dirty_states 

4374 if not dirty and not self._deleted and not self._new: 

4375 self.identity_map._modified.clear() 

4376 return 

4377 

4378 flush_context = UOWTransaction(self) 

4379 

4380 if self.dispatch.before_flush: 

4381 self.dispatch.before_flush(self, flush_context, objects) 

4382 # re-establish "dirty states" in case the listeners 

4383 # added 

4384 dirty = self._dirty_states 

4385 

4386 deleted = set(self._deleted) 

4387 new = set(self._new) 

4388 

4389 dirty = set(dirty).difference(deleted) 

4390 

4391 # create the set of all objects we want to operate upon 

4392 if objects: 

4393 # specific list passed in 

4394 objset = set() 

4395 for o in objects: 

4396 try: 

4397 state = attributes.instance_state(o) 

4398 

4399 except exc.NO_STATE as err: 

4400 raise exc.UnmappedInstanceError(o) from err 

4401 objset.add(state) 

4402 else: 

4403 objset = None 

4404 

4405 # store objects whose fate has been decided 

4406 processed = set() 

4407 

4408 # put all saves/updates into the flush context. detect top-level 

4409 # orphans and throw them into deleted. 

4410 if objset: 

4411 proc = new.union(dirty).intersection(objset).difference(deleted) 

4412 else: 

4413 proc = new.union(dirty).difference(deleted) 

4414 

4415 for state in proc: 

4416 is_orphan = _state_mapper(state)._is_orphan(state) 

4417 

4418 is_persistent_orphan = is_orphan and state.has_identity 

4419 

4420 if ( 

4421 is_orphan 

4422 and not is_persistent_orphan 

4423 and state._orphaned_outside_of_session 

4424 ): 

4425 self._expunge_states([state]) 

4426 else: 

4427 _reg = flush_context.register_object( 

4428 state, isdelete=is_persistent_orphan 

4429 ) 

4430 assert _reg, "Failed to add object to the flush context!" 

4431 processed.add(state) 

4432 

4433 # put all remaining deletes into the flush context. 

4434 if objset: 

4435 proc = deleted.intersection(objset).difference(processed) 

4436 else: 

4437 proc = deleted.difference(processed) 

4438 for state in proc: 

4439 _reg = flush_context.register_object(state, isdelete=True) 

4440 assert _reg, "Failed to add object to the flush context!" 

4441 

4442 if not flush_context.has_work: 

4443 return 

4444 

4445 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4446 try: 

4447 self._warn_on_events = True 

4448 try: 

4449 flush_context.execute() 

4450 finally: 

4451 self._warn_on_events = False 

4452 

4453 self.dispatch.after_flush(self, flush_context) 

4454 

4455 flush_context.finalize_flush_changes() 

4456 

4457 if not objects and self.identity_map._modified: 

4458 len_ = len(self.identity_map._modified) 

4459 

4460 statelib.InstanceState._commit_all_states( 

4461 [ 

4462 (state, state.dict) 

4463 for state in self.identity_map._modified 

4464 ], 

4465 instance_dict=self.identity_map, 

4466 ) 

4467 util.warn( 

4468 "Attribute history events accumulated on %d " 

4469 "previously clean instances " 

4470 "within inner-flush event handlers have been " 

4471 "reset, and will not result in database updates. " 

4472 "Consider using set_committed_value() within " 

4473 "inner-flush event handlers to avoid this warning." % len_ 

4474 ) 

4475 

4476 # useful assertions: 

4477 # if not objects: 

4478 # assert not self.identity_map._modified 

4479 # else: 

4480 # assert self.identity_map._modified == \ 

4481 # self.identity_map._modified.difference(objects) 

4482 

4483 self.dispatch.after_flush_postexec(self, flush_context) 

4484 

4485 transaction.commit() 

4486 

4487 except: 

4488 with util.safe_reraise(): 

4489 transaction.rollback(_capture_exception=True) 

4490 

4491 def bulk_save_objects( 

4492 self, 

4493 objects: Iterable[object], 

4494 return_defaults: bool = False, 

4495 update_changed_only: bool = True, 

4496 preserve_order: bool = True, 

4497 ) -> None: 

4498 """Perform a bulk save of the given list of objects. 

4499 

4500 .. legacy:: 

4501 

4502 This method is a legacy feature as of the 2.0 series of 

4503 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4504 the sections :ref:`orm_queryguide_bulk_insert` and 

4505 :ref:`orm_queryguide_bulk_update`. 

4506 

4507 For general INSERT and UPDATE of existing ORM mapped objects, 

4508 prefer standard :term:`unit of work` data management patterns, 

4509 introduced in the :ref:`unified_tutorial` at 

4510 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4511 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4512 which solves previous issues of bulk INSERT slowness. 

4513 

4514 :param objects: a sequence of mapped object instances. The mapped 

4515 objects are persisted as is, and are **not** associated with the 

4516 :class:`.Session` afterwards. 

4517 

4518 For each object, whether the object is sent as an INSERT or an 

4519 UPDATE is dependent on the same rules used by the :class:`.Session` 

4520 in traditional operation; if the object has the 

4521 :attr:`.InstanceState.key` 

4522 attribute set, then the object is assumed to be "detached" and 

4523 will result in an UPDATE. Otherwise, an INSERT is used. 

4524 

4525 In the case of an UPDATE, statements are grouped based on which 

4526 attributes have changed, and are thus to be the subject of each 

4527 SET clause. If ``update_changed_only`` is False, then all 

4528 attributes present within each object are applied to the UPDATE 

4529 statement, which may help in allowing the statements to be grouped 

4530 together into a larger executemany(), and will also reduce the 

4531 overhead of checking history on attributes. 

4532 

4533 :param return_defaults: when True, rows that are missing values which 

4534 generate defaults, namely integer primary key defaults and sequences, 

4535 will be inserted **one at a time**, so that the primary key value 

4536 is available. In particular this will allow joined-inheritance 

4537 and other multi-table mappings to insert correctly without the need 

4538 to provide primary key values ahead of time; however, 

4539 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4540 reduces the performance gains** of the method overall. It is strongly 

4541 advised to please use the standard :meth:`_orm.Session.add_all` 

4542 approach. 

4543 

4544 :param update_changed_only: when True, UPDATE statements are rendered 

4545 based on those attributes in each state that have logged changes. 

4546 When False, all attributes present are rendered into the SET clause 

4547 with the exception of primary key attributes. 

4548 

4549 :param preserve_order: when True, the order of inserts and updates 

4550 matches exactly the order in which the objects are given. When 

4551 False, common types of objects are grouped into inserts 

4552 and updates, to allow for more batching opportunities. 

4553 

4554 .. seealso:: 

4555 

4556 :doc:`queryguide/dml` 

4557 

4558 :meth:`.Session.bulk_insert_mappings` 

4559 

4560 :meth:`.Session.bulk_update_mappings` 

4561 

4562 """ 

4563 

4564 obj_states: Iterable[InstanceState[Any]] 

4565 

4566 obj_states = (attributes.instance_state(obj) for obj in objects) 

4567 

4568 if not preserve_order: 

4569 # the purpose of this sort is just so that common mappers 

4570 # and persistence states are grouped together, so that groupby 

4571 # will return a single group for a particular type of mapper. 

4572 # it's not trying to be deterministic beyond that. 

4573 obj_states = sorted( 

4574 obj_states, 

4575 key=lambda state: (id(state.mapper), state.key is not None), 

4576 ) 

4577 

4578 def grouping_key( 

4579 state: InstanceState[_O], 

4580 ) -> Tuple[Mapper[_O], bool]: 

4581 return (state.mapper, state.key is not None) 

4582 

4583 for (mapper, isupdate), states in itertools.groupby( 

4584 obj_states, grouping_key 

4585 ): 

4586 self._bulk_save_mappings( 

4587 mapper, 

4588 states, 

4589 isupdate=isupdate, 

4590 isstates=True, 

4591 return_defaults=return_defaults, 

4592 update_changed_only=update_changed_only, 

4593 render_nulls=False, 

4594 ) 

4595 

4596 def bulk_insert_mappings( 

4597 self, 

4598 mapper: _EntityBindKey[Any], 

4599 mappings: Iterable[Dict[str, Any]], 

4600 return_defaults: bool = False, 

4601 render_nulls: bool = False, 

4602 ) -> None: 

4603 """Perform a bulk insert of the given list of mapping dictionaries. 

4604 

4605 .. legacy:: 

4606 

4607 This method is a legacy feature as of the 2.0 series of 

4608 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4609 the sections :ref:`orm_queryguide_bulk_insert` and 

4610 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4611 implementation details with this method and adds new features 

4612 as well. 

4613 

4614 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4615 object, 

4616 representing the single kind of object represented within the mapping 

4617 list. 

4618 

4619 :param mappings: a sequence of dictionaries, each one containing the 

4620 state of the mapped row to be inserted, in terms of the attribute 

4621 names on the mapped class. If the mapping refers to multiple tables, 

4622 such as a joined-inheritance mapping, each dictionary must contain all 

4623 keys to be populated into all tables. 

4624 

4625 :param return_defaults: when True, the INSERT process will be altered 

4626 to ensure that newly generated primary key values will be fetched. 

4627 The rationale for this parameter is typically to enable 

4628 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4629 be bulk inserted. 

4630 

4631 .. note:: for backends that don't support RETURNING, the 

4632 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4633 parameter can significantly decrease performance as INSERT 

4634 statements can no longer be batched. See 

4635 :ref:`engine_insertmanyvalues` 

4636 for background on which backends are affected. 

4637 

4638 :param render_nulls: When True, a value of ``None`` will result 

4639 in a NULL value being included in the INSERT statement, rather 

4640 than the column being omitted from the INSERT. This allows all 

4641 the rows being INSERTed to have the identical set of columns which 

4642 allows the full set of rows to be batched to the DBAPI. Normally, 

4643 each column-set that contains a different combination of NULL values 

4644 than the previous row must omit a different series of columns from 

4645 the rendered INSERT statement, which means it must be emitted as a 

4646 separate statement. By passing this flag, the full set of rows 

4647 are guaranteed to be batchable into one batch; the cost however is 

4648 that server-side defaults which are invoked by an omitted column will 

4649 be skipped, so care must be taken to ensure that these are not 

4650 necessary. 

4651 

4652 .. warning:: 

4653 

4654 When this flag is set, **server side default SQL values will 

4655 not be invoked** for those columns that are inserted as NULL; 

4656 the NULL value will be sent explicitly. Care must be taken 

4657 to ensure that no server-side default functions need to be 

4658 invoked for the operation as a whole. 

4659 

4660 .. seealso:: 

4661 

4662 :doc:`queryguide/dml` 

4663 

4664 :meth:`.Session.bulk_save_objects` 

4665 

4666 :meth:`.Session.bulk_update_mappings` 

4667 

4668 """ 

4669 self._bulk_save_mappings( 

4670 mapper, 

4671 mappings, 

4672 isupdate=False, 

4673 isstates=False, 

4674 return_defaults=return_defaults, 

4675 update_changed_only=False, 

4676 render_nulls=render_nulls, 

4677 ) 

4678 

4679 def bulk_update_mappings( 

4680 self, mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]] 

4681 ) -> None: 

4682 """Perform a bulk update of the given list of mapping dictionaries. 

4683 

4684 .. legacy:: 

4685 

4686 This method is a legacy feature as of the 2.0 series of 

4687 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4688 the sections :ref:`orm_queryguide_bulk_insert` and 

4689 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4690 implementation details with this method and adds new features 

4691 as well. 

4692 

4693 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4694 object, 

4695 representing the single kind of object represented within the mapping 

4696 list. 

4697 

4698 :param mappings: a sequence of dictionaries, each one containing the 

4699 state of the mapped row to be updated, in terms of the attribute names 

4700 on the mapped class. If the mapping refers to multiple tables, such 

4701 as a joined-inheritance mapping, each dictionary may contain keys 

4702 corresponding to all tables. All those keys which are present and 

4703 are not part of the primary key are applied to the SET clause of the 

4704 UPDATE statement; the primary key values, which are required, are 

4705 applied to the WHERE clause. 

4706 

4707 

4708 .. seealso:: 

4709 

4710 :doc:`queryguide/dml` 

4711 

4712 :meth:`.Session.bulk_insert_mappings` 

4713 

4714 :meth:`.Session.bulk_save_objects` 

4715 

4716 """ 

4717 self._bulk_save_mappings( 

4718 mapper, 

4719 mappings, 

4720 isupdate=True, 

4721 isstates=False, 

4722 return_defaults=False, 

4723 update_changed_only=False, 

4724 render_nulls=False, 

4725 ) 

4726 

4727 def _bulk_save_mappings( 

4728 self, 

4729 mapper: _EntityBindKey[_O], 

4730 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4731 *, 

4732 isupdate: bool, 

4733 isstates: bool, 

4734 return_defaults: bool, 

4735 update_changed_only: bool, 

4736 render_nulls: bool, 

4737 ) -> None: 

4738 mapper = _class_to_mapper(mapper) 

4739 self._flushing = True 

4740 

4741 transaction = self._autobegin_t()._begin() 

4742 try: 

4743 if isupdate: 

4744 bulk_persistence._bulk_update( 

4745 mapper, 

4746 mappings, 

4747 transaction, 

4748 isstates=isstates, 

4749 update_changed_only=update_changed_only, 

4750 ) 

4751 else: 

4752 bulk_persistence._bulk_insert( 

4753 mapper, 

4754 mappings, 

4755 transaction, 

4756 isstates=isstates, 

4757 return_defaults=return_defaults, 

4758 render_nulls=render_nulls, 

4759 ) 

4760 transaction.commit() 

4761 

4762 except: 

4763 with util.safe_reraise(): 

4764 transaction.rollback(_capture_exception=True) 

4765 finally: 

4766 self._flushing = False 

4767 

4768 def is_modified( 

4769 self, instance: object, include_collections: bool = True 

4770 ) -> bool: 

4771 r"""Return ``True`` if the given instance has locally 

4772 modified attributes. 

4773 

4774 This method retrieves the history for each instrumented 

4775 attribute on the instance and performs a comparison of the current 

4776 value to its previously flushed or committed value, if any. 

4777 

4778 It is in effect a more expensive and accurate 

4779 version of checking for the given instance in the 

4780 :attr:`.Session.dirty` collection; a full test for 

4781 each attribute's net "dirty" status is performed. 

4782 

4783 E.g.:: 

4784 

4785 return session.is_modified(someobject) 

4786 

4787 A few caveats to this method apply: 

4788 

4789 * Instances present in the :attr:`.Session.dirty` collection may 

4790 report ``False`` when tested with this method. This is because 

4791 the object may have received change events via attribute mutation, 

4792 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4793 is the same as that loaded from the database, resulting in no net 

4794 change here. 

4795 * Scalar attributes may not have recorded the previously set 

4796 value when a new value was applied, if the attribute was not loaded, 

4797 or was expired, at the time the new value was received - in these 

4798 cases, the attribute is assumed to have a change, even if there is 

4799 ultimately no net change against its database value. SQLAlchemy in 

4800 most cases does not need the "old" value when a set event occurs, so 

4801 it skips the expense of a SQL call if the old value isn't present, 

4802 based on the assumption that an UPDATE of the scalar value is 

4803 usually needed, and in those few cases where it isn't, is less 

4804 expensive on average than issuing a defensive SELECT. 

4805 

4806 The "old" value is fetched unconditionally upon set only if the 

4807 attribute container has the ``active_history`` flag set to ``True``. 

4808 This flag is set typically for primary key attributes and scalar 

4809 object references that are not a simple many-to-one. To set this 

4810 flag for any arbitrary mapped column, use the ``active_history`` 

4811 argument with :func:`.column_property`. 

4812 

4813 :param instance: mapped instance to be tested for pending changes. 

4814 :param include_collections: Indicates if multivalued collections 

4815 should be included in the operation. Setting this to ``False`` is a 

4816 way to detect only local-column based properties (i.e. scalar columns 

4817 or many-to-one foreign keys) that would result in an UPDATE for this 

4818 instance upon flush. 

4819 

4820 """ 

4821 state = object_state(instance) 

4822 

4823 if not state.modified: 

4824 return False 

4825 

4826 dict_ = state.dict 

4827 

4828 for attr in state.manager.attributes: 

4829 if ( 

4830 not include_collections 

4831 and hasattr(attr.impl, "get_collection") 

4832 ) or not hasattr(attr.impl, "get_history"): 

4833 continue 

4834 

4835 added, unchanged, deleted = attr.impl.get_history( 

4836 state, dict_, passive=PassiveFlag.NO_CHANGE 

4837 ) 

4838 

4839 if added or deleted: 

4840 return True 

4841 else: 

4842 return False 

4843 

4844 @property 

4845 def is_active(self) -> bool: 

4846 """True if this :class:`.Session` not in "partial rollback" state. 

4847 

4848 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4849 a new transaction immediately, so this attribute will be False 

4850 when the :class:`_orm.Session` is first instantiated. 

4851 

4852 "partial rollback" state typically indicates that the flush process 

4853 of the :class:`_orm.Session` has failed, and that the 

4854 :meth:`_orm.Session.rollback` method must be emitted in order to 

4855 fully roll back the transaction. 

4856 

4857 If this :class:`_orm.Session` is not in a transaction at all, the 

4858 :class:`_orm.Session` will autobegin when it is first used, so in this 

4859 case :attr:`_orm.Session.is_active` will return True. 

4860 

4861 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4862 and that transaction has not been rolled back internally, the 

4863 :attr:`_orm.Session.is_active` will also return True. 

4864 

4865 .. seealso:: 

4866 

4867 :ref:`faq_session_rollback` 

4868 

4869 :meth:`_orm.Session.in_transaction` 

4870 

4871 """ 

4872 return self._transaction is None or self._transaction.is_active 

4873 

4874 @property 

4875 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4876 """The set of all persistent states considered dirty. 

4877 

4878 This method returns all states that were modified including 

4879 those that were possibly deleted. 

4880 

4881 """ 

4882 return self.identity_map._dirty_states() 

4883 

4884 @property 

4885 def dirty(self) -> IdentitySet: 

4886 """The set of all persistent instances considered dirty. 

4887 

4888 E.g.:: 

4889 

4890 some_mapped_object in session.dirty 

4891 

4892 Instances are considered dirty when they were modified but not 

4893 deleted. 

4894 

4895 Note that this 'dirty' calculation is 'optimistic'; most 

4896 attribute-setting or collection modification operations will 

4897 mark an instance as 'dirty' and place it in this set, even if 

4898 there is no net change to the attribute's value. At flush 

4899 time, the value of each attribute is compared to its 

4900 previously saved value, and if there's no net change, no SQL 

4901 operation will occur (this is a more expensive operation so 

4902 it's only done at flush time). 

4903 

4904 To check if an instance has actionable net changes to its 

4905 attributes, use the :meth:`.Session.is_modified` method. 

4906 

4907 """ 

4908 return IdentitySet( 

4909 [ 

4910 state.obj() 

4911 for state in self._dirty_states 

4912 if state not in self._deleted 

4913 ] 

4914 ) 

4915 

4916 @property 

4917 def deleted(self) -> IdentitySet: 

4918 "The set of all instances marked as 'deleted' within this ``Session``" 

4919 

4920 return util.IdentitySet(list(self._deleted.values())) 

4921 

4922 @property 

4923 def new(self) -> IdentitySet: 

4924 "The set of all instances marked as 'new' within this ``Session``." 

4925 

4926 return util.IdentitySet(list(self._new.values())) 

4927 

4928 

4929_S = TypeVar("_S", bound="Session") 

4930 

4931 

4932class sessionmaker(_SessionClassMethods, Generic[_S]): 

4933 """A configurable :class:`.Session` factory. 

4934 

4935 The :class:`.sessionmaker` factory generates new 

4936 :class:`.Session` objects when called, creating them given 

4937 the configurational arguments established here. 

4938 

4939 e.g.:: 

4940 

4941 from sqlalchemy import create_engine 

4942 from sqlalchemy.orm import sessionmaker 

4943 

4944 # an Engine, which the Session will use for connection 

4945 # resources 

4946 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

4947 

4948 Session = sessionmaker(engine) 

4949 

4950 with Session() as session: 

4951 session.add(some_object) 

4952 session.add(some_other_object) 

4953 session.commit() 

4954 

4955 Context manager use is optional; otherwise, the returned 

4956 :class:`_orm.Session` object may be closed explicitly via the 

4957 :meth:`_orm.Session.close` method. Using a 

4958 ``try:/finally:`` block is optional, however will ensure that the close 

4959 takes place even if there are database errors:: 

4960 

4961 session = Session() 

4962 try: 

4963 session.add(some_object) 

4964 session.add(some_other_object) 

4965 session.commit() 

4966 finally: 

4967 session.close() 

4968 

4969 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

4970 objects in the same way as an :class:`_engine.Engine` acts as a factory 

4971 for :class:`_engine.Connection` objects. In this way it also includes 

4972 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

4973 manager which both begins and commits a transaction, as well as closes 

4974 out the :class:`_orm.Session` when complete, rolling back the transaction 

4975 if any errors occur:: 

4976 

4977 Session = sessionmaker(engine) 

4978 

4979 with Session.begin() as session: 

4980 session.add(some_object) 

4981 session.add(some_other_object) 

4982 # commits transaction, closes session 

4983 

4984 .. versionadded:: 1.4 

4985 

4986 When calling upon :class:`_orm.sessionmaker` to construct a 

4987 :class:`_orm.Session`, keyword arguments may also be passed to the 

4988 method; these arguments will override that of the globally configured 

4989 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

4990 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

4991 bound to a specific :class:`_engine.Connection` procured from that engine:: 

4992 

4993 Session = sessionmaker(engine) 

4994 

4995 # bind an individual session to a connection 

4996 

4997 with engine.connect() as connection: 

4998 with Session(bind=connection) as session: 

4999 ... # work with session 

5000 

5001 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5002 can be used to specify additional keyword arguments to the factory, which 

5003 will take effect for subsequent :class:`.Session` objects generated. This 

5004 is usually used to associate one or more :class:`_engine.Engine` objects 

5005 with an existing 

5006 :class:`.sessionmaker` factory before it is first used:: 

5007 

5008 # application starts, sessionmaker does not have 

5009 # an engine bound yet 

5010 Session = sessionmaker() 

5011 

5012 # ... later, when an engine URL is read from a configuration 

5013 # file or other events allow the engine to be created 

5014 engine = create_engine("sqlite:///foo.db") 

5015 Session.configure(bind=engine) 

5016 

5017 sess = Session() 

5018 # work with session 

5019 

5020 .. seealso:: 

5021 

5022 :ref:`session_getting` - introductory text on creating 

5023 sessions using :class:`.sessionmaker`. 

5024 

5025 """ 

5026 

5027 class_: Type[_S] 

5028 

5029 @overload 

5030 def __init__( 

5031 self, 

5032 bind: Optional[_SessionBind] = ..., 

5033 *, 

5034 class_: Type[_S], 

5035 autoflush: bool = ..., 

5036 expire_on_commit: bool = ..., 

5037 info: Optional[_InfoType] = ..., 

5038 **kw: Any, 

5039 ): ... 

5040 

5041 @overload 

5042 def __init__( 

5043 self: "sessionmaker[Session]", 

5044 bind: Optional[_SessionBind] = ..., 

5045 *, 

5046 autoflush: bool = ..., 

5047 expire_on_commit: bool = ..., 

5048 info: Optional[_InfoType] = ..., 

5049 **kw: Any, 

5050 ): ... 

5051 

5052 def __init__( 

5053 self, 

5054 bind: Optional[_SessionBind] = None, 

5055 *, 

5056 class_: Type[_S] = Session, # type: ignore 

5057 autoflush: bool = True, 

5058 expire_on_commit: bool = True, 

5059 info: Optional[_InfoType] = None, 

5060 **kw: Any, 

5061 ): 

5062 r"""Construct a new :class:`.sessionmaker`. 

5063 

5064 All arguments here except for ``class_`` correspond to arguments 

5065 accepted by :class:`.Session` directly. See the 

5066 :meth:`.Session.__init__` docstring for more details on parameters. 

5067 

5068 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5069 with 

5070 which newly created :class:`.Session` objects will be associated. 

5071 :param class\_: class to use in order to create new :class:`.Session` 

5072 objects. Defaults to :class:`.Session`. 

5073 :param autoflush: The autoflush setting to use with newly created 

5074 :class:`.Session` objects. 

5075 

5076 .. seealso:: 

5077 

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

5079 

5080 :param expire_on_commit=True: the 

5081 :paramref:`_orm.Session.expire_on_commit` setting to use 

5082 with newly created :class:`.Session` objects. 

5083 

5084 :param info: optional dictionary of information that will be available 

5085 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5086 replaced, when the ``info`` parameter is specified to the specific 

5087 :class:`.Session` construction operation. 

5088 

5089 :param \**kw: all other keyword arguments are passed to the 

5090 constructor of newly created :class:`.Session` objects. 

5091 

5092 """ 

5093 kw["bind"] = bind 

5094 kw["autoflush"] = autoflush 

5095 kw["expire_on_commit"] = expire_on_commit 

5096 if info is not None: 

5097 kw["info"] = info 

5098 self.kw = kw 

5099 # make our own subclass of the given class, so that 

5100 # events can be associated with it specifically. 

5101 self.class_ = type(class_.__name__, (class_,), {}) 

5102 

5103 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5104 """Produce a context manager that both provides a new 

5105 :class:`_orm.Session` as well as a transaction that commits. 

5106 

5107 

5108 e.g.:: 

5109 

5110 Session = sessionmaker(some_engine) 

5111 

5112 with Session.begin() as session: 

5113 session.add(some_object) 

5114 

5115 # commits transaction, closes session 

5116 

5117 .. versionadded:: 1.4 

5118 

5119 

5120 """ 

5121 

5122 session = self() 

5123 return session._maker_context_manager() 

5124 

5125 def __call__(self, **local_kw: Any) -> _S: 

5126 """Produce a new :class:`.Session` object using the configuration 

5127 established in this :class:`.sessionmaker`. 

5128 

5129 In Python, the ``__call__`` method is invoked on an object when 

5130 it is "called" in the same way as a function:: 

5131 

5132 Session = sessionmaker(some_engine) 

5133 session = Session() # invokes sessionmaker.__call__() 

5134 

5135 """ 

5136 for k, v in self.kw.items(): 

5137 if k == "info" and "info" in local_kw: 

5138 d = v.copy() 

5139 d.update(local_kw["info"]) 

5140 local_kw["info"] = d 

5141 else: 

5142 local_kw.setdefault(k, v) 

5143 return self.class_(**local_kw) 

5144 

5145 def configure(self, **new_kw: Any) -> None: 

5146 """(Re)configure the arguments for this sessionmaker. 

5147 

5148 e.g.:: 

5149 

5150 Session = sessionmaker() 

5151 

5152 Session.configure(bind=create_engine("sqlite://")) 

5153 """ 

5154 self.kw.update(new_kw) 

5155 

5156 def __repr__(self) -> str: 

5157 return "%s(class_=%r, %s)" % ( 

5158 self.__class__.__name__, 

5159 self.class_.__name__, 

5160 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5161 ) 

5162 

5163 

5164def close_all_sessions() -> None: 

5165 """Close all sessions in memory. 

5166 

5167 This function consults a global registry of all :class:`.Session` objects 

5168 and calls :meth:`.Session.close` on them, which resets them to a clean 

5169 state. 

5170 

5171 This function is not for general use but may be useful for test suites 

5172 within the teardown scheme. 

5173 

5174 .. versionadded:: 1.3 

5175 

5176 """ 

5177 

5178 for sess in _sessions.values(): 

5179 sess.close() 

5180 

5181 

5182def make_transient(instance: object) -> None: 

5183 """Alter the state of the given instance so that it is :term:`transient`. 

5184 

5185 .. note:: 

5186 

5187 :func:`.make_transient` is a special-case function for 

5188 advanced use cases only. 

5189 

5190 The given mapped instance is assumed to be in the :term:`persistent` or 

5191 :term:`detached` state. The function will remove its association with any 

5192 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5193 effect is that the object will behave as though it were newly constructed, 

5194 except retaining any attribute / collection values that were loaded at the 

5195 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5196 if this object had been deleted as a result of using 

5197 :meth:`.Session.delete`. 

5198 

5199 .. warning:: 

5200 

5201 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5202 load ORM-mapped attributes that are not currently loaded at the time 

5203 the function is called. This includes attributes which: 

5204 

5205 * were expired via :meth:`.Session.expire` 

5206 

5207 * were expired as the natural effect of committing a session 

5208 transaction, e.g. :meth:`.Session.commit` 

5209 

5210 * are normally :term:`lazy loaded` but are not currently loaded 

5211 

5212 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5213 not yet loaded 

5214 

5215 * were not present in the query which loaded this object, such as that 

5216 which is common in joined table inheritance and other scenarios. 

5217 

5218 After :func:`.make_transient` is called, unloaded attributes such 

5219 as those above will normally resolve to the value ``None`` when 

5220 accessed, or an empty collection for a collection-oriented attribute. 

5221 As the object is transient and un-associated with any database 

5222 identity, it will no longer retrieve these values. 

5223 

5224 .. seealso:: 

5225 

5226 :func:`.make_transient_to_detached` 

5227 

5228 """ 

5229 state = attributes.instance_state(instance) 

5230 s = _state_session(state) 

5231 if s: 

5232 s._expunge_states([state]) 

5233 

5234 # remove expired state 

5235 state.expired_attributes.clear() 

5236 

5237 # remove deferred callables 

5238 if state.callables: 

5239 del state.callables 

5240 

5241 if state.key: 

5242 del state.key 

5243 if state._deleted: 

5244 del state._deleted 

5245 

5246 

5247def make_transient_to_detached(instance: object) -> None: 

5248 """Make the given transient instance :term:`detached`. 

5249 

5250 .. note:: 

5251 

5252 :func:`.make_transient_to_detached` is a special-case function for 

5253 advanced use cases only. 

5254 

5255 All attribute history on the given instance 

5256 will be reset as though the instance were freshly loaded 

5257 from a query. Missing attributes will be marked as expired. 

5258 The primary key attributes of the object, which are required, will be made 

5259 into the "key" of the instance. 

5260 

5261 The object can then be added to a session, or merged 

5262 possibly with the load=False flag, at which point it will look 

5263 as if it were loaded that way, without emitting SQL. 

5264 

5265 This is a special use case function that differs from a normal 

5266 call to :meth:`.Session.merge` in that a given persistent state 

5267 can be manufactured without any SQL calls. 

5268 

5269 .. seealso:: 

5270 

5271 :func:`.make_transient` 

5272 

5273 :meth:`.Session.enable_relationship_loading` 

5274 

5275 """ 

5276 state = attributes.instance_state(instance) 

5277 if state.session_id or state.key: 

5278 raise sa_exc.InvalidRequestError("Given object must be transient") 

5279 state.key = state.mapper._identity_key_from_state(state) 

5280 if state._deleted: 

5281 del state._deleted 

5282 state._commit_all(state.dict) 

5283 state._expire_attributes(state.dict, state.unloaded) 

5284 

5285 

5286def object_session(instance: object) -> Optional[Session]: 

5287 """Return the :class:`.Session` to which the given instance belongs. 

5288 

5289 This is essentially the same as the :attr:`.InstanceState.session` 

5290 accessor. See that attribute for details. 

5291 

5292 """ 

5293 

5294 try: 

5295 state = attributes.instance_state(instance) 

5296 except exc.NO_STATE as err: 

5297 raise exc.UnmappedInstanceError(instance) from err 

5298 else: 

5299 return _state_session(state) 

5300 

5301 

5302_new_sessionid = util.counter()