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

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

1436 statements  

1# orm/session.py 

2# Copyright (C) 2005-2024 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 Protocol 

29from typing import Sequence 

30from typing import Set 

31from typing import Tuple 

32from typing import Type 

33from typing import TYPE_CHECKING 

34from typing import TypeVar 

35from typing import Union 

36import weakref 

37 

38from . import attributes 

39from . import bulk_persistence 

40from . import context 

41from . import descriptor_props 

42from . import exc 

43from . import identity 

44from . import loading 

45from . import query 

46from . import state as statelib 

47from ._typing import _O 

48from ._typing import insp_is_mapper 

49from ._typing import is_composite_class 

50from ._typing import is_orm_option 

51from ._typing import is_user_defined_option 

52from .base import _class_to_mapper 

53from .base import _none_set 

54from .base import _state_mapper 

55from .base import instance_str 

56from .base import LoaderCallableStatus 

57from .base import object_mapper 

58from .base import object_state 

59from .base import PassiveFlag 

60from .base import state_str 

61from .context import FromStatement 

62from .context import ORMCompileState 

63from .identity import IdentityMap 

64from .query import Query 

65from .state import InstanceState 

66from .state_changes import _StateChange 

67from .state_changes import _StateChangeState 

68from .state_changes import _StateChangeStates 

69from .unitofwork import UOWTransaction 

70from .. import engine 

71from .. import exc as sa_exc 

72from .. import sql 

73from .. import util 

74from ..engine import Connection 

75from ..engine import Engine 

76from ..engine.util import TransactionalContext 

77from ..event import dispatcher 

78from ..event import EventTarget 

79from ..inspection import inspect 

80from ..inspection import Inspectable 

81from ..sql import coercions 

82from ..sql import dml 

83from ..sql import roles 

84from ..sql import Select 

85from ..sql import TableClause 

86from ..sql import visitors 

87from ..sql.base import _NoArg 

88from ..sql.base import CompileState 

89from ..sql.schema import Table 

90from ..sql.selectable import ForUpdateArg 

91from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

92from ..util import IdentitySet 

93from ..util.typing import Literal 

94from ..util.typing import TupleAny 

95from ..util.typing import TypeVarTuple 

96from ..util.typing import Unpack 

97 

98 

99if typing.TYPE_CHECKING: 

100 from ._typing import _EntityType 

101 from ._typing import _IdentityKeyType 

102 from ._typing import _InstanceDict 

103 from ._typing import OrmExecuteOptionsParameter 

104 from .interfaces import ORMOption 

105 from .interfaces import UserDefinedOption 

106 from .mapper import Mapper 

107 from .path_registry import PathRegistry 

108 from .query import RowReturningQuery 

109 from ..engine import CursorResult 

110 from ..engine import Result 

111 from ..engine import Row 

112 from ..engine import RowMapping 

113 from ..engine.base import Transaction 

114 from ..engine.base import TwoPhaseTransaction 

115 from ..engine.interfaces import _CoreAnyExecuteParams 

116 from ..engine.interfaces import _CoreSingleExecuteParams 

117 from ..engine.interfaces import _ExecuteOptions 

118 from ..engine.interfaces import CoreExecuteOptionsParameter 

119 from ..engine.result import ScalarResult 

120 from ..event import _InstanceLevelDispatch 

121 from ..sql._typing import _ColumnsClauseArgument 

122 from ..sql._typing import _InfoType 

123 from ..sql._typing import _T0 

124 from ..sql._typing import _T1 

125 from ..sql._typing import _T2 

126 from ..sql._typing import _T3 

127 from ..sql._typing import _T4 

128 from ..sql._typing import _T5 

129 from ..sql._typing import _T6 

130 from ..sql._typing import _T7 

131 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

132 from ..sql.base import Executable 

133 from ..sql.base import ExecutableOption 

134 from ..sql.dml import UpdateBase 

135 from ..sql.elements import ClauseElement 

136 from ..sql.roles import TypedColumnsClauseRole 

137 from ..sql.selectable import ForUpdateParameter 

138 from ..sql.selectable import TypedReturnsRows 

139 

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

141_Ts = TypeVarTuple("_Ts") 

142 

143__all__ = [ 

144 "Session", 

145 "SessionTransaction", 

146 "sessionmaker", 

147 "ORMExecuteState", 

148 "close_all_sessions", 

149 "make_transient", 

150 "make_transient_to_detached", 

151 "object_session", 

152] 

153 

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

155 weakref.WeakValueDictionary() 

156) 

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

158""" 

159 

160statelib._sessions = _sessions 

161 

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

163 

164_BindArguments = Dict[str, Any] 

165 

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

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

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

169 

170JoinTransactionMode = Literal[ 

171 "conditional_savepoint", 

172 "rollback_only", 

173 "control_fully", 

174 "create_savepoint", 

175] 

176 

177 

178class _ConnectionCallableProto(Protocol): 

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

180 

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

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

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

184 as persistence time. 

185 

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

187 is established when using the horizontal sharding extension. 

188 

189 """ 

190 

191 def __call__( 

192 self, 

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

194 instance: Optional[object] = None, 

195 **kw: Any, 

196 ) -> Connection: ... 

197 

198 

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

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

201 associated, if any. 

202 """ 

203 return state.session 

204 

205 

206class _SessionClassMethods: 

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

208 

209 @classmethod 

210 @util.deprecated( 

211 "1.3", 

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

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

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

215 ) 

216 def close_all(cls) -> None: 

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

218 

219 close_all_sessions() 

220 

221 @classmethod 

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

223 def identity_key( 

224 cls, 

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

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

227 *, 

228 instance: Optional[Any] = None, 

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

230 identity_token: Optional[Any] = None, 

231 ) -> _IdentityKeyType[Any]: 

232 """Return an identity key. 

233 

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

235 

236 """ 

237 return util.preloaded.orm_util.identity_key( 

238 class_, 

239 ident, 

240 instance=instance, 

241 row=row, 

242 identity_token=identity_token, 

243 ) 

244 

245 @classmethod 

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

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

248 

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

250 

251 """ 

252 

253 return object_session(instance) 

254 

255 

256class SessionTransactionState(_StateChangeState): 

257 ACTIVE = 1 

258 PREPARED = 2 

259 COMMITTED = 3 

260 DEACTIVE = 4 

261 CLOSED = 5 

262 PROVISIONING_CONNECTION = 6 

263 

264 

265# backwards compatibility 

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

267 SessionTransactionState 

268) 

269 

270 

271class ORMExecuteState(util.MemoizedSlots): 

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

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

274 

275 .. versionadded:: 1.4 

276 

277 .. seealso:: 

278 

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

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

281 

282 """ 

283 

284 __slots__ = ( 

285 "session", 

286 "statement", 

287 "parameters", 

288 "execution_options", 

289 "local_execution_options", 

290 "bind_arguments", 

291 "identity_token", 

292 "_compile_state_cls", 

293 "_starting_event_idx", 

294 "_events_todo", 

295 "_update_execution_options", 

296 ) 

297 

298 session: Session 

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

300 

301 statement: Executable 

302 """The SQL statement being invoked. 

303 

304 For an ORM selection as would 

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

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

307 """ 

308 

309 parameters: Optional[_CoreAnyExecuteParams] 

310 """Dictionary of parameters that was passed to 

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

312 

313 execution_options: _ExecuteOptions 

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

315 

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

317 locally passed execution options. 

318 

319 .. seealso:: 

320 

321 :attr:`_orm.ORMExecuteState.local_execution_options` 

322 

323 :meth:`_sql.Executable.execution_options` 

324 

325 :ref:`orm_queryguide_execution_options` 

326 

327 """ 

328 

329 local_execution_options: _ExecuteOptions 

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

331 :meth:`.Session.execute` method. 

332 

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

334 being invoked. 

335 

336 .. seealso:: 

337 

338 :attr:`_orm.ORMExecuteState.execution_options` 

339 

340 """ 

341 

342 bind_arguments: _BindArguments 

343 """The dictionary passed as the 

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

345 

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

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

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

349 

350 """ 

351 

352 _compile_state_cls: Optional[Type[ORMCompileState]] 

353 _starting_event_idx: int 

354 _events_todo: List[Any] 

355 _update_execution_options: Optional[_ExecuteOptions] 

356 

357 def __init__( 

358 self, 

359 session: Session, 

360 statement: Executable, 

361 parameters: Optional[_CoreAnyExecuteParams], 

362 execution_options: _ExecuteOptions, 

363 bind_arguments: _BindArguments, 

364 compile_state_cls: Optional[Type[ORMCompileState]], 

365 events_todo: List[_InstanceLevelDispatch[Session]], 

366 ): 

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

368 

369 this object is constructed internally. 

370 

371 """ 

372 self.session = session 

373 self.statement = statement 

374 self.parameters = parameters 

375 self.local_execution_options = execution_options 

376 self.execution_options = statement._execution_options.union( 

377 execution_options 

378 ) 

379 self.bind_arguments = bind_arguments 

380 self._compile_state_cls = compile_state_cls 

381 self._events_todo = list(events_todo) 

382 

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

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

385 

386 def invoke_statement( 

387 self, 

388 statement: Optional[Executable] = None, 

389 params: Optional[_CoreAnyExecuteParams] = None, 

390 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

391 bind_arguments: Optional[_BindArguments] = None, 

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

393 """Execute the statement represented by this 

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

395 already proceeded. 

396 

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

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

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

400 that want to override how the ultimate 

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

402 retrieve results from an offline cache or which concatenate results 

403 from multiple executions. 

404 

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

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

407 is propagated to the calling 

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

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

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

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

412 

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

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

415 

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

417 which will be merged into the existing 

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

419 

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

421 for executemany executions. 

422 

423 :param execution_options: optional dictionary of execution options 

424 will be merged into the existing 

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

426 :class:`.ORMExecuteState`. 

427 

428 :param bind_arguments: optional dictionary of bind_arguments 

429 which will be merged amongst the current 

430 :attr:`.ORMExecuteState.bind_arguments` 

431 of this :class:`.ORMExecuteState`. 

432 

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

434 

435 .. seealso:: 

436 

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

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

439 

440 

441 """ 

442 

443 if statement is None: 

444 statement = self.statement 

445 

446 _bind_arguments = dict(self.bind_arguments) 

447 if bind_arguments: 

448 _bind_arguments.update(bind_arguments) 

449 _bind_arguments["_sa_skip_events"] = True 

450 

451 _params: Optional[_CoreAnyExecuteParams] 

452 if params: 

453 if self.is_executemany: 

454 _params = [] 

455 exec_many_parameters = cast( 

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

457 ) 

458 for _existing_params, _new_params in itertools.zip_longest( 

459 exec_many_parameters, 

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

461 ): 

462 if _existing_params is None or _new_params is None: 

463 raise sa_exc.InvalidRequestError( 

464 f"Can't apply executemany parameters to " 

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

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

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

468 f"to ORMExecuteState.invoke_statement() " 

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

470 ) 

471 _existing_params = dict(_existing_params) 

472 _existing_params.update(_new_params) 

473 _params.append(_existing_params) 

474 else: 

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

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

477 else: 

478 _params = self.parameters 

479 

480 _execution_options = self.local_execution_options 

481 if execution_options: 

482 _execution_options = _execution_options.union(execution_options) 

483 

484 return self.session._execute_internal( 

485 statement, 

486 _params, 

487 execution_options=_execution_options, 

488 bind_arguments=_bind_arguments, 

489 _parent_execute_state=self, 

490 ) 

491 

492 @property 

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

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

495 

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

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

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

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

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

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

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

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

504 would be selected. 

505 

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

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

508 way of getting this mapper. 

509 

510 .. versionadded:: 1.4.0b2 

511 

512 .. seealso:: 

513 

514 :attr:`_orm.ORMExecuteState.all_mappers` 

515 

516 

517 """ 

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

519 return mp 

520 

521 @property 

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

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

524 involved at the top level of this statement. 

525 

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

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

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

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

530 

531 .. versionadded:: 1.4.0b2 

532 

533 .. seealso:: 

534 

535 :attr:`_orm.ORMExecuteState.bind_mapper` 

536 

537 

538 

539 """ 

540 if not self.is_orm_statement: 

541 return [] 

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

543 result = [] 

544 seen = set() 

545 for d in self.statement.column_descriptions: 

546 ent = d["entity"] 

547 if ent: 

548 insp = inspect(ent, raiseerr=False) 

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

550 seen.add(insp.mapper) 

551 result.append(insp.mapper) 

552 return result 

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

554 return [self.bind_mapper] 

555 else: 

556 return [] 

557 

558 @property 

559 def is_orm_statement(self) -> bool: 

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

561 

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

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

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

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

566 and no ORM-level automation takes place. 

567 

568 """ 

569 return self._compile_state_cls is not None 

570 

571 @property 

572 def is_executemany(self) -> bool: 

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

574 dictionaries with more than one dictionary. 

575 

576 .. versionadded:: 2.0 

577 

578 """ 

579 return isinstance(self.parameters, list) 

580 

581 @property 

582 def is_select(self) -> bool: 

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

584 

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

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

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

588 ``select(Entity).from_statement(select(..))`` 

589 

590 """ 

591 return self.statement.is_select 

592 

593 @property 

594 def is_from_statement(self) -> bool: 

595 """return True if this operation is a 

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

597 

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

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

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

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

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

603 :class:`_sql.Select` construct. 

604 

605 .. versionadded:: 2.0.30 

606 

607 """ 

608 return self.statement.is_from_statement 

609 

610 @property 

611 def is_insert(self) -> bool: 

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

613 

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

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

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

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

618 

619 """ 

620 return self.statement.is_dml and self.statement.is_insert 

621 

622 @property 

623 def is_update(self) -> bool: 

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

625 

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

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

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

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

630 

631 """ 

632 return self.statement.is_dml and self.statement.is_update 

633 

634 @property 

635 def is_delete(self) -> bool: 

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

637 

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

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

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

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

642 

643 """ 

644 return self.statement.is_dml and self.statement.is_delete 

645 

646 @property 

647 def _is_crud(self) -> bool: 

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

649 

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

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

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

653 

654 def _orm_compile_options( 

655 self, 

656 ) -> Optional[ 

657 Union[ 

658 context.ORMCompileState.default_compile_options, 

659 Type[context.ORMCompileState.default_compile_options], 

660 ] 

661 ]: 

662 if not self.is_select: 

663 return None 

664 try: 

665 opts = self.statement._compile_options 

666 except AttributeError: 

667 return None 

668 

669 if opts is not None and opts.isinstance( 

670 context.ORMCompileState.default_compile_options 

671 ): 

672 return opts # type: ignore 

673 else: 

674 return None 

675 

676 @property 

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

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

679 for a lazy load operation. 

680 

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

682 sharding extension, where it is available within specific query 

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

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

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

686 compilation time. 

687 

688 """ 

689 return self.load_options._lazy_loaded_from 

690 

691 @property 

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

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

694 

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

696 when a particular object or collection is being loaded. 

697 

698 """ 

699 opts = self._orm_compile_options() 

700 if opts is not None: 

701 return opts._current_path 

702 else: 

703 return None 

704 

705 @property 

706 def is_column_load(self) -> bool: 

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

708 attributes on an existing ORM object. 

709 

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

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

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

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

714 loaded. 

715 

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

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

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

719 and loader options travelling with the instance 

720 will have already been added to the query. 

721 

722 .. versionadded:: 1.4.0b2 

723 

724 .. seealso:: 

725 

726 :attr:`_orm.ORMExecuteState.is_relationship_load` 

727 

728 """ 

729 opts = self._orm_compile_options() 

730 return opts is not None and opts._for_refresh_state 

731 

732 @property 

733 def is_relationship_load(self) -> bool: 

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

735 relationship. 

736 

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

738 SelectInLoader, SubqueryLoader, or similar, and the entire 

739 SELECT statement being emitted is on behalf of a relationship 

740 load. 

741 

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

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

744 capable of being propagated to relationship loaders and should 

