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

1435 statements  

1# orm/session.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

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

9 

10from __future__ import annotations 

11 

12import contextlib 

13from enum import Enum 

14import itertools 

15import sys 

16import typing 

17from typing import Any 

18from typing import Callable 

19from typing import cast 

20from typing import Dict 

21from typing import Generic 

22from typing import Iterable 

23from typing import Iterator 

24from typing import List 

25from typing import 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: Optional[_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 

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

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

378 

379 def invoke_statement( 

380 self, 

381 statement: Optional[Executable] = None, 

382 params: Optional[_CoreAnyExecuteParams] = None, 

383 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

384 bind_arguments: Optional[_BindArguments] = None, 

385 ) -> Result[Any]: 

386 """Execute the statement represented by this 

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

388 already proceeded. 

389 

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

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

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

393 that want to override how the ultimate 

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

395 retrieve results from an offline cache or which concatenate results 

396 from multiple executions. 

397 

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

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

400 is propagated to the calling 

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

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

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

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

405 

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

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

408 

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

410 which will be merged into the existing 

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

412 

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

414 for executemany executions. 

415 

416 :param execution_options: optional dictionary of execution options 

417 will be merged into the existing 

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

419 :class:`.ORMExecuteState`. 

420 

421 :param bind_arguments: optional dictionary of bind_arguments 

422 which will be merged amongst the current 

423 :attr:`.ORMExecuteState.bind_arguments` 

424 of this :class:`.ORMExecuteState`. 

425 

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

427 

428 .. seealso:: 

429 

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

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

432 

433 

434 """ 

435 

436 if statement is None: 

437 statement = self.statement 

438 

439 _bind_arguments = dict(self.bind_arguments) 

440 if bind_arguments: 

441 _bind_arguments.update(bind_arguments) 

442 _bind_arguments["_sa_skip_events"] = True 

443 

444 _params: Optional[_CoreAnyExecuteParams] 

445 if params: 

446 if self.is_executemany: 

447 _params = [] 

448 exec_many_parameters = cast( 

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

450 ) 

451 for _existing_params, _new_params in itertools.zip_longest( 

452 exec_many_parameters, 

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

454 ): 

455 if _existing_params is None or _new_params is None: 

456 raise sa_exc.InvalidRequestError( 

457 f"Can't apply executemany parameters to " 

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

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

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

461 f"to ORMExecuteState.invoke_statement() " 

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

463 ) 

464 _existing_params = dict(_existing_params) 

465 _existing_params.update(_new_params) 

466 _params.append(_existing_params) 

467 else: 

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

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

470 else: 

471 _params = self.parameters 

472 

473 _execution_options = self.local_execution_options 

474 if execution_options: 

475 _execution_options = _execution_options.union(execution_options) 

476 

477 return self.session._execute_internal( 

478 statement, 

479 _params, 

480 execution_options=_execution_options, 

481 bind_arguments=_bind_arguments, 

482 _parent_execute_state=self, 

483 ) 

484 

485 @property 

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

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

488 

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

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

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

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

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

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

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

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

497 would be selected. 

498 

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

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

501 way of getting this mapper. 

502 

503 .. versionadded:: 1.4.0b2 

504 

505 .. seealso:: 

506 

507 :attr:`_orm.ORMExecuteState.all_mappers` 

508 

509 

510 """ 

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

512 return mp 

513 

514 @property 

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

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

517 involved at the top level of this statement. 

518 

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

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

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

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

523 

524 .. versionadded:: 1.4.0b2 

525 

526 .. seealso:: 

527 

528 :attr:`_orm.ORMExecuteState.bind_mapper` 

529 

530 

531 

532 """ 

533 if not self.is_orm_statement: 

534 return [] 

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

536 result = [] 

537 seen = set() 

538 for d in self.statement.column_descriptions: 

539 ent = d["entity"] 

540 if ent: 

541 insp = inspect(ent, raiseerr=False) 

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

543 seen.add(insp.mapper) 

544 result.append(insp.mapper) 

545 return result 

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

547 return [self.bind_mapper] 

548 else: 

549 return [] 

550 

551 @property 

552 def is_orm_statement(self) -> bool: 

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

554 

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

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

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

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

559 and no ORM-level automation takes place. 

560 

561 """ 

562 return self._compile_state_cls is not None 

563 

564 @property 

565 def is_executemany(self) -> bool: 

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

567 dictionaries with more than one dictionary. 

568 

569 .. versionadded:: 2.0 

570 

571 """ 

572 return isinstance(self.parameters, list) 

573 

574 @property 

575 def is_select(self) -> bool: 

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

577 

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

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

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

581 ``select(Entity).from_statement(select(..))`` 

582 

583 """ 

584 return self.statement.is_select 

585 

586 @property 

587 def is_from_statement(self) -> bool: 

588 """return True if this operation is a 

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

590 

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

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

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

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

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

596 :class:`_sql.Select` construct. 

597 

598 .. versionadded:: 2.0.30 

599 

600 """ 

601 return self.statement.is_from_statement 

602 

603 @property 

604 def is_insert(self) -> bool: 

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

606 

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

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

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

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

611 

612 """ 

613 return self.statement.is_dml and self.statement.is_insert 

614 

615 @property 

616 def is_update(self) -> bool: 

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

618 

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

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

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

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

623 

624 """ 

625 return self.statement.is_dml and self.statement.is_update 

626 

627 @property 

628 def is_delete(self) -> bool: 

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

630 

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

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

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

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

635 

636 """ 

637 return self.statement.is_dml and self.statement.is_delete 

638 

639 @property 

640 def _is_crud(self) -> bool: 

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

642 

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

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

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

646 

647 def _orm_compile_options( 

648 self, 

649 ) -> Optional[ 

650 Union[ 

651 context.ORMCompileState.default_compile_options, 

652 Type[context.ORMCompileState.default_compile_options], 

653 ] 

654 ]: 

655 if not self.is_select: 

656 return None 

657 try: 

658 opts = self.statement._compile_options 

659 except AttributeError: 

660 return None 

661 

662 if opts is not None and opts.isinstance( 

663 context.ORMCompileState.default_compile_options 

664 ): 

665 return opts # type: ignore 

666 else: 

667 return None 

668 

669 @property 

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

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

672 for a lazy load operation. 

673 

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

675 sharding extension, where it is available within specific query 

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

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

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

679 compilation time. 

680 

681 """ 

682 return self.load_options._lazy_loaded_from 

683 

684 @property 

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

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

687 

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

689 when a particular object or collection is being loaded. 

690 

691 """ 

692 opts = self._orm_compile_options() 

693 if opts is not None: 

694 return opts._current_path 

695 else: 

696 return None 

697 

698 @property 

699 def is_column_load(self) -> bool: 

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

701 attributes on an existing ORM object. 

702 

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

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

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

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

707 loaded. 

708 

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

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

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

712 and loader options travelling with the instance 

713 will have already been added to the query. 

714 

715 .. versionadded:: 1.4.0b2 

716 

717 .. seealso:: 

718 

719 :attr:`_orm.ORMExecuteState.is_relationship_load` 

720 

721 """ 

722 opts = self._orm_compile_options() 

723 return opts is not None and opts._for_refresh_state 

724 

725 @property 

726 def is_relationship_load(self) -> bool: 

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

728 relationship. 

729 

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

731 SelectInLoader, SubqueryLoader, or similar, and the entire 

732 SELECT statement being emitted is on behalf of a relationship 

733 load. 

734 

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

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

737 capable of being propagated to relationship loaders and should 

738 be already present. 

739 

740 .. seealso:: 

741 

742 :attr:`_orm.ORMExecuteState.is_column_load` 

743 

744 """ 

745 opts = self._orm_compile_options() 

746 if opts is None: 

747 return False 

748 path = self.loader_strategy_path 

749 return path is not None and not path.is_root 

750 

751 @property 

752 def load_options( 

753 self, 

754 ) -> Union[ 

755 context.QueryContext.default_load_options, 

756 Type[context.QueryContext.default_load_options], 

757 ]: 

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

759 

760 if not self.is_select: 

761 raise sa_exc.InvalidRequestError( 

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

763 "so there are no load options." 

764 ) 

765 

766 lo: Union[ 

767 context.QueryContext.default_load_options, 

768 Type[context.QueryContext.default_load_options], 

769 ] = self.execution_options.get( 

770 "_sa_orm_load_options", context.QueryContext.default_load_options 

771 ) 

772 return lo 

773 

774 @property 

775 def update_delete_options( 

776 self, 

777 ) -> Union[ 

778 bulk_persistence.BulkUDCompileState.default_update_options, 

779 Type[bulk_persistence.BulkUDCompileState.default_update_options], 

780 ]: 

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

782 execution.""" 

783 

784 if not self._is_crud: 

785 raise sa_exc.InvalidRequestError( 

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

787 "statement so there are no update options." 

788 ) 

789 uo: Union[ 

790 bulk_persistence.BulkUDCompileState.default_update_options, 

791 Type[bulk_persistence.BulkUDCompileState.default_update_options], 

792 ] = self.execution_options.get( 

793 "_sa_orm_update_options", 

794 bulk_persistence.BulkUDCompileState.default_update_options, 

795 ) 

796 return uo 

797 

798 @property 

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

800 return [ 

801 opt 

802 for opt in self.statement._with_options 

803 if is_orm_option(opt) and not opt._is_compile_state 

804 ] 

805 

806 @property 

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

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

809 associated with the statement being invoked. 

810 

811 """ 

812 return [ 

813 opt 

814 for opt in self.statement._with_options 

815 if is_user_defined_option(opt) 

816 ] 

817 

818 

819class SessionTransactionOrigin(Enum): 

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

821 

822 This enumeration is present on the 

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

824 :class:`.SessionTransaction` object. 

825 

826 .. versionadded:: 2.0 

827 

828 """ 

829 

830 AUTOBEGIN = 0 

831 """transaction were started by autobegin""" 

832 

833 BEGIN = 1 

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

835 

836 BEGIN_NESTED = 2 

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

838 

839 SUBTRANSACTION = 3 

840 """transaction is an internal "subtransaction" """ 

841 

842 

843class SessionTransaction(_StateChange, TransactionalContext): 

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

845 

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

847 :meth:`_orm.Session.begin` 

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

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

850 transactions. 

851 

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

853 at: :ref:`unitofwork_transaction`. 

854 

855 

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

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

858 

859 .. seealso:: 

860 

861 :ref:`unitofwork_transaction` 

862 

863 :meth:`.Session.begin` 

864 

865 :meth:`.Session.begin_nested` 

866 

867 :meth:`.Session.rollback` 

868 

869 :meth:`.Session.commit` 

870 

871 :meth:`.Session.in_transaction` 

872 

873 :meth:`.Session.in_nested_transaction` 

874 

875 :meth:`.Session.get_transaction` 

876 

877 :meth:`.Session.get_nested_transaction` 

878 

879 

880 """ 

881 

882 _rollback_exception: Optional[BaseException] = None 

883 

884 _connections: Dict[ 

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

886 ] 

887 session: Session 

888 _parent: Optional[SessionTransaction] 

889 

890 _state: SessionTransactionState 

891 

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

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

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

895 _key_switches: weakref.WeakKeyDictionary[ 

896 InstanceState[Any], Tuple[Any, Any] 

897 ] 

898 

899 origin: SessionTransactionOrigin 

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

901 

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

903 enumeration indicating the source event that led to constructing 

904 this :class:`_orm.SessionTransaction`. 

905 

906 .. versionadded:: 2.0 

907 

908 """ 

909 

910 nested: bool = False 

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

912 

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

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

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

916 

917 .. seealso:: 

918 

919 :attr:`.SessionTransaction.origin` 

920 

921 """ 

922 

923 def __init__( 

924 self, 

925 session: Session, 

926 origin: SessionTransactionOrigin, 

927 parent: Optional[SessionTransaction] = None, 

928 ): 

929 TransactionalContext._trans_ctx_check(session) 

930 

931 self.session = session 

932 self._connections = {} 

933 self._parent = parent 

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

935 self.origin = origin 

936 

937 if session._close_state is _SessionCloseState.CLOSED: 

938 raise sa_exc.InvalidRequestError( 

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

940 "to handle any more transaction requests." 

941 ) 

942 

943 if nested: 

944 if not parent: 

945 raise sa_exc.InvalidRequestError( 

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

947 "transaction is in progress" 

948 ) 

949 

950 self._previous_nested_transaction = session._nested_transaction 

951 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

952 assert parent is not None 

953 else: 

954 assert parent is None 

955 

956 self._state = SessionTransactionState.ACTIVE 

957 

958 self._take_snapshot() 

959 

960 # make sure transaction is assigned before we call the 

961 # dispatch 

962 self.session._transaction = self 

963 

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

965 

966 def _raise_for_prerequisite_state( 

967 self, operation_name: str, state: _StateChangeState 

968 ) -> NoReturn: 

969 if state is SessionTransactionState.DEACTIVE: 

970 if self._rollback_exception: 

971 raise sa_exc.PendingRollbackError( 

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

973 "due to a previous exception during flush." 

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

975 "first issue Session.rollback()." 

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

977 code="7s2a", 

978 ) 

979 else: 

980 raise sa_exc.InvalidRequestError( 

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

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

983 "can be emitted within this transaction." 

984 ) 

985 elif state is SessionTransactionState.CLOSED: 

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

987 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

988 raise sa_exc.InvalidRequestError( 

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

990 "operations are not permitted", 

991 code="isce", 

992 ) 

993 else: 

994 raise sa_exc.InvalidRequestError( 

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

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

997 ) 

998 

999 @property 

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

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

1002 :class:`.SessionTransaction`. 

1003 

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

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

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

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

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

1009 "nested" / SAVEPOINT transaction. If the 

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

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

1012 

1013 """ 

1014 return self._parent 

1015 

1016 @property 

1017 def is_active(self) -> bool: 

1018 return ( 

1019 self.session is not None 

1020 and self._state is SessionTransactionState.ACTIVE 

1021 ) 

1022 

1023 @property 

1024 def _is_transaction_boundary(self) -> bool: 

1025 return self.nested or not self._parent 

1026 

1027 @_StateChange.declare_states( 

1028 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1029 ) 

1030 def connection( 

1031 self, 

1032 bindkey: Optional[Mapper[Any]], 

1033 execution_options: Optional[_ExecuteOptions] = None, 

1034 **kwargs: Any, 

1035 ) -> Connection: 

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

1037 return self._connection_for_bind(bind, execution_options) 

1038 

1039 @_StateChange.declare_states( 

1040 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1041 ) 

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

1043 return SessionTransaction( 

1044 self.session, 

1045 ( 

1046 SessionTransactionOrigin.BEGIN_NESTED 

1047 if nested 

1048 else SessionTransactionOrigin.SUBTRANSACTION 

1049 ), 

1050 self, 

1051 ) 

1052 

1053 def _iterate_self_and_parents( 

1054 self, upto: Optional[SessionTransaction] = None 

1055 ) -> Iterable[SessionTransaction]: 

1056 current = self 

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

1058 while current: 

1059 result += (current,) 

1060 if current._parent is upto: 

1061 break 

1062 elif current._parent is None: 

1063 raise sa_exc.InvalidRequestError( 

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

1065 % (upto) 

1066 ) 

1067 else: 

1068 current = current._parent 

1069 

1070 return result 

1071 

1072 def _take_snapshot(self) -> None: 

1073 if not self._is_transaction_boundary: 

1074 parent = self._parent 

1075 assert parent is not None 

1076 self._new = parent._new 

1077 self._deleted = parent._deleted 

1078 self._dirty = parent._dirty 

1079 self._key_switches = parent._key_switches 

1080 return 

1081 

1082 is_begin = self.origin in ( 

1083 SessionTransactionOrigin.BEGIN, 

1084 SessionTransactionOrigin.AUTOBEGIN, 

1085 ) 

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

1087 self.session.flush() 

1088 

1089 self._new = weakref.WeakKeyDictionary() 

1090 self._deleted = weakref.WeakKeyDictionary() 

1091 self._dirty = weakref.WeakKeyDictionary() 

1092 self._key_switches = weakref.WeakKeyDictionary() 

1093 

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

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

1096 

1097 Corresponds to a rollback. 

1098 

1099 """ 

1100 assert self._is_transaction_boundary 

1101 

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

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

1104 

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

1106 # we probably can do this conditionally based on 

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

1108 self.session.identity_map.safe_discard(s) 

1109 

1110 # restore the old key 

1111 s.key = oldkey 

1112 

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

1114 if s not in to_expunge: 

1115 self.session.identity_map.replace(s) 

1116 

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

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

1119 

1120 assert not self.session._deleted 

1121 

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

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

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

1125 

1126 def _remove_snapshot(self) -> None: 

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

1128 

1129 Corresponds to a commit. 

1130 

1131 """ 

1132 assert self._is_transaction_boundary 

1133 

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

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

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

1137 

1138 statelib.InstanceState._detach_states( 

1139 list(self._deleted), self.session 

1140 ) 

1141 self._deleted.clear() 

1142 elif self.nested: 

1143 parent = self._parent 

1144 assert parent is not None 

1145 parent._new.update(self._new) 

1146 parent._dirty.update(self._dirty) 

1147 parent._deleted.update(self._deleted) 

1148 parent._key_switches.update(self._key_switches) 

1149 

1150 @_StateChange.declare_states( 

1151 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1152 ) 

1153 def _connection_for_bind( 

1154 self, 

1155 bind: _SessionBind, 

1156 execution_options: Optional[CoreExecuteOptionsParameter], 

1157 ) -> Connection: 

1158 if bind in self._connections: 

1159 if execution_options: 

1160 util.warn( 

1161 "Connection is already established for the " 

1162 "given bind; execution_options ignored" 

1163 ) 

1164 return self._connections[bind][0] 

1165 

1166 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1167 

1168 local_connect = False 

1169 should_commit = True 

1170 

1171 try: 

1172 if self._parent: 

1173 conn = self._parent._connection_for_bind( 

1174 bind, execution_options 

1175 ) 

1176 if not self.nested: 

1177 return conn 

1178 else: 

1179 if isinstance(bind, engine.Connection): 

1180 conn = bind 

1181 if conn.engine in self._connections: 

1182 raise sa_exc.InvalidRequestError( 

1183 "Session already has a Connection associated " 

1184 "for the given Connection's Engine" 

1185 ) 

1186 else: 

1187 conn = bind.connect() 

1188 local_connect = True 

1189 

1190 try: 

1191 if execution_options: 

1192 conn = conn.execution_options(**execution_options) 

1193 

1194 transaction: Transaction 

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

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

1197 # conn.in_transaction() ? 

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

1199 # that it is in fact twophase. 

1200 transaction = conn.begin_twophase() 

1201 elif self.nested: 

1202 transaction = conn.begin_nested() 

1203 elif conn.in_transaction(): 

1204 join_transaction_mode = self.session.join_transaction_mode 

1205 

1206 if join_transaction_mode == "conditional_savepoint": 

1207 if conn.in_nested_transaction(): 

1208 join_transaction_mode = "create_savepoint" 

1209 else: 

1210 join_transaction_mode = "rollback_only" 

1211 

1212 if local_connect: 

1213 util.warn( 

1214 "The engine provided as bind produced a " 

1215 "connection that is already in a transaction. " 

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

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

1218 "transaction open. The effective join " 

1219 "transaction mode used by this session is " 

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

1221 "warning, do not leave transactions open" 

1222 ) 

1223 if join_transaction_mode in ( 

1224 "control_fully", 

1225 "rollback_only", 

1226 ): 

1227 if conn.in_nested_transaction(): 

1228 transaction = ( 

1229 conn._get_required_nested_transaction() 

1230 ) 

1231 else: 

1232 transaction = conn._get_required_transaction() 

1233 if join_transaction_mode == "rollback_only": 

1234 should_commit = False 

1235 elif join_transaction_mode == "create_savepoint": 

1236 transaction = conn.begin_nested() 

1237 else: 

1238 assert False, join_transaction_mode 

1239 else: 

1240 transaction = conn.begin() 

1241 except: 

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

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

1244 if local_connect: 

1245 conn.close() 

1246 raise 

1247 else: 

1248 bind_is_connection = isinstance(bind, engine.Connection) 

1249 

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

1251 conn, 

1252 transaction, 

1253 should_commit, 

1254 not bind_is_connection, 

1255 ) 

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

1257 return conn 

1258 finally: 

1259 self._state = SessionTransactionState.ACTIVE 

1260 

1261 def prepare(self) -> None: 

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

1263 raise sa_exc.InvalidRequestError( 

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

1265 "can't prepare." 

1266 ) 

1267 self._prepare_impl() 

1268 

1269 @_StateChange.declare_states( 

1270 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1271 ) 

1272 def _prepare_impl(self) -> None: 

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

1274 self.session.dispatch.before_commit(self.session) 

1275 

1276 stx = self.session._transaction 

1277 assert stx is not None 

1278 if stx is not self: 

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

1280 subtransaction.commit() 

1281 

1282 if not self.session._flushing: 

1283 for _flush_guard in range(100): 

1284 if self.session._is_clean(): 

1285 break 

1286 self.session.flush() 

1287 else: 

1288 raise exc.FlushError( 

1289 "Over 100 subsequent flushes have occurred within " 

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

1291 "creating new objects?" 

1292 ) 

1293 

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

1295 try: 

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

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

1298 except: 

1299 with util.safe_reraise(): 

1300 self.rollback() 

1301 

1302 self._state = SessionTransactionState.PREPARED 

1303 

1304 @_StateChange.declare_states( 

1305 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1306 SessionTransactionState.CLOSED, 

1307 ) 

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

1309 if self._state is not SessionTransactionState.PREPARED: 

1310 with self._expect_state(SessionTransactionState.PREPARED): 

1311 self._prepare_impl() 

1312 

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

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

1315 self._connections.values() 

1316 ): 

1317 if should_commit: 

1318 trans.commit() 

1319 

1320 self._state = SessionTransactionState.COMMITTED 

1321 self.session.dispatch.after_commit(self.session) 

1322 

1323 self._remove_snapshot() 

1324 

1325 with self._expect_state(SessionTransactionState.CLOSED): 

1326 self.close() 

1327 

1328 if _to_root and self._parent: 

1329 self._parent.commit(_to_root=True) 

1330 

1331 @_StateChange.declare_states( 

1332 ( 

1333 SessionTransactionState.ACTIVE, 

1334 SessionTransactionState.DEACTIVE, 

1335 SessionTransactionState.PREPARED, 

1336 ), 

1337 SessionTransactionState.CLOSED, 

1338 ) 

1339 def rollback( 

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

1341 ) -> None: 

1342 stx = self.session._transaction 

1343 assert stx is not None 

1344 if stx is not self: 

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

1346 subtransaction.close() 

1347 

1348 boundary = self 

1349 rollback_err = None 

1350 if self._state in ( 

1351 SessionTransactionState.ACTIVE, 

1352 SessionTransactionState.PREPARED, 

1353 ): 

1354 for transaction in self._iterate_self_and_parents(): 

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

1356 try: 

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

1358 t[1].rollback() 

1359 

1360 transaction._state = SessionTransactionState.DEACTIVE 

1361 self.session.dispatch.after_rollback(self.session) 

1362 except: 

1363 rollback_err = sys.exc_info() 

1364 finally: 

1365 transaction._state = SessionTransactionState.DEACTIVE 

1366 transaction._restore_snapshot( 

1367 dirty_only=transaction.nested 

1368 ) 

1369 boundary = transaction 

1370 break 

1371 else: 

1372 transaction._state = SessionTransactionState.DEACTIVE 

1373 

1374 sess = self.session 

1375 

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

1377 # if items were added, deleted, or mutated 

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

1379 util.warn( 

1380 "Session's state has been changed on " 

1381 "a non-active transaction - this state " 

1382 "will be discarded." 

1383 ) 

1384 boundary._restore_snapshot(dirty_only=boundary.nested) 

1385 

1386 with self._expect_state(SessionTransactionState.CLOSED): 

1387 self.close() 

1388 

1389 if self._parent and _capture_exception: 

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

1391 

1392 if rollback_err and rollback_err[1]: 

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

1394 

1395 sess.dispatch.after_soft_rollback(sess, self) 

1396 

1397 if _to_root and self._parent: 

1398 self._parent.rollback(_to_root=True) 

1399 

1400 @_StateChange.declare_states( 

1401 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1402 ) 

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

1404 if self.nested: 

1405 self.session._nested_transaction = ( 

1406 self._previous_nested_transaction 

1407 ) 

1408 

1409 self.session._transaction = self._parent 

1410 

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

1412 self._connections.values() 

1413 ): 

1414 if invalidate and self._parent is None: 

1415 connection.invalidate() 

1416 if should_commit and transaction.is_active: 

1417 transaction.close() 

1418 if autoclose and self._parent is None: 

1419 connection.close() 

1420 

1421 self._state = SessionTransactionState.CLOSED 

1422 sess = self.session 

1423 

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

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

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

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

1428 # passes with these commented out. 

1429 # self.session = None # type: ignore 

1430 # self._connections = None # type: ignore 

1431 

1432 sess.dispatch.after_transaction_end(sess, self) 

1433 

1434 def _get_subject(self) -> Session: 

1435 return self.session 

1436 

1437 def _transaction_is_active(self) -> bool: 

1438 return self._state is SessionTransactionState.ACTIVE 

1439 

1440 def _transaction_is_closed(self) -> bool: 

1441 return self._state is SessionTransactionState.CLOSED 

1442 

1443 def _rollback_can_be_called(self) -> bool: 

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

1445 

1446 

1447class _SessionCloseState(Enum): 

1448 ACTIVE = 1 

1449 CLOSED = 2 

1450 CLOSE_IS_RESET = 3 

1451 

1452 

1453class Session(_SessionClassMethods, EventTarget): 

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

1455 

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

1457 See :ref:`session_faq_threadsafe` for background. 

1458 

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

1460 

1461 

1462 """ 

1463 

1464 _is_asyncio = False 

1465 

1466 dispatch: dispatcher[Session] 

1467 

1468 identity_map: IdentityMap 

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

1470 

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

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

1473 that have row identity) currently in the session. 