745 be already present. 

746 

747 .. seealso:: 

748 

749 :attr:`_orm.ORMExecuteState.is_column_load` 

750 

751 """ 

752 opts = self._orm_compile_options() 

753 if opts is None: 

754 return False 

755 path = self.loader_strategy_path 

756 return path is not None and not path.is_root 

757 

758 @property 

759 def load_options( 

760 self, 

761 ) -> Union[ 

762 context.QueryContext.default_load_options, 

763 Type[context.QueryContext.default_load_options], 

764 ]: 

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

766 

767 if not self.is_select: 

768 raise sa_exc.InvalidRequestError( 

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

770 "so there are no load options." 

771 ) 

772 

773 lo: Union[ 

774 context.QueryContext.default_load_options, 

775 Type[context.QueryContext.default_load_options], 

776 ] = self.execution_options.get( 

777 "_sa_orm_load_options", context.QueryContext.default_load_options 

778 ) 

779 return lo 

780 

781 @property 

782 def update_delete_options( 

783 self, 

784 ) -> Union[ 

785 bulk_persistence.BulkUDCompileState.default_update_options, 

786 Type[bulk_persistence.BulkUDCompileState.default_update_options], 

787 ]: 

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

789 execution.""" 

790 

791 if not self._is_crud: 

792 raise sa_exc.InvalidRequestError( 

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

794 "statement so there are no update options." 

795 ) 

796 uo: Union[ 

797 bulk_persistence.BulkUDCompileState.default_update_options, 

798 Type[bulk_persistence.BulkUDCompileState.default_update_options], 

799 ] = self.execution_options.get( 

800 "_sa_orm_update_options", 

801 bulk_persistence.BulkUDCompileState.default_update_options, 

802 ) 

803 return uo 

804 

805 @property 

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

807 return [ 

808 opt 

809 for opt in self.statement._with_options 

810 if is_orm_option(opt) and not opt._is_compile_state 

811 ] 

812 

813 @property 

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

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

816 associated with the statement being invoked. 

817 

818 """ 

819 return [ 

820 opt 

821 for opt in self.statement._with_options 

822 if is_user_defined_option(opt) 

823 ] 

824 

825 

826class SessionTransactionOrigin(Enum): 

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

828 

829 This enumeration is present on the 

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

831 :class:`.SessionTransaction` object. 

832 

833 .. versionadded:: 2.0 

834 

835 """ 

836 

837 AUTOBEGIN = 0 

838 """transaction were started by autobegin""" 

839 

840 BEGIN = 1 

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

842 

843 BEGIN_NESTED = 2 

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

845 

846 SUBTRANSACTION = 3 

847 """transaction is an internal "subtransaction" """ 

848 

849 

850class SessionTransaction(_StateChange, TransactionalContext): 

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

852 

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

854 :meth:`_orm.Session.begin` 

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

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

857 transactions. 

858 

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

860 at: :ref:`unitofwork_transaction`. 

861 

862 

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

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

865 

866 .. seealso:: 

867 

868 :ref:`unitofwork_transaction` 

869 

870 :meth:`.Session.begin` 

871 

872 :meth:`.Session.begin_nested` 

873 

874 :meth:`.Session.rollback` 

875 

876 :meth:`.Session.commit` 

877 

878 :meth:`.Session.in_transaction` 

879 

880 :meth:`.Session.in_nested_transaction` 

881 

882 :meth:`.Session.get_transaction` 

883 

884 :meth:`.Session.get_nested_transaction` 

885 

886 

887 """ 

888 

889 _rollback_exception: Optional[BaseException] = None 

890 

891 _connections: Dict[ 

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

893 ] 

894 session: Session 

895 _parent: Optional[SessionTransaction] 

896 

897 _state: SessionTransactionState 

898 

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

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

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

902 _key_switches: weakref.WeakKeyDictionary[ 

903 InstanceState[Any], Tuple[Any, Any] 

904 ] 

905 

906 origin: SessionTransactionOrigin 

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

908 

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

910 enumeration indicating the source event that led to constructing 

911 this :class:`_orm.SessionTransaction`. 

912 

913 .. versionadded:: 2.0 

914 

915 """ 

916 

917 nested: bool = False 

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

919 

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

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

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

923 

924 .. seealso:: 

925 

926 :attr:`.SessionTransaction.origin` 

927 

928 """ 

929 

930 def __init__( 

931 self, 

932 session: Session, 

933 origin: SessionTransactionOrigin, 

934 parent: Optional[SessionTransaction] = None, 

935 ): 

936 TransactionalContext._trans_ctx_check(session) 

937 

938 self.session = session 

939 self._connections = {} 

940 self._parent = parent 

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

942 self.origin = origin 

943 

944 if session._close_state is _SessionCloseState.CLOSED: 

945 raise sa_exc.InvalidRequestError( 

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

947 "to handle any more transaction requests." 

948 ) 

949 

950 if nested: 

951 if not parent: 

952 raise sa_exc.InvalidRequestError( 

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

954 "transaction is in progress" 

955 ) 

956 

957 self._previous_nested_transaction = session._nested_transaction 

958 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

959 assert parent is not None 

960 else: 

961 assert parent is None 

962 

963 self._state = SessionTransactionState.ACTIVE 

964 

965 self._take_snapshot() 

966 

967 # make sure transaction is assigned before we call the 

968 # dispatch 

969 self.session._transaction = self 

970 

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

972 

973 def _raise_for_prerequisite_state( 

974 self, operation_name: str, state: _StateChangeState 

975 ) -> NoReturn: 

976 if state is SessionTransactionState.DEACTIVE: 

977 if self._rollback_exception: 

978 raise sa_exc.PendingRollbackError( 

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

980 "due to a previous exception during flush." 

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

982 "first issue Session.rollback()." 

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

984 code="7s2a", 

985 ) 

986 else: 

987 raise sa_exc.InvalidRequestError( 

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

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

990 "can be emitted within this transaction." 

991 ) 

992 elif state is SessionTransactionState.CLOSED: 

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

994 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

995 raise sa_exc.InvalidRequestError( 

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

997 "operations are not permitted", 

998 code="isce", 

999 ) 

1000 else: 

1001 raise sa_exc.InvalidRequestError( 

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

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

1004 ) 

1005 

1006 @property 

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

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

1009 :class:`.SessionTransaction`. 

1010 

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

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

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

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

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

1016 "nested" / SAVEPOINT transaction. If the 

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

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

1019 

1020 """ 

1021 return self._parent 

1022 

1023 @property 

1024 def is_active(self) -> bool: 

1025 return ( 

1026 self.session is not None 

1027 and self._state is SessionTransactionState.ACTIVE 

1028 ) 

1029 

1030 @property 

1031 def _is_transaction_boundary(self) -> bool: 

1032 return self.nested or not self._parent 

1033 

1034 @_StateChange.declare_states( 

1035 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1036 ) 

1037 def connection( 

1038 self, 

1039 bindkey: Optional[Mapper[Any]], 

1040 execution_options: Optional[_ExecuteOptions] = None, 

1041 **kwargs: Any, 

1042 ) -> Connection: 

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

1044 return self._connection_for_bind(bind, execution_options) 

1045 

1046 @_StateChange.declare_states( 

1047 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1048 ) 

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

1050 return SessionTransaction( 

1051 self.session, 

1052 ( 

1053 SessionTransactionOrigin.BEGIN_NESTED 

1054 if nested 

1055 else SessionTransactionOrigin.SUBTRANSACTION 

1056 ), 

1057 self, 

1058 ) 

1059 

1060 def _iterate_self_and_parents( 

1061 self, upto: Optional[SessionTransaction] = None 

1062 ) -> Iterable[SessionTransaction]: 

1063 current = self 

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

1065 while current: 

1066 result += (current,) 

1067 if current._parent is upto: 

1068 break 

1069 elif current._parent is None: 

1070 raise sa_exc.InvalidRequestError( 

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

1072 % (upto) 

1073 ) 

1074 else: 

1075 current = current._parent 

1076 

1077 return result 

1078 

1079 def _take_snapshot(self) -> None: 

1080 if not self._is_transaction_boundary: 

1081 parent = self._parent 

1082 assert parent is not None 

1083 self._new = parent._new 

1084 self._deleted = parent._deleted 

1085 self._dirty = parent._dirty 

1086 self._key_switches = parent._key_switches 

1087 return 

1088 

1089 is_begin = self.origin in ( 

1090 SessionTransactionOrigin.BEGIN, 

1091 SessionTransactionOrigin.AUTOBEGIN, 

1092 ) 

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

1094 self.session.flush() 

1095 

1096 self._new = weakref.WeakKeyDictionary() 

1097 self._deleted = weakref.WeakKeyDictionary() 

1098 self._dirty = weakref.WeakKeyDictionary() 

1099 self._key_switches = weakref.WeakKeyDictionary() 

1100 

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

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

1103 

1104 Corresponds to a rollback. 

1105 

1106 """ 

1107 assert self._is_transaction_boundary 

1108 

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

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

1111 

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

1113 # we probably can do this conditionally based on 

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

1115 self.session.identity_map.safe_discard(s) 

1116 

1117 # restore the old key 

1118 s.key = oldkey 

1119 

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

1121 if s not in to_expunge: 

1122 self.session.identity_map.replace(s) 

1123 

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

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

1126 

1127 assert not self.session._deleted 

1128 

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

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

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

1132 

1133 def _remove_snapshot(self) -> None: 

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

1135 

1136 Corresponds to a commit. 

1137 

1138 """ 

1139 assert self._is_transaction_boundary 

1140 

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

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

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

1144 

1145 statelib.InstanceState._detach_states( 

1146 list(self._deleted), self.session 

1147 ) 

1148 self._deleted.clear() 

1149 elif self.nested: 

1150 parent = self._parent 

1151 assert parent is not None 

1152 parent._new.update(self._new) 

1153 parent._dirty.update(self._dirty) 

1154 parent._deleted.update(self._deleted) 

1155 parent._key_switches.update(self._key_switches) 

1156 

1157 @_StateChange.declare_states( 

1158 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1159 ) 

1160 def _connection_for_bind( 

1161 self, 

1162 bind: _SessionBind, 

1163 execution_options: Optional[CoreExecuteOptionsParameter], 

1164 ) -> Connection: 

1165 if bind in self._connections: 

1166 if execution_options: 

1167 util.warn( 

1168 "Connection is already established for the " 

1169 "given bind; execution_options ignored" 

1170 ) 

1171 return self._connections[bind][0] 

1172 

1173 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1174 

1175 local_connect = False 

1176 should_commit = True 

1177 

1178 try: 

1179 if self._parent: 

1180 conn = self._parent._connection_for_bind( 

1181 bind, execution_options 

1182 ) 

1183 if not self.nested: 

1184 return conn 

1185 else: 

1186 if isinstance(bind, engine.Connection): 

1187 conn = bind 

1188 if conn.engine in self._connections: 

1189 raise sa_exc.InvalidRequestError( 

1190 "Session already has a Connection associated " 

1191 "for the given Connection's Engine" 

1192 ) 

1193 else: 

1194 conn = bind.connect() 

1195 local_connect = True 

1196 

1197 try: 

1198 if execution_options: 

1199 conn = conn.execution_options(**execution_options) 

1200 

1201 transaction: Transaction 

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

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

1204 # conn.in_transaction() ? 

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

1206 # that it is in fact twophase. 

1207 transaction = conn.begin_twophase() 

1208 elif self.nested: 

1209 transaction = conn.begin_nested() 

1210 elif conn.in_transaction(): 

1211 

1212 if local_connect: 

1213 _trans = conn.get_transaction() 

1214 assert _trans is not None 

1215 transaction = _trans 

1216 else: 

1217 join_transaction_mode = ( 

1218 self.session.join_transaction_mode 

1219 ) 

1220 

1221 if join_transaction_mode == "conditional_savepoint": 

1222 if conn.in_nested_transaction(): 

1223 join_transaction_mode = "create_savepoint" 

1224 else: 

1225 join_transaction_mode = "rollback_only" 

1226 

1227 if join_transaction_mode in ( 

1228 "control_fully", 

1229 "rollback_only", 

1230 ): 

1231 if conn.in_nested_transaction(): 

1232 transaction = ( 

1233 conn._get_required_nested_transaction() 

1234 ) 

1235 else: 

1236 transaction = conn._get_required_transaction() 

1237 if join_transaction_mode == "rollback_only": 

1238 should_commit = False 

1239 elif join_transaction_mode == "create_savepoint": 

1240 transaction = conn.begin_nested() 

1241 else: 

1242 assert False, join_transaction_mode 

1243 else: 

1244 transaction = conn.begin() 

1245 except: 

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

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

1248 if local_connect: 

1249 conn.close() 

1250 raise 

1251 else: 

1252 bind_is_connection = isinstance(bind, engine.Connection) 

1253 

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

1255 conn, 

1256 transaction, 

1257 should_commit, 

1258 not bind_is_connection, 

1259 ) 

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

1261 return conn 

1262 finally: 

1263 self._state = SessionTransactionState.ACTIVE 

1264 

1265 def prepare(self) -> None: 

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

1267 raise sa_exc.InvalidRequestError( 

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

1269 "can't prepare." 

1270 ) 

1271 self._prepare_impl() 

1272 

1273 @_StateChange.declare_states( 

1274 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1275 ) 

1276 def _prepare_impl(self) -> None: 

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

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

1279 

1280 stx = self.session._transaction 

1281 assert stx is not None 

1282 if stx is not self: 

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

1284 subtransaction.commit() 

1285 

1286 if not self.session._flushing: 

1287 for _flush_guard in range(100): 

1288 if self.session._is_clean(): 

1289 break 

1290 self.session.flush() 

1291 else: 

1292 raise exc.FlushError( 

1293 "Over 100 subsequent flushes have occurred within " 

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

1295 "creating new objects?" 

1296 ) 

1297 

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

1299 try: 

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

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

1302 except: 

1303 with util.safe_reraise(): 

1304 self.rollback() 

1305 

1306 self._state = SessionTransactionState.PREPARED 

1307 

1308 @_StateChange.declare_states( 

1309 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1310 SessionTransactionState.CLOSED, 

1311 ) 

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

1313 if self._state is not SessionTransactionState.PREPARED: 

1314 with self._expect_state(SessionTransactionState.PREPARED): 

1315 self._prepare_impl() 

1316 

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

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

1319 self._connections.values() 

1320 ): 

1321 if should_commit: 

1322 trans.commit() 

1323 

1324 self._state = SessionTransactionState.COMMITTED 

1325 self.session.dispatch.after_commit(self.session) 

1326 

1327 self._remove_snapshot() 

1328 

1329 with self._expect_state(SessionTransactionState.CLOSED): 

1330 self.close() 

1331 

1332 if _to_root and self._parent: 

1333 self._parent.commit(_to_root=True) 

1334 

1335 @_StateChange.declare_states( 

1336 ( 

1337 SessionTransactionState.ACTIVE, 

1338 SessionTransactionState.DEACTIVE, 

1339 SessionTransactionState.PREPARED, 

1340 ), 

1341 SessionTransactionState.CLOSED, 

1342 ) 

1343 def rollback( 

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

1345 ) -> None: 

1346 stx = self.session._transaction 

1347 assert stx is not None 

1348 if stx is not self: 

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

1350 subtransaction.close() 

1351 

1352 boundary = self 

1353 rollback_err = None 

1354 if self._state in ( 

1355 SessionTransactionState.ACTIVE, 

1356 SessionTransactionState.PREPARED, 

1357 ): 

1358 for transaction in self._iterate_self_and_parents(): 

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

1360 try: 

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

1362 t[1].rollback() 

1363 

1364 transaction._state = SessionTransactionState.DEACTIVE 

1365 self.session.dispatch.after_rollback(self.session) 

1366 except: 

1367 rollback_err = sys.exc_info() 

1368 finally: 

1369 transaction._state = SessionTransactionState.DEACTIVE 

1370 transaction._restore_snapshot( 

1371 dirty_only=transaction.nested 

1372 ) 

1373 boundary = transaction 

1374 break 

1375 else: 

1376 transaction._state = SessionTransactionState.DEACTIVE 

1377 

1378 sess = self.session 

1379 

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

1381 # if items were added, deleted, or mutated 

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

1383 util.warn( 

1384 "Session's state has been changed on " 

1385 "a non-active transaction - this state " 

1386 "will be discarded." 

1387 ) 

1388 boundary._restore_snapshot(dirty_only=boundary.nested) 

1389 

1390 with self._expect_state(SessionTransactionState.CLOSED): 

1391 self.close() 

1392 

1393 if self._parent and _capture_exception: 

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

1395 

1396 if rollback_err and rollback_err[1]: 

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

1398 

1399 sess.dispatch.after_soft_rollback(sess, self) 

1400 

1401 if _to_root and self._parent: 

1402 self._parent.rollback(_to_root=True) 

1403 

1404 @_StateChange.declare_states( 

1405 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1406 ) 

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

1408 if self.nested: 

1409 self.session._nested_transaction = ( 

1410 self._previous_nested_transaction 

1411 ) 

1412 

1413 self.session._transaction = self._parent 

1414 

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

1416 self._connections.values() 

1417 ): 

1418 if invalidate and self._parent is None: 

1419 connection.invalidate() 

1420 if should_commit and transaction.is_active: 

1421 transaction.close() 

1422 if autoclose and self._parent is None: 

1423 connection.close() 

1424 

1425 self._state = SessionTransactionState.CLOSED 

1426 sess = self.session 

1427 

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

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

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

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

1432 # passes with these commented out. 

1433 # self.session = None # type: ignore 

1434 # self._connections = None # type: ignore 

1435 

1436 sess.dispatch.after_transaction_end(sess, self) 

1437 

1438 def _get_subject(self) -> Session: 

1439 return self.session 

1440 

1441 def _transaction_is_active(self) -> bool: 

1442 return self._state is SessionTransactionState.ACTIVE 

1443 

1444 def _transaction_is_closed(self) -> bool: 

1445 return self._state is SessionTransactionState.CLOSED 

1446 

1447 def _rollback_can_be_called(self) -> bool: 

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

1449 

1450 

1451class _SessionCloseState(Enum): 

1452 ACTIVE = 1 

1453 CLOSED = 2 

1454 CLOSE_IS_RESET = 3 

1455 

1456 

1457class Session(_SessionClassMethods, EventTarget): 

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

1459 

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

1461 See :ref:`session_faq_threadsafe` for background. 

1462 

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

1464 

1465 

1466 """ 

1467 

1468 _is_asyncio = False 

1469 

1470 dispatch: dispatcher[Session] 

1471 

1472 identity_map: IdentityMap 

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

1474 

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

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

1477 that have row identity) currently in the session. 

1478 

1479 .. seealso:: 

1480 

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

1482 in this dictionary. 

1483 

1484 """ 

1485 

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

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

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

1489 __binds: Dict[_SessionBindKey, _SessionBind] 

1490 _flushing: bool 

1491 _warn_on_events: bool 

1492 _transaction: Optional[SessionTransaction] 

1493 _nested_transaction: Optional[SessionTransaction] 

1494 hash_key: int 

1495 autoflush: bool 

1496 expire_on_commit: bool 

1497 enable_baked_queries: bool 

1498 twophase: bool 

1499 join_transaction_mode: JoinTransactionMode 

1500 _query_cls: Type[Query[Any]] 

1501 _close_state: _SessionCloseState 

1502 

1503 def __init__( 

1504 self, 

1505 bind: Optional[_SessionBind] = None, 

1506 *, 

1507 autoflush: bool = True, 

1508 future: Literal[True] = True, 

1509 expire_on_commit: bool = True, 

1510 autobegin: bool = True, 

1511 twophase: bool = False, 

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

1513 enable_baked_queries: bool = True, 

1514 info: Optional[_InfoType] = None, 

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

1516 autocommit: Literal[False] = False, 

1517 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1519 ): 

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

1521 

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

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

1524 set of arguments. 

1525 

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

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

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

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

1530 results. 

1531 

1532 .. seealso:: 

1533 

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

1535 

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

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

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

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

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

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

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

1543 

1544 .. versionadded:: 2.0 

1545 

1546 .. seealso:: 

1547 

1548 :ref:`session_autobegin_disable` 

1549 

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

1551 :class:`_engine.Connection` to 

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

1553 operations performed by this session will execute via this 

1554 connectable. 

1555 

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

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

1558 objects as the source of 

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

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

1561 arbitrary Python classes that are bases for mapped classes, 

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

1563 The 

1564 values of the dictionary are then instances of 

1565 :class:`_engine.Engine` 

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

1567 Operations which 

1568 proceed relative to a particular mapped class will consult this 

1569 dictionary for the closest matching entity in order to determine 

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

1571 operation. The complete heuristics for resolution are 

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

1573 

1574 Session = sessionmaker(binds={ 

1575 SomeMappedClass: create_engine('postgresql+psycopg2://engine1'), 

1576 SomeDeclarativeBase: create_engine('postgresql+psycopg2://engine2'), 

1577 some_mapper: create_engine('postgresql+psycopg2://engine3'), 

1578 some_table: create_engine('postgresql+psycopg2://engine4'), 

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 or close_resets_only is _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[Unpack[TupleAny]]: ... 

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[Unpack[TupleAny]]] = fn( 

2210 orm_exec_state 

2211 ) 

2212 if fn_result: 

2213 if _scalar_result: 

2214 return fn_result.scalar() 

2215 else: 

2216 return fn_result 

2217 

2218 statement = orm_exec_state.statement 

2219 execution_options = orm_exec_state.local_execution_options 

2220 

2221 if compile_state_cls is not None: 

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

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

2224 # new execution_options into load_options / update_delete_options, 

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

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

2227 ( 

2228 statement, 

2229 execution_options, 

2230 ) = compile_state_cls.orm_pre_session_exec( 

2231 self, 

2232 statement, 

2233 params, 

2234 execution_options, 

2235 bind_arguments, 

2236 False, 

2237 ) 

2238 

2239 bind = self.get_bind(**bind_arguments) 

2240 

2241 conn = self._connection_for_bind(bind) 

2242 

2243 if _scalar_result and not compile_state_cls: 

2244 if TYPE_CHECKING: 

2245 params = cast(_CoreSingleExecuteParams, params) 

2246 return conn.scalar( 

2247 statement, params or {}, execution_options=execution_options 

2248 ) 

2249 

2250 if compile_state_cls: 

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

2252 compile_state_cls.orm_execute_statement( 

2253 self, 

2254 statement, 

2255 params or {}, 

2256 execution_options, 

2257 bind_arguments, 

2258 conn, 

2259 ) 

2260 ) 

2261 else: 

2262 result = conn.execute( 

2263 statement, params, execution_options=execution_options 

2264 ) 

2265 

2266 if _scalar_result: 

2267 return result.scalar() 

2268 else: 

2269 return result 

2270 

2271 @overload 