1474 

1475 .. seealso:: 

1476 

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

1478 in this dictionary. 

1479 

1480 """ 

1481 

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

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

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

1485 __binds: Dict[_SessionBindKey, _SessionBind] 

1486 _flushing: bool 

1487 _warn_on_events: bool 

1488 _transaction: Optional[SessionTransaction] 

1489 _nested_transaction: Optional[SessionTransaction] 

1490 hash_key: int 

1491 autoflush: bool 

1492 expire_on_commit: bool 

1493 enable_baked_queries: bool 

1494 twophase: bool 

1495 join_transaction_mode: JoinTransactionMode 

1496 _query_cls: Type[Query[Any]] 

1497 _close_state: _SessionCloseState 

1498 

1499 def __init__( 

1500 self, 

1501 bind: Optional[_SessionBind] = None, 

1502 *, 

1503 autoflush: bool = True, 

1504 future: Literal[True] = True, 

1505 expire_on_commit: bool = True, 

1506 autobegin: bool = True, 

1507 twophase: bool = False, 

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

1509 enable_baked_queries: bool = True, 

1510 info: Optional[_InfoType] = None, 

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

1512 autocommit: Literal[False] = False, 

1513 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1515 ): 

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

1517 

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

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

1520 set of arguments. 

1521 

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

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

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

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

1526 results. 

1527 

1528 .. seealso:: 

1529 

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

1531 

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

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

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

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

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

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

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

1539 

1540 .. versionadded:: 2.0 

1541 

1542 .. seealso:: 

1543 

1544 :ref:`session_autobegin_disable` 

1545 

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

1547 :class:`_engine.Connection` to 

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

1549 operations performed by this session will execute via this 

1550 connectable. 

1551 

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

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

1554 objects as the source of 

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

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

1557 arbitrary Python classes that are bases for mapped classes, 

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

1559 The 

1560 values of the dictionary are then instances of 

1561 :class:`_engine.Engine` 

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

1563 Operations which 

1564 proceed relative to a particular mapped class will consult this 

1565 dictionary for the closest matching entity in order to determine 

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

1567 operation. The complete heuristics for resolution are 

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

1569 

1570 Session = sessionmaker( 

1571 binds={ 

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

1573 SomeDeclarativeBase: create_engine( 

1574 "postgresql+psycopg2://engine2" 

1575 ), 

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

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

1578 } 

1579 ) 

1580 

1581 .. seealso:: 

1582 

1583 :ref:`session_partitioning` 

1584 

1585 :meth:`.Session.bind_mapper` 

1586 

1587 :meth:`.Session.bind_table` 

1588 

1589 :meth:`.Session.get_bind` 

1590 

1591 

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

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

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

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

1596 constructor for ``Session``. 

1597 

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

1599 A parameter consumed 

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

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

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

1603 this particular extension is disabled. 

1604 

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

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

1607 flag therefore only affects applications that are making explicit 

1608 use of this extension within their own code. 

1609 

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

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

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

1613 transaction will load from the most recent database state. 

1614 

1615 .. seealso:: 

1616 

1617 :ref:`session_committing` 

1618 

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

1620 

1621 .. seealso:: 

1622 

1623 :ref:`migration_20_toplevel` 

1624 

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

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

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

1628 construction time so that modifications to the per- 

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

1630 :class:`.Session`. 

1631 

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

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

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

1635 

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

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

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

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

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

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

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

1643 transaction, before each transaction is committed. 

1644 

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

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

1647 

1648 :param join_transaction_mode: Describes the transactional behavior to 

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

1650 has already begun a transaction outside the scope of this 

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

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

1653 

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

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

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

1657 etc. are actually invoked: 

1658 

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

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

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

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

1663 a SAVEPOINT, in other words 

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

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

1666 

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

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

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

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

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

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

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

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

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

1676 

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

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

1679 its own transaction. This transaction by its nature rides 

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

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

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

1683 external transaction will remain unaffected throughout the 

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

1685 

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

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

1688 initiated transaction should remain unaffected; however, it relies 

1689 on proper SAVEPOINT support from the underlying driver and 

1690 database. 

1691 

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

1693 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1694 without workarounds. See the sections 

1695 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1696 for details on current workarounds. 

1697 

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

1699 control of the given transaction as its own; 

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

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

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

1703 call ``.rollback`` on the transaction. 

1704 

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

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

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

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

1709 SAVEPOINT. 

1710 

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

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

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

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

1715 given transaction. 

1716 

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

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

1719 regular database transaction (i.e. 

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

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

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

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

1724 

1725 .. versionadded:: 2.0.0rc1 

1726 

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

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

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

1730 

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

1732 A future SQLAlchemy version may change the default value of 

1733 this flag to ``False``. 

1734 

1735 .. seealso:: 

1736 

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

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

1739 

1740 """ # noqa 

1741 

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

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

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

1745 # of cases including in our own test suite 

1746 if autocommit: 

1747 raise sa_exc.ArgumentError( 

1748 "autocommit=True is no longer supported" 

1749 ) 

1750 self.identity_map = identity.WeakInstanceDict() 

1751 

1752 if not future: 

1753 raise sa_exc.ArgumentError( 

1754 "The 'future' parameter passed to " 

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

1756 ) 

1757 

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

1759 self._deleted = {} # same 

1760 self.bind = bind 

1761 self.__binds = {} 

1762 self._flushing = False 

1763 self._warn_on_events = False 

1764 self._transaction = None 

1765 self._nested_transaction = None 

1766 self.hash_key = _new_sessionid() 

1767 self.autobegin = autobegin 

1768 self.autoflush = autoflush 

1769 self.expire_on_commit = expire_on_commit 

1770 self.enable_baked_queries = enable_baked_queries 

1771 

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

1773 # the default will switch to close_resets_only=False. 

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

1775 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1776 else: 

1777 self._close_state = _SessionCloseState.ACTIVE 

1778 if ( 

1779 join_transaction_mode 

1780 and join_transaction_mode 

1781 not in JoinTransactionMode.__args__ # type: ignore 

1782 ): 

1783 raise sa_exc.ArgumentError( 

1784 f"invalid selection for join_transaction_mode: " 

1785 f'"{join_transaction_mode}"' 

1786 ) 

1787 self.join_transaction_mode = join_transaction_mode 

1788 

1789 self.twophase = twophase 

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

1791 if info: 

1792 self.info.update(info) 

1793 

1794 if binds is not None: 

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

1796 self._add_bind(key, bind) 

1797 

1798 _sessions[self.hash_key] = self 

1799 

1800 # used by sqlalchemy.engine.util.TransactionalContext 

1801 _trans_context_manager: Optional[TransactionalContext] = None 

1802 

1803 connection_callable: Optional[_ConnectionCallableProto] = None 

1804 

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

1806 return self 

1807 

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

1809 self.close() 

1810 

1811 @contextlib.contextmanager 

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

1813 with self: 

1814 with self.begin(): 

1815 yield self 

1816 

1817 def in_transaction(self) -> bool: 

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

1819 

1820 .. versionadded:: 1.4 

1821 

1822 .. seealso:: 

1823 

1824 :attr:`_orm.Session.is_active` 

1825 

1826 

1827 """ 

1828 return self._transaction is not None 

1829 

1830 def in_nested_transaction(self) -> bool: 

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

1832 transaction, e.g. SAVEPOINT. 

1833 

1834 .. versionadded:: 1.4 

1835 

1836 """ 

1837 return self._nested_transaction is not None 

1838 

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

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

1841 

1842 .. versionadded:: 1.4 

1843 

1844 """ 

1845 trans = self._transaction 

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

1847 trans = trans._parent 

1848 return trans 

1849 

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

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

1852 

1853 .. versionadded:: 1.4 

1854 

1855 """ 

1856 

1857 return self._nested_transaction 

1858 

1859 @util.memoized_property 

1860 def info(self) -> _InfoType: 

1861 """A user-modifiable dictionary. 

1862 

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

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

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

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

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

1868 

1869 """ 

1870 return {} 

1871 

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

1873 if self._transaction is None: 

1874 if not begin and not self.autobegin: 

1875 raise sa_exc.InvalidRequestError( 

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

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

1878 ) 

1879 trans = SessionTransaction( 

1880 self, 

1881 ( 

1882 SessionTransactionOrigin.BEGIN 

1883 if begin 

1884 else SessionTransactionOrigin.AUTOBEGIN 

1885 ), 

1886 ) 

1887 assert self._transaction is trans 

1888 return trans 

1889 

1890 return self._transaction 

1891 

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

1893 """Begin a transaction, or nested transaction, 

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

1895 

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

1897 so that normally it is not necessary to call the 

1898 :meth:`_orm.Session.begin` 

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

1900 the scope of when the transactional state is begun. 

1901 

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

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

1904 

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

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

1907 documentation on SAVEPOINT transactions, please see 

1908 :ref:`session_begin_nested`. 

1909 

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

1911 :class:`.SessionTransaction` 

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

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

1914 an example. 

1915 

1916 .. seealso:: 

1917 

1918 :ref:`session_autobegin` 

1919 

1920 :ref:`unitofwork_transaction` 

1921 

1922 :meth:`.Session.begin_nested` 

1923 

1924 

1925 """ 

1926 

1927 trans = self._transaction 

1928 if trans is None: 

1929 trans = self._autobegin_t(begin=True) 

1930 

1931 if not nested: 

1932 return trans 

1933 

1934 assert trans is not None 

1935 

1936 if nested: 

1937 trans = trans._begin(nested=nested) 

1938 assert self._transaction is trans 

1939 self._nested_transaction = trans 

1940 else: 

1941 raise sa_exc.InvalidRequestError( 

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

1943 ) 

1944 

1945 return trans # needed for __enter__/__exit__ hook 

1946 

1947 def begin_nested(self) -> SessionTransaction: 

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

1949 

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

1951 SAVEPOINT for this method to function correctly. 

1952 

1953 For documentation on SAVEPOINT 

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

1955 

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

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

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

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

1960 

1961 .. seealso:: 

1962 

1963 :ref:`session_begin_nested` 

1964 

1965 :ref:`pysqlite_serializable` - special workarounds required 

1966 with the SQLite driver in order for SAVEPOINT to work 

1967 correctly. For asyncio use cases, see the section 

1968 :ref:`aiosqlite_serializable`. 

1969 

1970 """ 

1971 return self.begin(nested=True) 

1972 

1973 def rollback(self) -> None: 

1974 """Rollback the current transaction in progress. 

1975 

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

1977 

1978 The method always rolls back 

1979 the topmost database transaction, discarding any nested 

1980 transactions that may be in progress. 

1981 

1982 .. seealso:: 

1983 

1984 :ref:`session_rollback` 

1985 

1986 :ref:`unitofwork_transaction` 

1987 

1988 """ 

1989 if self._transaction is None: 

1990 pass 

1991 else: 

1992 self._transaction.rollback(_to_root=True) 

1993 

1994 def commit(self) -> None: 

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

1996 

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

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

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

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

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

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

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

2004 to disable this behavior. 

2005 

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

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

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

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

2010 normally affect the database unless pending flush changes were 

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

2012 rules. 

2013 

2014 The outermost database transaction is committed unconditionally, 

2015 automatically releasing any SAVEPOINTs in effect. 

2016 

2017 .. seealso:: 

2018 

2019 :ref:`session_committing` 

2020 

2021 :ref:`unitofwork_transaction` 

2022 

2023 :ref:`asyncio_orm_avoid_lazyloads` 

2024 

2025 """ 

2026 trans = self._transaction 

2027 if trans is None: 

2028 trans = self._autobegin_t() 

2029 

2030 trans.commit(_to_root=True) 

2031 

2032 def prepare(self) -> None: 

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

2034 

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

2036 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2037 

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

2039 current transaction is not such, an 

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

2041 

2042 """ 

2043 trans = self._transaction 

2044 if trans is None: 

2045 trans = self._autobegin_t() 

2046 

2047 trans.prepare() 

2048 

2049 def connection( 

2050 self, 

2051 bind_arguments: Optional[_BindArguments] = None, 

2052 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2053 ) -> Connection: 

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

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

2056 

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

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

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

2060 returned (note that no 

2061 transactional state is established with the DBAPI until the first 

2062 SQL statement is emitted). 

2063 

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

2065 resolved through any of the optional keyword arguments. This 

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

2067 

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

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

2070 to :meth:`.Session.get_bind`. 

2071 

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

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

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

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

2076 the arguments are ignored. 

2077 

2078 .. seealso:: 

2079 

2080 :ref:`session_transaction_isolation` 

2081 

2082 """ 

2083 

2084 if bind_arguments: 

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

2086 

2087 if bind is None: 

2088 bind = self.get_bind(**bind_arguments) 

2089 else: 

2090 bind = self.get_bind() 

2091 

2092 return self._connection_for_bind( 

2093 bind, 

2094 execution_options=execution_options, 

2095 ) 

2096 

2097 def _connection_for_bind( 

2098 self, 

2099 engine: _SessionBind, 

2100 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2101 **kw: Any, 

2102 ) -> Connection: 

2103 TransactionalContext._trans_ctx_check(self) 

2104 

2105 trans = self._transaction 

2106 if trans is None: 

2107 trans = self._autobegin_t() 

2108 return trans._connection_for_bind(engine, execution_options) 

2109 

2110 @overload 

2111 def _execute_internal( 

2112 self, 

2113 statement: Executable, 

2114 params: Optional[_CoreSingleExecuteParams] = None, 

2115 *, 

2116 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2117 bind_arguments: Optional[_BindArguments] = None, 

2118 _parent_execute_state: Optional[Any] = None, 

2119 _add_event: Optional[Any] = None, 

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

2121 ) -> Any: ... 

2122 

2123 @overload 

2124 def _execute_internal( 

2125 self, 

2126 statement: Executable, 

2127 params: Optional[_CoreAnyExecuteParams] = None, 

2128 *, 

2129 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2130 bind_arguments: Optional[_BindArguments] = None, 

2131 _parent_execute_state: Optional[Any] = None, 

2132 _add_event: Optional[Any] = None, 

2133 _scalar_result: bool = ..., 

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

2135 

2136 def _execute_internal( 

2137 self, 

2138 statement: Executable, 

2139 params: Optional[_CoreAnyExecuteParams] = None, 

2140 *, 

2141 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2142 bind_arguments: Optional[_BindArguments] = None, 

2143 _parent_execute_state: Optional[Any] = None, 

2144 _add_event: Optional[Any] = None, 

2145 _scalar_result: bool = False, 

2146 ) -> Any: 

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

2148 

2149 if not bind_arguments: 

2150 bind_arguments = {} 

2151 else: 

2152 bind_arguments = dict(bind_arguments) 

2153 

2154 if ( 

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

2156 == "orm" 

2157 ): 

2158 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2159 statement, "orm" 

2160 ) 

2161 if TYPE_CHECKING: 

2162 assert isinstance( 

2163 compile_state_cls, context.AbstractORMCompileState 

2164 ) 

2165 else: 

2166 compile_state_cls = None 

2167 bind_arguments.setdefault("clause", statement) 

2168 

2169 execution_options = util.coerce_to_immutabledict(execution_options) 

2170 

2171 if _parent_execute_state: 

2172 events_todo = _parent_execute_state._remaining_events() 

2173 else: 

2174 events_todo = self.dispatch.do_orm_execute 

2175 if _add_event: 

2176 events_todo = list(events_todo) + [_add_event] 

2177 

2178 if events_todo: 

2179 if compile_state_cls is not None: 

2180 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2186 ( 

2187 statement, 

2188 execution_options, 

2189 ) = compile_state_cls.orm_pre_session_exec( 

2190 self, 

2191 statement, 

2192 params, 

2193 execution_options, 

2194 bind_arguments, 

2195 True, 

2196 ) 

2197 

2198 orm_exec_state = ORMExecuteState( 

2199 self, 

2200 statement, 

2201 params, 

2202 execution_options, 

2203 bind_arguments, 

2204 compile_state_cls, 

2205 events_todo, 

2206 ) 

2207 for idx, fn in enumerate(events_todo): 

2208 orm_exec_state._starting_event_idx = idx 

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

2210 if fn_result: 

2211 if _scalar_result: 

2212 return fn_result.scalar() 

2213 else: 

2214 return fn_result 

2215 

2216 statement = orm_exec_state.statement 

2217 execution_options = orm_exec_state.local_execution_options 

2218 

2219 if compile_state_cls is not None: 

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

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

2222 # new execution_options into load_options / update_delete_options, 

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

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

2225 ( 

2226 statement, 

2227 execution_options, 

2228 ) = compile_state_cls.orm_pre_session_exec( 

2229 self, 

2230 statement, 

2231 params, 

2232 execution_options, 

2233 bind_arguments, 

2234 False, 

2235 ) 

2236 

2237 bind = self.get_bind(**bind_arguments) 

2238 

2239 conn = self._connection_for_bind(bind) 

2240 

2241 if _scalar_result and not compile_state_cls: 

2242 if TYPE_CHECKING: 

2243 params = cast(_CoreSingleExecuteParams, params) 

2244 return conn.scalar( 

2245 statement, params or {}, execution_options=execution_options 

2246 ) 

2247 

2248 if compile_state_cls: 

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

2250 self, 

2251 statement, 

2252 params or {}, 

2253 execution_options, 

2254 bind_arguments, 

2255 conn, 

2256 ) 

2257 else: 

2258 result = conn.execute( 

2259 statement, params or {}, execution_options=execution_options 

2260 ) 

2261 

2262 if _scalar_result: 

2263 return result.scalar() 

2264 else: 

2265 return result 

2266 

2267 @overload 