2272 def execute( 

2273 self, 

2274 statement: TypedReturnsRows[Unpack[_Ts]], 

2275 params: Optional[_CoreAnyExecuteParams] = None, 

2276 *, 

2277 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2278 bind_arguments: Optional[_BindArguments] = None, 

2279 _parent_execute_state: Optional[Any] = None, 

2280 _add_event: Optional[Any] = None, 

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

2282 

2283 @overload 

2284 def execute( 

2285 self, 

2286 statement: UpdateBase, 

2287 params: Optional[_CoreAnyExecuteParams] = None, 

2288 *, 

2289 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2290 bind_arguments: Optional[_BindArguments] = None, 

2291 _parent_execute_state: Optional[Any] = None, 

2292 _add_event: Optional[Any] = None, 

2293 ) -> CursorResult[Unpack[TupleAny]]: ... 

2294 

2295 @overload 

2296 def execute( 

2297 self, 

2298 statement: Executable, 

2299 params: Optional[_CoreAnyExecuteParams] = None, 

2300 *, 

2301 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2302 bind_arguments: Optional[_BindArguments] = None, 

2303 _parent_execute_state: Optional[Any] = None, 

2304 _add_event: Optional[Any] = None, 

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

2306 

2307 def execute( 

2308 self, 

2309 statement: Executable, 

2310 params: Optional[_CoreAnyExecuteParams] = None, 

2311 *, 

2312 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2313 bind_arguments: Optional[_BindArguments] = None, 

2314 _parent_execute_state: Optional[Any] = None, 

2315 _add_event: Optional[Any] = None, 

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

2317 r"""Execute a SQL expression construct. 

2318 

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

2320 results of the statement execution. 

2321 

2322 E.g.:: 

2323 

2324 from sqlalchemy import select 

2325 result = session.execute( 

2326 select(User).where(User.id == 5) 

2327 ) 

2328 

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

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

2331 of :class:`_engine.Connection`. 

2332 

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

2334 now the primary point of ORM statement execution when using 

2335 :term:`2.0 style` ORM usage. 

2336 

2337 :param statement: 

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

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

2340 

2341 :param params: 

2342 Optional dictionary, or list of dictionaries, containing 

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

2344 execution occurs; if a list of dictionaries, an 

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

2346 must correspond to parameter names present in the statement. 

2347 

2348 :param execution_options: optional dictionary of execution options, 

2349 which will be associated with the statement execution. This 

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

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

2352 provide additional options understood only in an ORM context. 

2353 

2354 .. seealso:: 

2355 

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

2357 options 

2358 

2359 :param bind_arguments: dictionary of additional arguments to determine 

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

2361 Contents of this dictionary are passed to the 

2362 :meth:`.Session.get_bind` method. 

2363 

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

2365 

2366 

2367 """ 

2368 return self._execute_internal( 

2369 statement, 

2370 params, 

2371 execution_options=execution_options, 

2372 bind_arguments=bind_arguments, 

2373 _parent_execute_state=_parent_execute_state, 

2374 _add_event=_add_event, 

2375 ) 

2376 

2377 @overload 

2378 def scalar( 

2379 self, 

2380 statement: TypedReturnsRows[_T], 

2381 params: Optional[_CoreSingleExecuteParams] = None, 

2382 *, 

2383 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2384 bind_arguments: Optional[_BindArguments] = None, 

2385 **kw: Any, 

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

2387 

2388 @overload 

2389 def scalar( 

2390 self, 

2391 statement: Executable, 

2392 params: Optional[_CoreSingleExecuteParams] = None, 

2393 *, 

2394 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2395 bind_arguments: Optional[_BindArguments] = None, 

2396 **kw: Any, 

2397 ) -> Any: ... 

2398 

2399 def scalar( 

2400 self, 

2401 statement: Executable, 

2402 params: Optional[_CoreSingleExecuteParams] = None, 

2403 *, 

2404 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2405 bind_arguments: Optional[_BindArguments] = None, 

2406 **kw: Any, 

2407 ) -> Any: 

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

2409 

2410 Usage and parameters are the same as that of 

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

2412 value. 

2413 

2414 """ 

2415 

2416 return self._execute_internal( 

2417 statement, 

2418 params, 

2419 execution_options=execution_options, 

2420 bind_arguments=bind_arguments, 

2421 _scalar_result=True, 

2422 **kw, 

2423 ) 

2424 

2425 @overload 

2426 def scalars( 

2427 self, 

2428 statement: TypedReturnsRows[_T], 

2429 params: Optional[_CoreAnyExecuteParams] = None, 

2430 *, 

2431 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2432 bind_arguments: Optional[_BindArguments] = None, 

2433 **kw: Any, 

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

2435 

2436 @overload 

2437 def scalars( 

2438 self, 

2439 statement: Executable, 

2440 params: Optional[_CoreAnyExecuteParams] = None, 

2441 *, 

2442 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2443 bind_arguments: Optional[_BindArguments] = None, 

2444 **kw: Any, 

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

2446 

2447 def scalars( 

2448 self, 

2449 statement: Executable, 

2450 params: Optional[_CoreAnyExecuteParams] = None, 

2451 *, 

2452 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2453 bind_arguments: Optional[_BindArguments] = None, 

2454 **kw: Any, 

2455 ) -> ScalarResult[Any]: 

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

2457 

2458 Usage and parameters are the same as that of 

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

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

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

2462 

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

2464 

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

2466 

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

2468 

2469 .. seealso:: 

2470 

2471 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2473 

2474 """ 

2475 

2476 return self._execute_internal( 

2477 statement, 

2478 params=params, 

2479 execution_options=execution_options, 

2480 bind_arguments=bind_arguments, 

2481 _scalar_result=False, # mypy appreciates this 

2482 **kw, 

2483 ).scalars() 

2484 

2485 def close(self) -> None: 

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

2487 :class:`_orm.Session`. 

2488 

2489 This expunges all ORM objects associated with this 

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

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

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

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

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

2495 

2496 .. tip:: 

2497 

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

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

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

2501 distinct "closed" state; it merely means 

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

2503 and ORM objects. 

2504 

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

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

2507 any further action on the session will be forbidden. 

2508 

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

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

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

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

2513 

2514 .. seealso:: 

2515 

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

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

2518 

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

2520 ``close()`` with the parameter 

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

2522 

2523 """ 

2524 self._close_impl(invalidate=False) 

2525 

2526 def reset(self) -> None: 

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

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

2529 

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

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

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

2533 brand new, and ready to be used again. 

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

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

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

2537 

2538 .. versionadded:: 2.0.22 

2539 

2540 .. seealso:: 

2541 

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

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

2544 

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

2546 prevent re-use of the Session when the parameter 

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

2548 """ 

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

2550 

2551 def invalidate(self) -> None: 

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

2553 

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

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

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

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

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

2559 multiple engines). 

2560 

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

2562 the connections are no longer safe to be used. 

2563 

2564 Below illustrates a scenario when using `gevent 

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

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

2567 

2568 import gevent 

2569 

2570 try: 

2571 sess = Session() 

2572 sess.add(User()) 

2573 sess.commit() 

2574 except gevent.Timeout: 

2575 sess.invalidate() 

2576 raise 

2577 except: 

2578 sess.rollback() 

2579 raise 

2580 

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

2582 does, including that all ORM objects are expunged. 

2583 

2584 """ 

2585 self._close_impl(invalidate=True) 

2586 

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

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

2589 self._close_state = _SessionCloseState.CLOSED 

2590 self.expunge_all() 

2591 if self._transaction is not None: 

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

2593 transaction.close(invalidate) 

2594 

2595 def expunge_all(self) -> None: 

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

2597 

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

2599 ``Session``. 

2600 

2601 """ 

2602 

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

2604 self.identity_map._kill() 

2605 self.identity_map = identity.WeakInstanceDict() 

2606 self._new = {} 

2607 self._deleted = {} 

2608 

2609 statelib.InstanceState._detach_states(all_states, self) 

2610 

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

2612 try: 

2613 insp = inspect(key) 

2614 except sa_exc.NoInspectionAvailable as err: 

2615 if not isinstance(key, type): 

2616 raise sa_exc.ArgumentError( 

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

2618 ) from err 

2619 else: 

2620 self.__binds[key] = bind 

2621 else: 

2622 if TYPE_CHECKING: 

2623 assert isinstance(insp, Inspectable) 

2624 

2625 if isinstance(insp, TableClause): 

2626 self.__binds[insp] = bind 

2627 elif insp_is_mapper(insp): 

2628 self.__binds[insp.class_] = bind 

2629 for _selectable in insp._all_tables: 

2630 self.__binds[_selectable] = bind 

2631 else: 

2632 raise sa_exc.ArgumentError( 

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

2634 ) 

2635 

2636 def bind_mapper( 

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

2638 ) -> None: 

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

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

2641 :class:`_engine.Connection`. 

2642 

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

2644 :meth:`.Session.get_bind` method. 

2645 

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

2647 or an instance of a mapped 

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

2649 classes. 

2650 

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

2652 object. 

2653 

2654 .. seealso:: 

2655 

2656 :ref:`session_partitioning` 

2657 

2658 :paramref:`.Session.binds` 

2659 

2660 :meth:`.Session.bind_table` 

2661 

2662 

2663 """ 

2664 self._add_bind(mapper, bind) 

2665 

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

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

2668 :class:`_engine.Engine` 

2669 or :class:`_engine.Connection`. 

2670 

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

2672 :meth:`.Session.get_bind` method. 

2673 

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

2675 which is typically the target 

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

2677 mapped. 

2678 

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

2680 object. 

2681 

2682 .. seealso:: 

2683 

2684 :ref:`session_partitioning` 

2685 

2686 :paramref:`.Session.binds` 

2687 

2688 :meth:`.Session.bind_mapper` 

2689 

2690 

2691 """ 

2692 self._add_bind(table, bind) 

2693 

2694 def get_bind( 

2695 self, 

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

2697 *, 

2698 clause: Optional[ClauseElement] = None, 

2699 bind: Optional[_SessionBind] = None, 

2700 _sa_skip_events: Optional[bool] = None, 

2701 _sa_skip_for_implicit_returning: bool = False, 

2702 **kw: Any, 

2703 ) -> Union[Engine, Connection]: 

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

2705 

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

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

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

2709 

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

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

2712 appropriate bind to return. 

2713 

2714 Note that the "mapper" argument is usually present 

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

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

2717 individual INSERT/UPDATE/DELETE operation within a 

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

2719 

2720 The order of resolution is: 

2721 

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

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

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

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

2726 superclasses to more general. 

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

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

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

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

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

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

2733 associated with the clause. 

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

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

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

2737 selectable to which the mapper is mapped. 

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

2739 is raised. 

2740 

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

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

2743 of bind resolution scheme. See the example at 

2744 :ref:`session_custom_partitioning`. 

2745 

2746 :param mapper: 

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

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

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

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

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

2752 mapped for a bind. 

2753 

2754 :param clause: 

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

2756 :func:`_expression.select`, 

2757 :func:`_expression.text`, 

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

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

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

2761 associated with 

2762 bound :class:`_schema.MetaData`. 

2763 

2764 .. seealso:: 

2765 

2766 :ref:`session_partitioning` 

2767 

2768 :paramref:`.Session.binds` 

2769 

2770 :meth:`.Session.bind_mapper` 

2771 

2772 :meth:`.Session.bind_table` 

2773 

2774 """ 

2775 

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

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

2778 if bind: 

2779 return bind 

2780 elif not self.__binds and self.bind: 

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

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

2783 return self.bind 

2784 

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

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

2787 # mapper and the clause 

2788 if mapper is None and clause is None: 

2789 if self.bind: 

2790 return self.bind 

2791 else: 

2792 raise sa_exc.UnboundExecutionError( 

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

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

2795 "a binding." 

2796 ) 

2797 

2798 # look more closely at the mapper. 

2799 if mapper is not None: 

2800 try: 

2801 inspected_mapper = inspect(mapper) 

2802 except sa_exc.NoInspectionAvailable as err: 

2803 if isinstance(mapper, type): 

2804 raise exc.UnmappedClassError(mapper) from err 

2805 else: 

2806 raise 

2807 else: 

2808 inspected_mapper = None 

2809 

2810 # match up the mapper or clause in the __binds 

2811 if self.__binds: 

2812 # matching mappers and selectables to entries in the 

2813 # binds dictionary; supported use case. 

2814 if inspected_mapper: 

2815 for cls in inspected_mapper.class_.__mro__: 

2816 if cls in self.__binds: 

2817 return self.__binds[cls] 

2818 if clause is None: 

2819 clause = inspected_mapper.persist_selectable 

2820 

2821 if clause is not None: 

2822 plugin_subject = clause._propagate_attrs.get( 

2823 "plugin_subject", None 

2824 ) 

2825 

2826 if plugin_subject is not None: 

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

2828 if cls in self.__binds: 

2829 return self.__binds[cls] 

2830 

2831 for obj in visitors.iterate(clause): 

2832 if obj in self.__binds: 

2833 if TYPE_CHECKING: 

2834 assert isinstance(obj, Table) 

2835 return self.__binds[obj] 

2836 

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

2838 # return that 

2839 if self.bind: 

2840 return self.bind 

2841 

2842 context = [] 

2843 if inspected_mapper is not None: 

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

2845 if clause is not None: 

2846 context.append("SQL expression") 

2847 

2848 raise sa_exc.UnboundExecutionError( 

2849 f"Could not locate a bind configured on " 

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

2851 ) 

2852 

2853 @overload 

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

2855 

2856 @overload 

2857 def query( 

2858 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2860 

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

2862 

2863 # code within this block is **programmatically, 

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

2865 

2866 @overload 

2867 def query( 

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

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

2870 

2871 @overload 

2872 def query( 

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

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

2875 

2876 @overload 

2877 def query( 

2878 self, 

2879 __ent0: _TCCA[_T0], 

2880 __ent1: _TCCA[_T1], 

2881 __ent2: _TCCA[_T2], 

2882 __ent3: _TCCA[_T3], 

2883 /, 

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

2885 

2886 @overload 

2887 def query( 

2888 self, 

2889 __ent0: _TCCA[_T0], 

2890 __ent1: _TCCA[_T1], 

2891 __ent2: _TCCA[_T2], 

2892 __ent3: _TCCA[_T3], 

2893 __ent4: _TCCA[_T4], 

2894 /, 

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

2896 

2897 @overload 

2898 def query( 

2899 self, 

2900 __ent0: _TCCA[_T0], 

2901 __ent1: _TCCA[_T1], 

2902 __ent2: _TCCA[_T2], 

2903 __ent3: _TCCA[_T3], 

2904 __ent4: _TCCA[_T4], 

2905 __ent5: _TCCA[_T5], 

2906 /, 

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

2908 

2909 @overload 

2910 def query( 

2911 self, 

2912 __ent0: _TCCA[_T0], 

2913 __ent1: _TCCA[_T1], 

2914 __ent2: _TCCA[_T2], 

2915 __ent3: _TCCA[_T3], 

2916 __ent4: _TCCA[_T4], 

2917 __ent5: _TCCA[_T5], 

2918 __ent6: _TCCA[_T6], 

2919 /, 

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

2921 

2922 @overload 

2923 def query( 

2924 self, 

2925 __ent0: _TCCA[_T0], 

2926 __ent1: _TCCA[_T1], 

2927 __ent2: _TCCA[_T2], 

2928 __ent3: _TCCA[_T3], 

2929 __ent4: _TCCA[_T4], 

2930 __ent5: _TCCA[_T5], 

2931 __ent6: _TCCA[_T6], 

2932 __ent7: _TCCA[_T7], 

2933 /, 

2934 *entities: _ColumnsClauseArgument[Any], 

2935 ) -> RowReturningQuery[ 

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

2937 ]: ... 

2938 

2939 # END OVERLOADED FUNCTIONS self.query 

2940 

2941 @overload 

2942 def query( 

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

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

2945 

2946 def query( 

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

2948 ) -> Query[Any]: 

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

2950 :class:`_orm.Session`. 

2951 

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

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

2954 to construct ORM queries. 

2955 

2956 .. seealso:: 

2957 

2958 :ref:`unified_tutorial` 

2959 

2960 :ref:`queryguide_toplevel` 

2961 

2962 :ref:`query_api_toplevel` - legacy API doc 

2963 

2964 """ 

2965 

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

2967 

2968 def _identity_lookup( 

2969 self, 

2970 mapper: Mapper[_O], 

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

2972 identity_token: Any = None, 

2973 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

2975 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2976 bind_arguments: Optional[_BindArguments] = None, 

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

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

2979 

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

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

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

2983 check if was deleted). 

2984 

2985 e.g.:: 

2986 

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

2988 

2989 :param mapper: mapper in use 

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

2991 a tuple. 

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

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

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

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

2996 :param passive: passive load flag passed to 

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

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

2999 if the flag allows for SQL to be emitted. 

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

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

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

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

3004 relationship-loaded). 

3005 

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

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

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

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

3010 

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

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

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

3014 :class:`_query.Query` object. 

3015 

3016 

3017 """ 

3018 

3019 key = mapper.identity_key_from_primary_key( 

3020 primary_key_identity, identity_token=identity_token 

3021 ) 

3022 

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

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

3025 return return_value 

3026 

3027 @util.non_memoized_property 

3028 @contextlib.contextmanager 

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

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

3031 

3032 e.g.:: 

3033 

3034 with session.no_autoflush: 

3035 

3036 some_object = SomeClass() 

3037 session.add(some_object) 

3038 # won't autoflush 

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

3040 

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

3042 will not be subject to flushes occurring upon query 

3043 access. This is useful when initializing a series 

3044 of objects which involve existing database queries, 

3045 where the uncompleted object should not yet be flushed. 

3046 

3047 """ 

3048 autoflush = self.autoflush 

3049 self.autoflush = False 

3050 try: 

3051 yield self 

3052 finally: 

3053 self.autoflush = autoflush 

3054 

3055 @util.langhelpers.tag_method_for_warnings( 

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

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

3058 "operation.", 

3059 sa_exc.SAWarning, 

3060 ) 

3061 def _autoflush(self) -> None: 

3062 if self.autoflush and not self._flushing: 

3063 try: 

3064 self.flush() 

3065 except sa_exc.StatementError as e: 

3066 # note we are reraising StatementError as opposed to 

3067 # raising FlushError with "chaining" to remain compatible 

3068 # with code that catches StatementError, IntegrityError, 

3069 # etc. 

3070 e.add_detail( 

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

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

3073 "flush is occurring prematurely" 

3074 ) 

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

3076 

3077 def refresh( 

3078 self, 

3079 instance: object, 

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

3081 with_for_update: ForUpdateParameter = None, 

3082 ) -> None: 

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

3084 

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

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

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

3088 value available in the current transaction. 

3089 

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

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

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

3093 

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

3095 can also refresh eagerly loaded attributes. 

3096 

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

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

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

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

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

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

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

3104 refreshed. 

3105 

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

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

3108 attributes for those which are named explicitly in the 

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

3110 

3111 .. tip:: 

3112 

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

3114 refreshing both column and relationship oriented attributes, its 

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

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

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

3118 once while having explicit control over relationship loader 

3119 strategies, use the 

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

3121 instead. 

3122 

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

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

3125 in database state outside of that transaction. Refreshing 

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

3127 where database rows have not yet been accessed. 

3128 

3129 :param attribute_names: optional. An iterable collection of 

3130 string attribute names indicating a subset of attributes to 

3131 be refreshed. 

3132 

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

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

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

3136 flags should match the parameters of 

3137 :meth:`_query.Query.with_for_update`. 

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

3139 

3140 .. seealso:: 

3141 

3142 :ref:`session_expire` - introductory material 

3143 

3144 :meth:`.Session.expire` 

3145 

3146 :meth:`.Session.expire_all` 

3147 

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

3149 to refresh objects as they would be loaded normally. 

3150 

3151 """ 

3152 try: 

3153 state = attributes.instance_state(instance) 

3154 except exc.NO_STATE as err: 

3155 raise exc.UnmappedInstanceError(instance) from err 

3156 

3157 self._expire_state(state, attribute_names) 

3158 

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

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

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

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

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

3164 # load_on_ident. 

3165 self._autoflush() 

3166 

3167 if with_for_update == {}: 

3168 raise sa_exc.ArgumentError( 

3169 "with_for_update should be the boolean value " 

3170 "True, or a dictionary with options. " 

3171 "A blank dictionary is ambiguous." 

3172 ) 

3173 

3174 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3175 

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

3177 if ( 

3178 loading.load_on_ident( 

3179 self, 

3180 stmt, 

3181 state.key, 

3182 refresh_state=state, 

3183 with_for_update=with_for_update, 

3184 only_load_props=attribute_names, 

3185 require_pk_cols=True, 

3186 # technically unnecessary as we just did autoflush 

3187 # above, however removes the additional unnecessary 

3188 # call to _autoflush() 

3189 no_autoflush=True, 

3190 is_user_refresh=True, 

3191 ) 

3192 is None 

3193 ): 

3194 raise sa_exc.InvalidRequestError( 

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

3196 ) 

3197 

3198 def expire_all(self) -> None: 

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

3200 

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

3202 a query will be issued using the 

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

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

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

3206 previously read in that same transaction, regardless of changes 

3207 in database state outside of that transaction. 

3208 

3209 To expire individual objects and individual attributes 

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

3211 

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

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

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

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

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

3217 assuming the transaction is isolated. 

3218 

3219 .. seealso:: 

3220 

3221 :ref:`session_expire` - introductory material 

3222 

3223 :meth:`.Session.expire` 

3224 

3225 :meth:`.Session.refresh` 

3226 

3227 :meth:`_orm.Query.populate_existing` 

3228 

3229 """ 

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

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

3232 

3233 def expire( 

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

3235 ) -> None: 

3236 """Expire the attributes on an instance. 

3237 

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

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

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

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

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

3243 previously read in that same transaction, regardless of changes 

3244 in database state outside of that transaction. 

3245 

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

3247 use :meth:`Session.expire_all`. 

3248 

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

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

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

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

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

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

3255 transaction. 

3256 

3257 :param instance: The instance to be refreshed. 

3258 :param attribute_names: optional list of string attribute names 

3259 indicating a subset of attributes to be expired. 

3260 

3261 .. seealso:: 

3262 

3263 :ref:`session_expire` - introductory material 

3264 

3265 :meth:`.Session.expire` 

3266 

3267 :meth:`.Session.refresh` 

3268 

3269 :meth:`_orm.Query.populate_existing` 

3270 

3271 """ 

3272 try: 

3273 state = attributes.instance_state(instance) 

3274 except exc.NO_STATE as err: 

3275 raise exc.UnmappedInstanceError(instance) from err 

3276 self._expire_state(state, attribute_names) 

3277 

3278 def _expire_state( 

3279 self, 

3280 state: InstanceState[Any], 

3281 attribute_names: Optional[Iterable[str]], 

3282 ) -> None: 

3283 self._validate_persistent(state) 

3284 if attribute_names: 

3285 state._expire_attributes(state.dict, attribute_names) 

3286 else: 

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

3288 # remove associations 

3289 cascaded = list( 

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

3291 ) 

3292 self._conditional_expire(state) 

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

3294 self._conditional_expire(st_) 

3295 

3296 def _conditional_expire( 

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

3298 ) -> None: 

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

3300 

3301 if state.key: 

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

3303 elif state in self._new: 

3304 self._new.pop(state) 

3305 state._detach(self) 

3306 

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

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

3309 

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

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

3312 

3313 """ 

3314 try: 

3315 state = attributes.instance_state(instance) 

3316 except exc.NO_STATE as err: 

3317 raise exc.UnmappedInstanceError(instance) from err 

3318 if state.session_id is not self.hash_key: 

3319 raise sa_exc.InvalidRequestError( 

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

3321 ) 

3322 

3323 cascaded = list( 

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

3325 ) 

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

3327 

3328 def _expunge_states( 

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

3330 ) -> None: 

3331 for state in states: 

3332 if state in self._new: 

3333 self._new.pop(state) 

3334 elif self.identity_map.contains_state(state): 

3335 self.identity_map.safe_discard(state) 

3336 self._deleted.pop(state, None) 

3337 elif self._transaction: 

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

3339 # in the transaction snapshot 

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

3341 statelib.InstanceState._detach_states( 

3342 states, self, to_transient=to_transient 

3343 ) 

3344 

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

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

3347 

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

3349 state as well as already persistent objects. 

3350 

3351 """ 

3352 

3353 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3354 for state in states: 

3355 mapper = _state_mapper(state) 

3356 

3357 # prevent against last minute dereferences of the object 

3358 obj = state.obj() 

3359 if obj is not None: 

3360 instance_key = mapper._identity_key_from_state(state) 

3361 

3362 if ( 

3363 _none_set.intersection(instance_key[1]) 

3364 and not mapper.allow_partial_pks 

3365 or _none_set.issuperset(instance_key[1]) 

3366 ): 

3367 raise exc.FlushError( 

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

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

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

3371 "that the mapped Column object is configured to " 

3372 "expect these generated values. Ensure also that " 

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

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

3375 % state_str(state) 

3376 ) 

3377 

3378 if state.key is None: 

3379 state.key = instance_key 

3380 elif state.key != instance_key: 

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

3382 # state has already replaced this one in the identity 

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

3384 self.identity_map.safe_discard(state) 

3385 trans = self._transaction 

3386 assert trans is not None 

3387 if state in trans._key_switches: 

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

3389 else: 

3390 orig_key = state.key 

3391 trans._key_switches[state] = ( 

3392 orig_key, 

3393 instance_key, 

3394 ) 

3395 state.key = instance_key 

3396 

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

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

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

3400 old = self.identity_map.replace(state) 

3401 if ( 

3402 old is not None 

3403 and mapper._identity_key_from_state(old) == instance_key 

3404 and old.obj() is not None 

3405 ): 

3406 util.warn( 

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

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

3409 "load operations occurring inside of an event handler " 

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

3411 ) 

3412 state._orphaned_outside_of_session = False 

3413 

3414 statelib.InstanceState._commit_all_states( 

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

3416 ) 

3417 

3418 self._register_altered(states) 

3419 

3420 if pending_to_persistent is not None: 

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

3422 pending_to_persistent(self, state) 

3423 

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

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

3426 self._new.pop(state) 

3427 

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

3429 if self._transaction: 

3430 for state in states: 

3431 if state in self._new: 

3432 self._transaction._new[state] = True 

3433 else: 

3434 self._transaction._dirty[state] = True 

3435 

3436 def _remove_newly_deleted( 

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

3438 ) -> None: 

3439 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3440 for state in states: 

3441 if self._transaction: 

3442 self._transaction._deleted[state] = True 

3443 

3444 if persistent_to_deleted is not None: 

3445 # get a strong reference before we pop out of 

3446 # self._deleted 

3447 obj = state.obj() # noqa 

3448 

3449 self.identity_map.safe_discard(state) 

3450 self._deleted.pop(state, None) 

3451 state._deleted = True 

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

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

3454 # tracked as part of that 

3455 if persistent_to_deleted is not None: 

3456 persistent_to_deleted(self, state) 

3457 

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

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

3460 

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

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

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

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

3465 

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

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

3468 state directly. 

3469 

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

3471 objects which were transient when they were passed to 

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

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

3474 :class:`_orm.Session`. 

3475 

3476 .. seealso:: 

3477 

3478 :meth:`_orm.Session.add_all` 

3479 

3480 :ref:`session_adding` - at :ref:`session_basics` 

3481 

3482 """ 

3483 if _warn and self._warn_on_events: 

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

3485 

3486 try: 

3487 state = attributes.instance_state(instance) 

3488 except exc.NO_STATE as err: 

3489 raise exc.UnmappedInstanceError(instance) from err 

3490 

3491 self._save_or_update_state(state) 

3492 

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

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

3495 

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

3497 behavioral description. 

3498 

3499 .. seealso:: 

3500 

3501 :meth:`_orm.Session.add` 

3502 

3503 :ref:`session_adding` - at :ref:`session_basics` 

3504 

3505 """ 

3506 

3507 if self._warn_on_events: 

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

3509 

3510 for instance in instances: 

3511 self.add(instance, _warn=False) 

3512 

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

3514 state._orphaned_outside_of_session = False 

3515 self._save_or_update_impl(state) 

3516 

3517 mapper = _state_mapper(state) 

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

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

3520 ): 

3521 self._save_or_update_impl(st_) 

3522 

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

3524 """Mark an instance as deleted. 

3525 

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

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

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

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

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

3531 

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

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

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

3535 is successfully committed, 

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

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

3538 

3539 .. seealso:: 

3540 

3541 :ref:`session_deleting` - at :ref:`session_basics` 

3542 

3543 """ 

3544 if self._warn_on_events: 

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

3546 

3547 try: 

3548 state = attributes.instance_state(instance) 

3549 except exc.NO_STATE as err: 

3550 raise exc.UnmappedInstanceError(instance) from err 

3551 

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

3553 

3554 def _delete_impl( 

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

3556 ) -> None: 

3557 if state.key is None: 

3558 if head: 

3559 raise sa_exc.InvalidRequestError( 

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

3561 ) 

3562 else: 

3563 return 

3564 

3565 to_attach = self._before_attach(state, obj) 

3566 

3567 if state in self._deleted: 

3568 return 

3569 

3570 self.identity_map.add(state) 

3571 

3572 if to_attach: 

3573 self._after_attach(state, obj) 

3574 

3575 if head: 

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

3577 # so that autoflush does not delete the item 

3578 # the strong reference to the instance itself is significant here 

3579 cascade_states = list( 

3580 state.manager.mapper.cascade_iterator("delete", state) 

3581 ) 

3582 else: 

3583 cascade_states = None 

3584 

3585 self._deleted[state] = obj 

3586 

3587 if head: 

3588 if TYPE_CHECKING: 

3589 assert cascade_states is not None 

3590 for o, m, st_, dct_ in cascade_states: 

3591 self._delete_impl(st_, o, False) 

3592 

3593 def get( 

3594 self, 

3595 entity: _EntityBindKey[_O], 

3596 ident: _PKIdentityArgument, 

3597 *, 

3598 options: Optional[Sequence[ORMOption]] = None, 

3599 populate_existing: bool = False, 

3600 with_for_update: ForUpdateParameter = None, 

3601 identity_token: Optional[Any] = None, 

3602 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3603 bind_arguments: Optional[_BindArguments] = None, 

3604 ) -> Optional[_O]: 

3605 """Return an instance based on the given primary key identifier, 

3606 or ``None`` if not found. 

3607 

3608 E.g.:: 

3609 

3610 my_user = session.get(User, 5) 

3611 

3612 some_object = session.get(VersionedFoo, (5, 10)) 

3613 

3614 some_object = session.get( 

3615 VersionedFoo, 

3616 {"id": 5, "version_id": 10} 

3617 ) 

3618 

3619 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3620 from the now legacy :meth:`_orm.Query.get` method. 

3621 

3622 :meth:`_orm.Session.get` is special in that it provides direct 

3623 access to the identity map of the :class:`.Session`. 

3624 If the given primary key identifier is present 

3625 in the local identity map, the object is returned 

3626 directly from this collection and no SQL is emitted, 

3627 unless the object has been marked fully expired. 

3628 If not present, 

3629 a SELECT is performed in order to locate the object. 

3630 

3631 :meth:`_orm.Session.get` also will perform a check if 

3632 the object is present in the identity map and 

3633 marked as expired - a SELECT 

3634 is emitted to refresh the object as well as to 

3635 ensure that the row is still present. 

3636 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3637 

3638 :param entity: a mapped class or :class:`.Mapper` indicating the 

3639 type of entity to be loaded. 

3640 

3641 :param ident: A scalar, tuple, or dictionary representing the 

3642 primary key. For a composite (e.g. multiple column) primary key, 

3643 a tuple or dictionary should be passed. 

3644 

3645 For a single-column primary key, the scalar calling form is typically 

3646 the most expedient. If the primary key of a row is the value "5", 

3647 the call looks like:: 

3648 

3649 my_object = session.get(SomeClass, 5) 

3650 

3651 The tuple form contains primary key values typically in 

3652 the order in which they correspond to the mapped 

3653 :class:`_schema.Table` 

3654 object's primary key columns, or if the 

3655 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3656 used, in 

3657 the order used for that parameter. For example, if the primary key 

3658 of a row is represented by the integer 

3659 digits "5, 10" the call would look like:: 

3660 

3661 my_object = session.get(SomeClass, (5, 10)) 

3662 

3663 The dictionary form should include as keys the mapped attribute names 

3664 corresponding to each element of the primary key. If the mapped class 

3665 has the attributes ``id``, ``version_id`` as the attributes which 

3666 store the object's primary key value, the call would look like:: 

3667 

3668 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3669 

3670 :param options: optional sequence of loader options which will be 

3671 applied to the query, if one is emitted. 

3672 

3673 :param populate_existing: causes the method to unconditionally emit 

3674 a SQL query and refresh the object with the newly loaded data, 

3675 regardless of whether or not the object is already present. 

3676 

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

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

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

3680 flags should match the parameters of 

3681 :meth:`_query.Query.with_for_update`. 

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

3683 

3684 :param execution_options: optional dictionary of execution options, 

3685 which will be associated with the query execution if one is emitted. 

3686 This dictionary can provide a subset of the options that are 

3687 accepted by :meth:`_engine.Connection.execution_options`, and may 

3688 also provide additional options understood only in an ORM context. 

3689 

3690 .. versionadded:: 1.4.29 

3691 

3692 .. seealso:: 

3693 

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

3695 options 

3696 

3697 :param bind_arguments: dictionary of additional arguments to determine 

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

3699 Contents of this dictionary are passed to the 

3700 :meth:`.Session.get_bind` method. 

3701 

3702 .. versionadded: 2.0.0rc1 

3703 

3704 :return: The object instance, or ``None``. 

3705 

3706 """ 

3707 return self._get_impl( 

3708 entity, 

3709 ident, 

3710 loading.load_on_pk_identity, 

3711 options=options, 

3712 populate_existing=populate_existing, 

3713 with_for_update=with_for_update, 

3714 identity_token=identity_token, 

3715 execution_options=execution_options, 

3716 bind_arguments=bind_arguments, 

3717 ) 

3718 

3719 def get_one( 

3720 self, 

3721 entity: _EntityBindKey[_O], 

3722 ident: _PKIdentityArgument, 

3723 *, 

3724 options: Optional[Sequence[ORMOption]] = None, 

3725 populate_existing: bool = False, 

3726 with_for_update: ForUpdateParameter = None, 

3727 identity_token: Optional[Any] = None, 

3728 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3729 bind_arguments: Optional[_BindArguments] = None, 

3730 ) -> _O: 

3731 """Return exactly one instance based on the given primary key 

3732 identifier, or raise an exception if not found. 

3733 

3734 Raises ``sqlalchemy.orm.exc.NoResultFound`` if the query 

3735 selects no rows. 

3736 

3737 For a detailed documentation of the arguments see the 

3738 method :meth:`.Session.get`. 

3739 

3740 .. versionadded:: 2.0.22 

3741 

3742 :return: The object instance. 

3743 

3744 .. seealso:: 

3745 

3746 :meth:`.Session.get` - equivalent method that instead 

3747 returns ``None`` if no row was found with the provided primary 

3748 key 

3749 

3750 """ 

3751 

3752 instance = self.get( 

3753 entity, 

3754 ident, 

3755 options=options, 

3756 populate_existing=populate_existing, 

3757 with_for_update=with_for_update, 

3758 identity_token=identity_token, 

3759 execution_options=execution_options, 

3760 bind_arguments=bind_arguments, 

3761 ) 

3762 

3763 if instance is None: 

3764 raise sa_exc.NoResultFound( 

3765 "No row was found when one was required" 

3766 ) 

3767 

3768 return instance 

3769 

3770 def _get_impl( 

3771 self, 

3772 entity: _EntityBindKey[_O], 

3773 primary_key_identity: _PKIdentityArgument, 

3774 db_load_fn: Callable[..., _O], 

3775 *, 

3776 options: Optional[Sequence[ExecutableOption]] = None, 

3777 populate_existing: bool = False, 

3778 with_for_update: ForUpdateParameter = None, 

3779 identity_token: Optional[Any] = None, 

3780 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3781 bind_arguments: Optional[_BindArguments] = None, 

3782 ) -> Optional[_O]: 

3783 # convert composite types to individual args 

3784 if ( 

3785 is_composite_class(primary_key_identity) 

3786 and type(primary_key_identity) 

3787 in descriptor_props._composite_getters 

3788 ): 

3789 getter = descriptor_props._composite_getters[ 

3790 type(primary_key_identity) 

3791 ] 

3792 primary_key_identity = getter(primary_key_identity) 

3793 

3794 mapper: Optional[Mapper[_O]] = inspect(entity) 

3795 

3796 if mapper is None or not mapper.is_mapper: 

3797 raise sa_exc.ArgumentError( 

3798 "Expected mapped class or mapper, got: %r" % entity 

3799 ) 

3800 

3801 is_dict = isinstance(primary_key_identity, dict) 

3802 if not is_dict: 

3803 primary_key_identity = util.to_list( 

3804 primary_key_identity, default=[None] 

3805 ) 

3806 

3807 if len(primary_key_identity) != len(mapper.primary_key): 

3808 raise sa_exc.InvalidRequestError( 

3809 "Incorrect number of values in identifier to formulate " 

3810 "primary key for session.get(); primary key columns " 

3811 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3812 ) 

3813 

3814 if is_dict: 

3815 pk_synonyms = mapper._pk_synonyms 

3816 

3817 if pk_synonyms: 

3818 correct_keys = set(pk_synonyms).intersection( 

3819 primary_key_identity 

3820 ) 

3821 

3822 if correct_keys: 

3823 primary_key_identity = dict(primary_key_identity) 

3824 for k in correct_keys: 

3825 primary_key_identity[pk_synonyms[k]] = ( 

3826 primary_key_identity[k] 

3827 ) 

3828 

3829 try: 

3830 primary_key_identity = list( 

3831 primary_key_identity[prop.key] 

3832 for prop in mapper._identity_key_props 

3833 ) 

3834 

3835 except KeyError as err: 

3836 raise sa_exc.InvalidRequestError( 

3837 "Incorrect names of values in identifier to formulate " 

3838 "primary key for session.get(); primary key attribute " 

3839 "names are %s (synonym names are also accepted)" 

3840 % ",".join( 

3841 "'%s'" % prop.key 

3842 for prop in mapper._identity_key_props 

3843 ) 

3844 ) from err 

3845 

3846 if ( 

3847 not populate_existing 

3848 and not mapper.always_refresh 

3849 and with_for_update is None 

3850 ): 

3851 instance = self._identity_lookup( 

3852 mapper, 

3853 primary_key_identity, 

3854 identity_token=identity_token, 

3855 execution_options=execution_options, 

3856 bind_arguments=bind_arguments, 

3857 ) 

3858 

3859 if instance is not None: 

3860 # reject calls for id in identity map but class 

3861 # mismatch. 

3862 if not isinstance(instance, mapper.class_): 

3863 return None 

3864 return instance 

3865 

3866 # TODO: this was being tested before, but this is not possible 

3867 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3868 

3869 # set_label_style() not strictly necessary, however this will ensure 

3870 # that tablename_colname style is used which at the moment is 

3871 # asserted in a lot of unit tests :) 

3872 

3873 load_options = context.QueryContext.default_load_options 

3874 

3875 if populate_existing: 

3876 load_options += {"_populate_existing": populate_existing} 

3877 statement = sql.select(mapper).set_label_style( 

3878 LABEL_STYLE_TABLENAME_PLUS_COL 

3879 ) 

3880 if with_for_update is not None: 

3881 statement._for_update_arg = ForUpdateArg._from_argument( 

3882 with_for_update 

3883 ) 

3884 

3885 if options: 

3886 statement = statement.options(*options) 

3887 return db_load_fn( 

3888 self, 

3889 statement, 

3890 primary_key_identity, 

3891 load_options=load_options, 

3892 identity_token=identity_token, 

3893 execution_options=execution_options, 

3894 bind_arguments=bind_arguments, 

3895 ) 

3896 

3897 def merge( 

3898 self, 

3899 instance: _O, 

3900 *, 

3901 load: bool = True, 

3902 options: Optional[Sequence[ORMOption]] = None, 

3903 ) -> _O: 

3904 """Copy the state of a given instance into a corresponding instance 

3905 within this :class:`.Session`. 

3906 

3907 :meth:`.Session.merge` examines the primary key attributes of the 

3908 source instance, and attempts to reconcile it with an instance of the 

3909 same primary key in the session. If not found locally, it attempts 

3910 to load the object from the database based on primary key, and if 

3911 none can be located, creates a new instance. The state of each 

3912 attribute on the source instance is then copied to the target 

3913 instance. The resulting target instance is then returned by the 

3914 method; the original source instance is left unmodified, and 

3915 un-associated with the :class:`.Session` if not already. 

3916 

3917 This operation cascades to associated instances if the association is 

3918 mapped with ``cascade="merge"``. 

3919 

3920 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3921 

3922 :param instance: Instance to be merged. 

3923 :param load: Boolean, when False, :meth:`.merge` switches into 

3924 a "high performance" mode which causes it to forego emitting history 

3925 events as well as all database access. This flag is used for 

3926 cases such as transferring graphs of objects into a :class:`.Session` 

3927 from a second level cache, or to transfer just-loaded objects 

3928 into the :class:`.Session` owned by a worker thread or process 

3929 without re-querying the database. 

3930 

3931 The ``load=False`` use case adds the caveat that the given 

3932 object has to be in a "clean" state, that is, has no pending changes 

3933 to be flushed - even if the incoming object is detached from any 

3934 :class:`.Session`. This is so that when 

3935 the merge operation populates local attributes and 

3936 cascades to related objects and 

3937 collections, the values can be "stamped" onto the 

3938 target object as is, without generating any history or attribute 

3939 events, and without the need to reconcile the incoming data with 

3940 any existing related objects or collections that might not 

3941 be loaded. The resulting objects from ``load=False`` are always 

3942 produced as "clean", so it is only appropriate that the given objects 

3943 should be "clean" as well, else this suggests a mis-use of the 

3944 method. 

3945 :param options: optional sequence of loader options which will be 

3946 applied to the :meth:`_orm.Session.get` method when the merge 

3947 operation loads the existing version of the object from the database. 

3948 

3949 .. versionadded:: 1.4.24 

3950 

3951 

3952 .. seealso:: 

3953 

3954 :func:`.make_transient_to_detached` - provides for an alternative 

3955 means of "merging" a single object into the :class:`.Session` 

3956 

3957 """ 

3958 

3959 if self._warn_on_events: 

3960 self._flush_warning("Session.merge()") 

3961 

3962 _recursive: Dict[InstanceState[Any], object] = {} 

3963 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object] = {} 

3964 

3965 if load: 

3966 # flush current contents if we expect to load data 

3967 self._autoflush() 

3968 

3969 object_mapper(instance) # verify mapped 

3970 autoflush = self.autoflush 

3971 try: 

3972 self.autoflush = False 

3973 return self._merge( 

3974 attributes.instance_state(instance), 

3975 attributes.instance_dict(instance), 

3976 load=load, 

3977 options=options, 

3978 _recursive=_recursive, 

3979 _resolve_conflict_map=_resolve_conflict_map, 

3980 ) 

3981 finally: 

3982 self.autoflush = autoflush 

3983 

3984 def _merge( 

3985 self, 

3986 state: InstanceState[_O], 

3987 state_dict: _InstanceDict, 

3988 *, 

3989 options: Optional[Sequence[ORMOption]] = None, 

3990 load: bool, 

3991 _recursive: Dict[Any, object], 

3992 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

3993 ) -> _O: 

3994 mapper: Mapper[_O] = _state_mapper(state) 

3995 if state in _recursive: 

3996 return cast(_O, _recursive[state]) 

3997 

3998 new_instance = False 

3999 key = state.key 

4000 

4001 merged: Optional[_O] 

4002 

4003 if key is None: 

4004 if state in self._new: 

4005 util.warn( 

4006 "Instance %s is already pending in this Session yet is " 

4007 "being merged again; this is probably not what you want " 

4008 "to do" % state_str(state) 

4009 ) 

4010 

4011 if not load: 

4012 raise sa_exc.InvalidRequestError( 

4013 "merge() with load=False option does not support " 

4014 "objects transient (i.e. unpersisted) objects. flush() " 

4015 "all changes on mapped instances before merging with " 

4016 "load=False." 

4017 ) 

4018 key = mapper._identity_key_from_state(state) 

4019 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4020 1 

4021 ] and ( 

4022 not _none_set.intersection(key[1]) 

4023 or ( 

4024 mapper.allow_partial_pks 

4025 and not _none_set.issuperset(key[1]) 

4026 ) 

4027 ) 

4028 else: 

4029 key_is_persistent = True 

4030 

4031 if key in self.identity_map: 

4032 try: 

4033 merged = self.identity_map[key] 

4034 except KeyError: 

4035 # object was GC'ed right as we checked for it 

4036 merged = None 

4037 else: 

4038 merged = None 

4039 

4040 if merged is None: 

4041 if key_is_persistent and key in _resolve_conflict_map: 

4042 merged = cast(_O, _resolve_conflict_map[key]) 

4043 

4044 elif not load: 

4045 if state.modified: 

4046 raise sa_exc.InvalidRequestError( 

4047 "merge() with load=False option does not support " 

4048 "objects marked as 'dirty'. flush() all changes on " 

4049 "mapped instances before merging with load=False." 

4050 ) 

4051 merged = mapper.class_manager.new_instance() 

4052 merged_state = attributes.instance_state(merged) 

4053 merged_state.key = key 

4054 self._update_impl(merged_state) 

4055 new_instance = True 

4056 

4057 elif key_is_persistent: 

4058 merged = self.get( 

4059 mapper.class_, 

4060 key[1], 

4061 identity_token=key[2], 

4062 options=options, 

4063 ) 

4064 

4065 if merged is None: 

4066 merged = mapper.class_manager.new_instance() 

4067 merged_state = attributes.instance_state(merged) 

4068 merged_dict = attributes.instance_dict(merged) 

4069 new_instance = True 

4070 self._save_or_update_state(merged_state) 

4071 else: 

4072 merged_state = attributes.instance_state(merged) 

4073 merged_dict = attributes.instance_dict(merged) 

4074 

4075 _recursive[state] = merged 

4076 _resolve_conflict_map[key] = merged 

4077 

4078 # check that we didn't just pull the exact same 

4079 # state out. 

4080 if state is not merged_state: 

4081 # version check if applicable 

4082 if mapper.version_id_col is not None: 

4083 existing_version = mapper._get_state_attr_by_column( 

4084 state, 

4085 state_dict, 

4086 mapper.version_id_col, 

4087 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4088 ) 

4089 

4090 merged_version = mapper._get_state_attr_by_column( 

4091 merged_state, 

4092 merged_dict, 

4093 mapper.version_id_col, 

4094 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4095 ) 

4096 

4097 if ( 

4098 existing_version 

4099 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4100 and merged_version 

4101 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4102 and existing_version != merged_version 

4103 ): 

4104 raise exc.StaleDataError( 

4105 "Version id '%s' on merged state %s " 

4106 "does not match existing version '%s'. " 

4107 "Leave the version attribute unset when " 

4108 "merging to update the most recent version." 

4109 % ( 

4110 existing_version, 

4111 state_str(merged_state), 

4112 merged_version, 

4113 ) 

4114 ) 

4115 

4116 merged_state.load_path = state.load_path 

4117 merged_state.load_options = state.load_options 

4118 

4119 # since we are copying load_options, we need to copy 

4120 # the callables_ that would have been generated by those 

4121 # load_options. 

4122 # assumes that the callables we put in state.callables_ 

4123 # are not instance-specific (which they should not be) 

4124 merged_state._copy_callables(state) 

4125 

4126 for prop in mapper.iterate_properties: 

4127 prop.merge( 

4128 self, 

4129 state, 

4130 state_dict, 

4131 merged_state, 

4132 merged_dict, 

4133 load, 

4134 _recursive, 

4135 _resolve_conflict_map, 

4136 ) 

4137 

4138 if not load: 

4139 # remove any history 

4140 merged_state._commit_all(merged_dict, self.identity_map) 

4141 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4142 merged_state, None 

4143 ) 

4144 

4145 if new_instance: 

4146 merged_state.manager.dispatch.load(merged_state, None) 

4147 

4148 return merged 

4149 

4150 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4151 if not self.identity_map.contains_state(state): 

4152 raise sa_exc.InvalidRequestError( 

4153 "Instance '%s' is not persistent within this Session" 

4154 % state_str(state) 

4155 ) 

4156 

4157 def _save_impl(self, state: InstanceState[Any]) -> None: 

4158 if state.key is not None: 

4159 raise sa_exc.InvalidRequestError( 

4160 "Object '%s' already has an identity - " 

4161 "it can't be registered as pending" % state_str(state) 

4162 ) 

4163 

4164 obj = state.obj() 

4165 to_attach = self._before_attach(state, obj) 

4166 if state not in self._new: 

4167 self._new[state] = obj 

4168 state.insert_order = len(self._new) 

4169 if to_attach: 

4170 self._after_attach(state, obj) 

4171 

4172 def _update_impl( 

4173 self, state: InstanceState[Any], revert_deletion: bool = False 

4174 ) -> None: 

4175 if state.key is None: 

4176 raise sa_exc.InvalidRequestError( 

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

4178 ) 

4179 

4180 if state._deleted: 

4181 if revert_deletion: 

4182 if not state._attached: 

4183 return 

4184 del state._deleted 

4185 else: 

4186 raise sa_exc.InvalidRequestError( 

4187 "Instance '%s' has been deleted. " 

4188 "Use the make_transient() " 

4189 "function to send this object back " 

4190 "to the transient state." % state_str(state) 

4191 ) 

4192 

4193 obj = state.obj() 

4194 

4195 # check for late gc 

4196 if obj is None: 

4197 return 

4198 

4199 to_attach = self._before_attach(state, obj) 

4200 

4201 self._deleted.pop(state, None) 

4202 if revert_deletion: 

4203 self.identity_map.replace(state) 

4204 else: 

4205 self.identity_map.add(state) 

4206 

4207 if to_attach: 

4208 self._after_attach(state, obj) 

4209 elif revert_deletion: 

4210 self.dispatch.deleted_to_persistent(self, state) 

4211 

4212 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4213 if state.key is None: 

4214 self._save_impl(state) 

4215 else: 

4216 self._update_impl(state) 

4217 

4218 def enable_relationship_loading(self, obj: object) -> None: 

4219 """Associate an object with this :class:`.Session` for related 

4220 object loading. 

4221 

4222 .. warning:: 

4223 

4224 :meth:`.enable_relationship_loading` exists to serve special 

4225 use cases and is not recommended for general use. 

4226 

4227 Accesses of attributes mapped with :func:`_orm.relationship` 

4228 will attempt to load a value from the database using this 

4229 :class:`.Session` as the source of connectivity. The values 

4230 will be loaded based on foreign key and primary key values 

4231 present on this object - if not present, then those relationships 

4232 will be unavailable. 

4233 

4234 The object will be attached to this session, but will 

4235 **not** participate in any persistence operations; its state 

4236 for almost all purposes will remain either "transient" or 

4237 "detached", except for the case of relationship loading. 

4238 

4239 Also note that backrefs will often not work as expected. 

4240 Altering a relationship-bound attribute on the target object 

4241 may not fire off a backref event, if the effective value 

4242 is what was already loaded from a foreign-key-holding value. 

4243 

4244 The :meth:`.Session.enable_relationship_loading` method is 

4245 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4246 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4247 an object to remain transient while still being able to load 

4248 related items. 

4249 

4250 To make a transient object associated with a :class:`.Session` 

4251 via :meth:`.Session.enable_relationship_loading` pending, add 

4252 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4253 If the object instead represents an existing identity in the database, 

4254 it should be merged using :meth:`.Session.merge`. 

4255 

4256 :meth:`.Session.enable_relationship_loading` does not improve 

4257 behavior when the ORM is used normally - object references should be 

4258 constructed at the object level, not at the foreign key level, so 

4259 that they are present in an ordinary way before flush() 

4260 proceeds. This method is not intended for general use. 

4261 

4262 .. seealso:: 

4263 

4264 :paramref:`_orm.relationship.load_on_pending` - this flag 

4265 allows per-relationship loading of many-to-ones on items that 

4266 are pending. 

4267 

4268 :func:`.make_transient_to_detached` - allows for an object to 

4269 be added to a :class:`.Session` without SQL emitted, which then 

4270 will unexpire attributes on access. 

4271 

4272 """ 

4273 try: 

4274 state = attributes.instance_state(obj) 

4275 except exc.NO_STATE as err: 

4276 raise exc.UnmappedInstanceError(obj) from err 

4277 

4278 to_attach = self._before_attach(state, obj) 

4279 state._load_pending = True 

4280 if to_attach: 

4281 self._after_attach(state, obj) 

4282 

4283 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4284 self._autobegin_t() 

4285 

4286 if state.session_id == self.hash_key: 

4287 return False 

4288 

4289 if state.session_id and state.session_id in _sessions: 

4290 raise sa_exc.InvalidRequestError( 

4291 "Object '%s' is already attached to session '%s' " 

4292 "(this is '%s')" 

4293 % (state_str(state), state.session_id, self.hash_key) 

4294 ) 

4295 

4296 self.dispatch.before_attach(self, state) 

4297 

4298 return True 

4299 

4300 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4301 state.session_id = self.hash_key 

4302 if state.modified and state._strong_obj is None: 

4303 state._strong_obj = obj 

4304 self.dispatch.after_attach(self, state) 

4305 

4306 if state.key: 

4307 self.dispatch.detached_to_persistent(self, state) 

4308 else: 

4309 self.dispatch.transient_to_pending(self, state) 

4310 

4311 def __contains__(self, instance: object) -> bool: 

4312 """Return True if the instance is associated with this session. 

4313 

4314 The instance may be pending or persistent within the Session for a 

4315 result of True. 

4316 

4317 """ 

4318 try: 

4319 state = attributes.instance_state(instance) 

4320 except exc.NO_STATE as err: 

4321 raise exc.UnmappedInstanceError(instance) from err 

4322 return self._contains_state(state) 

4323 

4324 def __iter__(self) -> Iterator[object]: 

4325 """Iterate over all pending or persistent instances within this 

4326 Session. 

4327 

4328 """ 

4329 return iter( 

4330 list(self._new.values()) + list(self.identity_map.values()) 

4331 ) 

4332 

4333 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4334 return state in self._new or self.identity_map.contains_state(state) 

4335 

4336 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4337 """Flush all the object changes to the database. 

4338 

4339 Writes out all pending object creations, deletions and modifications 

4340 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4341 automatically ordered by the Session's unit of work dependency 

4342 solver. 

4343 

4344 Database operations will be issued in the current transactional 

4345 context and do not affect the state of the transaction, unless an 

4346 error occurs, in which case the entire transaction is rolled back. 

4347 You may flush() as often as you like within a transaction to move 

4348 changes from Python to the database's transaction buffer. 

4349 

4350 :param objects: Optional; restricts the flush operation to operate 

4351 only on elements that are in the given collection. 

4352 

4353 This feature is for an extremely narrow set of use cases where 

4354 particular objects may need to be operated upon before the 

4355 full flush() occurs. It is not intended for general use. 

4356 

4357 """ 

4358 

4359 if self._flushing: 

4360 raise sa_exc.InvalidRequestError("Session is already flushing") 

4361 

4362 if self._is_clean(): 

4363 return 

4364 try: 

4365 self._flushing = True 

4366 self._flush(objects) 

4367 finally: 

4368 self._flushing = False 

4369 

4370 def _flush_warning(self, method: Any) -> None: 

4371 util.warn( 

4372 "Usage of the '%s' operation is not currently supported " 

4373 "within the execution stage of the flush process. " 

4374 "Results may not be consistent. Consider using alternative " 

4375 "event listeners or connection-level operations instead." % method 

4376 ) 

4377 

4378 def _is_clean(self) -> bool: 

4379 return ( 

4380 not self.identity_map.check_modified() 

4381 and not self._deleted 

4382 and not self._new 

4383 ) 

4384 

4385 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4386 dirty = self._dirty_states 

4387 if not dirty and not self._deleted and not self._new: 

4388 self.identity_map._modified.clear() 

4389 return 

4390 

4391 flush_context = UOWTransaction(self) 

4392 

4393 if self.dispatch.before_flush: 

4394 self.dispatch.before_flush(self, flush_context, objects) 

4395 # re-establish "dirty states" in case the listeners 

4396 # added 

4397 dirty = self._dirty_states 

4398 

4399 deleted = set(self._deleted) 

4400 new = set(self._new) 

4401 

4402 dirty = set(dirty).difference(deleted) 

4403 

4404 # create the set of all objects we want to operate upon 

4405 if objects: 

4406 # specific list passed in 

4407 objset = set() 

4408 for o in objects: 

4409 try: 

4410 state = attributes.instance_state(o) 

4411 

4412 except exc.NO_STATE as err: 

4413 raise exc.UnmappedInstanceError(o) from err 

4414 objset.add(state) 

4415 else: 

4416 objset = None 

4417 

4418 # store objects whose fate has been decided 

4419 processed = set() 

4420 

4421 # put all saves/updates into the flush context. detect top-level 

4422 # orphans and throw them into deleted. 

4423 if objset: 

4424 proc = new.union(dirty).intersection(objset).difference(deleted) 

4425 else: 

4426 proc = new.union(dirty).difference(deleted) 

4427 

4428 for state in proc: 

4429 is_orphan = _state_mapper(state)._is_orphan(state) 

4430 

4431 is_persistent_orphan = is_orphan and state.has_identity 

4432 

4433 if ( 

4434 is_orphan 

4435 and not is_persistent_orphan 

4436 and state._orphaned_outside_of_session 

4437 ): 

4438 self._expunge_states([state]) 

4439 else: 

4440 _reg = flush_context.register_object( 

4441 state, isdelete=is_persistent_orphan 

4442 ) 

4443 assert _reg, "Failed to add object to the flush context!" 

4444 processed.add(state) 

4445 

4446 # put all remaining deletes into the flush context. 

4447 if objset: 

4448 proc = deleted.intersection(objset).difference(processed) 

4449 else: 

4450 proc = deleted.difference(processed) 

4451 for state in proc: 

4452 _reg = flush_context.register_object(state, isdelete=True) 

4453 assert _reg, "Failed to add object to the flush context!" 

4454 

4455 if not flush_context.has_work: 

4456 return 

4457 

4458 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4459 try: 

4460 self._warn_on_events = True 

4461 try: 

4462 flush_context.execute() 

4463 finally: 

4464 self._warn_on_events = False 

4465 

4466 self.dispatch.after_flush(self, flush_context) 

4467 

4468 flush_context.finalize_flush_changes() 

4469 

4470 if not objects and self.identity_map._modified: 

4471 len_ = len(self.identity_map._modified) 

4472 

4473 statelib.InstanceState._commit_all_states( 

4474 [ 

4475 (state, state.dict) 

4476 for state in self.identity_map._modified 

4477 ], 

4478 instance_dict=self.identity_map, 

4479 ) 

4480 util.warn( 

4481 "Attribute history events accumulated on %d " 

4482 "previously clean instances " 

4483 "within inner-flush event handlers have been " 

4484 "reset, and will not result in database updates. " 

4485 "Consider using set_committed_value() within " 

4486 "inner-flush event handlers to avoid this warning." % len_ 

4487 ) 

4488 

4489 # useful assertions: 

4490 # if not objects: 

4491 # assert not self.identity_map._modified 

4492 # else: 

4493 # assert self.identity_map._modified == \ 

4494 # self.identity_map._modified.difference(objects) 

4495 

4496 self.dispatch.after_flush_postexec(self, flush_context) 

4497 

4498 transaction.commit() 

4499 

4500 except: 

4501 with util.safe_reraise(): 

4502 transaction.rollback(_capture_exception=True) 

4503 

4504 def bulk_save_objects( 

4505 self, 

4506 objects: Iterable[object], 

4507 return_defaults: bool = False, 

4508 update_changed_only: bool = True, 

4509 preserve_order: bool = True, 

4510 ) -> None: 

4511 """Perform a bulk save of the given list of objects. 

4512 

4513 .. legacy:: 

4514 

4515 This method is a legacy feature as of the 2.0 series of 

4516 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4517 the sections :ref:`orm_queryguide_bulk_insert` and 

4518 :ref:`orm_queryguide_bulk_update`. 

4519 

4520 For general INSERT and UPDATE of existing ORM mapped objects, 

4521 prefer standard :term:`unit of work` data management patterns, 

4522 introduced in the :ref:`unified_tutorial` at 

4523 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4524 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4525 which solves previous issues of bulk INSERT slowness. 

4526 

4527 :param objects: a sequence of mapped object instances. The mapped 

4528 objects are persisted as is, and are **not** associated with the 

4529 :class:`.Session` afterwards. 

4530 

4531 For each object, whether the object is sent as an INSERT or an 

4532 UPDATE is dependent on the same rules used by the :class:`.Session` 

4533 in traditional operation; if the object has the 

4534 :attr:`.InstanceState.key` 

4535 attribute set, then the object is assumed to be "detached" and 

4536 will result in an UPDATE. Otherwise, an INSERT is used. 

4537 

4538 In the case of an UPDATE, statements are grouped based on which 

4539 attributes have changed, and are thus to be the subject of each 

4540 SET clause. If ``update_changed_only`` is False, then all 

4541 attributes present within each object are applied to the UPDATE 

4542 statement, which may help in allowing the statements to be grouped 

4543 together into a larger executemany(), and will also reduce the 

4544 overhead of checking history on attributes. 

4545 

4546 :param return_defaults: when True, rows that are missing values which 

4547 generate defaults, namely integer primary key defaults and sequences, 

4548 will be inserted **one at a time**, so that the primary key value 

4549 is available. In particular this will allow joined-inheritance 

4550 and other multi-table mappings to insert correctly without the need 

4551 to provide primary key values ahead of time; however, 

4552 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4553 reduces the performance gains** of the method overall. It is strongly 

4554 advised to please use the standard :meth:`_orm.Session.add_all` 

4555 approach. 

4556 

4557 :param update_changed_only: when True, UPDATE statements are rendered 

4558 based on those attributes in each state that have logged changes. 

4559 When False, all attributes present are rendered into the SET clause 

4560 with the exception of primary key attributes. 

4561 

4562 :param preserve_order: when True, the order of inserts and updates 

4563 matches exactly the order in which the objects are given. When 

4564 False, common types of objects are grouped into inserts 

4565 and updates, to allow for more batching opportunities. 

4566 

4567 .. seealso:: 

4568 

4569 :doc:`queryguide/dml` 

4570 

4571 :meth:`.Session.bulk_insert_mappings` 

4572 

4573 :meth:`.Session.bulk_update_mappings` 

4574 

4575 """ 

4576 

4577 obj_states: Iterable[InstanceState[Any]] 

4578 

4579 obj_states = (attributes.instance_state(obj) for obj in objects) 

4580 

4581 if not preserve_order: 

4582 # the purpose of this sort is just so that common mappers 

4583 # and persistence states are grouped together, so that groupby 

4584 # will return a single group for a particular type of mapper. 

4585 # it's not trying to be deterministic beyond that. 

4586 obj_states = sorted( 

4587 obj_states, 

4588 key=lambda state: (id(state.mapper), state.key is not None), 

4589 ) 

4590 

4591 def grouping_key( 

4592 state: InstanceState[_O], 

4593 ) -> Tuple[Mapper[_O], bool]: 

4594 return (state.mapper, state.key is not None) 

4595 

4596 for (mapper, isupdate), states in itertools.groupby( 

4597 obj_states, grouping_key 

4598 ): 

4599 self._bulk_save_mappings( 

4600 mapper, 

4601 states, 

4602 isupdate=isupdate, 

4603 isstates=True, 

4604 return_defaults=return_defaults, 

4605 update_changed_only=update_changed_only, 

4606 render_nulls=False, 

4607 ) 

4608 

4609 def bulk_insert_mappings( 

4610 self, 

4611 mapper: Mapper[Any], 

4612 mappings: Iterable[Dict[str, Any]], 

4613 return_defaults: bool = False, 

4614 render_nulls: bool = False, 

4615 ) -> None: 

4616 """Perform a bulk insert of the given list of mapping dictionaries. 

4617 

4618 .. legacy:: 

4619 

4620 This method is a legacy feature as of the 2.0 series of 

4621 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4622 the sections :ref:`orm_queryguide_bulk_insert` and 

4623 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4624 implementation details with this method and adds new features 

4625 as well. 

4626 

4627 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4628 object, 

4629 representing the single kind of object represented within the mapping 

4630 list. 

4631 

4632 :param mappings: a sequence of dictionaries, each one containing the 

4633 state of the mapped row to be inserted, in terms of the attribute 

4634 names on the mapped class. If the mapping refers to multiple tables, 

4635 such as a joined-inheritance mapping, each dictionary must contain all 

4636 keys to be populated into all tables. 

4637 

4638 :param return_defaults: when True, the INSERT process will be altered 

4639 to ensure that newly generated primary key values will be fetched. 

4640 The rationale for this parameter is typically to enable 

4641 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4642 be bulk inserted. 

4643 

4644 .. note:: for backends that don't support RETURNING, the 

4645 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4646 parameter can significantly decrease performance as INSERT 

4647 statements can no longer be batched. See 

4648 :ref:`engine_insertmanyvalues` 

4649 for background on which backends are affected. 

4650 

4651 :param render_nulls: When True, a value of ``None`` will result 

4652 in a NULL value being included in the INSERT statement, rather 

4653 than the column being omitted from the INSERT. This allows all 

4654 the rows being INSERTed to have the identical set of columns which 

4655 allows the full set of rows to be batched to the DBAPI. Normally, 

4656 each column-set that contains a different combination of NULL values 

4657 than the previous row must omit a different series of columns from 

4658 the rendered INSERT statement, which means it must be emitted as a 

4659 separate statement. By passing this flag, the full set of rows 

4660 are guaranteed to be batchable into one batch; the cost however is 

4661 that server-side defaults which are invoked by an omitted column will 

4662 be skipped, so care must be taken to ensure that these are not 

4663 necessary. 

4664 

4665 .. warning:: 

4666 

4667 When this flag is set, **server side default SQL values will 

4668 not be invoked** for those columns that are inserted as NULL; 

4669 the NULL value will be sent explicitly. Care must be taken 

4670 to ensure that no server-side default functions need to be 

4671 invoked for the operation as a whole. 

4672 

4673 .. seealso:: 

4674 

4675 :doc:`queryguide/dml` 

4676 

4677 :meth:`.Session.bulk_save_objects` 

4678 

4679 :meth:`.Session.bulk_update_mappings` 

4680 

4681 """ 

4682 self._bulk_save_mappings( 

4683 mapper, 

4684 mappings, 

4685 isupdate=False, 

4686 isstates=False, 

4687 return_defaults=return_defaults, 

4688 update_changed_only=False, 

4689 render_nulls=render_nulls, 

4690 ) 

4691 

4692 def bulk_update_mappings( 

4693 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4694 ) -> None: 

4695 """Perform a bulk update of the given list of mapping dictionaries. 

4696 

4697 .. legacy:: 

4698 

4699 This method is a legacy feature as of the 2.0 series of 

4700 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4701 the sections :ref:`orm_queryguide_bulk_insert` and 

4702 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4703 implementation details with this method and adds new features 

4704 as well. 

4705 

4706 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4707 object, 

4708 representing the single kind of object represented within the mapping 

4709 list. 

4710 

4711 :param mappings: a sequence of dictionaries, each one containing the 

4712 state of the mapped row to be updated, in terms of the attribute names 

4713 on the mapped class. If the mapping refers to multiple tables, such 

4714 as a joined-inheritance mapping, each dictionary may contain keys 

4715 corresponding to all tables. All those keys which are present and 

4716 are not part of the primary key are applied to the SET clause of the 

4717 UPDATE statement; the primary key values, which are required, are 

4718 applied to the WHERE clause. 

4719 

4720 

4721 .. seealso:: 

4722 

4723 :doc:`queryguide/dml` 

4724 

4725 :meth:`.Session.bulk_insert_mappings` 

4726 

4727 :meth:`.Session.bulk_save_objects` 

4728 

4729 """ 

4730 self._bulk_save_mappings( 

4731 mapper, 

4732 mappings, 

4733 isupdate=True, 

4734 isstates=False, 

4735 return_defaults=False, 

4736 update_changed_only=False, 

4737 render_nulls=False, 

4738 ) 

4739 

4740 def _bulk_save_mappings( 

4741 self, 

4742 mapper: Mapper[_O], 

4743 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4744 *, 

4745 isupdate: bool, 

4746 isstates: bool, 

4747 return_defaults: bool, 

4748 update_changed_only: bool, 

4749 render_nulls: bool, 

4750 ) -> None: 

4751 mapper = _class_to_mapper(mapper) 

4752 self._flushing = True 

4753 

4754 transaction = self._autobegin_t()._begin() 

4755 try: 

4756 if isupdate: 

4757 bulk_persistence._bulk_update( 

4758 mapper, 

4759 mappings, 

4760 transaction, 

4761 isstates=isstates, 

4762 update_changed_only=update_changed_only, 

4763 ) 

4764 else: 

4765 bulk_persistence._bulk_insert( 

4766 mapper, 

4767 mappings, 

4768 transaction, 

4769 isstates=isstates, 

4770 return_defaults=return_defaults, 

4771 render_nulls=render_nulls, 

4772 ) 

4773 transaction.commit() 

4774 

4775 except: 

4776 with util.safe_reraise(): 

4777 transaction.rollback(_capture_exception=True) 

4778 finally: 

4779 self._flushing = False 

4780 

4781 def is_modified( 

4782 self, instance: object, include_collections: bool = True 

4783 ) -> bool: 

4784 r"""Return ``True`` if the given instance has locally 

4785 modified attributes. 

4786 

4787 This method retrieves the history for each instrumented 

4788 attribute on the instance and performs a comparison of the current 

4789 value to its previously flushed or committed value, if any. 

4790 

4791 It is in effect a more expensive and accurate 

4792 version of checking for the given instance in the 

4793 :attr:`.Session.dirty` collection; a full test for 

4794 each attribute's net "dirty" status is performed. 

4795 

4796 E.g.:: 

4797 

4798 return session.is_modified(someobject) 

4799 

4800 A few caveats to this method apply: 

4801 

4802 * Instances present in the :attr:`.Session.dirty` collection may 

4803 report ``False`` when tested with this method. This is because 

4804 the object may have received change events via attribute mutation, 

4805 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4806 is the same as that loaded from the database, resulting in no net 

4807 change here. 

4808 * Scalar attributes may not have recorded the previously set 

4809 value when a new value was applied, if the attribute was not loaded, 

4810 or was expired, at the time the new value was received - in these 

4811 cases, the attribute is assumed to have a change, even if there is 

4812 ultimately no net change against its database value. SQLAlchemy in 

4813 most cases does not need the "old" value when a set event occurs, so 

4814 it skips the expense of a SQL call if the old value isn't present, 

4815 based on the assumption that an UPDATE of the scalar value is 

4816 usually needed, and in those few cases where it isn't, is less 

4817 expensive on average than issuing a defensive SELECT. 

4818 

4819 The "old" value is fetched unconditionally upon set only if the 

4820 attribute container has the ``active_history`` flag set to ``True``. 

4821 This flag is set typically for primary key attributes and scalar 

4822 object references that are not a simple many-to-one. To set this 

4823 flag for any arbitrary mapped column, use the ``active_history`` 

4824 argument with :func:`.column_property`. 

4825 

4826 :param instance: mapped instance to be tested for pending changes. 

4827 :param include_collections: Indicates if multivalued collections 

4828 should be included in the operation. Setting this to ``False`` is a 

4829 way to detect only local-column based properties (i.e. scalar columns 

4830 or many-to-one foreign keys) that would result in an UPDATE for this 

4831 instance upon flush. 

4832 

4833 """ 

4834 state = object_state(instance) 

4835 

4836 if not state.modified: 

4837 return False 

4838 

4839 dict_ = state.dict 

4840 

4841 for attr in state.manager.attributes: 

4842 if ( 

4843 not include_collections 

4844 and hasattr(attr.impl, "get_collection") 

4845 ) or not hasattr(attr.impl, "get_history"): 

4846 continue 

4847 

4848 (added, unchanged, deleted) = attr.impl.get_history( 

4849 state, dict_, passive=PassiveFlag.NO_CHANGE 

4850 ) 

4851 

4852 if added or deleted: 

4853 return True 

4854 else: 

4855 return False 

4856 

4857 @property 

4858 def is_active(self) -> bool: 

4859 """True if this :class:`.Session` not in "partial rollback" state. 

4860 

4861 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4862 a new transaction immediately, so this attribute will be False 

4863 when the :class:`_orm.Session` is first instantiated. 

4864 

4865 "partial rollback" state typically indicates that the flush process 

4866 of the :class:`_orm.Session` has failed, and that the 

4867 :meth:`_orm.Session.rollback` method must be emitted in order to 

4868 fully roll back the transaction. 

4869 

4870 If this :class:`_orm.Session` is not in a transaction at all, the 

4871 :class:`_orm.Session` will autobegin when it is first used, so in this 

4872 case :attr:`_orm.Session.is_active` will return True. 

4873 

4874 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4875 and that transaction has not been rolled back internally, the 

4876 :attr:`_orm.Session.is_active` will also return True. 

4877 

4878 .. seealso:: 

4879 

4880 :ref:`faq_session_rollback` 

4881 

4882 :meth:`_orm.Session.in_transaction` 

4883 

4884 """ 

4885 return self._transaction is None or self._transaction.is_active 

4886 

4887 @property 

4888 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4889 """The set of all persistent states considered dirty. 

4890 

4891 This method returns all states that were modified including 

4892 those that were possibly deleted. 

4893 

4894 """ 

4895 return self.identity_map._dirty_states() 

4896 

4897 @property 

4898 def dirty(self) -> IdentitySet: 

4899 """The set of all persistent instances considered dirty. 

4900 

4901 E.g.:: 

4902 

4903 some_mapped_object in session.dirty 

4904 

4905 Instances are considered dirty when they were modified but not 

4906 deleted. 

4907 

4908 Note that this 'dirty' calculation is 'optimistic'; most 

4909 attribute-setting or collection modification operations will 

4910 mark an instance as 'dirty' and place it in this set, even if 

4911 there is no net change to the attribute's value. At flush 

4912 time, the value of each attribute is compared to its 

4913 previously saved value, and if there's no net change, no SQL 

4914 operation will occur (this is a more expensive operation so 

4915 it's only done at flush time). 

4916 

4917 To check if an instance has actionable net changes to its 

4918 attributes, use the :meth:`.Session.is_modified` method. 

4919 

4920 """ 

4921 return IdentitySet( 

4922 [ 

4923 state.obj() 

4924 for state in self._dirty_states 

4925 if state not in self._deleted 

4926 ] 

4927 ) 

4928 

4929 @property 

4930 def deleted(self) -> IdentitySet: 

4931 "The set of all instances marked as 'deleted' within this ``Session``" 

4932 

4933 return util.IdentitySet(list(self._deleted.values())) 

4934 

4935 @property 

4936 def new(self) -> IdentitySet: 

4937 "The set of all instances marked as 'new' within this ``Session``." 

4938 

4939 return util.IdentitySet(list(self._new.values())) 

4940 

4941 

4942_S = TypeVar("_S", bound="Session") 

4943 

4944 

4945class sessionmaker(_SessionClassMethods, Generic[_S]): 

4946 """A configurable :class:`.Session` factory. 

4947 

4948 The :class:`.sessionmaker` factory generates new 

4949 :class:`.Session` objects when called, creating them given 

4950 the configurational arguments established here. 

4951 

4952 e.g.:: 

4953 

4954 from sqlalchemy import create_engine 

4955 from sqlalchemy.orm import sessionmaker 

4956 

4957 # an Engine, which the Session will use for connection 

4958 # resources 

4959 engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/') 

4960 

4961 Session = sessionmaker(engine) 

4962 

4963 with Session() as session: 

4964 session.add(some_object) 

4965 session.add(some_other_object) 

4966 session.commit() 

4967 

4968 Context manager use is optional; otherwise, the returned 

4969 :class:`_orm.Session` object may be closed explicitly via the 

4970 :meth:`_orm.Session.close` method. Using a 

4971 ``try:/finally:`` block is optional, however will ensure that the close 

4972 takes place even if there are database errors:: 

4973 

4974 session = Session() 

4975 try: 

4976 session.add(some_object) 

4977 session.add(some_other_object) 

4978 session.commit() 

4979 finally: 

4980 session.close() 

4981 

4982 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

4983 objects in the same way as an :class:`_engine.Engine` acts as a factory 

4984 for :class:`_engine.Connection` objects. In this way it also includes 

4985 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

4986 manager which both begins and commits a transaction, as well as closes 

4987 out the :class:`_orm.Session` when complete, rolling back the transaction 

4988 if any errors occur:: 

4989 

4990 Session = sessionmaker(engine) 

4991 

4992 with Session.begin() as session: 

4993 session.add(some_object) 

4994 session.add(some_other_object) 

4995 # commits transaction, closes session 

4996 

4997 .. versionadded:: 1.4 

4998 

4999 When calling upon :class:`_orm.sessionmaker` to construct a 

5000 :class:`_orm.Session`, keyword arguments may also be passed to the 

5001 method; these arguments will override that of the globally configured 

5002 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5003 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5004 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5005 

5006 Session = sessionmaker(engine) 

5007 

5008 # bind an individual session to a connection 

5009 

5010 with engine.connect() as connection: 

5011 with Session(bind=connection) as session: 

5012 # work with session 

5013 

5014 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5015 can be used to specify additional keyword arguments to the factory, which 

5016 will take effect for subsequent :class:`.Session` objects generated. This 

5017 is usually used to associate one or more :class:`_engine.Engine` objects 

5018 with an existing 

5019 :class:`.sessionmaker` factory before it is first used:: 

5020 

5021 # application starts, sessionmaker does not have 

5022 # an engine bound yet 

5023 Session = sessionmaker() 

5024 

5025 # ... later, when an engine URL is read from a configuration 

5026 # file or other events allow the engine to be created 

5027 engine = create_engine('sqlite:///foo.db') 

5028 Session.configure(bind=engine) 

5029 

5030 sess = Session() 

5031 # work with session 

5032 

5033 .. seealso:: 

5034 

5035 :ref:`session_getting` - introductory text on creating 

5036 sessions using :class:`.sessionmaker`. 

5037 

5038 """ 

5039 

5040 class_: Type[_S] 

5041 

5042 @overload 

5043 def __init__( 

5044 self, 

5045 bind: Optional[_SessionBind] = ..., 

5046 *, 

5047 class_: Type[_S], 

5048 autoflush: bool = ..., 

5049 expire_on_commit: bool = ..., 

5050 info: Optional[_InfoType] = ..., 

5051 **kw: Any, 

5052 ): ... 

5053 

5054 @overload 

5055 def __init__( 

5056 self: "sessionmaker[Session]", 

5057 bind: Optional[_SessionBind] = ..., 

5058 *, 

5059 autoflush: bool = ..., 

5060 expire_on_commit: bool = ..., 

5061 info: Optional[_InfoType] = ..., 

5062 **kw: Any, 

5063 ): ... 

5064 

5065 def __init__( 

5066 self, 

5067 bind: Optional[_SessionBind] = None, 

5068 *, 

5069 class_: Type[_S] = Session, # type: ignore 

5070 autoflush: bool = True, 

5071 expire_on_commit: bool = True, 

5072 info: Optional[_InfoType] = None, 

5073 **kw: Any, 

5074 ): 

5075 r"""Construct a new :class:`.sessionmaker`. 

5076 

5077 All arguments here except for ``class_`` correspond to arguments 

5078 accepted by :class:`.Session` directly. See the 

5079 :meth:`.Session.__init__` docstring for more details on parameters. 

5080 

5081 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5082 with 

5083 which newly created :class:`.Session` objects will be associated. 

5084 :param class\_: class to use in order to create new :class:`.Session` 

5085 objects. Defaults to :class:`.Session`. 

5086 :param autoflush: The autoflush setting to use with newly created 

5087 :class:`.Session` objects. 

5088 

5089 .. seealso:: 

5090 

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

5092 

5093 :param expire_on_commit=True: the 

5094 :paramref:`_orm.Session.expire_on_commit` setting to use 

5095 with newly created :class:`.Session` objects. 

5096 

5097 :param info: optional dictionary of information that will be available 

5098 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5099 replaced, when the ``info`` parameter is specified to the specific 

5100 :class:`.Session` construction operation. 

5101 

5102 :param \**kw: all other keyword arguments are passed to the 

5103 constructor of newly created :class:`.Session` objects. 

5104 

5105 """ 

5106 kw["bind"] = bind 

5107 kw["autoflush"] = autoflush 

5108 kw["expire_on_commit"] = expire_on_commit 

5109 if info is not None: 

5110 kw["info"] = info 

5111 self.kw = kw 

5112 # make our own subclass of the given class, so that 

5113 # events can be associated with it specifically. 

5114 self.class_ = type(class_.__name__, (class_,), {}) 

5115 

5116 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5117 """Produce a context manager that both provides a new 

5118 :class:`_orm.Session` as well as a transaction that commits. 

5119 

5120 

5121 e.g.:: 

5122 

5123 Session = sessionmaker(some_engine) 

5124 

5125 with Session.begin() as session: 

5126 session.add(some_object) 

5127 

5128 # commits transaction, closes session 

5129 

5130 .. versionadded:: 1.4 

5131 

5132 

5133 """ 

5134 

5135 session = self() 

5136 return session._maker_context_manager() 

5137 

5138 def __call__(self, **local_kw: Any) -> _S: 

5139 """Produce a new :class:`.Session` object using the configuration 

5140 established in this :class:`.sessionmaker`. 

5141 

5142 In Python, the ``__call__`` method is invoked on an object when 

5143 it is "called" in the same way as a function:: 

5144 

5145 Session = sessionmaker(some_engine) 

5146 session = Session() # invokes sessionmaker.__call__() 

5147 

5148 """ 

5149 for k, v in self.kw.items(): 

5150 if k == "info" and "info" in local_kw: 

5151 d = v.copy() 

5152 d.update(local_kw["info"]) 

5153 local_kw["info"] = d 

5154 else: 

5155 local_kw.setdefault(k, v) 

5156 return self.class_(**local_kw) 

5157 

5158 def configure(self, **new_kw: Any) -> None: 

5159 """(Re)configure the arguments for this sessionmaker. 

5160 

5161 e.g.:: 

5162 

5163 Session = sessionmaker() 

5164 

5165 Session.configure(bind=create_engine('sqlite://')) 

5166 """ 

5167 self.kw.update(new_kw) 

5168 

5169 def __repr__(self) -> str: 

5170 return "%s(class_=%r, %s)" % ( 

5171 self.__class__.__name__, 

5172 self.class_.__name__, 

5173 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5174 ) 

5175 

5176 

5177def close_all_sessions() -> None: 

5178 """Close all sessions in memory. 

5179 

5180 This function consults a global registry of all :class:`.Session` objects 

5181 and calls :meth:`.Session.close` on them, which resets them to a clean 

5182 state. 

5183 

5184 This function is not for general use but may be useful for test suites 

5185 within the teardown scheme. 

5186 

5187 .. versionadded:: 1.3 

5188 

5189 """ 

5190 

5191 for sess in _sessions.values(): 

5192 sess.close() 

5193 

5194 

5195def make_transient(instance: object) -> None: 

5196 """Alter the state of the given instance so that it is :term:`transient`. 

5197 

5198 .. note:: 

5199 

5200 :func:`.make_transient` is a special-case function for 

5201 advanced use cases only. 

5202 

5203 The given mapped instance is assumed to be in the :term:`persistent` or 

5204 :term:`detached` state. The function will remove its association with any 

5205 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5206 effect is that the object will behave as though it were newly constructed, 

5207 except retaining any attribute / collection values that were loaded at the 

5208 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5209 if this object had been deleted as a result of using 

5210 :meth:`.Session.delete`. 

5211 

5212 .. warning:: 

5213 

5214 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5215 load ORM-mapped attributes that are not currently loaded at the time 

5216 the function is called. This includes attributes which: 

5217 

5218 * were expired via :meth:`.Session.expire` 

5219 

5220 * were expired as the natural effect of committing a session 

5221 transaction, e.g. :meth:`.Session.commit` 

5222 

5223 * are normally :term:`lazy loaded` but are not currently loaded 

5224 

5225 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5226 not yet loaded 

5227 

5228 * were not present in the query which loaded this object, such as that 

5229 which is common in joined table inheritance and other scenarios. 

5230 

5231 After :func:`.make_transient` is called, unloaded attributes such 

5232 as those above will normally resolve to the value ``None`` when 

5233 accessed, or an empty collection for a collection-oriented attribute. 

5234 As the object is transient and un-associated with any database 

5235 identity, it will no longer retrieve these values. 

5236 

5237 .. seealso:: 

5238 

5239 :func:`.make_transient_to_detached` 

5240 

5241 """ 

5242 state = attributes.instance_state(instance) 

5243 s = _state_session(state) 

5244 if s: 

5245 s._expunge_states([state]) 

5246 

5247 # remove expired state 

5248 state.expired_attributes.clear() 

5249 

5250 # remove deferred callables 

5251 if state.callables: 

5252 del state.callables 

5253 

5254 if state.key: 

5255 del state.key 

5256 if state._deleted: 

5257 del state._deleted 

5258 

5259 

5260def make_transient_to_detached(instance: object) -> None: 

5261 """Make the given transient instance :term:`detached`. 

5262 

5263 .. note:: 

5264 

5265 :func:`.make_transient_to_detached` is a special-case function for 

5266 advanced use cases only. 

5267 

5268 All attribute history on the given instance 

5269 will be reset as though the instance were freshly loaded 

5270 from a query. Missing attributes will be marked as expired. 

5271 The primary key attributes of the object, which are required, will be made 

5272 into the "key" of the instance. 

5273 

5274 The object can then be added to a session, or merged 

5275 possibly with the load=False flag, at which point it will look 

5276 as if it were loaded that way, without emitting SQL. 

5277 

5278 This is a special use case function that differs from a normal 

5279 call to :meth:`.Session.merge` in that a given persistent state 

5280 can be manufactured without any SQL calls. 

5281 

5282 .. seealso:: 

5283 

5284 :func:`.make_transient` 

5285 

5286 :meth:`.Session.enable_relationship_loading` 

5287 

5288 """ 

5289 state = attributes.instance_state(instance) 

5290 if state.session_id or state.key: 

5291 raise sa_exc.InvalidRequestError("Given object must be transient") 

5292 state.key = state.mapper._identity_key_from_state(state) 

5293 if state._deleted: 

5294 del state._deleted 

5295 state._commit_all(state.dict) 

5296 state._expire_attributes(state.dict, state.unloaded) 

5297 

5298 

5299def object_session(instance: object) -> Optional[Session]: 

5300 """Return the :class:`.Session` to which the given instance belongs. 

5301 

5302 This is essentially the same as the :attr:`.InstanceState.session` 

5303 accessor. See that attribute for details. 

5304 

5305 """ 

5306 

5307 try: 

5308 state = attributes.instance_state(instance) 

5309 except exc.NO_STATE as err: 

5310 raise exc.UnmappedInstanceError(instance) from err 

5311 else: 

5312 return _state_session(state) 

5313 

5314 

5315_new_sessionid = util.counter()