2268 def execute( 

2269 self, 

2270 statement: TypedReturnsRows[_T], 

2271 params: Optional[_CoreAnyExecuteParams] = None, 

2272 *, 

2273 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2274 bind_arguments: Optional[_BindArguments] = None, 

2275 _parent_execute_state: Optional[Any] = None, 

2276 _add_event: Optional[Any] = None, 

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

2278 

2279 @overload 

2280 def execute( 

2281 self, 

2282 statement: Executable, 

2283 params: Optional[_CoreAnyExecuteParams] = None, 

2284 *, 

2285 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2286 bind_arguments: Optional[_BindArguments] = None, 

2287 _parent_execute_state: Optional[Any] = None, 

2288 _add_event: Optional[Any] = None, 

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

2290 

2291 def execute( 

2292 self, 

2293 statement: Executable, 

2294 params: Optional[_CoreAnyExecuteParams] = None, 

2295 *, 

2296 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2297 bind_arguments: Optional[_BindArguments] = None, 

2298 _parent_execute_state: Optional[Any] = None, 

2299 _add_event: Optional[Any] = None, 

2300 ) -> Result[Any]: 

2301 r"""Execute a SQL expression construct. 

2302 

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

2304 results of the statement execution. 

2305 

2306 E.g.:: 

2307 

2308 from sqlalchemy import select 

2309 

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

2311 

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

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

2314 of :class:`_engine.Connection`. 

2315 

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

2317 now the primary point of ORM statement execution when using 

2318 :term:`2.0 style` ORM usage. 

2319 

2320 :param statement: 

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

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

2323 

2324 :param params: 

2325 Optional dictionary, or list of dictionaries, containing 

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

2327 execution occurs; if a list of dictionaries, an 

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

2329 must correspond to parameter names present in the statement. 

2330 

2331 :param execution_options: optional dictionary of execution options, 

2332 which will be associated with the statement execution. This 

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

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

2335 provide additional options understood only in an ORM context. 

2336 

2337 .. seealso:: 

2338 

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

2340 options 

2341 

2342 :param bind_arguments: dictionary of additional arguments to determine 

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

2344 Contents of this dictionary are passed to the 

2345 :meth:`.Session.get_bind` method. 

2346 

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

2348 

2349 

2350 """ 

2351 return self._execute_internal( 

2352 statement, 

2353 params, 

2354 execution_options=execution_options, 

2355 bind_arguments=bind_arguments, 

2356 _parent_execute_state=_parent_execute_state, 

2357 _add_event=_add_event, 

2358 ) 

2359 

2360 @overload 

2361 def scalar( 

2362 self, 

2363 statement: TypedReturnsRows[Tuple[_T]], 

2364 params: Optional[_CoreSingleExecuteParams] = None, 

2365 *, 

2366 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2367 bind_arguments: Optional[_BindArguments] = None, 

2368 **kw: Any, 

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

2370 

2371 @overload 

2372 def scalar( 

2373 self, 

2374 statement: Executable, 

2375 params: Optional[_CoreSingleExecuteParams] = None, 

2376 *, 

2377 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2378 bind_arguments: Optional[_BindArguments] = None, 

2379 **kw: Any, 

2380 ) -> Any: ... 

2381 

2382 def scalar( 

2383 self, 

2384 statement: Executable, 

2385 params: Optional[_CoreSingleExecuteParams] = None, 

2386 *, 

2387 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2388 bind_arguments: Optional[_BindArguments] = None, 

2389 **kw: Any, 

2390 ) -> Any: 

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

2392 

2393 Usage and parameters are the same as that of 

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

2395 value. 

2396 

2397 """ 

2398 

2399 return self._execute_internal( 

2400 statement, 

2401 params, 

2402 execution_options=execution_options, 

2403 bind_arguments=bind_arguments, 

2404 _scalar_result=True, 

2405 **kw, 

2406 ) 

2407 

2408 @overload 

2409 def scalars( 

2410 self, 

2411 statement: TypedReturnsRows[Tuple[_T]], 

2412 params: Optional[_CoreAnyExecuteParams] = None, 

2413 *, 

2414 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2415 bind_arguments: Optional[_BindArguments] = None, 

2416 **kw: Any, 

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

2418 

2419 @overload 

2420 def scalars( 

2421 self, 

2422 statement: Executable, 

2423 params: Optional[_CoreAnyExecuteParams] = None, 

2424 *, 

2425 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2426 bind_arguments: Optional[_BindArguments] = None, 

2427 **kw: Any, 

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

2429 

2430 def scalars( 

2431 self, 

2432 statement: Executable, 

2433 params: Optional[_CoreAnyExecuteParams] = None, 

2434 *, 

2435 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2436 bind_arguments: Optional[_BindArguments] = None, 

2437 **kw: Any, 

2438 ) -> ScalarResult[Any]: 

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

2440 

2441 Usage and parameters are the same as that of 

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

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

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

2445 

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

2447 

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

2449 

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

2451 

2452 .. seealso:: 

2453 

2454 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2456 

2457 """ 

2458 

2459 return self._execute_internal( 

2460 statement, 

2461 params=params, 

2462 execution_options=execution_options, 

2463 bind_arguments=bind_arguments, 

2464 _scalar_result=False, # mypy appreciates this 

2465 **kw, 

2466 ).scalars() 

2467 

2468 def close(self) -> None: 

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

2470 :class:`_orm.Session`. 

2471 

2472 This expunges all ORM objects associated with this 

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

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

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

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

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

2478 

2479 .. tip:: 

2480 

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

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

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

2484 distinct "closed" state; it merely means 

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

2486 and ORM objects. 

2487 

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

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

2490 any further action on the session will be forbidden. 

2491 

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

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

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

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

2496 

2497 .. seealso:: 

2498 

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

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

2501 

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

2503 ``close()`` with the parameter 

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

2505 

2506 """ 

2507 self._close_impl(invalidate=False) 

2508 

2509 def reset(self) -> None: 

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

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

2512 

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

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

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

2516 brand new, and ready to be used again. 

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

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

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

2520 

2521 .. versionadded:: 2.0.22 

2522 

2523 .. seealso:: 

2524 

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

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

2527 

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

2529 prevent re-use of the Session when the parameter 

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

2531 """ 

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

2533 

2534 def invalidate(self) -> None: 

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

2536 

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

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

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

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

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

2542 multiple engines). 

2543 

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

2545 the connections are no longer safe to be used. 

2546 

2547 Below illustrates a scenario when using `gevent 

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

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

2550 

2551 import gevent 

2552 

2553 try: 

2554 sess = Session() 

2555 sess.add(User()) 

2556 sess.commit() 

2557 except gevent.Timeout: 

2558 sess.invalidate() 

2559 raise 

2560 except: 

2561 sess.rollback() 

2562 raise 

2563 

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

2565 does, including that all ORM objects are expunged. 

2566 

2567 """ 

2568 self._close_impl(invalidate=True) 

2569 

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

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

2572 self._close_state = _SessionCloseState.CLOSED 

2573 self.expunge_all() 

2574 if self._transaction is not None: 

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

2576 transaction.close(invalidate) 

2577 

2578 def expunge_all(self) -> None: 

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

2580 

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

2582 ``Session``. 

2583 

2584 """ 

2585 

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

2587 self.identity_map._kill() 

2588 self.identity_map = identity.WeakInstanceDict() 

2589 self._new = {} 

2590 self._deleted = {} 

2591 

2592 statelib.InstanceState._detach_states(all_states, self) 

2593 

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

2595 try: 

2596 insp = inspect(key) 

2597 except sa_exc.NoInspectionAvailable as err: 

2598 if not isinstance(key, type): 

2599 raise sa_exc.ArgumentError( 

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

2601 ) from err 

2602 else: 

2603 self.__binds[key] = bind 

2604 else: 

2605 if TYPE_CHECKING: 

2606 assert isinstance(insp, Inspectable) 

2607 

2608 if isinstance(insp, TableClause): 

2609 self.__binds[insp] = bind 

2610 elif insp_is_mapper(insp): 

2611 self.__binds[insp.class_] = bind 

2612 for _selectable in insp._all_tables: 

2613 self.__binds[_selectable] = bind 

2614 else: 

2615 raise sa_exc.ArgumentError( 

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

2617 ) 

2618 

2619 def bind_mapper( 

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

2621 ) -> None: 

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

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

2624 :class:`_engine.Connection`. 

2625 

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

2627 :meth:`.Session.get_bind` method. 

2628 

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

2630 or an instance of a mapped 

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

2632 classes. 

2633 

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

2635 object. 

2636 

2637 .. seealso:: 

2638 

2639 :ref:`session_partitioning` 

2640 

2641 :paramref:`.Session.binds` 

2642 

2643 :meth:`.Session.bind_table` 

2644 

2645 

2646 """ 

2647 self._add_bind(mapper, bind) 

2648 

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

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

2651 :class:`_engine.Engine` 

2652 or :class:`_engine.Connection`. 

2653 

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

2655 :meth:`.Session.get_bind` method. 

2656 

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

2658 which is typically the target 

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

2660 mapped. 

2661 

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

2663 object. 

2664 

2665 .. seealso:: 

2666 

2667 :ref:`session_partitioning` 

2668 

2669 :paramref:`.Session.binds` 

2670 

2671 :meth:`.Session.bind_mapper` 

2672 

2673 

2674 """ 

2675 self._add_bind(table, bind) 

2676 

2677 def get_bind( 

2678 self, 

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

2680 *, 

2681 clause: Optional[ClauseElement] = None, 

2682 bind: Optional[_SessionBind] = None, 

2683 _sa_skip_events: Optional[bool] = None, 

2684 _sa_skip_for_implicit_returning: bool = False, 

2685 **kw: Any, 

2686 ) -> Union[Engine, Connection]: 

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

2688 

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

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

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

2692 

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

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

2695 appropriate bind to return. 

2696 

2697 Note that the "mapper" argument is usually present 

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

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

2700 individual INSERT/UPDATE/DELETE operation within a 

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

2702 

2703 The order of resolution is: 

2704 

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

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

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

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

2709 superclasses to more general. 

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

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

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

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

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

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

2716 associated with the clause. 

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

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

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

2720 selectable to which the mapper is mapped. 

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

2722 is raised. 

2723 

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

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

2726 of bind resolution scheme. See the example at 

2727 :ref:`session_custom_partitioning`. 

2728 

2729 :param mapper: 

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

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

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

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

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

2735 mapped for a bind. 

2736 

2737 :param clause: 

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

2739 :func:`_expression.select`, 

2740 :func:`_expression.text`, 

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

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

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

2744 associated with 

2745 bound :class:`_schema.MetaData`. 

2746 

2747 .. seealso:: 

2748 

2749 :ref:`session_partitioning` 

2750 

2751 :paramref:`.Session.binds` 

2752 

2753 :meth:`.Session.bind_mapper` 

2754 

2755 :meth:`.Session.bind_table` 

2756 

2757 """ 

2758 

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

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

2761 if bind: 

2762 return bind 

2763 elif not self.__binds and self.bind: 

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

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

2766 return self.bind 

2767 

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

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

2770 # mapper and the clause 

2771 if mapper is None and clause is None: 

2772 if self.bind: 

2773 return self.bind 

2774 else: 

2775 raise sa_exc.UnboundExecutionError( 

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

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

2778 "a binding." 

2779 ) 

2780 

2781 # look more closely at the mapper. 

2782 if mapper is not None: 

2783 try: 

2784 inspected_mapper = inspect(mapper) 

2785 except sa_exc.NoInspectionAvailable as err: 

2786 if isinstance(mapper, type): 

2787 raise exc.UnmappedClassError(mapper) from err 

2788 else: 

2789 raise 

2790 else: 

2791 inspected_mapper = None 

2792 

2793 # match up the mapper or clause in the __binds 

2794 if self.__binds: 

2795 # matching mappers and selectables to entries in the 

2796 # binds dictionary; supported use case. 

2797 if inspected_mapper: 

2798 for cls in inspected_mapper.class_.__mro__: 

2799 if cls in self.__binds: 

2800 return self.__binds[cls] 

2801 if clause is None: 

2802 clause = inspected_mapper.persist_selectable 

2803 

2804 if clause is not None: 

2805 plugin_subject = clause._propagate_attrs.get( 

2806 "plugin_subject", None 

2807 ) 

2808 

2809 if plugin_subject is not None: 

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

2811 if cls in self.__binds: 

2812 return self.__binds[cls] 

2813 

2814 for obj in visitors.iterate(clause): 

2815 if obj in self.__binds: 

2816 if TYPE_CHECKING: 

2817 assert isinstance(obj, Table) 

2818 return self.__binds[obj] 

2819 

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

2821 # return that 

2822 if self.bind: 

2823 return self.bind 

2824 

2825 context = [] 

2826 if inspected_mapper is not None: 

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

2828 if clause is not None: 

2829 context.append("SQL expression") 

2830 

2831 raise sa_exc.UnboundExecutionError( 

2832 f"Could not locate a bind configured on " 

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

2834 ) 

2835 

2836 @overload 

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

2838 

2839 @overload 

2840 def query( 

2841 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2843 

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

2845 

2846 # code within this block is **programmatically, 

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

2848 

2849 @overload 

2850 def query( 

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

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

2853 

2854 @overload 

2855 def query( 

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

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

2858 

2859 @overload 

2860 def query( 

2861 self, 

2862 __ent0: _TCCA[_T0], 

2863 __ent1: _TCCA[_T1], 

2864 __ent2: _TCCA[_T2], 

2865 __ent3: _TCCA[_T3], 

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

2867 

2868 @overload 

2869 def query( 

2870 self, 

2871 __ent0: _TCCA[_T0], 

2872 __ent1: _TCCA[_T1], 

2873 __ent2: _TCCA[_T2], 

2874 __ent3: _TCCA[_T3], 

2875 __ent4: _TCCA[_T4], 

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

2877 

2878 @overload 

2879 def query( 

2880 self, 

2881 __ent0: _TCCA[_T0], 

2882 __ent1: _TCCA[_T1], 

2883 __ent2: _TCCA[_T2], 

2884 __ent3: _TCCA[_T3], 

2885 __ent4: _TCCA[_T4], 

2886 __ent5: _TCCA[_T5], 

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

2888 

2889 @overload 

2890 def query( 

2891 self, 

2892 __ent0: _TCCA[_T0], 

2893 __ent1: _TCCA[_T1], 

2894 __ent2: _TCCA[_T2], 

2895 __ent3: _TCCA[_T3], 

2896 __ent4: _TCCA[_T4], 

2897 __ent5: _TCCA[_T5], 

2898 __ent6: _TCCA[_T6], 

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

2900 

2901 @overload 

2902 def query( 

2903 self, 

2904 __ent0: _TCCA[_T0], 

2905 __ent1: _TCCA[_T1], 

2906 __ent2: _TCCA[_T2], 

2907 __ent3: _TCCA[_T3], 

2908 __ent4: _TCCA[_T4], 

2909 __ent5: _TCCA[_T5], 

2910 __ent6: _TCCA[_T6], 

2911 __ent7: _TCCA[_T7], 

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

2913 

2914 # END OVERLOADED FUNCTIONS self.query 

2915 

2916 @overload 

2917 def query( 

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

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

2920 

2921 def query( 

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

2923 ) -> Query[Any]: 

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

2925 :class:`_orm.Session`. 

2926 

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

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

2929 to construct ORM queries. 

2930 

2931 .. seealso:: 

2932 

2933 :ref:`unified_tutorial` 

2934 

2935 :ref:`queryguide_toplevel` 

2936 

2937 :ref:`query_api_toplevel` - legacy API doc 

2938 

2939 """ 

2940 

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

2942 

2943 def _identity_lookup( 

2944 self, 

2945 mapper: Mapper[_O], 

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

2947 identity_token: Any = None, 

2948 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

2950 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2951 bind_arguments: Optional[_BindArguments] = None, 

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

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

2954 

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

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

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

2958 check if was deleted). 

2959 

2960 e.g.:: 

2961 

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

2963 

2964 :param mapper: mapper in use 

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

2966 a tuple. 

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

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

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

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

2971 :param passive: passive load flag passed to 

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

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

2974 if the flag allows for SQL to be emitted. 

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

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

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

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

2979 relationship-loaded). 

2980 

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

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

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

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

2985 

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

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

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

2989 :class:`_query.Query` object. 

2990 

2991 

2992 """ 

2993 

2994 key = mapper.identity_key_from_primary_key( 

2995 primary_key_identity, identity_token=identity_token 

2996 ) 

2997 

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

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

3000 return return_value 

3001 

3002 @util.non_memoized_property 

3003 @contextlib.contextmanager 

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

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

3006 

3007 e.g.:: 

3008 

3009 with session.no_autoflush: 

3010 

3011 some_object = SomeClass() 

3012 session.add(some_object) 

3013 # won't autoflush 

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

3015 

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

3017 will not be subject to flushes occurring upon query 

3018 access. This is useful when initializing a series 

3019 of objects which involve existing database queries, 

3020 where the uncompleted object should not yet be flushed. 

3021 

3022 """ 

3023 autoflush = self.autoflush 

3024 self.autoflush = False 

3025 try: 

3026 yield self 

3027 finally: 

3028 self.autoflush = autoflush 

3029 

3030 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3034 "warning happened while initializing objects.", 

3035 sa_exc.SAWarning, 

3036 ) 

3037 def _autoflush(self) -> None: 

3038 if self.autoflush and not self._flushing: 

3039 try: 

3040 self.flush() 

3041 except sa_exc.StatementError as e: 

3042 # note we are reraising StatementError as opposed to 

3043 # raising FlushError with "chaining" to remain compatible 

3044 # with code that catches StatementError, IntegrityError, 

3045 # etc. 

3046 e.add_detail( 

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

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

3049 "flush is occurring prematurely" 

3050 ) 

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

3052 

3053 def refresh( 

3054 self, 

3055 instance: object, 

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

3057 with_for_update: ForUpdateParameter = None, 

3058 ) -> None: 

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

3060 

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

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

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

3064 value available in the current transaction. 

3065 

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

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

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

3069 

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

3071 can also refresh eagerly loaded attributes. 

3072 

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

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

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

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

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

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

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

3080 refreshed. 

3081 

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

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

3084 attributes for those which are named explicitly in the 

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

3086 

3087 .. tip:: 

3088 

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

3090 refreshing both column and relationship oriented attributes, its 

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

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

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

3094 once while having explicit control over relationship loader 

3095 strategies, use the 

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

3097 instead. 

3098 

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

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

3101 in database state outside of that transaction. Refreshing 

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

3103 where database rows have not yet been accessed. 

3104 

3105 :param attribute_names: optional. An iterable collection of 

3106 string attribute names indicating a subset of attributes to 

3107 be refreshed. 

3108 

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

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

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

3112 flags should match the parameters of 

3113 :meth:`_query.Query.with_for_update`. 

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

3115 

3116 .. seealso:: 

3117 

3118 :ref:`session_expire` - introductory material 

3119 

3120 :meth:`.Session.expire` 

3121 

3122 :meth:`.Session.expire_all` 

3123 

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

3125 to refresh objects as they would be loaded normally. 

3126 

3127 """ 

3128 try: 

3129 state = attributes.instance_state(instance) 

3130 except exc.NO_STATE as err: 

3131 raise exc.UnmappedInstanceError(instance) from err 

3132 

3133 self._expire_state(state, attribute_names) 

3134 

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

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

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

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

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

3140 # load_on_ident. 

3141 self._autoflush() 

3142 

3143 if with_for_update == {}: 

3144 raise sa_exc.ArgumentError( 

3145 "with_for_update should be the boolean value " 

3146 "True, or a dictionary with options. " 

3147 "A blank dictionary is ambiguous." 

3148 ) 

3149 

3150 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3151 

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

3153 if ( 

3154 loading.load_on_ident( 

3155 self, 

3156 stmt, 

3157 state.key, 

3158 refresh_state=state, 

3159 with_for_update=with_for_update, 

3160 only_load_props=attribute_names, 

3161 require_pk_cols=True, 

3162 # technically unnecessary as we just did autoflush 

3163 # above, however removes the additional unnecessary 

3164 # call to _autoflush() 

3165 no_autoflush=True, 

3166 is_user_refresh=True, 

3167 ) 

3168 is None 

3169 ): 

3170 raise sa_exc.InvalidRequestError( 

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

3172 ) 

3173 

3174 def expire_all(self) -> None: 

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

3176 

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

3178 a query will be issued using the 

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

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

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

3182 previously read in that same transaction, regardless of changes 

3183 in database state outside of that transaction. 

3184 

3185 To expire individual objects and individual attributes 

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

3187 

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

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

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

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

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

3193 assuming the transaction is isolated. 

3194 

3195 .. seealso:: 

3196 

3197 :ref:`session_expire` - introductory material 

3198 

3199 :meth:`.Session.expire` 

3200 

3201 :meth:`.Session.refresh` 

3202 

3203 :meth:`_orm.Query.populate_existing` 

3204 

3205 """ 

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

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

3208 

3209 def expire( 

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

3211 ) -> None: 

3212 """Expire the attributes on an instance. 

3213 

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

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

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

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

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

3219 previously read in that same transaction, regardless of changes 

3220 in database state outside of that transaction. 

3221 

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

3223 use :meth:`Session.expire_all`. 

3224 

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

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

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

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

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

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

3231 transaction. 

3232 

3233 :param instance: The instance to be refreshed. 

3234 :param attribute_names: optional list of string attribute names 

3235 indicating a subset of attributes to be expired. 

3236 

3237 .. seealso:: 

3238 

3239 :ref:`session_expire` - introductory material 

3240 

3241 :meth:`.Session.expire` 

3242 

3243 :meth:`.Session.refresh` 

3244 

3245 :meth:`_orm.Query.populate_existing` 

3246 

3247 """ 

3248 try: 

3249 state = attributes.instance_state(instance) 

3250 except exc.NO_STATE as err: 

3251 raise exc.UnmappedInstanceError(instance) from err 

3252 self._expire_state(state, attribute_names) 

3253 

3254 def _expire_state( 

3255 self, 

3256 state: InstanceState[Any], 

3257 attribute_names: Optional[Iterable[str]], 

3258 ) -> None: 

3259 self._validate_persistent(state) 

3260 if attribute_names: 

3261 state._expire_attributes(state.dict, attribute_names) 

3262 else: 

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

3264 # remove associations 

3265 cascaded = list( 

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

3267 ) 

3268 self._conditional_expire(state) 

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

3270 self._conditional_expire(st_) 

3271 

3272 def _conditional_expire( 

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

3274 ) -> None: 

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

3276 

3277 if state.key: 

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

3279 elif state in self._new: 

3280 self._new.pop(state) 

3281 state._detach(self) 

3282 

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

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

3285 

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

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

3288 

3289 """ 

3290 try: 

3291 state = attributes.instance_state(instance) 

3292 except exc.NO_STATE as err: 

3293 raise exc.UnmappedInstanceError(instance) from err 

3294 if state.session_id is not self.hash_key: 

3295 raise sa_exc.InvalidRequestError( 

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

3297 ) 

3298 

3299 cascaded = list( 

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

3301 ) 

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

3303 

3304 def _expunge_states( 

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

3306 ) -> None: 

3307 for state in states: 

3308 if state in self._new: 

3309 self._new.pop(state) 

3310 elif self.identity_map.contains_state(state): 

3311 self.identity_map.safe_discard(state) 

3312 self._deleted.pop(state, None) 

3313 elif self._transaction: 

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

3315 # in the transaction snapshot 

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

3317 statelib.InstanceState._detach_states( 

3318 states, self, to_transient=to_transient 

3319 ) 

3320 

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

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

3323 

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

3325 state as well as already persistent objects. 

3326 

3327 """ 

3328 

3329 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3330 for state in states: 

3331 mapper = _state_mapper(state) 

3332 

3333 # prevent against last minute dereferences of the object 

3334 obj = state.obj() 

3335 if obj is not None: 

3336 instance_key = mapper._identity_key_from_state(state) 

3337 

3338 if ( 

3339 _none_set.intersection(instance_key[1]) 

3340 and not mapper.allow_partial_pks 

3341 or _none_set.issuperset(instance_key[1]) 

3342 ): 

3343 raise exc.FlushError( 

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

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

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

3347 "that the mapped Column object is configured to " 

3348 "expect these generated values. Ensure also that " 

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

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

3351 % state_str(state) 

3352 ) 

3353 

3354 if state.key is None: 

3355 state.key = instance_key 

3356 elif state.key != instance_key: 

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

3358 # state has already replaced this one in the identity 

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

3360 self.identity_map.safe_discard(state) 

3361 trans = self._transaction 

3362 assert trans is not None 

3363 if state in trans._key_switches: 

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

3365 else: 

3366 orig_key = state.key 

3367 trans._key_switches[state] = ( 

3368 orig_key, 

3369 instance_key, 

3370 ) 

3371 state.key = instance_key 

3372 

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

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

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

3376 old = self.identity_map.replace(state) 

3377 if ( 

3378 old is not None 

3379 and mapper._identity_key_from_state(old) == instance_key 

3380 and old.obj() is not None 

3381 ): 

3382 util.warn( 

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

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

3385 "load operations occurring inside of an event handler " 

3386 "within the flush?" % (instance_key,) 

3387 ) 

3388 state._orphaned_outside_of_session = False 

3389 

3390 statelib.InstanceState._commit_all_states( 

3391 ((state, state.dict) for state in states), self.identity_map 

3392 ) 

3393 

3394 self._register_altered(states) 

3395 

3396 if pending_to_persistent is not None: 

3397 for state in states.intersection(self._new): 

3398 pending_to_persistent(self, state) 

3399 

3400 # remove from new last, might be the last strong ref 

3401 for state in set(states).intersection(self._new): 

3402 self._new.pop(state) 

3403 

3404 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None: 

3405 if self._transaction: 

3406 for state in states: 

3407 if state in self._new: 

3408 self._transaction._new[state] = True 

3409 else: 

3410 self._transaction._dirty[state] = True 

3411 

3412 def _remove_newly_deleted( 

3413 self, states: Iterable[InstanceState[Any]] 

3414 ) -> None: 

3415 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3416 for state in states: 

3417 if self._transaction: 

3418 self._transaction._deleted[state] = True 

3419 

3420 if persistent_to_deleted is not None: 

3421 # get a strong reference before we pop out of 

3422 # self._deleted 

3423 obj = state.obj() # noqa 

3424 

3425 self.identity_map.safe_discard(state) 

3426 self._deleted.pop(state, None) 

3427 state._deleted = True 

3428 # can't call state._detach() here, because this state 

3429 # is still in the transaction snapshot and needs to be 

3430 # tracked as part of that 

3431 if persistent_to_deleted is not None: 

3432 persistent_to_deleted(self, state) 

3433 

3434 def add(self, instance: object, _warn: bool = True) -> None: 

3435 """Place an object into this :class:`_orm.Session`. 

3436 

3437 Objects that are in the :term:`transient` state when passed to the 

3438 :meth:`_orm.Session.add` method will move to the 

3439 :term:`pending` state, until the next flush, at which point they 

3440 will move to the :term:`persistent` state. 

3441 

3442 Objects that are in the :term:`detached` state when passed to the 

3443 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3444 state directly. 

3445 

3446 If the transaction used by the :class:`_orm.Session` is rolled back, 

3447 objects which were transient when they were passed to 

3448 :meth:`_orm.Session.add` will be moved back to the 

3449 :term:`transient` state, and will no longer be present within this 

3450 :class:`_orm.Session`. 

3451 

3452 .. seealso:: 

3453 

3454 :meth:`_orm.Session.add_all` 

3455 

3456 :ref:`session_adding` - at :ref:`session_basics` 

3457 

3458 """ 

3459 if _warn and self._warn_on_events: 

3460 self._flush_warning("Session.add()") 

3461 

3462 try: 

3463 state = attributes.instance_state(instance) 

3464 except exc.NO_STATE as err: 

3465 raise exc.UnmappedInstanceError(instance) from err 

3466 

3467 self._save_or_update_state(state) 

3468 

3469 def add_all(self, instances: Iterable[object]) -> None: 

3470 """Add the given collection of instances to this :class:`_orm.Session`. 

3471 

3472 See the documentation for :meth:`_orm.Session.add` for a general 

3473 behavioral description. 

3474 

3475 .. seealso:: 

3476 

3477 :meth:`_orm.Session.add` 

3478 

3479 :ref:`session_adding` - at :ref:`session_basics` 

3480 

3481 """ 

3482 

3483 if self._warn_on_events: 

3484 self._flush_warning("Session.add_all()") 

3485 

3486 for instance in instances: 

3487 self.add(instance, _warn=False) 

3488 

3489 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3490 state._orphaned_outside_of_session = False 

3491 self._save_or_update_impl(state) 

3492 

3493 mapper = _state_mapper(state) 

3494 for o, m, st_, dct_ in mapper.cascade_iterator( 

3495 "save-update", state, halt_on=self._contains_state 

3496 ): 

3497 self._save_or_update_impl(st_) 

3498 

3499 def delete(self, instance: object) -> None: 

3500 """Mark an instance as deleted. 

3501 

3502 The object is assumed to be either :term:`persistent` or 

3503 :term:`detached` when passed; after the method is called, the 

3504 object will remain in the :term:`persistent` state until the next 

3505 flush proceeds. During this time, the object will also be a member 

3506 of the :attr:`_orm.Session.deleted` collection. 

3507 

3508 When the next flush proceeds, the object will move to the 

3509 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3510 for its row within the current transaction. When the transaction 

3511 is successfully committed, 

3512 the deleted object is moved to the :term:`detached` state and is 

3513 no longer present within this :class:`_orm.Session`. 

3514 

3515 .. seealso:: 

3516 

3517 :ref:`session_deleting` - at :ref:`session_basics` 

3518 

3519 """ 

3520 if self._warn_on_events: 

3521 self._flush_warning("Session.delete()") 

3522 

3523 try: 

3524 state = attributes.instance_state(instance) 

3525 except exc.NO_STATE as err: 

3526 raise exc.UnmappedInstanceError(instance) from err 

3527 

3528 self._delete_impl(state, instance, head=True) 

3529 

3530 def _delete_impl( 

3531 self, state: InstanceState[Any], obj: object, head: bool 

3532 ) -> None: 

3533 if state.key is None: 

3534 if head: 

3535 raise sa_exc.InvalidRequestError( 

3536 "Instance '%s' is not persisted" % state_str(state) 

3537 ) 

3538 else: 

3539 return 

3540 

3541 to_attach = self._before_attach(state, obj) 

3542 

3543 if state in self._deleted: 

3544 return 

3545 

3546 self.identity_map.add(state) 

3547 

3548 if to_attach: 

3549 self._after_attach(state, obj) 

3550 

3551 if head: 

3552 # grab the cascades before adding the item to the deleted list 

3553 # so that autoflush does not delete the item 

3554 # the strong reference to the instance itself is significant here 

3555 cascade_states = list( 

3556 state.manager.mapper.cascade_iterator("delete", state) 

3557 ) 

3558 else: 

3559 cascade_states = None 

3560 

3561 self._deleted[state] = obj 

3562 

3563 if head: 

3564 if TYPE_CHECKING: 

3565 assert cascade_states is not None 

3566 for o, m, st_, dct_ in cascade_states: 

3567 self._delete_impl(st_, o, False) 

3568 

3569 def get( 

3570 self, 

3571 entity: _EntityBindKey[_O], 

3572 ident: _PKIdentityArgument, 

3573 *, 

3574 options: Optional[Sequence[ORMOption]] = None, 

3575 populate_existing: bool = False, 

3576 with_for_update: ForUpdateParameter = None, 

3577 identity_token: Optional[Any] = None, 

3578 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3579 bind_arguments: Optional[_BindArguments] = None, 

3580 ) -> Optional[_O]: 

3581 """Return an instance based on the given primary key identifier, 

3582 or ``None`` if not found. 

3583 

3584 E.g.:: 

3585 

3586 my_user = session.get(User, 5) 

3587 

3588 some_object = session.get(VersionedFoo, (5, 10)) 

3589 

3590 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3591 

3592 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3593 from the now legacy :meth:`_orm.Query.get` method. 

3594 

3595 :meth:`_orm.Session.get` is special in that it provides direct 

3596 access to the identity map of the :class:`.Session`. 

3597 If the given primary key identifier is present 

3598 in the local identity map, the object is returned 

3599 directly from this collection and no SQL is emitted, 

3600 unless the object has been marked fully expired. 

3601 If not present, 

3602 a SELECT is performed in order to locate the object. 

3603 

3604 :meth:`_orm.Session.get` also will perform a check if 

3605 the object is present in the identity map and 

3606 marked as expired - a SELECT 

3607 is emitted to refresh the object as well as to 

3608 ensure that the row is still present. 

3609 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3610 

3611 :param entity: a mapped class or :class:`.Mapper` indicating the 

3612 type of entity to be loaded. 

3613 

3614 :param ident: A scalar, tuple, or dictionary representing the 

3615 primary key. For a composite (e.g. multiple column) primary key, 

3616 a tuple or dictionary should be passed. 

3617 

3618 For a single-column primary key, the scalar calling form is typically 

3619 the most expedient. If the primary key of a row is the value "5", 

3620 the call looks like:: 

3621 

3622 my_object = session.get(SomeClass, 5) 

3623 

3624 The tuple form contains primary key values typically in 

3625 the order in which they correspond to the mapped 

3626 :class:`_schema.Table` 

3627 object's primary key columns, or if the 

3628 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3629 used, in 

3630 the order used for that parameter. For example, if the primary key 

3631 of a row is represented by the integer 

3632 digits "5, 10" the call would look like:: 

3633 

3634 my_object = session.get(SomeClass, (5, 10)) 

3635 

3636 The dictionary form should include as keys the mapped attribute names 

3637 corresponding to each element of the primary key. If the mapped class 

3638 has the attributes ``id``, ``version_id`` as the attributes which 

3639 store the object's primary key value, the call would look like:: 

3640 

3641 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3642 

3643 :param options: optional sequence of loader options which will be 

3644 applied to the query, if one is emitted. 

3645 

3646 :param populate_existing: causes the method to unconditionally emit 

3647 a SQL query and refresh the object with the newly loaded data, 

3648 regardless of whether or not the object is already present. 

3649 

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

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

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

3653 flags should match the parameters of 

3654 :meth:`_query.Query.with_for_update`. 

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

3656 

3657 :param execution_options: optional dictionary of execution options, 

3658 which will be associated with the query execution if one is emitted. 

3659 This dictionary can provide a subset of the options that are 

3660 accepted by :meth:`_engine.Connection.execution_options`, and may 

3661 also provide additional options understood only in an ORM context. 

3662 

3663 .. versionadded:: 1.4.29 

3664 

3665 .. seealso:: 

3666 

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

3668 options 

3669 

3670 :param bind_arguments: dictionary of additional arguments to determine 

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

3672 Contents of this dictionary are passed to the 

3673 :meth:`.Session.get_bind` method. 

3674 

3675 .. versionadded: 2.0.0rc1 

3676 

3677 :return: The object instance, or ``None``. 

3678 

3679 """ # noqa: E501 

3680 return self._get_impl( 

3681 entity, 

3682 ident, 

3683 loading.load_on_pk_identity, 

3684 options=options, 

3685 populate_existing=populate_existing, 

3686 with_for_update=with_for_update, 

3687 identity_token=identity_token, 

3688 execution_options=execution_options, 

3689 bind_arguments=bind_arguments, 

3690 ) 

3691 

3692 def get_one( 

3693 self, 

3694 entity: _EntityBindKey[_O], 

3695 ident: _PKIdentityArgument, 

3696 *, 

3697 options: Optional[Sequence[ORMOption]] = None, 

3698 populate_existing: bool = False, 

3699 with_for_update: ForUpdateParameter = None, 

3700 identity_token: Optional[Any] = None, 

3701 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3702 bind_arguments: Optional[_BindArguments] = None, 

3703 ) -> _O: 

3704 """Return exactly one instance based on the given primary key 

3705 identifier, or raise an exception if not found. 

3706 

3707 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3708 

3709 For a detailed documentation of the arguments see the 

3710 method :meth:`.Session.get`. 

3711 

3712 .. versionadded:: 2.0.22 

3713 

3714 :return: The object instance. 

3715 

3716 .. seealso:: 

3717 

3718 :meth:`.Session.get` - equivalent method that instead 

3719 returns ``None`` if no row was found with the provided primary 

3720 key 

3721 

3722 """ 

3723 

3724 instance = self.get( 

3725 entity, 

3726 ident, 

3727 options=options, 

3728 populate_existing=populate_existing, 

3729 with_for_update=with_for_update, 

3730 identity_token=identity_token, 

3731 execution_options=execution_options, 

3732 bind_arguments=bind_arguments, 

3733 ) 

3734 

3735 if instance is None: 

3736 raise sa_exc.NoResultFound( 

3737 "No row was found when one was required" 

3738 ) 

3739 

3740 return instance 

3741 

3742 def _get_impl( 

3743 self, 

3744 entity: _EntityBindKey[_O], 

3745 primary_key_identity: _PKIdentityArgument, 

3746 db_load_fn: Callable[..., _O], 

3747 *, 

3748 options: Optional[Sequence[ExecutableOption]] = None, 

3749 populate_existing: bool = False, 

3750 with_for_update: ForUpdateParameter = None, 

3751 identity_token: Optional[Any] = None, 

3752 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3753 bind_arguments: Optional[_BindArguments] = None, 

3754 ) -> Optional[_O]: 

3755 # convert composite types to individual args 

3756 if ( 

3757 is_composite_class(primary_key_identity) 

3758 and type(primary_key_identity) 

3759 in descriptor_props._composite_getters 

3760 ): 

3761 getter = descriptor_props._composite_getters[ 

3762 type(primary_key_identity) 

3763 ] 

3764 primary_key_identity = getter(primary_key_identity) 

3765 

3766 mapper: Optional[Mapper[_O]] = inspect(entity) 

3767 

3768 if mapper is None or not mapper.is_mapper: 

3769 raise sa_exc.ArgumentError( 

3770 "Expected mapped class or mapper, got: %r" % entity 

3771 ) 

3772 

3773 is_dict = isinstance(primary_key_identity, dict) 

3774 if not is_dict: 

3775 primary_key_identity = util.to_list( 

3776 primary_key_identity, default=[None] 

3777 ) 

3778 

3779 if len(primary_key_identity) != len(mapper.primary_key): 

3780 raise sa_exc.InvalidRequestError( 

3781 "Incorrect number of values in identifier to formulate " 

3782 "primary key for session.get(); primary key columns " 

3783 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3784 ) 

3785 

3786 if is_dict: 

3787 pk_synonyms = mapper._pk_synonyms 

3788 

3789 if pk_synonyms: 

3790 correct_keys = set(pk_synonyms).intersection( 

3791 primary_key_identity 

3792 ) 

3793 

3794 if correct_keys: 

3795 primary_key_identity = dict(primary_key_identity) 

3796 for k in correct_keys: 

3797 primary_key_identity[pk_synonyms[k]] = ( 

3798 primary_key_identity[k] 

3799 ) 

3800 

3801 try: 

3802 primary_key_identity = list( 

3803 primary_key_identity[prop.key] 

3804 for prop in mapper._identity_key_props 

3805 ) 

3806 

3807 except KeyError as err: 

3808 raise sa_exc.InvalidRequestError( 

3809 "Incorrect names of values in identifier to formulate " 

3810 "primary key for session.get(); primary key attribute " 

3811 "names are %s (synonym names are also accepted)" 

3812 % ",".join( 

3813 "'%s'" % prop.key 

3814 for prop in mapper._identity_key_props 

3815 ) 

3816 ) from err 

3817 

3818 if ( 

3819 not populate_existing 

3820 and not mapper.always_refresh 

3821 and with_for_update is None 

3822 ): 

3823 instance = self._identity_lookup( 

3824 mapper, 

3825 primary_key_identity, 

3826 identity_token=identity_token, 

3827 execution_options=execution_options, 

3828 bind_arguments=bind_arguments, 

3829 ) 

3830 

3831 if instance is not None: 

3832 # reject calls for id in identity map but class 

3833 # mismatch. 

3834 if not isinstance(instance, mapper.class_): 

3835 return None 

3836 return instance 

3837 

3838 # TODO: this was being tested before, but this is not possible 

3839 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3840 

3841 # set_label_style() not strictly necessary, however this will ensure 

3842 # that tablename_colname style is used which at the moment is 

3843 # asserted in a lot of unit tests :) 

3844 

3845 load_options = context.QueryContext.default_load_options 

3846 

3847 if populate_existing: 

3848 load_options += {"_populate_existing": populate_existing} 

3849 statement = sql.select(mapper).set_label_style( 

3850 LABEL_STYLE_TABLENAME_PLUS_COL 

3851 ) 

3852 if with_for_update is not None: 

3853 statement._for_update_arg = ForUpdateArg._from_argument( 

3854 with_for_update 

3855 ) 

3856 

3857 if options: 

3858 statement = statement.options(*options) 

3859 return db_load_fn( 

3860 self, 

3861 statement, 

3862 primary_key_identity, 

3863 load_options=load_options, 

3864 identity_token=identity_token, 

3865 execution_options=execution_options, 

3866 bind_arguments=bind_arguments, 

3867 ) 

3868 

3869 def merge( 

3870 self, 

3871 instance: _O, 

3872 *, 

3873 load: bool = True, 

3874 options: Optional[Sequence[ORMOption]] = None, 

3875 ) -> _O: 

3876 """Copy the state of a given instance into a corresponding instance 

3877 within this :class:`.Session`. 

3878 

3879 :meth:`.Session.merge` examines the primary key attributes of the 

3880 source instance, and attempts to reconcile it with an instance of the 

3881 same primary key in the session. If not found locally, it attempts 

3882 to load the object from the database based on primary key, and if 

3883 none can be located, creates a new instance. The state of each 

3884 attribute on the source instance is then copied to the target 

3885 instance. The resulting target instance is then returned by the 

3886 method; the original source instance is left unmodified, and 

3887 un-associated with the :class:`.Session` if not already. 

3888 

3889 This operation cascades to associated instances if the association is 

3890 mapped with ``cascade="merge"``. 

3891 

3892 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3893 

3894 :param instance: Instance to be merged. 

3895 :param load: Boolean, when False, :meth:`.merge` switches into 

3896 a "high performance" mode which causes it to forego emitting history 

3897 events as well as all database access. This flag is used for 

3898 cases such as transferring graphs of objects into a :class:`.Session` 

3899 from a second level cache, or to transfer just-loaded objects 

3900 into the :class:`.Session` owned by a worker thread or process 

3901 without re-querying the database. 

3902 

3903 The ``load=False`` use case adds the caveat that the given 

3904 object has to be in a "clean" state, that is, has no pending changes 

3905 to be flushed - even if the incoming object is detached from any 

3906 :class:`.Session`. This is so that when 

3907 the merge operation populates local attributes and 

3908 cascades to related objects and 

3909 collections, the values can be "stamped" onto the 

3910 target object as is, without generating any history or attribute 

3911 events, and without the need to reconcile the incoming data with 

3912 any existing related objects or collections that might not 

3913 be loaded. The resulting objects from ``load=False`` are always 

3914 produced as "clean", so it is only appropriate that the given objects 

3915 should be "clean" as well, else this suggests a mis-use of the 

3916 method. 

3917 :param options: optional sequence of loader options which will be 

3918 applied to the :meth:`_orm.Session.get` method when the merge 

3919 operation loads the existing version of the object from the database. 

3920 

3921 .. versionadded:: 1.4.24 

3922 

3923 

3924 .. seealso:: 

3925 

3926 :func:`.make_transient_to_detached` - provides for an alternative 

3927 means of "merging" a single object into the :class:`.Session` 

3928 

3929 """ 

3930 

3931 if self._warn_on_events: 

3932 self._flush_warning("Session.merge()") 

3933 

3934 _recursive: Dict[InstanceState[Any], object] = {} 

3935 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object] = {} 

3936 

3937 if load: 

3938 # flush current contents if we expect to load data 

3939 self._autoflush() 

3940 

3941 object_mapper(instance) # verify mapped 

3942 autoflush = self.autoflush 

3943 try: 

3944 self.autoflush = False 

3945 return self._merge( 

3946 attributes.instance_state(instance), 

3947 attributes.instance_dict(instance), 

3948 load=load, 

3949 options=options, 

3950 _recursive=_recursive, 

3951 _resolve_conflict_map=_resolve_conflict_map, 

3952 ) 

3953 finally: 

3954 self.autoflush = autoflush 

3955 

3956 def _merge( 

3957 self, 

3958 state: InstanceState[_O], 

3959 state_dict: _InstanceDict, 

3960 *, 

3961 options: Optional[Sequence[ORMOption]] = None, 

3962 load: bool, 

3963 _recursive: Dict[Any, object], 

3964 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

3965 ) -> _O: 

3966 mapper: Mapper[_O] = _state_mapper(state) 

3967 if state in _recursive: 

3968 return cast(_O, _recursive[state]) 

3969 

3970 new_instance = False 

3971 key = state.key 

3972 

3973 merged: Optional[_O] 

3974 

3975 if key is None: 

3976 if state in self._new: 

3977 util.warn( 

3978 "Instance %s is already pending in this Session yet is " 

3979 "being merged again; this is probably not what you want " 

3980 "to do" % state_str(state) 

3981 ) 

3982 

3983 if not load: 

3984 raise sa_exc.InvalidRequestError( 

3985 "merge() with load=False option does not support " 

3986 "objects transient (i.e. unpersisted) objects. flush() " 

3987 "all changes on mapped instances before merging with " 

3988 "load=False." 

3989 ) 

3990 key = mapper._identity_key_from_state(state) 

3991 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

3992 1 

3993 ] and ( 

3994 not _none_set.intersection(key[1]) 

3995 or ( 

3996 mapper.allow_partial_pks 

3997 and not _none_set.issuperset(key[1]) 

3998 ) 

3999 ) 

4000 else: 

4001 key_is_persistent = True 

4002 

4003 merged = self.identity_map.get(key) 

4004 

4005 if merged is None: 

4006 if key_is_persistent and key in _resolve_conflict_map: 

4007 merged = cast(_O, _resolve_conflict_map[key]) 

4008 

4009 elif not load: 

4010 if state.modified: 

4011 raise sa_exc.InvalidRequestError( 

4012 "merge() with load=False option does not support " 

4013 "objects marked as 'dirty'. flush() all changes on " 

4014 "mapped instances before merging with load=False." 

4015 ) 

4016 merged = mapper.class_manager.new_instance() 

4017 merged_state = attributes.instance_state(merged) 

4018 merged_state.key = key 

4019 self._update_impl(merged_state) 

4020 new_instance = True 

4021 

4022 elif key_is_persistent: 

4023 merged = self.get( 

4024 mapper.class_, 

4025 key[1], 

4026 identity_token=key[2], 

4027 options=options, 

4028 ) 

4029 

4030 if merged is None: 

4031 merged = mapper.class_manager.new_instance() 

4032 merged_state = attributes.instance_state(merged) 

4033 merged_dict = attributes.instance_dict(merged) 

4034 new_instance = True 

4035 self._save_or_update_state(merged_state) 

4036 else: 

4037 merged_state = attributes.instance_state(merged) 

4038 merged_dict = attributes.instance_dict(merged) 

4039 

4040 _recursive[state] = merged 

4041 _resolve_conflict_map[key] = merged 

4042 

4043 # check that we didn't just pull the exact same 

4044 # state out. 

4045 if state is not merged_state: 

4046 # version check if applicable 

4047 if mapper.version_id_col is not None: 

4048 existing_version = mapper._get_state_attr_by_column( 

4049 state, 

4050 state_dict, 

4051 mapper.version_id_col, 

4052 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4053 ) 

4054 

4055 merged_version = mapper._get_state_attr_by_column( 

4056 merged_state, 

4057 merged_dict, 

4058 mapper.version_id_col, 

4059 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4060 ) 

4061 

4062 if ( 

4063 existing_version 

4064 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4065 and merged_version 

4066 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4067 and existing_version != merged_version 

4068 ): 

4069 raise exc.StaleDataError( 

4070 "Version id '%s' on merged state %s " 

4071 "does not match existing version '%s'. " 

4072 "Leave the version attribute unset when " 

4073 "merging to update the most recent version." 

4074 % ( 

4075 existing_version, 

4076 state_str(merged_state), 

4077 merged_version, 

4078 ) 

4079 ) 

4080 

4081 merged_state.load_path = state.load_path 

4082 merged_state.load_options = state.load_options 

4083 

4084 # since we are copying load_options, we need to copy 

4085 # the callables_ that would have been generated by those 

4086 # load_options. 

4087 # assumes that the callables we put in state.callables_ 

4088 # are not instance-specific (which they should not be) 

4089 merged_state._copy_callables(state) 

4090 

4091 for prop in mapper.iterate_properties: 

4092 prop.merge( 

4093 self, 

4094 state, 

4095 state_dict, 

4096 merged_state, 

4097 merged_dict, 

4098 load, 

4099 _recursive, 

4100 _resolve_conflict_map, 

4101 ) 

4102 

4103 if not load: 

4104 # remove any history 

4105 merged_state._commit_all(merged_dict, self.identity_map) 

4106 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4107 merged_state, None 

4108 ) 

4109 

4110 if new_instance: 

4111 merged_state.manager.dispatch.load(merged_state, None) 

4112 

4113 return merged 

4114 

4115 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4116 if not self.identity_map.contains_state(state): 

4117 raise sa_exc.InvalidRequestError( 

4118 "Instance '%s' is not persistent within this Session" 

4119 % state_str(state) 

4120 ) 

4121 

4122 def _save_impl(self, state: InstanceState[Any]) -> None: 

4123 if state.key is not None: 

4124 raise sa_exc.InvalidRequestError( 

4125 "Object '%s' already has an identity - " 

4126 "it can't be registered as pending" % state_str(state) 

4127 ) 

4128 

4129 obj = state.obj() 

4130 to_attach = self._before_attach(state, obj) 

4131 if state not in self._new: 

4132 self._new[state] = obj 

4133 state.insert_order = len(self._new) 

4134 if to_attach: 

4135 self._after_attach(state, obj) 

4136 

4137 def _update_impl( 

4138 self, state: InstanceState[Any], revert_deletion: bool = False 

4139 ) -> None: 

4140 if state.key is None: 

4141 raise sa_exc.InvalidRequestError( 

4142 "Instance '%s' is not persisted" % state_str(state) 

4143 ) 

4144 

4145 if state._deleted: 

4146 if revert_deletion: 

4147 if not state._attached: 

4148 return 

4149 del state._deleted 

4150 else: 

4151 raise sa_exc.InvalidRequestError( 

4152 "Instance '%s' has been deleted. " 

4153 "Use the make_transient() " 

4154 "function to send this object back " 

4155 "to the transient state." % state_str(state) 

4156 ) 

4157 

4158 obj = state.obj() 

4159 

4160 # check for late gc 

4161 if obj is None: 

4162 return 

4163 

4164 to_attach = self._before_attach(state, obj) 

4165 

4166 self._deleted.pop(state, None) 

4167 if revert_deletion: 

4168 self.identity_map.replace(state) 

4169 else: 

4170 self.identity_map.add(state) 

4171 

4172 if to_attach: 

4173 self._after_attach(state, obj) 

4174 elif revert_deletion: 

4175 self.dispatch.deleted_to_persistent(self, state) 

4176 

4177 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4178 if state.key is None: 

4179 self._save_impl(state) 

4180 else: 

4181 self._update_impl(state) 

4182 

4183 def enable_relationship_loading(self, obj: object) -> None: 

4184 """Associate an object with this :class:`.Session` for related 

4185 object loading. 

4186 

4187 .. warning:: 

4188 

4189 :meth:`.enable_relationship_loading` exists to serve special 

4190 use cases and is not recommended for general use. 

4191 

4192 Accesses of attributes mapped with :func:`_orm.relationship` 

4193 will attempt to load a value from the database using this 

4194 :class:`.Session` as the source of connectivity. The values 

4195 will be loaded based on foreign key and primary key values 

4196 present on this object - if not present, then those relationships 

4197 will be unavailable. 

4198 

4199 The object will be attached to this session, but will 

4200 **not** participate in any persistence operations; its state 

4201 for almost all purposes will remain either "transient" or 

4202 "detached", except for the case of relationship loading. 

4203 

4204 Also note that backrefs will often not work as expected. 

4205 Altering a relationship-bound attribute on the target object 

4206 may not fire off a backref event, if the effective value 

4207 is what was already loaded from a foreign-key-holding value. 

4208 

4209 The :meth:`.Session.enable_relationship_loading` method is 

4210 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4211 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4212 an object to remain transient while still being able to load 

4213 related items. 

4214 

4215 To make a transient object associated with a :class:`.Session` 

4216 via :meth:`.Session.enable_relationship_loading` pending, add 

4217 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4218 If the object instead represents an existing identity in the database, 

4219 it should be merged using :meth:`.Session.merge`. 

4220 

4221 :meth:`.Session.enable_relationship_loading` does not improve 

4222 behavior when the ORM is used normally - object references should be 

4223 constructed at the object level, not at the foreign key level, so 

4224 that they are present in an ordinary way before flush() 

4225 proceeds. This method is not intended for general use. 

4226 

4227 .. seealso:: 

4228 

4229 :paramref:`_orm.relationship.load_on_pending` - this flag 

4230 allows per-relationship loading of many-to-ones on items that 

4231 are pending. 

4232 

4233 :func:`.make_transient_to_detached` - allows for an object to 

4234 be added to a :class:`.Session` without SQL emitted, which then 

4235 will unexpire attributes on access. 

4236 

4237 """ 

4238 try: 

4239 state = attributes.instance_state(obj) 

4240 except exc.NO_STATE as err: 

4241 raise exc.UnmappedInstanceError(obj) from err 

4242 

4243 to_attach = self._before_attach(state, obj) 

4244 state._load_pending = True 

4245 if to_attach: 

4246 self._after_attach(state, obj) 

4247 

4248 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4249 self._autobegin_t() 

4250 

4251 if state.session_id == self.hash_key: 

4252 return False 

4253 

4254 if state.session_id and state.session_id in _sessions: 

4255 raise sa_exc.InvalidRequestError( 

4256 "Object '%s' is already attached to session '%s' " 

4257 "(this is '%s')" 

4258 % (state_str(state), state.session_id, self.hash_key) 

4259 ) 

4260 

4261 self.dispatch.before_attach(self, state) 

4262 

4263 return True 

4264 

4265 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4266 state.session_id = self.hash_key 

4267 if state.modified and state._strong_obj is None: 

4268 state._strong_obj = obj 

4269 self.dispatch.after_attach(self, state) 

4270 

4271 if state.key: 

4272 self.dispatch.detached_to_persistent(self, state) 

4273 else: 

4274 self.dispatch.transient_to_pending(self, state) 

4275 

4276 def __contains__(self, instance: object) -> bool: 

4277 """Return True if the instance is associated with this session. 

4278 

4279 The instance may be pending or persistent within the Session for a 

4280 result of True. 

4281 

4282 """ 

4283 try: 

4284 state = attributes.instance_state(instance) 

4285 except exc.NO_STATE as err: 

4286 raise exc.UnmappedInstanceError(instance) from err 

4287 return self._contains_state(state) 

4288 

4289 def __iter__(self) -> Iterator[object]: 

4290 """Iterate over all pending or persistent instances within this 

4291 Session. 

4292 

4293 """ 

4294 return iter( 

4295 list(self._new.values()) + list(self.identity_map.values()) 

4296 ) 

4297 

4298 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4299 return state in self._new or self.identity_map.contains_state(state) 

4300 

4301 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4302 """Flush all the object changes to the database. 

4303 

4304 Writes out all pending object creations, deletions and modifications 

4305 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4306 automatically ordered by the Session's unit of work dependency 

4307 solver. 

4308 

4309 Database operations will be issued in the current transactional 

4310 context and do not affect the state of the transaction, unless an 

4311 error occurs, in which case the entire transaction is rolled back. 

4312 You may flush() as often as you like within a transaction to move 

4313 changes from Python to the database's transaction buffer. 

4314 

4315 :param objects: Optional; restricts the flush operation to operate 

4316 only on elements that are in the given collection. 

4317 

4318 This feature is for an extremely narrow set of use cases where 

4319 particular objects may need to be operated upon before the 

4320 full flush() occurs. It is not intended for general use. 

4321 

4322 """ 

4323 

4324 if self._flushing: 

4325 raise sa_exc.InvalidRequestError("Session is already flushing") 

4326 

4327 if self._is_clean(): 

4328 return 

4329 try: 

4330 self._flushing = True 

4331 self._flush(objects) 

4332 finally: 

4333 self._flushing = False 

4334 

4335 def _flush_warning(self, method: Any) -> None: 

4336 util.warn( 

4337 "Usage of the '%s' operation is not currently supported " 

4338 "within the execution stage of the flush process. " 

4339 "Results may not be consistent. Consider using alternative " 

4340 "event listeners or connection-level operations instead." % method 

4341 ) 

4342 

4343 def _is_clean(self) -> bool: 

4344 return ( 

4345 not self.identity_map.check_modified() 

4346 and not self._deleted 

4347 and not self._new 

4348 ) 

4349 

4350 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4351 dirty = self._dirty_states 

4352 if not dirty and not self._deleted and not self._new: 

4353 self.identity_map._modified.clear() 

4354 return 

4355 

4356 flush_context = UOWTransaction(self) 

4357 

4358 if self.dispatch.before_flush: 

4359 self.dispatch.before_flush(self, flush_context, objects) 

4360 # re-establish "dirty states" in case the listeners 

4361 # added 

4362 dirty = self._dirty_states 

4363 

4364 deleted = set(self._deleted) 

4365 new = set(self._new) 

4366 

4367 dirty = set(dirty).difference(deleted) 

4368 

4369 # create the set of all objects we want to operate upon 

4370 if objects: 

4371 # specific list passed in 

4372 objset = set() 

4373 for o in objects: 

4374 try: 

4375 state = attributes.instance_state(o) 

4376 

4377 except exc.NO_STATE as err: 

4378 raise exc.UnmappedInstanceError(o) from err 

4379 objset.add(state) 

4380 else: 

4381 objset = None 

4382 

4383 # store objects whose fate has been decided 

4384 processed = set() 

4385 

4386 # put all saves/updates into the flush context. detect top-level 

4387 # orphans and throw them into deleted. 

4388 if objset: 

4389 proc = new.union(dirty).intersection(objset).difference(deleted) 

4390 else: 

4391 proc = new.union(dirty).difference(deleted) 

4392 

4393 for state in proc: 

4394 is_orphan = _state_mapper(state)._is_orphan(state) 

4395 

4396 is_persistent_orphan = is_orphan and state.has_identity 

4397 

4398 if ( 

4399 is_orphan 

4400 and not is_persistent_orphan 

4401 and state._orphaned_outside_of_session 

4402 ): 

4403 self._expunge_states([state]) 

4404 else: 

4405 _reg = flush_context.register_object( 

4406 state, isdelete=is_persistent_orphan 

4407 ) 

4408 assert _reg, "Failed to add object to the flush context!" 

4409 processed.add(state) 

4410 

4411 # put all remaining deletes into the flush context. 

4412 if objset: 

4413 proc = deleted.intersection(objset).difference(processed) 

4414 else: 

4415 proc = deleted.difference(processed) 

4416 for state in proc: 

4417 _reg = flush_context.register_object(state, isdelete=True) 

4418 assert _reg, "Failed to add object to the flush context!" 

4419 

4420 if not flush_context.has_work: 

4421 return 

4422 

4423 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4424 try: 

4425 self._warn_on_events = True 

4426 try: 

4427 flush_context.execute() 

4428 finally: 

4429 self._warn_on_events = False 

4430 

4431 self.dispatch.after_flush(self, flush_context) 

4432 

4433 flush_context.finalize_flush_changes() 

4434 

4435 if not objects and self.identity_map._modified: 

4436 len_ = len(self.identity_map._modified) 

4437 

4438 statelib.InstanceState._commit_all_states( 

4439 [ 

4440 (state, state.dict) 

4441 for state in self.identity_map._modified 

4442 ], 

4443 instance_dict=self.identity_map, 

4444 ) 

4445 util.warn( 

4446 "Attribute history events accumulated on %d " 

4447 "previously clean instances " 

4448 "within inner-flush event handlers have been " 

4449 "reset, and will not result in database updates. " 

4450 "Consider using set_committed_value() within " 

4451 "inner-flush event handlers to avoid this warning." % len_ 

4452 ) 

4453 

4454 # useful assertions: 

4455 # if not objects: 

4456 # assert not self.identity_map._modified 

4457 # else: 

4458 # assert self.identity_map._modified == \ 

4459 # self.identity_map._modified.difference(objects) 

4460 

4461 self.dispatch.after_flush_postexec(self, flush_context) 

4462 

4463 transaction.commit() 

4464 

4465 except: 

4466 with util.safe_reraise(): 

4467 transaction.rollback(_capture_exception=True) 

4468 

4469 def bulk_save_objects( 

4470 self, 

4471 objects: Iterable[object], 

4472 return_defaults: bool = False, 

4473 update_changed_only: bool = True, 

4474 preserve_order: bool = True, 

4475 ) -> None: 

4476 """Perform a bulk save of the given list of objects. 

4477 

4478 .. legacy:: 

4479 

4480 This method is a legacy feature as of the 2.0 series of 

4481 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4482 the sections :ref:`orm_queryguide_bulk_insert` and 

4483 :ref:`orm_queryguide_bulk_update`. 

4484 

4485 For general INSERT and UPDATE of existing ORM mapped objects, 

4486 prefer standard :term:`unit of work` data management patterns, 

4487 introduced in the :ref:`unified_tutorial` at 

4488 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4489 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4490 which solves previous issues of bulk INSERT slowness. 

4491 

4492 :param objects: a sequence of mapped object instances. The mapped 

4493 objects are persisted as is, and are **not** associated with the 

4494 :class:`.Session` afterwards. 

4495 

4496 For each object, whether the object is sent as an INSERT or an 

4497 UPDATE is dependent on the same rules used by the :class:`.Session` 

4498 in traditional operation; if the object has the 

4499 :attr:`.InstanceState.key` 

4500 attribute set, then the object is assumed to be "detached" and 

4501 will result in an UPDATE. Otherwise, an INSERT is used. 

4502 

4503 In the case of an UPDATE, statements are grouped based on which 

4504 attributes have changed, and are thus to be the subject of each 

4505 SET clause. If ``update_changed_only`` is False, then all 

4506 attributes present within each object are applied to the UPDATE 

4507 statement, which may help in allowing the statements to be grouped 

4508 together into a larger executemany(), and will also reduce the 

4509 overhead of checking history on attributes. 

4510 

4511 :param return_defaults: when True, rows that are missing values which 

4512 generate defaults, namely integer primary key defaults and sequences, 

4513 will be inserted **one at a time**, so that the primary key value 

4514 is available. In particular this will allow joined-inheritance 

4515 and other multi-table mappings to insert correctly without the need 

4516 to provide primary key values ahead of time; however, 

4517 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4518 reduces the performance gains** of the method overall. It is strongly 

4519 advised to please use the standard :meth:`_orm.Session.add_all` 

4520 approach. 

4521 

4522 :param update_changed_only: when True, UPDATE statements are rendered 

4523 based on those attributes in each state that have logged changes. 

4524 When False, all attributes present are rendered into the SET clause 

4525 with the exception of primary key attributes. 

4526 

4527 :param preserve_order: when True, the order of inserts and updates 

4528 matches exactly the order in which the objects are given. When 

4529 False, common types of objects are grouped into inserts 

4530 and updates, to allow for more batching opportunities. 

4531 

4532 .. seealso:: 

4533 

4534 :doc:`queryguide/dml` 

4535 

4536 :meth:`.Session.bulk_insert_mappings` 

4537 

4538 :meth:`.Session.bulk_update_mappings` 

4539 

4540 """ 

4541 

4542 obj_states: Iterable[InstanceState[Any]] 

4543 

4544 obj_states = (attributes.instance_state(obj) for obj in objects) 

4545 

4546 if not preserve_order: 

4547 # the purpose of this sort is just so that common mappers 

4548 # and persistence states are grouped together, so that groupby 

4549 # will return a single group for a particular type of mapper. 

4550 # it's not trying to be deterministic beyond that. 

4551 obj_states = sorted( 

4552 obj_states, 

4553 key=lambda state: (id(state.mapper), state.key is not None), 

4554 ) 

4555 

4556 def grouping_key( 

4557 state: InstanceState[_O], 

4558 ) -> Tuple[Mapper[_O], bool]: 

4559 return (state.mapper, state.key is not None) 

4560 

4561 for (mapper, isupdate), states in itertools.groupby( 

4562 obj_states, grouping_key 

4563 ): 

4564 self._bulk_save_mappings( 

4565 mapper, 

4566 states, 

4567 isupdate=isupdate, 

4568 isstates=True, 

4569 return_defaults=return_defaults, 

4570 update_changed_only=update_changed_only, 

4571 render_nulls=False, 

4572 ) 

4573 

4574 def bulk_insert_mappings( 

4575 self, 

4576 mapper: Mapper[Any], 

4577 mappings: Iterable[Dict[str, Any]], 

4578 return_defaults: bool = False, 

4579 render_nulls: bool = False, 

4580 ) -> None: 

4581 """Perform a bulk insert of the given list of mapping dictionaries. 

4582 

4583 .. legacy:: 

4584 

4585 This method is a legacy feature as of the 2.0 series of 

4586 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4587 the sections :ref:`orm_queryguide_bulk_insert` and 

4588 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4589 implementation details with this method and adds new features 

4590 as well. 

4591 

4592 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4593 object, 

4594 representing the single kind of object represented within the mapping 

4595 list. 

4596 

4597 :param mappings: a sequence of dictionaries, each one containing the 

4598 state of the mapped row to be inserted, in terms of the attribute 

4599 names on the mapped class. If the mapping refers to multiple tables, 

4600 such as a joined-inheritance mapping, each dictionary must contain all 

4601 keys to be populated into all tables. 

4602 

4603 :param return_defaults: when True, the INSERT process will be altered 

4604 to ensure that newly generated primary key values will be fetched. 

4605 The rationale for this parameter is typically to enable 

4606 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4607 be bulk inserted. 

4608 

4609 .. note:: for backends that don't support RETURNING, the 

4610 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4611 parameter can significantly decrease performance as INSERT 

4612 statements can no longer be batched. See 

4613 :ref:`engine_insertmanyvalues` 

4614 for background on which backends are affected. 

4615 

4616 :param render_nulls: When True, a value of ``None`` will result 

4617 in a NULL value being included in the INSERT statement, rather 

4618 than the column being omitted from the INSERT. This allows all 

4619 the rows being INSERTed to have the identical set of columns which 

4620 allows the full set of rows to be batched to the DBAPI. Normally, 

4621 each column-set that contains a different combination of NULL values 

4622 than the previous row must omit a different series of columns from 

4623 the rendered INSERT statement, which means it must be emitted as a 

4624 separate statement. By passing this flag, the full set of rows 

4625 are guaranteed to be batchable into one batch; the cost however is 

4626 that server-side defaults which are invoked by an omitted column will 

4627 be skipped, so care must be taken to ensure that these are not 

4628 necessary. 

4629 

4630 .. warning:: 

4631 

4632 When this flag is set, **server side default SQL values will 

4633 not be invoked** for those columns that are inserted as NULL; 

4634 the NULL value will be sent explicitly. Care must be taken 

4635 to ensure that no server-side default functions need to be 

4636 invoked for the operation as a whole. 

4637 

4638 .. seealso:: 

4639 

4640 :doc:`queryguide/dml` 

4641 

4642 :meth:`.Session.bulk_save_objects` 

4643 

4644 :meth:`.Session.bulk_update_mappings` 

4645 

4646 """ 

4647 self._bulk_save_mappings( 

4648 mapper, 

4649 mappings, 

4650 isupdate=False, 

4651 isstates=False, 

4652 return_defaults=return_defaults, 

4653 update_changed_only=False, 

4654 render_nulls=render_nulls, 

4655 ) 

4656 

4657 def bulk_update_mappings( 

4658 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4659 ) -> None: 

4660 """Perform a bulk update of the given list of mapping dictionaries. 

4661 

4662 .. legacy:: 

4663 

4664 This method is a legacy feature as of the 2.0 series of 

4665 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4666 the sections :ref:`orm_queryguide_bulk_insert` and 

4667 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4668 implementation details with this method and adds new features 

4669 as well. 

4670 

4671 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4672 object, 

4673 representing the single kind of object represented within the mapping 

4674 list. 

4675 

4676 :param mappings: a sequence of dictionaries, each one containing the 

4677 state of the mapped row to be updated, in terms of the attribute names 

4678 on the mapped class. If the mapping refers to multiple tables, such 

4679 as a joined-inheritance mapping, each dictionary may contain keys 

4680 corresponding to all tables. All those keys which are present and 

4681 are not part of the primary key are applied to the SET clause of the 

4682 UPDATE statement; the primary key values, which are required, are 

4683 applied to the WHERE clause. 

4684 

4685 

4686 .. seealso:: 

4687 

4688 :doc:`queryguide/dml` 

4689 

4690 :meth:`.Session.bulk_insert_mappings` 

4691 

4692 :meth:`.Session.bulk_save_objects` 

4693 

4694 """ 

4695 self._bulk_save_mappings( 

4696 mapper, 

4697 mappings, 

4698 isupdate=True, 

4699 isstates=False, 

4700 return_defaults=False, 

4701 update_changed_only=False, 

4702 render_nulls=False, 

4703 ) 

4704 

4705 def _bulk_save_mappings( 

4706 self, 

4707 mapper: Mapper[_O], 

4708 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4709 *, 

4710 isupdate: bool, 

4711 isstates: bool, 

4712 return_defaults: bool, 

4713 update_changed_only: bool, 

4714 render_nulls: bool, 

4715 ) -> None: 

4716 mapper = _class_to_mapper(mapper) 

4717 self._flushing = True 

4718 

4719 transaction = self._autobegin_t()._begin() 

4720 try: 

4721 if isupdate: 

4722 bulk_persistence._bulk_update( 

4723 mapper, 

4724 mappings, 

4725 transaction, 

4726 isstates=isstates, 

4727 update_changed_only=update_changed_only, 

4728 ) 

4729 else: 

4730 bulk_persistence._bulk_insert( 

4731 mapper, 

4732 mappings, 

4733 transaction, 

4734 isstates=isstates, 

4735 return_defaults=return_defaults, 

4736 render_nulls=render_nulls, 

4737 ) 

4738 transaction.commit() 

4739 

4740 except: 

4741 with util.safe_reraise(): 

4742 transaction.rollback(_capture_exception=True) 

4743 finally: 

4744 self._flushing = False 

4745 

4746 def is_modified( 

4747 self, instance: object, include_collections: bool = True 

4748 ) -> bool: 

4749 r"""Return ``True`` if the given instance has locally 

4750 modified attributes. 

4751 

4752 This method retrieves the history for each instrumented 

4753 attribute on the instance and performs a comparison of the current 

4754 value to its previously flushed or committed value, if any. 

4755 

4756 It is in effect a more expensive and accurate 

4757 version of checking for the given instance in the 

4758 :attr:`.Session.dirty` collection; a full test for 

4759 each attribute's net "dirty" status is performed. 

4760 

4761 E.g.:: 

4762 

4763 return session.is_modified(someobject) 

4764 

4765 A few caveats to this method apply: 

4766 

4767 * Instances present in the :attr:`.Session.dirty` collection may 

4768 report ``False`` when tested with this method. This is because 

4769 the object may have received change events via attribute mutation, 

4770 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4771 is the same as that loaded from the database, resulting in no net 

4772 change here. 

4773 * Scalar attributes may not have recorded the previously set 

4774 value when a new value was applied, if the attribute was not loaded, 

4775 or was expired, at the time the new value was received - in these 

4776 cases, the attribute is assumed to have a change, even if there is 

4777 ultimately no net change against its database value. SQLAlchemy in 

4778 most cases does not need the "old" value when a set event occurs, so 

4779 it skips the expense of a SQL call if the old value isn't present, 

4780 based on the assumption that an UPDATE of the scalar value is 

4781 usually needed, and in those few cases where it isn't, is less 

4782 expensive on average than issuing a defensive SELECT. 

4783 

4784 The "old" value is fetched unconditionally upon set only if the 

4785 attribute container has the ``active_history`` flag set to ``True``. 

4786 This flag is set typically for primary key attributes and scalar 

4787 object references that are not a simple many-to-one. To set this 

4788 flag for any arbitrary mapped column, use the ``active_history`` 

4789 argument with :func:`.column_property`. 

4790 

4791 :param instance: mapped instance to be tested for pending changes. 

4792 :param include_collections: Indicates if multivalued collections 

4793 should be included in the operation. Setting this to ``False`` is a 

4794 way to detect only local-column based properties (i.e. scalar columns 

4795 or many-to-one foreign keys) that would result in an UPDATE for this 

4796 instance upon flush. 

4797 

4798 """ 

4799 state = object_state(instance) 

4800 

4801 if not state.modified: 

4802 return False 

4803 

4804 dict_ = state.dict 

4805 

4806 for attr in state.manager.attributes: 

4807 if ( 

4808 not include_collections 

4809 and hasattr(attr.impl, "get_collection") 

4810 ) or not hasattr(attr.impl, "get_history"): 

4811 continue 

4812 

4813 (added, unchanged, deleted) = attr.impl.get_history( 

4814 state, dict_, passive=PassiveFlag.NO_CHANGE 

4815 ) 

4816 

4817 if added or deleted: 

4818 return True 

4819 else: 

4820 return False 

4821 

4822 @property 

4823 def is_active(self) -> bool: 

4824 """True if this :class:`.Session` not in "partial rollback" state. 

4825 

4826 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4827 a new transaction immediately, so this attribute will be False 

4828 when the :class:`_orm.Session` is first instantiated. 

4829 

4830 "partial rollback" state typically indicates that the flush process 

4831 of the :class:`_orm.Session` has failed, and that the 

4832 :meth:`_orm.Session.rollback` method must be emitted in order to 

4833 fully roll back the transaction. 

4834 

4835 If this :class:`_orm.Session` is not in a transaction at all, the 

4836 :class:`_orm.Session` will autobegin when it is first used, so in this 

4837 case :attr:`_orm.Session.is_active` will return True. 

4838 

4839 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4840 and that transaction has not been rolled back internally, the 

4841 :attr:`_orm.Session.is_active` will also return True. 

4842 

4843 .. seealso:: 

4844 

4845 :ref:`faq_session_rollback` 

4846 

4847 :meth:`_orm.Session.in_transaction` 

4848 

4849 """ 

4850 return self._transaction is None or self._transaction.is_active 

4851 

4852 @property 

4853 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4854 """The set of all persistent states considered dirty. 

4855 

4856 This method returns all states that were modified including 

4857 those that were possibly deleted. 

4858 

4859 """ 

4860 return self.identity_map._dirty_states() 

4861 

4862 @property 

4863 def dirty(self) -> IdentitySet: 

4864 """The set of all persistent instances considered dirty. 

4865 

4866 E.g.:: 

4867 

4868 some_mapped_object in session.dirty 

4869 

4870 Instances are considered dirty when they were modified but not 

4871 deleted. 

4872 

4873 Note that this 'dirty' calculation is 'optimistic'; most 

4874 attribute-setting or collection modification operations will 

4875 mark an instance as 'dirty' and place it in this set, even if 

4876 there is no net change to the attribute's value. At flush 

4877 time, the value of each attribute is compared to its 

4878 previously saved value, and if there's no net change, no SQL 

4879 operation will occur (this is a more expensive operation so 

4880 it's only done at flush time). 

4881 

4882 To check if an instance has actionable net changes to its 

4883 attributes, use the :meth:`.Session.is_modified` method. 

4884 

4885 """ 

4886 return IdentitySet( 

4887 [ 

4888 state.obj() 

4889 for state in self._dirty_states 

4890 if state not in self._deleted 

4891 ] 

4892 ) 

4893 

4894 @property 

4895 def deleted(self) -> IdentitySet: 

4896 "The set of all instances marked as 'deleted' within this ``Session``" 

4897 

4898 return util.IdentitySet(list(self._deleted.values())) 

4899 

4900 @property 

4901 def new(self) -> IdentitySet: 

4902 "The set of all instances marked as 'new' within this ``Session``." 

4903 

4904 return util.IdentitySet(list(self._new.values())) 

4905 

4906 

4907_S = TypeVar("_S", bound="Session") 

4908 

4909 

4910class sessionmaker(_SessionClassMethods, Generic[_S]): 

4911 """A configurable :class:`.Session` factory. 

4912 

4913 The :class:`.sessionmaker` factory generates new 

4914 :class:`.Session` objects when called, creating them given 

4915 the configurational arguments established here. 

4916 

4917 e.g.:: 

4918 

4919 from sqlalchemy import create_engine 

4920 from sqlalchemy.orm import sessionmaker 

4921 

4922 # an Engine, which the Session will use for connection 

4923 # resources 

4924 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

4925 

4926 Session = sessionmaker(engine) 

4927 

4928 with Session() as session: 

4929 session.add(some_object) 

4930 session.add(some_other_object) 

4931 session.commit() 

4932 

4933 Context manager use is optional; otherwise, the returned 

4934 :class:`_orm.Session` object may be closed explicitly via the 

4935 :meth:`_orm.Session.close` method. Using a 

4936 ``try:/finally:`` block is optional, however will ensure that the close 

4937 takes place even if there are database errors:: 

4938 

4939 session = Session() 

4940 try: 

4941 session.add(some_object) 

4942 session.add(some_other_object) 

4943 session.commit() 

4944 finally: 

4945 session.close() 

4946 

4947 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

4948 objects in the same way as an :class:`_engine.Engine` acts as a factory 

4949 for :class:`_engine.Connection` objects. In this way it also includes 

4950 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

4951 manager which both begins and commits a transaction, as well as closes 

4952 out the :class:`_orm.Session` when complete, rolling back the transaction 

4953 if any errors occur:: 

4954 

4955 Session = sessionmaker(engine) 

4956 

4957 with Session.begin() as session: 

4958 session.add(some_object) 

4959 session.add(some_other_object) 

4960 # commits transaction, closes session 

4961 

4962 .. versionadded:: 1.4 

4963 

4964 When calling upon :class:`_orm.sessionmaker` to construct a 

4965 :class:`_orm.Session`, keyword arguments may also be passed to the 

4966 method; these arguments will override that of the globally configured 

4967 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

4968 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

4969 bound to a specific :class:`_engine.Connection` procured from that engine:: 

4970 

4971 Session = sessionmaker(engine) 

4972 

4973 # bind an individual session to a connection 

4974 

4975 with engine.connect() as connection: 

4976 with Session(bind=connection) as session: 

4977 ... # work with session 

4978 

4979 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

4980 can be used to specify additional keyword arguments to the factory, which 

4981 will take effect for subsequent :class:`.Session` objects generated. This 

4982 is usually used to associate one or more :class:`_engine.Engine` objects 

4983 with an existing 

4984 :class:`.sessionmaker` factory before it is first used:: 

4985 

4986 # application starts, sessionmaker does not have 

4987 # an engine bound yet 

4988 Session = sessionmaker() 

4989 

4990 # ... later, when an engine URL is read from a configuration 

4991 # file or other events allow the engine to be created 

4992 engine = create_engine("sqlite:///foo.db") 

4993 Session.configure(bind=engine) 

4994 

4995 sess = Session() 

4996 # work with session 

4997 

4998 .. seealso:: 

4999 

5000 :ref:`session_getting` - introductory text on creating 

5001 sessions using :class:`.sessionmaker`. 

5002 

5003 """ 

5004 

5005 class_: Type[_S] 

5006 

5007 @overload 

5008 def __init__( 

5009 self, 

5010 bind: Optional[_SessionBind] = ..., 

5011 *, 

5012 class_: Type[_S], 

5013 autoflush: bool = ..., 

5014 expire_on_commit: bool = ..., 

5015 info: Optional[_InfoType] = ..., 

5016 **kw: Any, 

5017 ): ... 

5018 

5019 @overload 

5020 def __init__( 

5021 self: "sessionmaker[Session]", 

5022 bind: Optional[_SessionBind] = ..., 

5023 *, 

5024 autoflush: bool = ..., 

5025 expire_on_commit: bool = ..., 

5026 info: Optional[_InfoType] = ..., 

5027 **kw: Any, 

5028 ): ... 

5029 

5030 def __init__( 

5031 self, 

5032 bind: Optional[_SessionBind] = None, 

5033 *, 

5034 class_: Type[_S] = Session, # type: ignore 

5035 autoflush: bool = True, 

5036 expire_on_commit: bool = True, 

5037 info: Optional[_InfoType] = None, 

5038 **kw: Any, 

5039 ): 

5040 r"""Construct a new :class:`.sessionmaker`. 

5041 

5042 All arguments here except for ``class_`` correspond to arguments 

5043 accepted by :class:`.Session` directly. See the 

5044 :meth:`.Session.__init__` docstring for more details on parameters. 

5045 

5046 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5047 with 

5048 which newly created :class:`.Session` objects will be associated. 

5049 :param class\_: class to use in order to create new :class:`.Session` 

5050 objects. Defaults to :class:`.Session`. 

5051 :param autoflush: The autoflush setting to use with newly created 

5052 :class:`.Session` objects. 

5053 

5054 .. seealso:: 

5055 

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

5057 

5058 :param expire_on_commit=True: the 

5059 :paramref:`_orm.Session.expire_on_commit` setting to use 

5060 with newly created :class:`.Session` objects. 

5061 

5062 :param info: optional dictionary of information that will be available 

5063 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5064 replaced, when the ``info`` parameter is specified to the specific 

5065 :class:`.Session` construction operation. 

5066 

5067 :param \**kw: all other keyword arguments are passed to the 

5068 constructor of newly created :class:`.Session` objects. 

5069 

5070 """ 

5071 kw["bind"] = bind 

5072 kw["autoflush"] = autoflush 

5073 kw["expire_on_commit"] = expire_on_commit 

5074 if info is not None: 

5075 kw["info"] = info 

5076 self.kw = kw 

5077 # make our own subclass of the given class, so that 

5078 # events can be associated with it specifically. 

5079 self.class_ = type(class_.__name__, (class_,), {}) 

5080 

5081 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5082 """Produce a context manager that both provides a new 

5083 :class:`_orm.Session` as well as a transaction that commits. 

5084 

5085 

5086 e.g.:: 

5087 

5088 Session = sessionmaker(some_engine) 

5089 

5090 with Session.begin() as session: 

5091 session.add(some_object) 

5092 

5093 # commits transaction, closes session 

5094 

5095 .. versionadded:: 1.4 

5096 

5097 

5098 """ 

5099 

5100 session = self() 

5101 return session._maker_context_manager() 

5102 

5103 def __call__(self, **local_kw: Any) -> _S: 

5104 """Produce a new :class:`.Session` object using the configuration 

5105 established in this :class:`.sessionmaker`. 

5106 

5107 In Python, the ``__call__`` method is invoked on an object when 

5108 it is "called" in the same way as a function:: 

5109 

5110 Session = sessionmaker(some_engine) 

5111 session = Session() # invokes sessionmaker.__call__() 

5112 

5113 """ 

5114 for k, v in self.kw.items(): 

5115 if k == "info" and "info" in local_kw: 

5116 d = v.copy() 

5117 d.update(local_kw["info"]) 

5118 local_kw["info"] = d 

5119 else: 

5120 local_kw.setdefault(k, v) 

5121 return self.class_(**local_kw) 

5122 

5123 def configure(self, **new_kw: Any) -> None: 

5124 """(Re)configure the arguments for this sessionmaker. 

5125 

5126 e.g.:: 

5127 

5128 Session = sessionmaker() 

5129 

5130 Session.configure(bind=create_engine("sqlite://")) 

5131 """ 

5132 self.kw.update(new_kw) 

5133 

5134 def __repr__(self) -> str: 

5135 return "%s(class_=%r, %s)" % ( 

5136 self.__class__.__name__, 

5137 self.class_.__name__, 

5138 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5139 ) 

5140 

5141 

5142def close_all_sessions() -> None: 

5143 """Close all sessions in memory. 

5144 

5145 This function consults a global registry of all :class:`.Session` objects 

5146 and calls :meth:`.Session.close` on them, which resets them to a clean 

5147 state. 

5148 

5149 This function is not for general use but may be useful for test suites 

5150 within the teardown scheme. 

5151 

5152 .. versionadded:: 1.3 

5153 

5154 """ 

5155 

5156 for sess in _sessions.values(): 

5157 sess.close() 

5158 

5159 

5160def make_transient(instance: object) -> None: 

5161 """Alter the state of the given instance so that it is :term:`transient`. 

5162 

5163 .. note:: 

5164 

5165 :func:`.make_transient` is a special-case function for 

5166 advanced use cases only. 

5167 

5168 The given mapped instance is assumed to be in the :term:`persistent` or 

5169 :term:`detached` state. The function will remove its association with any 

5170 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5171 effect is that the object will behave as though it were newly constructed, 

5172 except retaining any attribute / collection values that were loaded at the 

5173 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5174 if this object had been deleted as a result of using 

5175 :meth:`.Session.delete`. 

5176 

5177 .. warning:: 

5178 

5179 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5180 load ORM-mapped attributes that are not currently loaded at the time 

5181 the function is called. This includes attributes which: 

5182 

5183 * were expired via :meth:`.Session.expire` 

5184 

5185 * were expired as the natural effect of committing a session 

5186 transaction, e.g. :meth:`.Session.commit` 

5187 

5188 * are normally :term:`lazy loaded` but are not currently loaded 

5189 

5190 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5191 not yet loaded 

5192 

5193 * were not present in the query which loaded this object, such as that 

5194 which is common in joined table inheritance and other scenarios. 

5195 

5196 After :func:`.make_transient` is called, unloaded attributes such 

5197 as those above will normally resolve to the value ``None`` when 

5198 accessed, or an empty collection for a collection-oriented attribute. 

5199 As the object is transient and un-associated with any database 

5200 identity, it will no longer retrieve these values. 

5201 

5202 .. seealso:: 

5203 

5204 :func:`.make_transient_to_detached` 

5205 

5206 """ 

5207 state = attributes.instance_state(instance) 

5208 s = _state_session(state) 

5209 if s: 

5210 s._expunge_states([state]) 

5211 

5212 # remove expired state 

5213 state.expired_attributes.clear() 

5214 

5215 # remove deferred callables 

5216 if state.callables: 

5217 del state.callables 

5218 

5219 if state.key: 

5220 del state.key 

5221 if state._deleted: 

5222 del state._deleted 

5223 

5224 

5225def make_transient_to_detached(instance: object) -> None: 

5226 """Make the given transient instance :term:`detached`. 

5227 

5228 .. note:: 

5229 

5230 :func:`.make_transient_to_detached` is a special-case function for 

5231 advanced use cases only. 

5232 

5233 All attribute history on the given instance 

5234 will be reset as though the instance were freshly loaded 

5235 from a query. Missing attributes will be marked as expired. 

5236 The primary key attributes of the object, which are required, will be made 

5237 into the "key" of the instance. 

5238 

5239 The object can then be added to a session, or merged 

5240 possibly with the load=False flag, at which point it will look 

5241 as if it were loaded that way, without emitting SQL. 

5242 

5243 This is a special use case function that differs from a normal 

5244 call to :meth:`.Session.merge` in that a given persistent state 

5245 can be manufactured without any SQL calls. 

5246 

5247 .. seealso:: 

5248 

5249 :func:`.make_transient` 

5250 

5251 :meth:`.Session.enable_relationship_loading` 

5252 

5253 """ 

5254 state = attributes.instance_state(instance) 

5255 if state.session_id or state.key: 

5256 raise sa_exc.InvalidRequestError("Given object must be transient") 

5257 state.key = state.mapper._identity_key_from_state(state) 

5258 if state._deleted: 

5259 del state._deleted 

5260 state._commit_all(state.dict) 

5261 state._expire_attributes(state.dict, state.unloaded) 

5262 

5263 

5264def object_session(instance: object) -> Optional[Session]: 

5265 """Return the :class:`.Session` to which the given instance belongs. 

5266 

5267 This is essentially the same as the :attr:`.InstanceState.session` 

5268 accessor. See that attribute for details. 

5269 

5270 """ 

5271 

5272 try: 

5273 state = attributes.instance_state(instance) 

5274 except exc.NO_STATE as err: 

5275 raise exc.UnmappedInstanceError(instance) from err 

5276 else: 

5277 return _state_session(state) 

5278 

5279 

5280_new_sessionid = util.counter()