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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1467 statements  

1# orm/session.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

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

9 

10from __future__ import annotations 

11 

12import contextlib 

13from enum import Enum 

14import itertools 

15import sys 

16import typing 

17from typing import Any 

18from typing import Callable 

19from typing import cast 

20from typing import Dict 

21from typing import Generic 

22from typing import Iterable 

23from typing import Iterator 

24from typing import List 

25from typing import Literal 

26from typing import NoReturn 

27from typing import Optional 

28from typing import overload 

29from typing import Protocol 

30from typing import Sequence 

31from typing import Set 

32from typing import Tuple 

33from typing import Type 

34from typing import TYPE_CHECKING 

35from typing import TypeVar 

36from typing import Union 

37import weakref 

38 

39from . import attributes 

40from . import bulk_persistence 

41from . import context 

42from . import descriptor_props 

43from . import exc 

44from . import identity 

45from . import loading 

46from . import query 

47from . import state as statelib 

48from ._typing import _O 

49from ._typing import insp_is_mapper 

50from ._typing import is_composite_class 

51from ._typing import is_orm_option 

52from ._typing import is_user_defined_option 

53from .base import _class_to_mapper 

54from .base import _none_set 

55from .base import _state_mapper 

56from .base import instance_str 

57from .base import LoaderCallableStatus 

58from .base import object_mapper 

59from .base import object_state 

60from .base import PassiveFlag 

61from .base import state_str 

62from .context import _ORMCompileState 

63from .context import FromStatement 

64from .identity import IdentityMap 

65from .query import Query 

66from .state import InstanceState 

67from .state_changes import _StateChange 

68from .state_changes import _StateChangeState 

69from .state_changes import _StateChangeStates 

70from .unitofwork import UOWTransaction 

71from .. import engine 

72from .. import exc as sa_exc 

73from .. import sql 

74from .. import util 

75from ..engine import Connection 

76from ..engine import Engine 

77from ..engine.util import TransactionalContext 

78from ..event import dispatcher 

79from ..event import EventTarget 

80from ..inspection import inspect 

81from ..inspection import Inspectable 

82from ..sql import coercions 

83from ..sql import dml 

84from ..sql import roles 

85from ..sql import Select 

86from ..sql import TableClause 

87from ..sql import visitors 

88from ..sql.base import _NoArg 

89from ..sql.base import CompileState 

90from ..sql.schema import Table 

91from ..sql.selectable import ForUpdateArg 

92from ..util import deprecated_params 

93from ..util import IdentitySet 

94from ..util.typing import Never 

95from ..util.typing import TupleAny 

96from ..util.typing import TypeVarTuple 

97from ..util.typing import Unpack 

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 Result 

110 from ..engine import Row 

111 from ..engine import RowMapping 

112 from ..engine.base import Transaction 

113 from ..engine.base import TwoPhaseTransaction 

114 from ..engine.interfaces import _CoreAnyExecuteParams 

115 from ..engine.interfaces import _CoreSingleExecuteParams 

116 from ..engine.interfaces import _ExecuteOptions 

117 from ..engine.interfaces import CoreExecuteOptionsParameter 

118 from ..engine.result import ScalarResult 

119 from ..event import _InstanceLevelDispatch 

120 from ..sql._typing import _ColumnsClauseArgument 

121 from ..sql._typing import _InfoType 

122 from ..sql._typing import _T0 

123 from ..sql._typing import _T1 

124 from ..sql._typing import _T2 

125 from ..sql._typing import _T3 

126 from ..sql._typing import _T4 

127 from ..sql._typing import _T5 

128 from ..sql._typing import _T6 

129 from ..sql._typing import _T7 

130 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

131 from ..sql.base import Executable 

132 from ..sql.base import ExecutableOption 

133 from ..sql.elements import ClauseElement 

134 from ..sql.roles import TypedColumnsClauseRole 

135 from ..sql.selectable import ForUpdateParameter 

136 from ..sql.selectable import TypedReturnsRows 

137 

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

139_Ts = TypeVarTuple("_Ts") 

140 

141__all__ = [ 

142 "Session", 

143 "SessionTransaction", 

144 "sessionmaker", 

145 "ORMExecuteState", 

146 "close_all_sessions", 

147 "make_transient", 

148 "make_transient_to_detached", 

149 "object_session", 

150] 

151 

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

153 weakref.WeakValueDictionary() 

154) 

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

156""" 

157 

158statelib._sessions = _sessions 

159 

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

161 

162_BindArguments = Dict[str, Any] 

163 

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

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

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

167 

168JoinTransactionMode = Literal[ 

169 "conditional_savepoint", 

170 "rollback_only", 

171 "control_fully", 

172 "create_savepoint", 

173] 

174 

175 

176class _ConnectionCallableProto(Protocol): 

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

178 

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

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

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

182 as persistence time. 

183 

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

185 is established when using the horizontal sharding extension. 

186 

187 """ 

188 

189 def __call__( 

190 self, 

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

192 instance: Optional[object] = None, 

193 **kw: Any, 

194 ) -> Connection: ... 

195 

196 

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

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

199 associated, if any. 

200 """ 

201 return state.session 

202 

203 

204class _SessionClassMethods: 

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

206 

207 @classmethod 

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

209 def identity_key( 

210 cls, 

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

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

213 *, 

214 instance: Optional[Any] = None, 

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

216 identity_token: Optional[Any] = None, 

217 ) -> _IdentityKeyType[Any]: 

218 """Return an identity key. 

219 

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

221 

222 """ 

223 return util.preloaded.orm_util.identity_key( 

224 class_, 

225 ident, 

226 instance=instance, 

227 row=row, 

228 identity_token=identity_token, 

229 ) 

230 

231 @classmethod 

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

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

234 

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

236 

237 """ 

238 

239 return object_session(instance) 

240 

241 

242class SessionTransactionState(_StateChangeState): 

243 ACTIVE = 1 

244 PREPARED = 2 

245 COMMITTED = 3 

246 DEACTIVE = 4 

247 CLOSED = 5 

248 PROVISIONING_CONNECTION = 6 

249 

250 

251# backwards compatibility 

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

253 SessionTransactionState 

254) 

255 

256 

257class ORMExecuteState(util.MemoizedSlots): 

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

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

260 

261 .. versionadded:: 1.4 

262 

263 .. seealso:: 

264 

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

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

267 

268 """ 

269 

270 __slots__ = ( 

271 "session", 

272 "statement", 

273 "parameters", 

274 "execution_options", 

275 "local_execution_options", 

276 "bind_arguments", 

277 "identity_token", 

278 "_compile_state_cls", 

279 "_starting_event_idx", 

280 "_events_todo", 

281 "_update_execution_options", 

282 ) 

283 

284 session: Session 

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

286 

287 statement: Executable 

288 """The SQL statement being invoked. 

289 

290 For an ORM selection as would 

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

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

293 """ 

294 

295 parameters: Optional[_CoreAnyExecuteParams] 

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

297 :meth:`_orm.Session.execute`. 

298 

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

300 effective parameters passed to the method. 

301 

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

303 mutated or replaced. 

304 

305 """ 

306 

307 execution_options: _ExecuteOptions 

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

309 

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

311 locally passed execution options. 

312 

313 .. seealso:: 

314 

315 :attr:`_orm.ORMExecuteState.local_execution_options` 

316 

317 :meth:`_sql.Executable.execution_options` 

318 

319 :ref:`orm_queryguide_execution_options` 

320 

321 """ 

322 

323 local_execution_options: _ExecuteOptions 

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

325 :meth:`.Session.execute` method. 

326 

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

328 being invoked. 

329 

330 .. seealso:: 

331 

332 :attr:`_orm.ORMExecuteState.execution_options` 

333 

334 """ 

335 

336 bind_arguments: _BindArguments 

337 """The dictionary passed as the 

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

339 

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

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

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

343 

344 """ 

345 

346 _compile_state_cls: Optional[Type[_ORMCompileState]] 

347 _starting_event_idx: int 

348 _events_todo: List[Any] 

349 _update_execution_options: _ExecuteOptions 

350 

351 def __init__( 

352 self, 

353 session: Session, 

354 statement: Executable, 

355 parameters: Optional[_CoreAnyExecuteParams], 

356 execution_options: _ExecuteOptions, 

357 bind_arguments: _BindArguments, 

358 compile_state_cls: Optional[Type[_ORMCompileState]], 

359 events_todo: List[_InstanceLevelDispatch[Session]], 

360 ): 

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

362 

363 this object is constructed internally. 

364 

365 """ 

366 self.session = session 

367 self.statement = statement 

368 self.parameters = parameters 

369 self.local_execution_options = execution_options 

370 self.execution_options = statement._execution_options.union( 

371 execution_options 

372 ) 

373 self.bind_arguments = bind_arguments 

374 self._compile_state_cls = compile_state_cls 

375 self._events_todo = list(events_todo) 

376 self._update_execution_options = util.EMPTY_DICT 

377 

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

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

380 

381 def invoke_statement( 

382 self, 

383 statement: Optional[Executable] = None, 

384 params: Optional[_CoreAnyExecuteParams] = None, 

385 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

386 bind_arguments: Optional[_BindArguments] = None, 

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

388 """Execute the statement represented by this 

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

390 already proceeded. 

391 

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

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

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

395 that want to override how the ultimate 

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

397 retrieve results from an offline cache or which concatenate results 

398 from multiple executions. 

399 

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

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

402 is propagated to the calling 

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

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

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

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

407 

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

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

410 

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

412 which will be merged into the existing 

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

414 

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

416 for executemany executions. 

417 

418 :param execution_options: optional dictionary of execution options 

419 will be merged into the existing 

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

421 :class:`.ORMExecuteState`. 

422 

423 :param bind_arguments: optional dictionary of bind_arguments 

424 which will be merged amongst the current 

425 :attr:`.ORMExecuteState.bind_arguments` 

426 of this :class:`.ORMExecuteState`. 

427 

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

429 

430 .. seealso:: 

431 

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

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

434 

435 

436 """ 

437 

438 if statement is None: 

439 statement = self.statement 

440 

441 _bind_arguments = dict(self.bind_arguments) 

442 if bind_arguments: 

443 _bind_arguments.update(bind_arguments) 

444 _bind_arguments["_sa_skip_events"] = True 

445 

446 _params: Optional[_CoreAnyExecuteParams] 

447 if params: 

448 if self.is_executemany: 

449 _params = [] 

450 exec_many_parameters = cast( 

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

452 ) 

453 for _existing_params, _new_params in itertools.zip_longest( 

454 exec_many_parameters, 

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

456 ): 

457 if _existing_params is None or _new_params is None: 

458 raise sa_exc.InvalidRequestError( 

459 f"Can't apply executemany parameters to " 

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

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

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

463 f"to ORMExecuteState.invoke_statement() " 

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

465 ) 

466 _existing_params = dict(_existing_params) 

467 _existing_params.update(_new_params) 

468 _params.append(_existing_params) 

469 else: 

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

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

472 else: 

473 _params = self.parameters 

474 

475 _execution_options = self.local_execution_options 

476 if execution_options: 

477 _execution_options = _execution_options.union(execution_options) 

478 

479 return self.session._execute_internal( 

480 statement, 

481 _params, 

482 execution_options=_execution_options, 

483 bind_arguments=_bind_arguments, 

484 _parent_execute_state=self, 

485 ) 

486 

487 @property 

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

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

490 

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

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

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

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

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

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

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

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

499 would be selected. 

500 

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

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

503 way of getting this mapper. 

504 

505 .. versionadded:: 1.4.0b2 

506 

507 .. seealso:: 

508 

509 :attr:`_orm.ORMExecuteState.all_mappers` 

510 

511 

512 """ 

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

514 return mp 

515 

516 @property 

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

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

519 involved at the top level of this statement. 

520 

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

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

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

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

525 

526 .. versionadded:: 1.4.0b2 

527 

528 .. seealso:: 

529 

530 :attr:`_orm.ORMExecuteState.bind_mapper` 

531 

532 

533 

534 """ 

535 if not self.is_orm_statement: 

536 return [] 

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

538 result = [] 

539 seen = set() 

540 for d in self.statement.column_descriptions: 

541 ent = d["entity"] 

542 if ent: 

543 insp = inspect(ent, raiseerr=False) 

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

545 seen.add(insp.mapper) 

546 result.append(insp.mapper) 

547 return result 

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

549 return [self.bind_mapper] 

550 else: 

551 return [] 

552 

553 @property 

554 def is_orm_statement(self) -> bool: 

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

556 

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

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

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

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

561 and no ORM-level automation takes place. 

562 

563 """ 

564 return self._compile_state_cls is not None 

565 

566 @property 

567 def is_executemany(self) -> bool: 

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

569 dictionaries with more than one dictionary. 

570 

571 .. versionadded:: 2.0 

572 

573 """ 

574 return isinstance(self.parameters, list) 

575 

576 @property 

577 def is_select(self) -> bool: 

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

579 

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

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

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

583 ``select(Entity).from_statement(select(..))`` 

584 

585 """ 

586 return self.statement.is_select 

587 

588 @property 

589 def is_from_statement(self) -> bool: 

590 """return True if this operation is a 

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

592 

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

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

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

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

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

598 :class:`_sql.Select` construct. 

599 

600 .. versionadded:: 2.0.30 

601 

602 """ 

603 return self.statement.is_from_statement 

604 

605 @property 

606 def is_insert(self) -> bool: 

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

608 

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

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

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

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

613 

614 """ 

615 return self.statement.is_dml and self.statement.is_insert 

616 

617 @property 

618 def is_update(self) -> bool: 

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

620 

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

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

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

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

625 

626 """ 

627 return self.statement.is_dml and self.statement.is_update 

628 

629 @property 

630 def is_delete(self) -> bool: 

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

632 

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

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

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

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

637 

638 """ 

639 return self.statement.is_dml and self.statement.is_delete 

640 

641 @property 

642 def _is_crud(self) -> bool: 

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

644 

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

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

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

648 self._update_execution_options = self._update_execution_options.union( 

649 opts 

650 ) 

651 

652 def _orm_compile_options( 

653 self, 

654 ) -> Optional[ 

655 Union[ 

656 context._ORMCompileState.default_compile_options, 

657 Type[context._ORMCompileState.default_compile_options], 

658 ] 

659 ]: 

660 if not self.is_select: 

661 return None 

662 try: 

663 opts = self.statement._compile_options 

664 except AttributeError: 

665 return None 

666 

667 if opts is not None and opts.isinstance( 

668 context._ORMCompileState.default_compile_options 

669 ): 

670 return opts # type: ignore 

671 else: 

672 return None 

673 

674 @property 

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

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

677 for a lazy load operation. 

678 

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

680 sharding extension, where it is available within specific query 

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

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

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

684 compilation time. 

685 

686 """ 

687 return self.load_options._lazy_loaded_from 

688 

689 @property 

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

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

692 

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

694 when a particular object or collection is being loaded. 

695 

696 """ 

697 opts = self._orm_compile_options() 

698 if opts is not None: 

699 return opts._current_path 

700 else: 

701 return None 

702 

703 @property 

704 def is_column_load(self) -> bool: 

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

706 attributes on an existing ORM object. 

707 

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

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

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

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

712 loaded. 

713 

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

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

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

717 and loader options travelling with the instance 

718 will have already been added to the query. 

719 

720 .. versionadded:: 1.4.0b2 

721 

722 .. seealso:: 

723 

724 :attr:`_orm.ORMExecuteState.is_relationship_load` 

725 

726 """ 

727 opts = self._orm_compile_options() 

728 return opts is not None and opts._for_refresh_state 

729 

730 @property 

731 def is_relationship_load(self) -> bool: 

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

733 relationship. 

734 

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

736 SelectInLoader, SubqueryLoader, or similar, and the entire 

737 SELECT statement being emitted is on behalf of a relationship 

738 load. 

739 

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

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

742 capable of being propagated to relationship loaders and should 

743 be already present. 

744 

745 .. seealso:: 

746 

747 :attr:`_orm.ORMExecuteState.is_column_load` 

748 

749 """ 

750 opts = self._orm_compile_options() 

751 if opts is None: 

752 return False 

753 path = self.loader_strategy_path 

754 return path is not None and not path.is_root 

755 

756 @property 

757 def load_options( 

758 self, 

759 ) -> Union[ 

760 context.QueryContext.default_load_options, 

761 Type[context.QueryContext.default_load_options], 

762 ]: 

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

764 

765 if not self.is_select: 

766 raise sa_exc.InvalidRequestError( 

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

768 "so there are no load options." 

769 ) 

770 

771 lo: Union[ 

772 context.QueryContext.default_load_options, 

773 Type[context.QueryContext.default_load_options], 

774 ] = self.execution_options.get( 

775 "_sa_orm_load_options", context.QueryContext.default_load_options 

776 ) 

777 return lo 

778 

779 @property 

780 def update_delete_options( 

781 self, 

782 ) -> Union[ 

783 bulk_persistence._BulkUDCompileState.default_update_options, 

784 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

785 ]: 

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

787 execution.""" 

788 

789 if not self._is_crud: 

790 raise sa_exc.InvalidRequestError( 

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

792 "statement so there are no update options." 

793 ) 

794 uo: Union[ 

795 bulk_persistence._BulkUDCompileState.default_update_options, 

796 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

797 ] = self.execution_options.get( 

798 "_sa_orm_update_options", 

799 bulk_persistence._BulkUDCompileState.default_update_options, 

800 ) 

801 return uo 

802 

803 @property 

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

805 return [ 

806 opt 

807 for opt in self.statement._with_options 

808 if is_orm_option(opt) and not opt._is_compile_state 

809 ] 

810 

811 @property 

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

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

814 associated with the statement being invoked. 

815 

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

817 consideration any options added before calling 

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

819 :meth:`_orm.Query.with_entities`. 

820 

821 """ 

822 items = [ 

823 self.statement, 

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

825 ] 

826 return [ 

827 opt 

828 for item in items 

829 for opt in item._with_options 

830 if is_user_defined_option(opt) 

831 ] 

832 

833 

834class SessionTransactionOrigin(Enum): 

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

836 

837 This enumeration is present on the 

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

839 :class:`.SessionTransaction` object. 

840 

841 .. versionadded:: 2.0 

842 

843 """ 

844 

845 AUTOBEGIN = 0 

846 """transaction were started by autobegin""" 

847 

848 BEGIN = 1 

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

850 

851 BEGIN_NESTED = 2 

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

853 

854 SUBTRANSACTION = 3 

855 """transaction is an internal "subtransaction" """ 

856 

857 

858class SessionTransaction(_StateChange, TransactionalContext): 

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

860 

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

862 :meth:`_orm.Session.begin` 

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

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

865 transactions. 

866 

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

868 at: :ref:`unitofwork_transaction`. 

869 

870 

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

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

873 

874 .. seealso:: 

875 

876 :ref:`unitofwork_transaction` 

877 

878 :meth:`.Session.begin` 

879 

880 :meth:`.Session.begin_nested` 

881 

882 :meth:`.Session.rollback` 

883 

884 :meth:`.Session.commit` 

885 

886 :meth:`.Session.in_transaction` 

887 

888 :meth:`.Session.in_nested_transaction` 

889 

890 :meth:`.Session.get_transaction` 

891 

892 :meth:`.Session.get_nested_transaction` 

893 

894 

895 """ 

896 

897 _rollback_exception: Optional[BaseException] = None 

898 

899 _connections: Dict[ 

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

901 ] 

902 session: Session 

903 _parent: Optional[SessionTransaction] 

904 

905 _state: SessionTransactionState 

906 

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

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

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

910 _key_switches: weakref.WeakKeyDictionary[ 

911 InstanceState[Any], Tuple[Any, Any] 

912 ] 

913 

914 origin: SessionTransactionOrigin 

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

916 

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

918 enumeration indicating the source event that led to constructing 

919 this :class:`_orm.SessionTransaction`. 

920 

921 .. versionadded:: 2.0 

922 

923 """ 

924 

925 nested: bool = False 

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

927 

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

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

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

931 

932 .. seealso:: 

933 

934 :attr:`.SessionTransaction.origin` 

935 

936 """ 

937 

938 def __init__( 

939 self, 

940 session: Session, 

941 origin: SessionTransactionOrigin, 

942 parent: Optional[SessionTransaction] = None, 

943 ): 

944 TransactionalContext._trans_ctx_check(session) 

945 

946 self.session = session 

947 self._connections = {} 

948 self._parent = parent 

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

950 self.origin = origin 

951 

952 if session._close_state is _SessionCloseState.CLOSED: 

953 raise sa_exc.InvalidRequestError( 

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

955 "to handle any more transaction requests." 

956 ) 

957 

958 if nested: 

959 if not parent: 

960 raise sa_exc.InvalidRequestError( 

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

962 "transaction is in progress" 

963 ) 

964 

965 self._previous_nested_transaction = session._nested_transaction 

966 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

967 assert parent is not None 

968 else: 

969 assert parent is None 

970 

971 self._state = SessionTransactionState.ACTIVE 

972 

973 self._take_snapshot() 

974 

975 # make sure transaction is assigned before we call the 

976 # dispatch 

977 self.session._transaction = self 

978 

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

980 

981 def _raise_for_prerequisite_state( 

982 self, operation_name: str, state: _StateChangeState 

983 ) -> NoReturn: 

984 if state is SessionTransactionState.DEACTIVE: 

985 if self._rollback_exception: 

986 raise sa_exc.PendingRollbackError( 

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

988 "due to a previous exception during flush." 

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

990 "first issue Session.rollback()." 

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

992 code="7s2a", 

993 ) 

994 else: 

995 raise sa_exc.InvalidRequestError( 

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

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

998 "can be emitted within this transaction." 

999 ) 

1000 elif state is SessionTransactionState.CLOSED: 

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

1002 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

1003 raise sa_exc.InvalidRequestError( 

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

1005 "operations are not permitted", 

1006 code="isce", 

1007 ) 

1008 else: 

1009 raise sa_exc.InvalidRequestError( 

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

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

1012 ) 

1013 

1014 @property 

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

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

1017 :class:`.SessionTransaction`. 

1018 

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

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

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

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

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

1024 "nested" / SAVEPOINT transaction. If the 

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

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

1027 

1028 """ 

1029 return self._parent 

1030 

1031 @property 

1032 def is_active(self) -> bool: 

1033 return ( 

1034 self.session is not None 

1035 and self._state is SessionTransactionState.ACTIVE 

1036 ) 

1037 

1038 @property 

1039 def _is_transaction_boundary(self) -> bool: 

1040 return self.nested or not self._parent 

1041 

1042 @_StateChange.declare_states( 

1043 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1044 ) 

1045 def connection( 

1046 self, 

1047 bindkey: Optional[Mapper[Any]], 

1048 execution_options: Optional[_ExecuteOptions] = None, 

1049 **kwargs: Any, 

1050 ) -> Connection: 

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

1052 return self._connection_for_bind(bind, execution_options) 

1053 

1054 @_StateChange.declare_states( 

1055 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1056 ) 

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

1058 return SessionTransaction( 

1059 self.session, 

1060 ( 

1061 SessionTransactionOrigin.BEGIN_NESTED 

1062 if nested 

1063 else SessionTransactionOrigin.SUBTRANSACTION 

1064 ), 

1065 self, 

1066 ) 

1067 

1068 def _iterate_self_and_parents( 

1069 self, upto: Optional[SessionTransaction] = None 

1070 ) -> Iterable[SessionTransaction]: 

1071 current = self 

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

1073 while current: 

1074 result += (current,) 

1075 if current._parent is upto: 

1076 break 

1077 elif current._parent is None: 

1078 raise sa_exc.InvalidRequestError( 

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

1080 % (upto) 

1081 ) 

1082 else: 

1083 current = current._parent 

1084 

1085 return result 

1086 

1087 def _take_snapshot(self) -> None: 

1088 if not self._is_transaction_boundary: 

1089 parent = self._parent 

1090 assert parent is not None 

1091 self._new = parent._new 

1092 self._deleted = parent._deleted 

1093 self._dirty = parent._dirty 

1094 self._key_switches = parent._key_switches 

1095 return 

1096 

1097 is_begin = self.origin in ( 

1098 SessionTransactionOrigin.BEGIN, 

1099 SessionTransactionOrigin.AUTOBEGIN, 

1100 ) 

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

1102 self.session.flush() 

1103 

1104 self._new = weakref.WeakKeyDictionary() 

1105 self._deleted = weakref.WeakKeyDictionary() 

1106 self._dirty = weakref.WeakKeyDictionary() 

1107 self._key_switches = weakref.WeakKeyDictionary() 

1108 

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

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

1111 

1112 Corresponds to a rollback. 

1113 

1114 """ 

1115 assert self._is_transaction_boundary 

1116 

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

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

1119 

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

1121 # we probably can do this conditionally based on 

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

1123 self.session.identity_map.safe_discard(s) 

1124 

1125 # restore the old key 

1126 s.key = oldkey 

1127 

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

1129 if s not in to_expunge: 

1130 self.session.identity_map.replace(s) 

1131 

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

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

1134 

1135 assert not self.session._deleted 

1136 

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

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

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

1140 

1141 def _remove_snapshot(self) -> None: 

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

1143 

1144 Corresponds to a commit. 

1145 

1146 """ 

1147 assert self._is_transaction_boundary 

1148 

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

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

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

1152 

1153 statelib.InstanceState._detach_states( 

1154 list(self._deleted), self.session 

1155 ) 

1156 self._deleted.clear() 

1157 elif self.nested: 

1158 parent = self._parent 

1159 assert parent is not None 

1160 parent._new.update(self._new) 

1161 parent._dirty.update(self._dirty) 

1162 parent._deleted.update(self._deleted) 

1163 parent._key_switches.update(self._key_switches) 

1164 

1165 @_StateChange.declare_states( 

1166 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1167 ) 

1168 def _connection_for_bind( 

1169 self, 

1170 bind: _SessionBind, 

1171 execution_options: Optional[CoreExecuteOptionsParameter], 

1172 ) -> Connection: 

1173 if bind in self._connections: 

1174 if execution_options: 

1175 util.warn( 

1176 "Connection is already established for the " 

1177 "given bind; execution_options ignored" 

1178 ) 

1179 return self._connections[bind][0] 

1180 

1181 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1182 

1183 local_connect = False 

1184 should_commit = True 

1185 

1186 try: 

1187 if self._parent: 

1188 conn = self._parent._connection_for_bind( 

1189 bind, execution_options 

1190 ) 

1191 if not self.nested: 

1192 return conn 

1193 else: 

1194 if isinstance(bind, engine.Connection): 

1195 conn = bind 

1196 if conn.engine in self._connections: 

1197 raise sa_exc.InvalidRequestError( 

1198 "Session already has a Connection associated " 

1199 "for the given Connection's Engine" 

1200 ) 

1201 else: 

1202 conn = bind.connect() 

1203 local_connect = True 

1204 

1205 try: 

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

1207 if self.session.execution_options: 

1208 conn_exec_opts.update(self.session.execution_options) 

1209 if execution_options: 

1210 conn_exec_opts.update(execution_options) 

1211 if conn_exec_opts: 

1212 conn = conn.execution_options(**conn_exec_opts) 

1213 

1214 transaction: Transaction 

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

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

1217 # conn.in_transaction() ? 

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

1219 # that it is in fact twophase. 

1220 transaction = conn.begin_twophase() 

1221 elif self.nested: 

1222 transaction = conn.begin_nested() 

1223 elif conn.in_transaction(): 

1224 

1225 if local_connect: 

1226 _trans = conn.get_transaction() 

1227 assert _trans is not None 

1228 transaction = _trans 

1229 else: 

1230 join_transaction_mode = ( 

1231 self.session.join_transaction_mode 

1232 ) 

1233 

1234 if join_transaction_mode == "conditional_savepoint": 

1235 if conn.in_nested_transaction(): 

1236 join_transaction_mode = "create_savepoint" 

1237 else: 

1238 join_transaction_mode = "rollback_only" 

1239 

1240 if join_transaction_mode in ( 

1241 "control_fully", 

1242 "rollback_only", 

1243 ): 

1244 if conn.in_nested_transaction(): 

1245 transaction = ( 

1246 conn._get_required_nested_transaction() 

1247 ) 

1248 else: 

1249 transaction = conn._get_required_transaction() 

1250 if join_transaction_mode == "rollback_only": 

1251 should_commit = False 

1252 elif join_transaction_mode == "create_savepoint": 

1253 transaction = conn.begin_nested() 

1254 else: 

1255 assert False, join_transaction_mode 

1256 else: 

1257 transaction = conn.begin() 

1258 except: 

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

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

1261 if local_connect: 

1262 conn.close() 

1263 raise 

1264 else: 

1265 bind_is_connection = isinstance(bind, engine.Connection) 

1266 

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

1268 conn, 

1269 transaction, 

1270 should_commit, 

1271 not bind_is_connection, 

1272 ) 

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

1274 return conn 

1275 finally: 

1276 self._state = SessionTransactionState.ACTIVE 

1277 

1278 def prepare(self) -> None: 

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

1280 raise sa_exc.InvalidRequestError( 

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

1282 "can't prepare." 

1283 ) 

1284 self._prepare_impl() 

1285 

1286 @_StateChange.declare_states( 

1287 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1288 ) 

1289 def _prepare_impl(self) -> None: 

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

1291 self.session.dispatch.before_commit(self.session) 

1292 

1293 stx = self.session._transaction 

1294 assert stx is not None 

1295 if stx is not self: 

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

1297 subtransaction.commit() 

1298 

1299 if not self.session._flushing: 

1300 for _flush_guard in range(100): 

1301 if self.session._is_clean(): 

1302 break 

1303 self.session.flush() 

1304 else: 

1305 raise exc.FlushError( 

1306 "Over 100 subsequent flushes have occurred within " 

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

1308 "creating new objects?" 

1309 ) 

1310 

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

1312 try: 

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

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

1315 except: 

1316 with util.safe_reraise(): 

1317 with self._expect_state(SessionTransactionState.CLOSED): 

1318 self.rollback() 

1319 

1320 self._state = SessionTransactionState.PREPARED 

1321 

1322 @_StateChange.declare_states( 

1323 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1324 SessionTransactionState.CLOSED, 

1325 ) 

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

1327 if self._state is not SessionTransactionState.PREPARED: 

1328 with self._expect_state(SessionTransactionState.PREPARED): 

1329 self._prepare_impl() 

1330 

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

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

1333 self._connections.values() 

1334 ): 

1335 if should_commit: 

1336 trans.commit() 

1337 

1338 self._state = SessionTransactionState.COMMITTED 

1339 self.session.dispatch.after_commit(self.session) 

1340 

1341 self._remove_snapshot() 

1342 

1343 with self._expect_state(SessionTransactionState.CLOSED): 

1344 self.close() 

1345 

1346 if _to_root and self._parent: 

1347 self._parent.commit(_to_root=True) 

1348 

1349 @_StateChange.declare_states( 

1350 ( 

1351 SessionTransactionState.ACTIVE, 

1352 SessionTransactionState.DEACTIVE, 

1353 SessionTransactionState.PREPARED, 

1354 ), 

1355 SessionTransactionState.CLOSED, 

1356 ) 

1357 def rollback( 

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

1359 ) -> None: 

1360 stx = self.session._transaction 

1361 assert stx is not None 

1362 if stx is not self: 

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

1364 subtransaction.close() 

1365 

1366 boundary = self 

1367 rollback_err = None 

1368 if self._state in ( 

1369 SessionTransactionState.ACTIVE, 

1370 SessionTransactionState.PREPARED, 

1371 ): 

1372 for transaction in self._iterate_self_and_parents(): 

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

1374 try: 

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

1376 t[1].rollback() 

1377 

1378 transaction._state = SessionTransactionState.DEACTIVE 

1379 self.session.dispatch.after_rollback(self.session) 

1380 except: 

1381 rollback_err = sys.exc_info() 

1382 finally: 

1383 transaction._state = SessionTransactionState.DEACTIVE 

1384 transaction._restore_snapshot( 

1385 dirty_only=transaction.nested 

1386 ) 

1387 boundary = transaction 

1388 break 

1389 else: 

1390 transaction._state = SessionTransactionState.DEACTIVE 

1391 

1392 sess = self.session 

1393 

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

1395 # if items were added, deleted, or mutated 

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

1397 util.warn( 

1398 "Session's state has been changed on " 

1399 "a non-active transaction - this state " 

1400 "will be discarded." 

1401 ) 

1402 boundary._restore_snapshot(dirty_only=boundary.nested) 

1403 

1404 with self._expect_state(SessionTransactionState.CLOSED): 

1405 self.close() 

1406 

1407 if self._parent and _capture_exception: 

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

1409 

1410 if rollback_err and rollback_err[1]: 

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

1412 

1413 sess.dispatch.after_soft_rollback(sess, self) 

1414 

1415 if _to_root and self._parent: 

1416 self._parent.rollback(_to_root=True) 

1417 

1418 @_StateChange.declare_states( 

1419 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1420 ) 

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

1422 if self.nested: 

1423 self.session._nested_transaction = ( 

1424 self._previous_nested_transaction 

1425 ) 

1426 

1427 self.session._transaction = self._parent 

1428 

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

1430 self._connections.values() 

1431 ): 

1432 if invalidate and self._parent is None: 

1433 connection.invalidate() 

1434 if should_commit and transaction.is_active: 

1435 transaction.close() 

1436 if autoclose and self._parent is None: 

1437 connection.close() 

1438 

1439 self._state = SessionTransactionState.CLOSED 

1440 sess = self.session 

1441 

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

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

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

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

1446 # passes with these commented out. 

1447 # self.session = None # type: ignore 

1448 # self._connections = None # type: ignore 

1449 

1450 sess.dispatch.after_transaction_end(sess, self) 

1451 

1452 def _get_subject(self) -> Session: 

1453 return self.session 

1454 

1455 def _transaction_is_active(self) -> bool: 

1456 return self._state is SessionTransactionState.ACTIVE 

1457 

1458 def _transaction_is_closed(self) -> bool: 

1459 return self._state is SessionTransactionState.CLOSED 

1460 

1461 def _rollback_can_be_called(self) -> bool: 

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

1463 

1464 

1465class _SessionCloseState(Enum): 

1466 ACTIVE = 1 

1467 CLOSED = 2 

1468 CLOSE_IS_RESET = 3 

1469 

1470 

1471class Session(_SessionClassMethods, EventTarget): 

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

1473 

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

1475 See :ref:`session_faq_threadsafe` for background. 

1476 

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

1478 

1479 

1480 """ 

1481 

1482 _is_asyncio = False 

1483 

1484 dispatch: dispatcher[Session] 

1485 

1486 identity_map: IdentityMap 

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

1488 

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

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

1491 that have row identity) currently in the session. 

1492 

1493 .. seealso:: 

1494 

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

1496 in this dictionary. 

1497 

1498 """ 

1499 

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

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

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

1503 __binds: Dict[_SessionBindKey, _SessionBind] 

1504 _flushing: bool 

1505 _warn_on_events: bool 

1506 _transaction: Optional[SessionTransaction] 

1507 _nested_transaction: Optional[SessionTransaction] 

1508 hash_key: int 

1509 autoflush: bool 

1510 expire_on_commit: bool 

1511 enable_baked_queries: bool 

1512 twophase: bool 

1513 join_transaction_mode: JoinTransactionMode 

1514 execution_options: _ExecuteOptions = util.EMPTY_DICT 

1515 _query_cls: Type[Query[Any]] 

1516 _close_state: _SessionCloseState 

1517 

1518 def __init__( 

1519 self, 

1520 bind: Optional[_SessionBind] = None, 

1521 *, 

1522 autoflush: bool = True, 

1523 future: Literal[True] = True, 

1524 expire_on_commit: bool = True, 

1525 autobegin: bool = True, 

1526 twophase: bool = False, 

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

1528 enable_baked_queries: bool = True, 

1529 info: Optional[_InfoType] = None, 

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

1531 autocommit: Literal[False] = False, 

1532 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1534 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

1535 ): 

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

1537 

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

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

1540 set of arguments. 

1541 

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

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

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

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

1546 results. 

1547 

1548 .. seealso:: 

1549 

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

1551 

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

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

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

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

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

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

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

1559 

1560 .. versionadded:: 2.0 

1561 

1562 .. seealso:: 

1563 

1564 :ref:`session_autobegin_disable` 

1565 

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

1567 :class:`_engine.Connection` to 

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

1569 operations performed by this session will execute via this 

1570 connectable. 

1571 

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

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

1574 objects as the source of 

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

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

1577 arbitrary Python classes that are bases for mapped classes, 

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

1579 The 

1580 values of the dictionary are then instances of 

1581 :class:`_engine.Engine` 

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

1583 Operations which 

1584 proceed relative to a particular mapped class will consult this 

1585 dictionary for the closest matching entity in order to determine 

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

1587 operation. The complete heuristics for resolution are 

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

1589 

1590 Session = sessionmaker( 

1591 binds={ 

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

1593 SomeDeclarativeBase: create_engine( 

1594 "postgresql+psycopg2://engine2" 

1595 ), 

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

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

1598 } 

1599 ) 

1600 

1601 .. seealso:: 

1602 

1603 :ref:`session_partitioning` 

1604 

1605 :meth:`.Session.bind_mapper` 

1606 

1607 :meth:`.Session.bind_table` 

1608 

1609 :meth:`.Session.get_bind` 

1610 

1611 

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

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

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

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

1616 constructor for ``Session``. 

1617 

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

1619 A parameter consumed 

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

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

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

1623 this particular extension is disabled. 

1624 

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

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

1627 flag therefore only affects applications that are making explicit 

1628 use of this extension within their own code. 

1629 

1630 :param execution_options: optional dictionary of execution options 

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

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

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

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

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

1636 event hooks such as 

1637 :meth:`_events.ConnectionEvents.before_cursor_execute`. 

1638 

1639 Execution options present in statements as well as options passed 

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

1641 precedence over the session-wide options. 

1642 

1643 .. versionadded:: 2.1 

1644 

1645 .. versionchanged:: 2.1.0b3 

1646 Session-level execution options are now applied to the 

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

1648 they take effect for flush operations as well as explicit 

1649 query executions. Previously, options were only applied to 

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

1651 

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

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

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

1655 transaction will load from the most recent database state. 

1656 

1657 .. seealso:: 

1658 

1659 :ref:`session_committing` 

1660 

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

1662 

1663 .. seealso:: 

1664 

1665 :ref:`migration_20_toplevel` 

1666 

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

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

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

1670 construction time so that modifications to the per- 

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

1672 :class:`.Session`. 

1673 

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

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

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

1677 

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

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

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

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

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

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

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

1685 transaction, before each transaction is committed. 

1686 

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

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

1689 

1690 :param join_transaction_mode: Describes the transactional behavior to 

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

1692 has already begun a transaction outside the scope of this 

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

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

1695 

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

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

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

1699 etc. are actually invoked: 

1700 

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

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

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

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

1705 a SAVEPOINT, in other words 

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

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

1708 

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

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

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

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

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

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

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

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

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

1718 

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

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

1721 its own transaction. This transaction by its nature rides 

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

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

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

1725 external transaction will remain unaffected throughout the 

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

1727 

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

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

1730 initiated transaction should remain unaffected; however, it relies 

1731 on proper SAVEPOINT support from the underlying driver and 

1732 database. 

1733 

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

1735 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1736 without workarounds. See the sections 

1737 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1738 for details on current workarounds. 

1739 

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

1741 control of the given transaction as its own; 

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

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

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

1745 call ``.rollback`` on the transaction. 

1746 

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

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

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

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

1751 SAVEPOINT. 

1752 

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

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

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

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

1757 given transaction. 

1758 

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

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

1761 regular database transaction (i.e. 

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

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

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

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

1766 

1767 .. versionadded:: 2.0.0rc1 

1768 

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

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

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

1772 

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

1774 A future SQLAlchemy version may change the default value of 

1775 this flag to ``False``. 

1776 

1777 .. seealso:: 

1778 

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

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

1781 

1782 """ # noqa 

1783 

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

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

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

1787 # of cases including in our own test suite 

1788 if autocommit: 

1789 raise sa_exc.ArgumentError( 

1790 "autocommit=True is no longer supported" 

1791 ) 

1792 self.identity_map = identity._WeakInstanceDict() 

1793 

1794 if not future: 

1795 raise sa_exc.ArgumentError( 

1796 "The 'future' parameter passed to " 

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

1798 ) 

1799 

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

1801 self._deleted = {} # same 

1802 self.bind = bind 

1803 self.__binds = {} 

1804 self._flushing = False 

1805 self._warn_on_events = False 

1806 self._transaction = None 

1807 self._nested_transaction = None 

1808 self.hash_key = _new_sessionid() 

1809 self.autobegin = autobegin 

1810 self.autoflush = autoflush 

1811 self.expire_on_commit = expire_on_commit 

1812 self.enable_baked_queries = enable_baked_queries 

1813 if execution_options: 

1814 self.execution_options = self.execution_options.union( 

1815 execution_options 

1816 ) 

1817 

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

1819 # the default will switch to close_resets_only=False. 

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

1821 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1822 else: 

1823 self._close_state = _SessionCloseState.ACTIVE 

1824 if ( 

1825 join_transaction_mode 

1826 and join_transaction_mode 

1827 not in JoinTransactionMode.__args__ # type: ignore 

1828 ): 

1829 raise sa_exc.ArgumentError( 

1830 f"invalid selection for join_transaction_mode: " 

1831 f'"{join_transaction_mode}"' 

1832 ) 

1833 self.join_transaction_mode = join_transaction_mode 

1834 

1835 self.twophase = twophase 

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

1837 if info: 

1838 self.info.update(info) 

1839 

1840 if binds is not None: 

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

1842 self._add_bind(key, bind) 

1843 

1844 _sessions[self.hash_key] = self 

1845 

1846 # used by sqlalchemy.engine.util.TransactionalContext 

1847 _trans_context_manager: Optional[TransactionalContext] = None 

1848 

1849 connection_callable: Optional[_ConnectionCallableProto] = None 

1850 

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

1852 return self 

1853 

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

1855 self.close() 

1856 

1857 @contextlib.contextmanager 

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

1859 with self: 

1860 with self.begin(): 

1861 yield self 

1862 

1863 def in_transaction(self) -> bool: 

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

1865 

1866 .. versionadded:: 1.4 

1867 

1868 .. seealso:: 

1869 

1870 :attr:`_orm.Session.is_active` 

1871 

1872 

1873 """ 

1874 return self._transaction is not None 

1875 

1876 def in_nested_transaction(self) -> bool: 

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

1878 transaction, e.g. SAVEPOINT. 

1879 

1880 .. versionadded:: 1.4 

1881 

1882 """ 

1883 return self._nested_transaction is not None 

1884 

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

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

1887 

1888 .. versionadded:: 1.4 

1889 

1890 """ 

1891 trans = self._transaction 

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

1893 trans = trans._parent 

1894 return trans 

1895 

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

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

1898 

1899 .. versionadded:: 1.4 

1900 

1901 """ 

1902 

1903 return self._nested_transaction 

1904 

1905 @util.memoized_property 

1906 def info(self) -> _InfoType: 

1907 """A user-modifiable dictionary. 

1908 

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

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

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

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

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

1914 

1915 """ 

1916 return {} 

1917 

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

1919 if self._transaction is None: 

1920 if not begin and not self.autobegin: 

1921 raise sa_exc.InvalidRequestError( 

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

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

1924 ) 

1925 trans = SessionTransaction( 

1926 self, 

1927 ( 

1928 SessionTransactionOrigin.BEGIN 

1929 if begin 

1930 else SessionTransactionOrigin.AUTOBEGIN 

1931 ), 

1932 ) 

1933 assert self._transaction is trans 

1934 return trans 

1935 

1936 return self._transaction 

1937 

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

1939 """Begin a transaction, or nested transaction, 

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

1941 

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

1943 so that normally it is not necessary to call the 

1944 :meth:`_orm.Session.begin` 

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

1946 the scope of when the transactional state is begun. 

1947 

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

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

1950 

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

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

1953 documentation on SAVEPOINT transactions, please see 

1954 :ref:`session_begin_nested`. 

1955 

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

1957 :class:`.SessionTransaction` 

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

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

1960 an example. 

1961 

1962 .. seealso:: 

1963 

1964 :ref:`session_autobegin` 

1965 

1966 :ref:`unitofwork_transaction` 

1967 

1968 :meth:`.Session.begin_nested` 

1969 

1970 

1971 """ 

1972 

1973 trans = self._transaction 

1974 if trans is None: 

1975 trans = self._autobegin_t(begin=True) 

1976 

1977 if not nested: 

1978 return trans 

1979 

1980 assert trans is not None 

1981 

1982 if nested: 

1983 trans = trans._begin(nested=nested) 

1984 assert self._transaction is trans 

1985 self._nested_transaction = trans 

1986 else: 

1987 raise sa_exc.InvalidRequestError( 

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

1989 ) 

1990 

1991 return trans # needed for __enter__/__exit__ hook 

1992 

1993 def begin_nested(self) -> SessionTransaction: 

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

1995 

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

1997 SAVEPOINT for this method to function correctly. 

1998 

1999 For documentation on SAVEPOINT 

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

2001 

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

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

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

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

2006 

2007 .. seealso:: 

2008 

2009 :ref:`session_begin_nested` 

2010 

2011 :ref:`pysqlite_serializable` - special workarounds required 

2012 with the SQLite driver in order for SAVEPOINT to work 

2013 correctly. For asyncio use cases, see the section 

2014 :ref:`aiosqlite_serializable`. 

2015 

2016 """ 

2017 return self.begin(nested=True) 

2018 

2019 def rollback(self) -> None: 

2020 """Rollback the current transaction in progress. 

2021 

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

2023 

2024 The method always rolls back 

2025 the topmost database transaction, discarding any nested 

2026 transactions that may be in progress. 

2027 

2028 .. seealso:: 

2029 

2030 :ref:`session_rollback` 

2031 

2032 :ref:`unitofwork_transaction` 

2033 

2034 """ 

2035 if self._transaction is None: 

2036 pass 

2037 else: 

2038 self._transaction.rollback(_to_root=True) 

2039 

2040 def commit(self) -> None: 

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

2042 

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

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

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

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

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

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

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

2050 to disable this behavior. 

2051 

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

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

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

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

2056 normally affect the database unless pending flush changes were 

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

2058 rules. 

2059 

2060 The outermost database transaction is committed unconditionally, 

2061 automatically releasing any SAVEPOINTs in effect. 

2062 

2063 .. seealso:: 

2064 

2065 :ref:`session_committing` 

2066 

2067 :ref:`unitofwork_transaction` 

2068 

2069 :ref:`asyncio_orm_avoid_lazyloads` 

2070 

2071 """ 

2072 trans = self._transaction 

2073 if trans is None: 

2074 trans = self._autobegin_t() 

2075 

2076 trans.commit(_to_root=True) 

2077 

2078 def prepare(self) -> None: 

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

2080 

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

2082 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2083 

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

2085 current transaction is not such, an 

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

2087 

2088 """ 

2089 trans = self._transaction 

2090 if trans is None: 

2091 trans = self._autobegin_t() 

2092 

2093 trans.prepare() 

2094 

2095 def connection( 

2096 self, 

2097 bind_arguments: Optional[_BindArguments] = None, 

2098 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2099 ) -> Connection: 

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

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

2102 

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

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

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

2106 returned (note that no 

2107 transactional state is established with the DBAPI until the first 

2108 SQL statement is emitted). 

2109 

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

2111 resolved through any of the optional keyword arguments. This 

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

2113 

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

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

2116 to :meth:`.Session.get_bind`. 

2117 

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

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

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

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

2122 the arguments are ignored. 

2123 

2124 .. seealso:: 

2125 

2126 :ref:`session_transaction_isolation` 

2127 

2128 """ 

2129 

2130 if bind_arguments: 

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

2132 

2133 if bind is None: 

2134 bind = self.get_bind(**bind_arguments) 

2135 else: 

2136 bind = self.get_bind() 

2137 

2138 return self._connection_for_bind( 

2139 bind, 

2140 execution_options=execution_options, 

2141 ) 

2142 

2143 def _connection_for_bind( 

2144 self, 

2145 engine: _SessionBind, 

2146 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2147 **kw: Any, 

2148 ) -> Connection: 

2149 TransactionalContext._trans_ctx_check(self) 

2150 

2151 trans = self._transaction 

2152 if trans is None: 

2153 trans = self._autobegin_t() 

2154 return trans._connection_for_bind(engine, execution_options) 

2155 

2156 @overload 

2157 def _execute_internal( 

2158 self, 

2159 statement: Executable, 

2160 params: Optional[_CoreSingleExecuteParams] = None, 

2161 *, 

2162 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2163 bind_arguments: Optional[_BindArguments] = None, 

2164 _parent_execute_state: Optional[Any] = None, 

2165 _add_event: Optional[Any] = None, 

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

2167 ) -> Any: ... 

2168 

2169 @overload 

2170 def _execute_internal( 

2171 self, 

2172 statement: Executable, 

2173 params: Optional[_CoreAnyExecuteParams] = None, 

2174 *, 

2175 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2176 bind_arguments: Optional[_BindArguments] = None, 

2177 _parent_execute_state: Optional[Any] = None, 

2178 _add_event: Optional[Any] = None, 

2179 _scalar_result: bool = ..., 

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

2181 

2182 def _execute_internal( 

2183 self, 

2184 statement: Executable, 

2185 params: Optional[_CoreAnyExecuteParams] = None, 

2186 *, 

2187 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2188 bind_arguments: Optional[_BindArguments] = None, 

2189 _parent_execute_state: Optional[Any] = None, 

2190 _add_event: Optional[Any] = None, 

2191 _scalar_result: bool = False, 

2192 ) -> Any: 

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

2194 

2195 if not bind_arguments: 

2196 bind_arguments = {} 

2197 else: 

2198 bind_arguments = dict(bind_arguments) 

2199 

2200 if ( 

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

2202 == "orm" 

2203 ): 

2204 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2205 statement, "orm" 

2206 ) 

2207 if TYPE_CHECKING: 

2208 assert isinstance( 

2209 compile_state_cls, context._AbstractORMCompileState 

2210 ) 

2211 else: 

2212 compile_state_cls = None 

2213 bind_arguments.setdefault("clause", statement) 

2214 

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

2216 util.coerce_to_immutabledict(execution_options) 

2217 ) 

2218 if self.execution_options: 

2219 # merge given execution options with session-wide execution 

2220 # options. if the statement also has execution_options, 

2221 # maintain priority of session.execution_options -> 

2222 # statement.execution_options -> method passed execution_options 

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

2224 # will come from the statement 

2225 if statement._execution_options: 

2226 combined_execution_options = util.immutabledict( 

2227 { 

2228 k: v 

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

2230 if k not in statement._execution_options 

2231 } 

2232 ).union(combined_execution_options) 

2233 else: 

2234 combined_execution_options = self.execution_options.union( 

2235 combined_execution_options 

2236 ) 

2237 

2238 if _parent_execute_state: 

2239 events_todo = _parent_execute_state._remaining_events() 

2240 else: 

2241 events_todo = self.dispatch.do_orm_execute 

2242 if _add_event: 

2243 events_todo = list(events_todo) + [_add_event] 

2244 

2245 if events_todo: 

2246 # save the original execution options before 

2247 # orm_pre_session_exec processes them, so that we can pass 

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

2249 # hooks) to the second orm_pre_session_exec call. This 

2250 # prevents internal state like _sa_orm_load_options and 

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

2252 # which would otherwise cause issues like yield_per incorrectly 

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

2254 # part of #13301. 

2255 original_execution_options = combined_execution_options 

2256 

2257 if compile_state_cls is not None: 

2258 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2264 ( 

2265 statement, 

2266 combined_execution_options, 

2267 params, 

2268 ) = compile_state_cls.orm_pre_session_exec( 

2269 self, 

2270 statement, 

2271 params, 

2272 combined_execution_options, 

2273 bind_arguments, 

2274 True, 

2275 ) 

2276 

2277 orm_exec_state = ORMExecuteState( 

2278 self, 

2279 statement, 

2280 params, 

2281 combined_execution_options, 

2282 bind_arguments, 

2283 compile_state_cls, 

2284 events_todo, 

2285 ) 

2286 for idx, fn in enumerate(events_todo): 

2287 orm_exec_state._starting_event_idx = idx 

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

2289 orm_exec_state 

2290 ) 

2291 if fn_result: 

2292 if _scalar_result: 

2293 return fn_result.scalar() 

2294 else: 

2295 return fn_result 

2296 

2297 statement = orm_exec_state.statement 

2298 params = orm_exec_state.parameters 

2299 

2300 # use the original execution options plus only the explicit 

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

2302 # the first orm_pre_session_exec call 

2303 combined_execution_options = original_execution_options.union( 

2304 orm_exec_state._update_execution_options 

2305 ) 

2306 

2307 if compile_state_cls is not None: 

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

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

2310 # new execution_options into load_options / update_delete_options, 

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

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

2313 ( 

2314 statement, 

2315 combined_execution_options, 

2316 params, 

2317 ) = compile_state_cls.orm_pre_session_exec( 

2318 self, 

2319 statement, 

2320 params, 

2321 combined_execution_options, 

2322 bind_arguments, 

2323 False, 

2324 ) 

2325 else: 

2326 # Issue #9809: unconditionally autoflush for Core statements 

2327 self._autoflush() 

2328 

2329 bind = self.get_bind(**bind_arguments) 

2330 

2331 conn = self._connection_for_bind(bind) 

2332 

2333 if _scalar_result and not compile_state_cls: 

2334 if TYPE_CHECKING: 

2335 params = cast(_CoreSingleExecuteParams, params) 

2336 return conn.scalar( 

2337 statement, 

2338 params or {}, 

2339 execution_options=combined_execution_options, 

2340 ) 

2341 

2342 if compile_state_cls: 

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

2344 compile_state_cls.orm_execute_statement( 

2345 self, 

2346 statement, 

2347 params or {}, 

2348 combined_execution_options, 

2349 bind_arguments, 

2350 conn, 

2351 ) 

2352 ) 

2353 else: 

2354 result = conn.execute( 

2355 statement, params, execution_options=combined_execution_options 

2356 ) 

2357 

2358 if _scalar_result: 

2359 return result.scalar() 

2360 else: 

2361 return result 

2362 

2363 @overload 

2364 def execute( 

2365 self, 

2366 statement: TypedReturnsRows[Unpack[_Ts]], 

2367 params: Optional[_CoreAnyExecuteParams] = None, 

2368 *, 

2369 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2370 bind_arguments: Optional[_BindArguments] = None, 

2371 _parent_execute_state: Optional[Any] = None, 

2372 _add_event: Optional[Any] = None, 

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

2374 

2375 @overload 

2376 def execute( 

2377 self, 

2378 statement: Executable, 

2379 params: Optional[_CoreAnyExecuteParams] = None, 

2380 *, 

2381 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2382 bind_arguments: Optional[_BindArguments] = None, 

2383 _parent_execute_state: Optional[Any] = None, 

2384 _add_event: Optional[Any] = None, 

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

2386 

2387 def execute( 

2388 self, 

2389 statement: Executable, 

2390 params: Optional[_CoreAnyExecuteParams] = None, 

2391 *, 

2392 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2393 bind_arguments: Optional[_BindArguments] = None, 

2394 _parent_execute_state: Optional[Any] = None, 

2395 _add_event: Optional[Any] = None, 

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

2397 r"""Execute a SQL expression construct. 

2398 

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

2400 results of the statement execution. 

2401 

2402 E.g.:: 

2403 

2404 from sqlalchemy import select 

2405 

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

2407 

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

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

2410 of :class:`_engine.Connection`. 

2411 

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

2413 now the primary point of ORM statement execution when using 

2414 :term:`2.0 style` ORM usage. 

2415 

2416 :param statement: 

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

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

2419 

2420 :param params: 

2421 Optional dictionary, or list of dictionaries, containing 

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

2423 execution occurs; if a list of dictionaries, an 

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

2425 must correspond to parameter names present in the statement. 

2426 

2427 :param execution_options: optional dictionary of execution options, 

2428 which will be associated with the statement execution. This 

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

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

2431 provide additional options understood only in an ORM context. 

2432 

2433 The execution_options are passed along to methods like 

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

2435 highest priority to execution_options that are passed to this 

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

2437 statement object if any, and finally those options present 

2438 session-wide. 

2439 

2440 .. seealso:: 

2441 

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

2443 options 

2444 

2445 :param bind_arguments: dictionary of additional arguments to determine 

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

2447 Contents of this dictionary are passed to the 

2448 :meth:`.Session.get_bind` method. 

2449 

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

2451 

2452 

2453 """ 

2454 return self._execute_internal( 

2455 statement, 

2456 params, 

2457 execution_options=execution_options, 

2458 bind_arguments=bind_arguments, 

2459 _parent_execute_state=_parent_execute_state, 

2460 _add_event=_add_event, 

2461 ) 

2462 

2463 # special case to handle mypy issue: 

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

2465 @overload 

2466 def scalar( 

2467 self, 

2468 statement: TypedReturnsRows[Never], 

2469 params: Optional[_CoreSingleExecuteParams] = None, 

2470 *, 

2471 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2472 bind_arguments: Optional[_BindArguments] = None, 

2473 **kw: Any, 

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

2475 

2476 @overload 

2477 def scalar( 

2478 self, 

2479 statement: TypedReturnsRows[_T], 

2480 params: Optional[_CoreSingleExecuteParams] = None, 

2481 *, 

2482 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2483 bind_arguments: Optional[_BindArguments] = None, 

2484 **kw: Any, 

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

2486 

2487 @overload 

2488 def scalar( 

2489 self, 

2490 statement: Executable, 

2491 params: Optional[_CoreSingleExecuteParams] = None, 

2492 *, 

2493 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2494 bind_arguments: Optional[_BindArguments] = None, 

2495 **kw: Any, 

2496 ) -> Any: ... 

2497 

2498 def scalar( 

2499 self, 

2500 statement: Executable, 

2501 params: Optional[_CoreSingleExecuteParams] = None, 

2502 *, 

2503 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2504 bind_arguments: Optional[_BindArguments] = None, 

2505 **kw: Any, 

2506 ) -> Any: 

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

2508 

2509 Usage and parameters are the same as that of 

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

2511 value. 

2512 

2513 """ 

2514 

2515 return self._execute_internal( 

2516 statement, 

2517 params, 

2518 execution_options=execution_options, 

2519 bind_arguments=bind_arguments, 

2520 _scalar_result=True, 

2521 **kw, 

2522 ) 

2523 

2524 @overload 

2525 def scalars( 

2526 self, 

2527 statement: TypedReturnsRows[_T], 

2528 params: Optional[_CoreAnyExecuteParams] = None, 

2529 *, 

2530 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2531 bind_arguments: Optional[_BindArguments] = None, 

2532 **kw: Any, 

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

2534 

2535 @overload 

2536 def scalars( 

2537 self, 

2538 statement: Executable, 

2539 params: Optional[_CoreAnyExecuteParams] = None, 

2540 *, 

2541 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2542 bind_arguments: Optional[_BindArguments] = None, 

2543 **kw: Any, 

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

2545 

2546 def scalars( 

2547 self, 

2548 statement: Executable, 

2549 params: Optional[_CoreAnyExecuteParams] = None, 

2550 *, 

2551 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2552 bind_arguments: Optional[_BindArguments] = None, 

2553 **kw: Any, 

2554 ) -> ScalarResult[Any]: 

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

2556 

2557 Usage and parameters are the same as that of 

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

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

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

2561 

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

2563 

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

2565 

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

2567 

2568 .. seealso:: 

2569 

2570 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2572 

2573 """ 

2574 

2575 return self._execute_internal( 

2576 statement, 

2577 params=params, 

2578 execution_options=execution_options, 

2579 bind_arguments=bind_arguments, 

2580 _scalar_result=False, # mypy appreciates this 

2581 **kw, 

2582 ).scalars() 

2583 

2584 def close(self) -> None: 

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

2586 :class:`_orm.Session`. 

2587 

2588 This expunges all ORM objects associated with this 

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

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

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

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

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

2594 

2595 .. tip:: 

2596 

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

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

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

2600 distinct "closed" state; it merely means 

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

2602 and ORM objects. 

2603 

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

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

2606 any further action on the session will be forbidden. 

2607 

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

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

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

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

2612 

2613 .. seealso:: 

2614 

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

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

2617 

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

2619 ``close()`` with the parameter 

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

2621 

2622 """ 

2623 self._close_impl(invalidate=False) 

2624 

2625 def reset(self) -> None: 

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

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

2628 

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

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

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

2632 brand new, and ready to be used again. 

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

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

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

2636 

2637 .. versionadded:: 2.0.22 

2638 

2639 .. seealso:: 

2640 

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

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

2643 

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

2645 prevent reuse of the Session when the parameter 

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

2647 """ 

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

2649 

2650 def invalidate(self) -> None: 

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

2652 

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

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

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

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

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

2658 multiple engines). 

2659 

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

2661 the connections are no longer safe to be used. 

2662 

2663 Below illustrates a scenario when using `gevent 

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

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

2666 

2667 import gevent 

2668 

2669 try: 

2670 sess = Session() 

2671 sess.add(User()) 

2672 sess.commit() 

2673 except gevent.Timeout: 

2674 sess.invalidate() 

2675 raise 

2676 except: 

2677 sess.rollback() 

2678 raise 

2679 

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

2681 does, including that all ORM objects are expunged. 

2682 

2683 """ 

2684 self._close_impl(invalidate=True) 

2685 

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

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

2688 self._close_state = _SessionCloseState.CLOSED 

2689 self.expunge_all() 

2690 if self._transaction is not None: 

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

2692 transaction.close(invalidate) 

2693 

2694 def expunge_all(self) -> None: 

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

2696 

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

2698 ``Session``. 

2699 

2700 """ 

2701 

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

2703 self.identity_map._kill() 

2704 self.identity_map = identity._WeakInstanceDict() 

2705 self._new = {} 

2706 self._deleted = {} 

2707 

2708 statelib.InstanceState._detach_states(all_states, self) 

2709 

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

2711 try: 

2712 insp = inspect(key) 

2713 except sa_exc.NoInspectionAvailable as err: 

2714 if not isinstance(key, type): 

2715 raise sa_exc.ArgumentError( 

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

2717 ) from err 

2718 else: 

2719 self.__binds[key] = bind 

2720 else: 

2721 if TYPE_CHECKING: 

2722 assert isinstance(insp, Inspectable) 

2723 

2724 if isinstance(insp, TableClause): 

2725 self.__binds[insp] = bind 

2726 elif insp_is_mapper(insp): 

2727 self.__binds[insp.class_] = bind 

2728 for _selectable in insp._all_tables: 

2729 self.__binds[_selectable] = bind 

2730 else: 

2731 raise sa_exc.ArgumentError( 

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

2733 ) 

2734 

2735 def bind_mapper( 

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

2737 ) -> None: 

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

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

2740 :class:`_engine.Connection`. 

2741 

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

2743 :meth:`.Session.get_bind` method. 

2744 

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

2746 or an instance of a mapped 

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

2748 classes. 

2749 

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

2751 object. 

2752 

2753 .. seealso:: 

2754 

2755 :ref:`session_partitioning` 

2756 

2757 :paramref:`.Session.binds` 

2758 

2759 :meth:`.Session.bind_table` 

2760 

2761 

2762 """ 

2763 self._add_bind(mapper, bind) 

2764 

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

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

2767 :class:`_engine.Engine` 

2768 or :class:`_engine.Connection`. 

2769 

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

2771 :meth:`.Session.get_bind` method. 

2772 

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

2774 which is typically the target 

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

2776 mapped. 

2777 

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

2779 object. 

2780 

2781 .. seealso:: 

2782 

2783 :ref:`session_partitioning` 

2784 

2785 :paramref:`.Session.binds` 

2786 

2787 :meth:`.Session.bind_mapper` 

2788 

2789 

2790 """ 

2791 self._add_bind(table, bind) 

2792 

2793 def get_bind( 

2794 self, 

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

2796 *, 

2797 clause: Optional[ClauseElement] = None, 

2798 bind: Optional[_SessionBind] = None, 

2799 _sa_skip_events: Optional[bool] = None, 

2800 _sa_skip_for_implicit_returning: bool = False, 

2801 **kw: Any, 

2802 ) -> Union[Engine, Connection]: 

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

2804 

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

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

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

2808 

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

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

2811 appropriate bind to return. 

2812 

2813 Note that the "mapper" argument is usually present 

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

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

2816 individual INSERT/UPDATE/DELETE operation within a 

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

2818 

2819 The order of resolution is: 

2820 

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

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

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

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

2825 superclasses to more general. 

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

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

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

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

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

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

2832 associated with the clause. 

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

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

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

2836 selectable to which the mapper is mapped. 

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

2838 is raised. 

2839 

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

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

2842 of bind resolution scheme. See the example at 

2843 :ref:`session_custom_partitioning`. 

2844 

2845 :param mapper: 

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

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

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

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

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

2851 mapped for a bind. 

2852 

2853 :param clause: 

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

2855 :func:`_expression.select`, 

2856 :func:`_expression.text`, 

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

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

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

2860 associated with 

2861 bound :class:`_schema.MetaData`. 

2862 

2863 .. seealso:: 

2864 

2865 :ref:`session_partitioning` 

2866 

2867 :paramref:`.Session.binds` 

2868 

2869 :meth:`.Session.bind_mapper` 

2870 

2871 :meth:`.Session.bind_table` 

2872 

2873 """ 

2874 

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

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

2877 if bind: 

2878 return bind 

2879 elif not self.__binds and self.bind: 

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

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

2882 return self.bind 

2883 

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

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

2886 # mapper and the clause 

2887 if mapper is None and clause is None: 

2888 if self.bind: 

2889 return self.bind 

2890 else: 

2891 raise sa_exc.UnboundExecutionError( 

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

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

2894 "a binding." 

2895 ) 

2896 

2897 # look more closely at the mapper. 

2898 if mapper is not None: 

2899 try: 

2900 inspected_mapper = inspect(mapper) 

2901 except sa_exc.NoInspectionAvailable as err: 

2902 if isinstance(mapper, type): 

2903 raise exc.UnmappedClassError(mapper) from err 

2904 else: 

2905 raise 

2906 else: 

2907 inspected_mapper = None 

2908 

2909 # match up the mapper or clause in the __binds 

2910 if self.__binds: 

2911 # matching mappers and selectables to entries in the 

2912 # binds dictionary; supported use case. 

2913 if inspected_mapper: 

2914 for cls in inspected_mapper.class_.__mro__: 

2915 if cls in self.__binds: 

2916 return self.__binds[cls] 

2917 if clause is None: 

2918 clause = inspected_mapper.persist_selectable 

2919 

2920 if clause is not None: 

2921 plugin_subject = clause._propagate_attrs.get( 

2922 "plugin_subject", None 

2923 ) 

2924 

2925 if plugin_subject is not None: 

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

2927 if cls in self.__binds: 

2928 return self.__binds[cls] 

2929 

2930 for obj in visitors.iterate(clause): 

2931 if obj in self.__binds: 

2932 if TYPE_CHECKING: 

2933 assert isinstance(obj, Table) 

2934 return self.__binds[obj] 

2935 

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

2937 # return that 

2938 if self.bind: 

2939 return self.bind 

2940 

2941 context = [] 

2942 if inspected_mapper is not None: 

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

2944 if clause is not None: 

2945 context.append("SQL expression") 

2946 

2947 raise sa_exc.UnboundExecutionError( 

2948 f"Could not locate a bind configured on " 

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

2950 ) 

2951 

2952 @overload 

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

2954 

2955 @overload 

2956 def query( 

2957 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2959 

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

2961 

2962 # code within this block is **programmatically, 

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

2964 

2965 @overload 

2966 def query( 

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

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

2969 

2970 @overload 

2971 def query( 

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

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

2974 

2975 @overload 

2976 def query( 

2977 self, 

2978 __ent0: _TCCA[_T0], 

2979 __ent1: _TCCA[_T1], 

2980 __ent2: _TCCA[_T2], 

2981 __ent3: _TCCA[_T3], 

2982 /, 

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

2984 

2985 @overload 

2986 def query( 

2987 self, 

2988 __ent0: _TCCA[_T0], 

2989 __ent1: _TCCA[_T1], 

2990 __ent2: _TCCA[_T2], 

2991 __ent3: _TCCA[_T3], 

2992 __ent4: _TCCA[_T4], 

2993 /, 

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

2995 

2996 @overload 

2997 def query( 

2998 self, 

2999 __ent0: _TCCA[_T0], 

3000 __ent1: _TCCA[_T1], 

3001 __ent2: _TCCA[_T2], 

3002 __ent3: _TCCA[_T3], 

3003 __ent4: _TCCA[_T4], 

3004 __ent5: _TCCA[_T5], 

3005 /, 

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

3007 

3008 @overload 

3009 def query( 

3010 self, 

3011 __ent0: _TCCA[_T0], 

3012 __ent1: _TCCA[_T1], 

3013 __ent2: _TCCA[_T2], 

3014 __ent3: _TCCA[_T3], 

3015 __ent4: _TCCA[_T4], 

3016 __ent5: _TCCA[_T5], 

3017 __ent6: _TCCA[_T6], 

3018 /, 

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

3020 

3021 @overload 

3022 def query( 

3023 self, 

3024 __ent0: _TCCA[_T0], 

3025 __ent1: _TCCA[_T1], 

3026 __ent2: _TCCA[_T2], 

3027 __ent3: _TCCA[_T3], 

3028 __ent4: _TCCA[_T4], 

3029 __ent5: _TCCA[_T5], 

3030 __ent6: _TCCA[_T6], 

3031 __ent7: _TCCA[_T7], 

3032 /, 

3033 *entities: _ColumnsClauseArgument[Any], 

3034 ) -> RowReturningQuery[ 

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

3036 ]: ... 

3037 

3038 # END OVERLOADED FUNCTIONS self.query 

3039 

3040 @overload 

3041 def query( 

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

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

3044 

3045 def query( 

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

3047 ) -> Query[Any]: 

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

3049 :class:`_orm.Session`. 

3050 

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

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

3053 to construct ORM queries. 

3054 

3055 .. seealso:: 

3056 

3057 :ref:`unified_tutorial` 

3058 

3059 :ref:`queryguide_toplevel` 

3060 

3061 :ref:`query_api_toplevel` - legacy API doc 

3062 

3063 """ 

3064 

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

3066 

3067 def _identity_lookup( 

3068 self, 

3069 mapper: Mapper[_O], 

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

3071 identity_token: Any = None, 

3072 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

3074 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3075 bind_arguments: Optional[_BindArguments] = None, 

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

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

3078 

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

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

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

3082 check if was deleted). 

3083 

3084 e.g.:: 

3085 

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

3087 

3088 :param mapper: mapper in use 

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

3090 a tuple. 

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

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

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

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

3095 :param passive: passive load flag passed to 

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

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

3098 if the flag allows for SQL to be emitted. 

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

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

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

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

3103 relationship-loaded). 

3104 

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

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

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

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

3109 

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

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

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

3113 :class:`_query.Query` object. 

3114 

3115 

3116 """ 

3117 

3118 key = mapper.identity_key_from_primary_key( 

3119 primary_key_identity, identity_token=identity_token 

3120 ) 

3121 

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

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

3124 return return_value 

3125 

3126 @util.non_memoized_property 

3127 @contextlib.contextmanager 

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

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

3130 

3131 e.g.:: 

3132 

3133 with session.no_autoflush: 

3134 

3135 some_object = SomeClass() 

3136 session.add(some_object) 

3137 # won't autoflush 

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

3139 

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

3141 will not be subject to flushes occurring upon query 

3142 access. This is useful when initializing a series 

3143 of objects which involve existing database queries, 

3144 where the uncompleted object should not yet be flushed. 

3145 

3146 """ 

3147 autoflush = self.autoflush 

3148 self.autoflush = False 

3149 try: 

3150 yield self 

3151 finally: 

3152 self.autoflush = autoflush 

3153 

3154 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3158 "warning happened while initializing objects.", 

3159 sa_exc.SAWarning, 

3160 ) 

3161 def _autoflush(self) -> None: 

3162 if self.autoflush and not self._flushing: 

3163 try: 

3164 self.flush() 

3165 except sa_exc.StatementError as e: 

3166 # note we are reraising StatementError as opposed to 

3167 # raising FlushError with "chaining" to remain compatible 

3168 # with code that catches StatementError, IntegrityError, 

3169 # etc. 

3170 e.add_detail( 

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

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

3173 "flush is occurring prematurely" 

3174 ) 

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

3176 

3177 def refresh( 

3178 self, 

3179 instance: object, 

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

3181 with_for_update: ForUpdateParameter = None, 

3182 ) -> None: 

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

3184 

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

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

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

3188 value available in the current transaction. 

3189 

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

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

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

3193 

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

3195 can also refresh eagerly loaded attributes. 

3196 

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

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

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

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

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

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

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

3204 refreshed. 

3205 

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

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

3208 attributes for those which are named explicitly in the 

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

3210 

3211 .. tip:: 

3212 

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

3214 refreshing both column and relationship oriented attributes, its 

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

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

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

3218 once while having explicit control over relationship loader 

3219 strategies, use the 

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

3221 instead. 

3222 

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

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

3225 in database state outside of that transaction. Refreshing 

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

3227 where database rows have not yet been accessed. 

3228 

3229 :param attribute_names: optional. An iterable collection of 

3230 string attribute names indicating a subset of attributes to 

3231 be refreshed. 

3232 

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

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

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

3236 flags should match the parameters of 

3237 :meth:`_query.Query.with_for_update`. 

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

3239 

3240 .. seealso:: 

3241 

3242 :ref:`session_expire` - introductory material 

3243 

3244 :meth:`.Session.expire` 

3245 

3246 :meth:`.Session.expire_all` 

3247 

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

3249 to refresh objects as they would be loaded normally. 

3250 

3251 """ 

3252 try: 

3253 state = attributes.instance_state(instance) 

3254 except exc.NO_STATE as err: 

3255 raise exc.UnmappedInstanceError(instance) from err 

3256 

3257 self._expire_state(state, attribute_names) 

3258 

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

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

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

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

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

3264 # load_on_ident. 

3265 self._autoflush() 

3266 

3267 if with_for_update == {}: 

3268 raise sa_exc.ArgumentError( 

3269 "with_for_update should be the boolean value " 

3270 "True, or a dictionary with options. " 

3271 "A blank dictionary is ambiguous." 

3272 ) 

3273 

3274 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3275 

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

3277 if ( 

3278 loading._load_on_ident( 

3279 self, 

3280 stmt, 

3281 state.key, 

3282 refresh_state=state, 

3283 with_for_update=with_for_update, 

3284 only_load_props=attribute_names, 

3285 require_pk_cols=True, 

3286 # technically unnecessary as we just did autoflush 

3287 # above, however removes the additional unnecessary 

3288 # call to _autoflush() 

3289 no_autoflush=True, 

3290 is_user_refresh=True, 

3291 ) 

3292 is None 

3293 ): 

3294 raise sa_exc.InvalidRequestError( 

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

3296 ) 

3297 

3298 def expire_all(self) -> None: 

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

3300 

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

3302 a query will be issued using the 

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

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

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

3306 previously read in that same transaction, regardless of changes 

3307 in database state outside of that transaction. 

3308 

3309 To expire individual objects and individual attributes 

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

3311 

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

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

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

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

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

3317 assuming the transaction is isolated. 

3318 

3319 .. seealso:: 

3320 

3321 :ref:`session_expire` - introductory material 

3322 

3323 :meth:`.Session.expire` 

3324 

3325 :meth:`.Session.refresh` 

3326 

3327 :meth:`_orm.Query.populate_existing` 

3328 

3329 """ 

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

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

3332 

3333 def expire( 

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

3335 ) -> None: 

3336 """Expire the attributes on an instance. 

3337 

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

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

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

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

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

3343 previously read in that same transaction, regardless of changes 

3344 in database state outside of that transaction. 

3345 

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

3347 use :meth:`Session.expire_all`. 

3348 

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

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

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

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

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

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

3355 transaction. 

3356 

3357 :param instance: The instance to be refreshed. 

3358 :param attribute_names: optional list of string attribute names 

3359 indicating a subset of attributes to be expired. 

3360 

3361 .. seealso:: 

3362 

3363 :ref:`session_expire` - introductory material 

3364 

3365 :meth:`.Session.expire` 

3366 

3367 :meth:`.Session.refresh` 

3368 

3369 :meth:`_orm.Query.populate_existing` 

3370 

3371 """ 

3372 try: 

3373 state = attributes.instance_state(instance) 

3374 except exc.NO_STATE as err: 

3375 raise exc.UnmappedInstanceError(instance) from err 

3376 self._expire_state(state, attribute_names) 

3377 

3378 def _expire_state( 

3379 self, 

3380 state: InstanceState[Any], 

3381 attribute_names: Optional[Iterable[str]], 

3382 ) -> None: 

3383 self._validate_persistent(state) 

3384 if attribute_names: 

3385 state._expire_attributes(state.dict, attribute_names) 

3386 else: 

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

3388 # remove associations 

3389 cascaded = list( 

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

3391 ) 

3392 self._conditional_expire(state) 

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

3394 self._conditional_expire(st_) 

3395 

3396 def _conditional_expire( 

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

3398 ) -> None: 

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

3400 

3401 if state.key: 

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

3403 elif state in self._new: 

3404 self._new.pop(state) 

3405 state._detach(self) 

3406 

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

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

3409 

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

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

3412 

3413 """ 

3414 try: 

3415 state = attributes.instance_state(instance) 

3416 except exc.NO_STATE as err: 

3417 raise exc.UnmappedInstanceError(instance) from err 

3418 if state.session_id is not self.hash_key: 

3419 raise sa_exc.InvalidRequestError( 

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

3421 ) 

3422 

3423 cascaded = list( 

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

3425 ) 

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

3427 

3428 def _expunge_states( 

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

3430 ) -> None: 

3431 for state in states: 

3432 if state in self._new: 

3433 self._new.pop(state) 

3434 elif self.identity_map.contains_state(state): 

3435 self.identity_map.safe_discard(state) 

3436 self._deleted.pop(state, None) 

3437 elif self._transaction: 

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

3439 # in the transaction snapshot 

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

3441 statelib.InstanceState._detach_states( 

3442 states, self, to_transient=to_transient 

3443 ) 

3444 

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

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

3447 

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

3449 state as well as already persistent objects. 

3450 

3451 """ 

3452 

3453 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3454 for state in states: 

3455 mapper = _state_mapper(state) 

3456 

3457 # prevent against last minute dereferences of the object 

3458 obj = state.obj() 

3459 if obj is not None: 

3460 instance_key = mapper._identity_key_from_state(state) 

3461 

3462 if ( 

3463 _none_set.intersection(instance_key[1]) 

3464 and not mapper.allow_partial_pks 

3465 or _none_set.issuperset(instance_key[1]) 

3466 ): 

3467 raise exc.FlushError( 

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

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

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

3471 "that the mapped Column object is configured to " 

3472 "expect these generated values. Ensure also that " 

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

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

3475 % state_str(state) 

3476 ) 

3477 

3478 if state.key is None: 

3479 state.key = instance_key 

3480 elif state.key != instance_key: 

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

3482 # state has already replaced this one in the identity 

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

3484 self.identity_map.safe_discard(state) 

3485 trans = self._transaction 

3486 assert trans is not None 

3487 if state in trans._key_switches: 

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

3489 else: 

3490 orig_key = state.key 

3491 trans._key_switches[state] = ( 

3492 orig_key, 

3493 instance_key, 

3494 ) 

3495 state.key = instance_key 

3496 

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

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

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

3500 old = self.identity_map.replace(state) 

3501 if ( 

3502 old is not None 

3503 and mapper._identity_key_from_state(old) == instance_key 

3504 and old.obj() is not None 

3505 ): 

3506 util.warn( 

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

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

3509 "load operations occurring inside of an event handler " 

3510 "within the flush?" % (instance_key,) 

3511 ) 

3512 state._orphaned_outside_of_session = False 

3513 

3514 statelib.InstanceState._commit_all_states( 

3515 ((state, state.dict) for state in states), self.identity_map 

3516 ) 

3517 

3518 self._register_altered(states) 

3519 

3520 if pending_to_persistent is not None: 

3521 for state in states.intersection(self._new): 

3522 pending_to_persistent(self, state) 

3523 

3524 # remove from new last, might be the last strong ref 

3525 for state in set(states).intersection(self._new): 

3526 self._new.pop(state) 

3527 

3528 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None: 

3529 if self._transaction: 

3530 for state in states: 

3531 if state in self._new: 

3532 self._transaction._new[state] = True 

3533 else: 

3534 self._transaction._dirty[state] = True 

3535 

3536 def _remove_newly_deleted( 

3537 self, states: Iterable[InstanceState[Any]] 

3538 ) -> None: 

3539 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3540 for state in states: 

3541 if self._transaction: 

3542 self._transaction._deleted[state] = True 

3543 

3544 if persistent_to_deleted is not None: 

3545 # get a strong reference before we pop out of 

3546 # self._deleted 

3547 obj = state.obj() # noqa 

3548 

3549 self.identity_map.safe_discard(state) 

3550 self._deleted.pop(state, None) 

3551 state._deleted = True 

3552 # can't call state._detach() here, because this state 

3553 # is still in the transaction snapshot and needs to be 

3554 # tracked as part of that 

3555 if persistent_to_deleted is not None: 

3556 persistent_to_deleted(self, state) 

3557 

3558 def add(self, instance: object, *, _warn: bool = True) -> None: 

3559 """Place an object into this :class:`_orm.Session`. 

3560 

3561 Objects that are in the :term:`transient` state when passed to the 

3562 :meth:`_orm.Session.add` method will move to the 

3563 :term:`pending` state, until the next flush, at which point they 

3564 will move to the :term:`persistent` state. 

3565 

3566 Objects that are in the :term:`detached` state when passed to the 

3567 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3568 state directly. 

3569 

3570 If the transaction used by the :class:`_orm.Session` is rolled back, 

3571 objects which were transient when they were passed to 

3572 :meth:`_orm.Session.add` will be moved back to the 

3573 :term:`transient` state, and will no longer be present within this 

3574 :class:`_orm.Session`. 

3575 

3576 .. seealso:: 

3577 

3578 :meth:`_orm.Session.add_all` 

3579 

3580 :ref:`session_adding` - at :ref:`session_basics` 

3581 

3582 """ 

3583 if _warn and self._warn_on_events: 

3584 self._flush_warning("Session.add()") 

3585 

3586 try: 

3587 state = attributes.instance_state(instance) 

3588 except exc.NO_STATE as err: 

3589 raise exc.UnmappedInstanceError(instance) from err 

3590 

3591 self._save_or_update_state(state) 

3592 

3593 def add_all(self, instances: Iterable[object]) -> None: 

3594 """Add the given collection of instances to this :class:`_orm.Session`. 

3595 

3596 See the documentation for :meth:`_orm.Session.add` for a general 

3597 behavioral description. 

3598 

3599 .. seealso:: 

3600 

3601 :meth:`_orm.Session.add` 

3602 

3603 :ref:`session_adding` - at :ref:`session_basics` 

3604 

3605 """ 

3606 

3607 if self._warn_on_events: 

3608 self._flush_warning("Session.add_all()") 

3609 

3610 for instance in instances: 

3611 self.add(instance, _warn=False) 

3612 

3613 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3614 state._orphaned_outside_of_session = False 

3615 self._save_or_update_impl(state) 

3616 

3617 mapper = _state_mapper(state) 

3618 for o, m, st_, dct_ in mapper.cascade_iterator( 

3619 "save-update", state, halt_on=self._contains_state 

3620 ): 

3621 self._save_or_update_impl(st_) 

3622 

3623 def delete(self, instance: object) -> None: 

3624 """Mark an instance as deleted. 

3625 

3626 The object is assumed to be either :term:`persistent` or 

3627 :term:`detached` when passed; after the method is called, the 

3628 object will remain in the :term:`persistent` state until the next 

3629 flush proceeds. During this time, the object will also be a member 

3630 of the :attr:`_orm.Session.deleted` collection. 

3631 

3632 When the next flush proceeds, the object will move to the 

3633 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3634 for its row within the current transaction. When the transaction 

3635 is successfully committed, 

3636 the deleted object is moved to the :term:`detached` state and is 

3637 no longer present within this :class:`_orm.Session`. 

3638 

3639 .. seealso:: 

3640 

3641 :ref:`session_deleting` - at :ref:`session_basics` 

3642 

3643 :meth:`.Session.delete_all` - multiple instance version 

3644 

3645 """ 

3646 if self._warn_on_events: 

3647 self._flush_warning("Session.delete()") 

3648 

3649 self._delete_impl(object_state(instance), instance, head=True) 

3650 

3651 def delete_all(self, instances: Iterable[object]) -> None: 

3652 """Calls :meth:`.Session.delete` on multiple instances. 

3653 

3654 .. seealso:: 

3655 

3656 :meth:`.Session.delete` - main documentation on delete 

3657 

3658 .. versionadded:: 2.1 

3659 

3660 """ 

3661 

3662 if self._warn_on_events: 

3663 self._flush_warning("Session.delete_all()") 

3664 

3665 for instance in instances: 

3666 self._delete_impl(object_state(instance), instance, head=True) 

3667 

3668 def _delete_impl( 

3669 self, state: InstanceState[Any], obj: object, head: bool 

3670 ) -> None: 

3671 if state.key is None: 

3672 if head: 

3673 raise sa_exc.InvalidRequestError( 

3674 "Instance '%s' is not persisted" % state_str(state) 

3675 ) 

3676 else: 

3677 return 

3678 

3679 to_attach = self._before_attach(state, obj) 

3680 

3681 if state in self._deleted: 

3682 return 

3683 

3684 self.identity_map.add(state) 

3685 

3686 if to_attach: 

3687 self._after_attach(state, obj) 

3688 

3689 if head: 

3690 # grab the cascades before adding the item to the deleted list 

3691 # so that autoflush does not delete the item 

3692 # the strong reference to the instance itself is significant here 

3693 cascade_states = list( 

3694 state.manager.mapper.cascade_iterator("delete", state) 

3695 ) 

3696 else: 

3697 cascade_states = None 

3698 

3699 self._deleted[state] = obj 

3700 

3701 if head: 

3702 if TYPE_CHECKING: 

3703 assert cascade_states is not None 

3704 for o, m, st_, dct_ in cascade_states: 

3705 self._delete_impl(st_, o, False) 

3706 

3707 def get( 

3708 self, 

3709 entity: _EntityBindKey[_O], 

3710 ident: _PKIdentityArgument, 

3711 *, 

3712 options: Optional[Sequence[ORMOption]] = None, 

3713 populate_existing: bool | None = None, 

3714 with_for_update: ForUpdateParameter = None, 

3715 identity_token: Optional[Any] = None, 

3716 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3717 bind_arguments: Optional[_BindArguments] = None, 

3718 ) -> Optional[_O]: 

3719 """Return an instance based on the given primary key identifier, 

3720 or ``None`` if not found. 

3721 

3722 E.g.:: 

3723 

3724 my_user = session.get(User, 5) 

3725 

3726 some_object = session.get(VersionedFoo, (5, 10)) 

3727 

3728 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3729 

3730 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3731 from the now legacy :meth:`_orm.Query.get` method. 

3732 

3733 :meth:`_orm.Session.get` is special in that it provides direct 

3734 access to the identity map of the :class:`.Session`. 

3735 If the given primary key identifier is present 

3736 in the local identity map, the object is returned 

3737 directly from this collection and no SQL is emitted, 

3738 unless the object has been marked fully expired. 

3739 If not present, 

3740 a SELECT is performed in order to locate the object. 

3741 

3742 :meth:`_orm.Session.get` also will perform a check if 

3743 the object is present in the identity map and 

3744 marked as expired - a SELECT 

3745 is emitted to refresh the object as well as to 

3746 ensure that the row is still present. 

3747 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3748 

3749 :param entity: a mapped class or :class:`.Mapper` indicating the 

3750 type of entity to be loaded. 

3751 

3752 :param ident: A scalar, tuple, or dictionary representing the 

3753 primary key. For a composite (e.g. multiple column) primary key, 

3754 a tuple or dictionary should be passed. 

3755 

3756 For a single-column primary key, the scalar calling form is typically 

3757 the most expedient. If the primary key of a row is the value "5", 

3758 the call looks like:: 

3759 

3760 my_object = session.get(SomeClass, 5) 

3761 

3762 The tuple form contains primary key values typically in 

3763 the order in which they correspond to the mapped 

3764 :class:`_schema.Table` 

3765 object's primary key columns, or if the 

3766 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3767 used, in 

3768 the order used for that parameter. For example, if the primary key 

3769 of a row is represented by the integer 

3770 digits "5, 10" the call would look like:: 

3771 

3772 my_object = session.get(SomeClass, (5, 10)) 

3773 

3774 The dictionary form should include as keys the mapped attribute names 

3775 corresponding to each element of the primary key. If the mapped class 

3776 has the attributes ``id``, ``version_id`` as the attributes which 

3777 store the object's primary key value, the call would look like:: 

3778 

3779 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3780 

3781 :param options: optional sequence of loader options which will be 

3782 applied to the query, if one is emitted. 

3783 

3784 :param populate_existing: causes the method to unconditionally emit 

3785 a SQL query and refresh the object with the newly loaded data, 

3786 regardless of whether or not the object is already present. 

3787 Setting this flag takes precedence over passing it as an 

3788 execution option. 

3789 

3790 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE 

3791 should be used, or may be a dictionary containing flags to 

3792 indicate a more specific set of FOR UPDATE flags for the SELECT; 

3793 flags should match the parameters of 

3794 :meth:`_query.Query.with_for_update`. 

3795 Supersedes the :paramref:`.Session.refresh.lockmode` parameter. 

3796 

3797 :param execution_options: optional dictionary of execution options, 

3798 which will be associated with the query execution if one is emitted. 

3799 This dictionary can provide a subset of the options that are 

3800 accepted by :meth:`_engine.Connection.execution_options`, and may 

3801 also provide additional options understood only in an ORM context. 

3802 

3803 .. versionadded:: 1.4.29 

3804 

3805 .. seealso:: 

3806 

3807 :ref:`orm_queryguide_execution_options` - ORM-specific execution 

3808 options 

3809 

3810 :param bind_arguments: dictionary of additional arguments to determine 

3811 the bind. May include "mapper", "bind", or other custom arguments. 

3812 Contents of this dictionary are passed to the 

3813 :meth:`.Session.get_bind` method. 

3814 

3815 .. versionadded:: 2.0.0rc1 

3816 

3817 :return: The object instance, or ``None``. 

3818 

3819 """ # noqa: E501 

3820 return self._get_impl( 

3821 entity, 

3822 ident, 

3823 loading._load_on_pk_identity, 

3824 options=options, 

3825 populate_existing=populate_existing, 

3826 with_for_update=with_for_update, 

3827 identity_token=identity_token, 

3828 execution_options=execution_options, 

3829 bind_arguments=bind_arguments, 

3830 ) 

3831 

3832 def get_one( 

3833 self, 

3834 entity: _EntityBindKey[_O], 

3835 ident: _PKIdentityArgument, 

3836 *, 

3837 options: Optional[Sequence[ORMOption]] = None, 

3838 populate_existing: bool | None = None, 

3839 with_for_update: ForUpdateParameter = None, 

3840 identity_token: Optional[Any] = None, 

3841 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3842 bind_arguments: Optional[_BindArguments] = None, 

3843 ) -> _O: 

3844 """Return exactly one instance based on the given primary key 

3845 identifier, or raise an exception if not found. 

3846 

3847 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3848 

3849 For a detailed documentation of the arguments see the 

3850 method :meth:`.Session.get`. 

3851 

3852 .. versionadded:: 2.0.22 

3853 

3854 :return: The object instance. 

3855 

3856 .. seealso:: 

3857 

3858 :meth:`.Session.get` - equivalent method that instead 

3859 returns ``None`` if no row was found with the provided primary 

3860 key 

3861 

3862 """ 

3863 

3864 instance = self.get( 

3865 entity, 

3866 ident, 

3867 options=options, 

3868 populate_existing=populate_existing, 

3869 with_for_update=with_for_update, 

3870 identity_token=identity_token, 

3871 execution_options=execution_options, 

3872 bind_arguments=bind_arguments, 

3873 ) 

3874 

3875 if instance is None: 

3876 raise sa_exc.NoResultFound( 

3877 "No row was found when one was required" 

3878 ) 

3879 

3880 return instance 

3881 

3882 def _get_impl( 

3883 self, 

3884 entity: _EntityBindKey[_O], 

3885 primary_key_identity: _PKIdentityArgument, 

3886 db_load_fn: Callable[..., _O], 

3887 *, 

3888 options: Optional[Sequence[ExecutableOption]] = None, 

3889 populate_existing: bool | None = None, 

3890 with_for_update: ForUpdateParameter = None, 

3891 identity_token: Optional[Any] = None, 

3892 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3893 bind_arguments: Optional[_BindArguments] = None, 

3894 ) -> Optional[_O]: 

3895 # set populate_existing value; direct parameter 

3896 # takes precedence over execution_options 

3897 if populate_existing is not None: 

3898 execution_options = { 

3899 **execution_options, # type: ignore[typeddict-item] 

3900 "populate_existing": populate_existing, 

3901 } 

3902 else: 

3903 populate_existing = execution_options.get( 

3904 "populate_existing", False 

3905 ) 

3906 

3907 # convert composite types to individual args 

3908 if ( 

3909 is_composite_class(primary_key_identity) 

3910 and type(primary_key_identity) 

3911 in descriptor_props._composite_getters 

3912 ): 

3913 getter = descriptor_props._composite_getters[ 

3914 type(primary_key_identity) 

3915 ] 

3916 primary_key_identity = getter(primary_key_identity) 

3917 

3918 mapper: Optional[Mapper[_O]] = inspect(entity) 

3919 

3920 if mapper is None or not mapper.is_mapper: 

3921 raise sa_exc.ArgumentError( 

3922 "Expected mapped class or mapper, got: %r" % entity 

3923 ) 

3924 

3925 is_dict = isinstance(primary_key_identity, dict) 

3926 if not is_dict: 

3927 primary_key_identity = util.to_list( 

3928 primary_key_identity, default=[None] 

3929 ) 

3930 

3931 if len(primary_key_identity) != len(mapper.primary_key): 

3932 raise sa_exc.InvalidRequestError( 

3933 "Incorrect number of values in identifier to formulate " 

3934 "primary key for session.get(); primary key columns " 

3935 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3936 ) 

3937 

3938 if is_dict: 

3939 pk_synonyms = mapper._pk_synonyms 

3940 

3941 if pk_synonyms: 

3942 correct_keys = set(pk_synonyms).intersection( 

3943 primary_key_identity 

3944 ) 

3945 

3946 if correct_keys: 

3947 primary_key_identity = dict(primary_key_identity) 

3948 for k in correct_keys: 

3949 primary_key_identity[pk_synonyms[k]] = ( 

3950 primary_key_identity[k] 

3951 ) 

3952 

3953 try: 

3954 primary_key_identity = list( 

3955 primary_key_identity[prop.key] 

3956 for prop in mapper._identity_key_props 

3957 ) 

3958 

3959 except KeyError as err: 

3960 raise sa_exc.InvalidRequestError( 

3961 "Incorrect names of values in identifier to formulate " 

3962 "primary key for session.get(); primary key attribute " 

3963 "names are %s (synonym names are also accepted)" 

3964 % ",".join( 

3965 "'%s'" % prop.key 

3966 for prop in mapper._identity_key_props 

3967 ) 

3968 ) from err 

3969 

3970 for_update_arg = ForUpdateArg._from_argument(with_for_update) 

3971 

3972 if ( 

3973 not populate_existing 

3974 and not mapper.always_refresh 

3975 and for_update_arg is None 

3976 ): 

3977 instance = self._identity_lookup( 

3978 mapper, 

3979 primary_key_identity, 

3980 identity_token=identity_token, 

3981 execution_options=execution_options, 

3982 bind_arguments=bind_arguments, 

3983 ) 

3984 

3985 if instance is not None: 

3986 # reject calls for id in identity map but class 

3987 # mismatch. 

3988 if not isinstance(instance, mapper.class_): 

3989 return None 

3990 return instance 

3991 

3992 # TODO: this was being tested before, but this is not possible 

3993 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3994 

3995 load_options = context.QueryContext.default_load_options 

3996 

3997 if populate_existing: 

3998 load_options += {"_populate_existing": populate_existing} 

3999 statement = sql.select(mapper) 

4000 if for_update_arg is not None: 

4001 statement._for_update_arg = for_update_arg 

4002 

4003 if options: 

4004 statement = statement.options(*options) 

4005 if self.execution_options: 

4006 execution_options = self.execution_options.union(execution_options) 

4007 return db_load_fn( 

4008 self, 

4009 statement, 

4010 primary_key_identity, 

4011 load_options=load_options, 

4012 identity_token=identity_token, 

4013 execution_options=execution_options, 

4014 bind_arguments=bind_arguments, 

4015 ) 

4016 

4017 def merge( 

4018 self, 

4019 instance: _O, 

4020 *, 

4021 load: bool = True, 

4022 options: Optional[Sequence[ORMOption]] = None, 

4023 ) -> _O: 

4024 """Copy the state of a given instance into a corresponding instance 

4025 within this :class:`.Session`. 

4026 

4027 :meth:`.Session.merge` examines the primary key attributes of the 

4028 source instance, and attempts to reconcile it with an instance of the 

4029 same primary key in the session. If not found locally, it attempts 

4030 to load the object from the database based on primary key, and if 

4031 none can be located, creates a new instance. The state of each 

4032 attribute on the source instance is then copied to the target 

4033 instance. The resulting target instance is then returned by the 

4034 method; the original source instance is left unmodified, and 

4035 un-associated with the :class:`.Session` if not already. 

4036 

4037 This operation cascades to associated instances if the association is 

4038 mapped with ``cascade="merge"``. 

4039 

4040 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

4041 

4042 :param instance: Instance to be merged. 

4043 :param load: Boolean, when False, :meth:`.merge` switches into 

4044 a "high performance" mode which causes it to forego emitting history 

4045 events as well as all database access. This flag is used for 

4046 cases such as transferring graphs of objects into a :class:`.Session` 

4047 from a second level cache, or to transfer just-loaded objects 

4048 into the :class:`.Session` owned by a worker thread or process 

4049 without re-querying the database. 

4050 

4051 The ``load=False`` use case adds the caveat that the given 

4052 object has to be in a "clean" state, that is, has no pending changes 

4053 to be flushed - even if the incoming object is detached from any 

4054 :class:`.Session`. This is so that when 

4055 the merge operation populates local attributes and 

4056 cascades to related objects and 

4057 collections, the values can be "stamped" onto the 

4058 target object as is, without generating any history or attribute 

4059 events, and without the need to reconcile the incoming data with 

4060 any existing related objects or collections that might not 

4061 be loaded. The resulting objects from ``load=False`` are always 

4062 produced as "clean", so it is only appropriate that the given objects 

4063 should be "clean" as well, else this suggests a mis-use of the 

4064 method. 

4065 :param options: optional sequence of loader options which will be 

4066 applied to the :meth:`_orm.Session.get` method when the merge 

4067 operation loads the existing version of the object from the database. 

4068 

4069 .. versionadded:: 1.4.24 

4070 

4071 

4072 .. seealso:: 

4073 

4074 :func:`.make_transient_to_detached` - provides for an alternative 

4075 means of "merging" a single object into the :class:`.Session` 

4076 

4077 :meth:`.Session.merge_all` - multiple instance version 

4078 

4079 """ 

4080 

4081 if self._warn_on_events: 

4082 self._flush_warning("Session.merge()") 

4083 

4084 if load: 

4085 # flush current contents if we expect to load data 

4086 self._autoflush() 

4087 

4088 with self.no_autoflush: 

4089 return self._merge( 

4090 object_state(instance), 

4091 attributes.instance_dict(instance), 

4092 load=load, 

4093 options=options, 

4094 _recursive={}, 

4095 _resolve_conflict_map={}, 

4096 ) 

4097 

4098 def merge_all( 

4099 self, 

4100 instances: Iterable[_O], 

4101 *, 

4102 load: bool = True, 

4103 options: Optional[Sequence[ORMOption]] = None, 

4104 ) -> Sequence[_O]: 

4105 """Calls :meth:`.Session.merge` on multiple instances. 

4106 

4107 .. seealso:: 

4108 

4109 :meth:`.Session.merge` - main documentation on merge 

4110 

4111 .. versionadded:: 2.1 

4112 

4113 """ 

4114 

4115 if self._warn_on_events: 

4116 self._flush_warning("Session.merge_all()") 

4117 

4118 if load: 

4119 # flush current contents if we expect to load data 

4120 self._autoflush() 

4121 

4122 return [ 

4123 self._merge( 

4124 object_state(instance), 

4125 attributes.instance_dict(instance), 

4126 load=load, 

4127 options=options, 

4128 _recursive={}, 

4129 _resolve_conflict_map={}, 

4130 ) 

4131 for instance in instances 

4132 ] 

4133 

4134 def _merge( 

4135 self, 

4136 state: InstanceState[_O], 

4137 state_dict: _InstanceDict, 

4138 *, 

4139 options: Optional[Sequence[ORMOption]] = None, 

4140 load: bool, 

4141 _recursive: Dict[Any, object], 

4142 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4143 ) -> _O: 

4144 mapper: Mapper[_O] = _state_mapper(state) 

4145 if state in _recursive: 

4146 return cast(_O, _recursive[state]) 

4147 

4148 new_instance = False 

4149 key = state.key 

4150 

4151 merged: Optional[_O] 

4152 

4153 if key is None: 

4154 if state in self._new: 

4155 util.warn( 

4156 "Instance %s is already pending in this Session yet is " 

4157 "being merged again; this is probably not what you want " 

4158 "to do" % state_str(state) 

4159 ) 

4160 

4161 if not load: 

4162 raise sa_exc.InvalidRequestError( 

4163 "merge() with load=False option does not support " 

4164 "objects transient (i.e. unpersisted) objects. flush() " 

4165 "all changes on mapped instances before merging with " 

4166 "load=False." 

4167 ) 

4168 key = mapper._identity_key_from_state(state) 

4169 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4170 1 

4171 ] and ( 

4172 not _none_set.intersection(key[1]) 

4173 or ( 

4174 mapper.allow_partial_pks 

4175 and not _none_set.issuperset(key[1]) 

4176 ) 

4177 ) 

4178 else: 

4179 key_is_persistent = True 

4180 

4181 merged = self.identity_map.get(key) 

4182 

4183 if merged is None: 

4184 if key_is_persistent and key in _resolve_conflict_map: 

4185 merged = cast(_O, _resolve_conflict_map[key]) 

4186 

4187 elif not load: 

4188 if state.modified: 

4189 raise sa_exc.InvalidRequestError( 

4190 "merge() with load=False option does not support " 

4191 "objects marked as 'dirty'. flush() all changes on " 

4192 "mapped instances before merging with load=False." 

4193 ) 

4194 merged = mapper.class_manager.new_instance() 

4195 merged_state = attributes.instance_state(merged) 

4196 merged_state.key = key 

4197 self._update_impl(merged_state) 

4198 new_instance = True 

4199 

4200 elif key_is_persistent: 

4201 merged = self.get( 

4202 mapper.class_, 

4203 key[1], 

4204 identity_token=key[2], 

4205 options=options, 

4206 ) 

4207 

4208 if merged is None: 

4209 merged = mapper.class_manager.new_instance() 

4210 merged_state = attributes.instance_state(merged) 

4211 merged_dict = attributes.instance_dict(merged) 

4212 new_instance = True 

4213 self._save_or_update_state(merged_state) 

4214 else: 

4215 merged_state = attributes.instance_state(merged) 

4216 merged_dict = attributes.instance_dict(merged) 

4217 

4218 _recursive[state] = merged 

4219 _resolve_conflict_map[key] = merged 

4220 

4221 # check that we didn't just pull the exact same 

4222 # state out. 

4223 if state is not merged_state: 

4224 # version check if applicable 

4225 if mapper.version_id_col is not None: 

4226 existing_version = mapper._get_state_attr_by_column( 

4227 state, 

4228 state_dict, 

4229 mapper.version_id_col, 

4230 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4231 ) 

4232 

4233 merged_version = mapper._get_state_attr_by_column( 

4234 merged_state, 

4235 merged_dict, 

4236 mapper.version_id_col, 

4237 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4238 ) 

4239 

4240 if ( 

4241 existing_version 

4242 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4243 and merged_version 

4244 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4245 and existing_version != merged_version 

4246 ): 

4247 raise exc.StaleDataError( 

4248 "Version id '%s' on merged state %s " 

4249 "does not match existing version '%s'. " 

4250 "Leave the version attribute unset when " 

4251 "merging to update the most recent version." 

4252 % ( 

4253 existing_version, 

4254 state_str(merged_state), 

4255 merged_version, 

4256 ) 

4257 ) 

4258 

4259 merged_state.load_path = state.load_path 

4260 merged_state.load_options = state.load_options 

4261 

4262 # since we are copying load_options, we need to copy 

4263 # the callables_ that would have been generated by those 

4264 # load_options. 

4265 # assumes that the callables we put in state.callables_ 

4266 # are not instance-specific (which they should not be) 

4267 merged_state._copy_callables(state) 

4268 

4269 for prop in mapper.iterate_properties: 

4270 prop.merge( 

4271 self, 

4272 state, 

4273 state_dict, 

4274 merged_state, 

4275 merged_dict, 

4276 load, 

4277 _recursive, 

4278 _resolve_conflict_map, 

4279 ) 

4280 

4281 if not load: 

4282 # remove any history 

4283 merged_state._commit_all(merged_dict, self.identity_map) 

4284 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4285 merged_state, None 

4286 ) 

4287 

4288 if new_instance: 

4289 merged_state.manager.dispatch.load(merged_state, None) 

4290 

4291 return merged 

4292 

4293 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4294 if not self.identity_map.contains_state(state): 

4295 raise sa_exc.InvalidRequestError( 

4296 "Instance '%s' is not persistent within this Session" 

4297 % state_str(state) 

4298 ) 

4299 

4300 def _save_impl(self, state: InstanceState[Any]) -> None: 

4301 if state.key is not None: 

4302 raise sa_exc.InvalidRequestError( 

4303 "Object '%s' already has an identity - " 

4304 "it can't be registered as pending" % state_str(state) 

4305 ) 

4306 

4307 obj = state.obj() 

4308 to_attach = self._before_attach(state, obj) 

4309 if state not in self._new: 

4310 self._new[state] = obj 

4311 state.insert_order = len(self._new) 

4312 if to_attach: 

4313 self._after_attach(state, obj) 

4314 

4315 def _update_impl( 

4316 self, state: InstanceState[Any], revert_deletion: bool = False 

4317 ) -> None: 

4318 if state.key is None: 

4319 raise sa_exc.InvalidRequestError( 

4320 "Instance '%s' is not persisted" % state_str(state) 

4321 ) 

4322 

4323 if state._deleted: 

4324 if revert_deletion: 

4325 if not state._attached: 

4326 return 

4327 del state._deleted 

4328 else: 

4329 raise sa_exc.InvalidRequestError( 

4330 "Instance '%s' has been deleted. " 

4331 "Use the make_transient() " 

4332 "function to send this object back " 

4333 "to the transient state." % state_str(state) 

4334 ) 

4335 

4336 obj = state.obj() 

4337 

4338 # check for late gc 

4339 if obj is None: 

4340 return 

4341 

4342 to_attach = self._before_attach(state, obj) 

4343 

4344 self._deleted.pop(state, None) 

4345 if revert_deletion: 

4346 self.identity_map.replace(state) 

4347 else: 

4348 self.identity_map.add(state) 

4349 

4350 if to_attach: 

4351 self._after_attach(state, obj) 

4352 elif revert_deletion: 

4353 self.dispatch.deleted_to_persistent(self, state) 

4354 

4355 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4356 if state.key is None: 

4357 self._save_impl(state) 

4358 else: 

4359 self._update_impl(state) 

4360 

4361 def enable_relationship_loading(self, obj: object) -> None: 

4362 """Associate an object with this :class:`.Session` for related 

4363 object loading. 

4364 

4365 .. warning:: 

4366 

4367 :meth:`.enable_relationship_loading` exists to serve special 

4368 use cases and is not recommended for general use. 

4369 

4370 Accesses of attributes mapped with :func:`_orm.relationship` 

4371 will attempt to load a value from the database using this 

4372 :class:`.Session` as the source of connectivity. The values 

4373 will be loaded based on foreign key and primary key values 

4374 present on this object - if not present, then those relationships 

4375 will be unavailable. 

4376 

4377 The object will be attached to this session, but will 

4378 **not** participate in any persistence operations; its state 

4379 for almost all purposes will remain either "transient" or 

4380 "detached", except for the case of relationship loading. 

4381 

4382 Also note that backrefs will often not work as expected. 

4383 Altering a relationship-bound attribute on the target object 

4384 may not fire off a backref event, if the effective value 

4385 is what was already loaded from a foreign-key-holding value. 

4386 

4387 The :meth:`.Session.enable_relationship_loading` method is 

4388 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4389 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4390 an object to remain transient while still being able to load 

4391 related items. 

4392 

4393 To make a transient object associated with a :class:`.Session` 

4394 via :meth:`.Session.enable_relationship_loading` pending, add 

4395 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4396 If the object instead represents an existing identity in the database, 

4397 it should be merged using :meth:`.Session.merge`. 

4398 

4399 :meth:`.Session.enable_relationship_loading` does not improve 

4400 behavior when the ORM is used normally - object references should be 

4401 constructed at the object level, not at the foreign key level, so 

4402 that they are present in an ordinary way before flush() 

4403 proceeds. This method is not intended for general use. 

4404 

4405 .. seealso:: 

4406 

4407 :paramref:`_orm.relationship.load_on_pending` - this flag 

4408 allows per-relationship loading of many-to-ones on items that 

4409 are pending. 

4410 

4411 :func:`.make_transient_to_detached` - allows for an object to 

4412 be added to a :class:`.Session` without SQL emitted, which then 

4413 will unexpire attributes on access. 

4414 

4415 """ 

4416 try: 

4417 state = attributes.instance_state(obj) 

4418 except exc.NO_STATE as err: 

4419 raise exc.UnmappedInstanceError(obj) from err 

4420 

4421 to_attach = self._before_attach(state, obj) 

4422 state._load_pending = True 

4423 if to_attach: 

4424 self._after_attach(state, obj) 

4425 

4426 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4427 self._autobegin_t() 

4428 

4429 if state.session_id == self.hash_key: 

4430 return False 

4431 

4432 if state.session_id and state.session_id in _sessions: 

4433 raise sa_exc.InvalidRequestError( 

4434 "Object '%s' is already attached to session '%s' " 

4435 "(this is '%s')" 

4436 % (state_str(state), state.session_id, self.hash_key) 

4437 ) 

4438 

4439 self.dispatch.before_attach(self, state) 

4440 

4441 return True 

4442 

4443 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4444 state.session_id = self.hash_key 

4445 if state.modified and state._strong_obj is None: 

4446 state._strong_obj = obj 

4447 self.dispatch.after_attach(self, state) 

4448 

4449 if state.key: 

4450 self.dispatch.detached_to_persistent(self, state) 

4451 else: 

4452 self.dispatch.transient_to_pending(self, state) 

4453 

4454 def __contains__(self, instance: object) -> bool: 

4455 """Return True if the instance is associated with this session. 

4456 

4457 The instance may be pending or persistent within the Session for a 

4458 result of True. 

4459 

4460 """ 

4461 try: 

4462 state = attributes.instance_state(instance) 

4463 except exc.NO_STATE as err: 

4464 raise exc.UnmappedInstanceError(instance) from err 

4465 return self._contains_state(state) 

4466 

4467 def __iter__(self) -> Iterator[object]: 

4468 """Iterate over all pending or persistent instances within this 

4469 Session. 

4470 

4471 """ 

4472 return iter( 

4473 list(self._new.values()) + list(self.identity_map.values()) 

4474 ) 

4475 

4476 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4477 return state in self._new or self.identity_map.contains_state(state) 

4478 

4479 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4480 """Flush all the object changes to the database. 

4481 

4482 Writes out all pending object creations, deletions and modifications 

4483 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4484 automatically ordered by the Session's unit of work dependency 

4485 solver. 

4486 

4487 Database operations will be issued in the current transactional 

4488 context and do not affect the state of the transaction, unless an 

4489 error occurs, in which case the entire transaction is rolled back. 

4490 You may flush() as often as you like within a transaction to move 

4491 changes from Python to the database's transaction buffer. 

4492 

4493 :param objects: Optional; restricts the flush operation to operate 

4494 only on elements that are in the given collection. 

4495 

4496 This feature is for an extremely narrow set of use cases where 

4497 particular objects may need to be operated upon before the 

4498 full flush() occurs. It is not intended for general use. 

4499 

4500 .. deprecated:: 2.1 

4501 

4502 """ 

4503 

4504 if self._flushing: 

4505 raise sa_exc.InvalidRequestError("Session is already flushing") 

4506 

4507 if self._is_clean(): 

4508 return 

4509 try: 

4510 self._flushing = True 

4511 self._flush(objects) 

4512 finally: 

4513 self._flushing = False 

4514 

4515 def _flush_warning(self, method: Any) -> None: 

4516 util.warn( 

4517 "Usage of the '%s' operation is not currently supported " 

4518 "within the execution stage of the flush process. " 

4519 "Results may not be consistent. Consider using alternative " 

4520 "event listeners or connection-level operations instead." % method 

4521 ) 

4522 

4523 def _is_clean(self) -> bool: 

4524 return ( 

4525 not self.identity_map.check_modified() 

4526 and not self._deleted 

4527 and not self._new 

4528 ) 

4529 

4530 # have this here since it otherwise causes issues with the proxy 

4531 # method generation 

4532 @deprecated_params( 

4533 objects=( 

4534 "2.1", 

4535 "The `objects` parameter of `Session.flush` is deprecated", 

4536 ) 

4537 ) 

4538 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4539 dirty = self._dirty_states 

4540 if not dirty and not self._deleted and not self._new: 

4541 self.identity_map._modified.clear() 

4542 return 

4543 

4544 flush_context = UOWTransaction(self) 

4545 

4546 if self.dispatch.before_flush: 

4547 self.dispatch.before_flush(self, flush_context, objects) 

4548 # re-establish "dirty states" in case the listeners 

4549 # added 

4550 dirty = self._dirty_states 

4551 

4552 deleted = set(self._deleted) 

4553 new = set(self._new) 

4554 

4555 dirty = set(dirty).difference(deleted) 

4556 

4557 # create the set of all objects we want to operate upon 

4558 if objects: 

4559 # specific list passed in 

4560 objset = set() 

4561 for o in objects: 

4562 try: 

4563 state = attributes.instance_state(o) 

4564 

4565 except exc.NO_STATE as err: 

4566 raise exc.UnmappedInstanceError(o) from err 

4567 objset.add(state) 

4568 else: 

4569 objset = None 

4570 

4571 # store objects whose fate has been decided 

4572 processed = set() 

4573 

4574 # put all saves/updates into the flush context. detect top-level 

4575 # orphans and throw them into deleted. 

4576 if objset: 

4577 proc = new.union(dirty).intersection(objset).difference(deleted) 

4578 else: 

4579 proc = new.union(dirty).difference(deleted) 

4580 

4581 for state in proc: 

4582 is_orphan = _state_mapper(state)._is_orphan(state) 

4583 

4584 is_persistent_orphan = is_orphan and state.has_identity 

4585 

4586 if ( 

4587 is_orphan 

4588 and not is_persistent_orphan 

4589 and state._orphaned_outside_of_session 

4590 ): 

4591 self._expunge_states([state]) 

4592 else: 

4593 _reg = flush_context.register_object( 

4594 state, isdelete=is_persistent_orphan 

4595 ) 

4596 assert _reg, "Failed to add object to the flush context!" 

4597 processed.add(state) 

4598 

4599 # put all remaining deletes into the flush context. 

4600 if objset: 

4601 proc = deleted.intersection(objset).difference(processed) 

4602 else: 

4603 proc = deleted.difference(processed) 

4604 for state in proc: 

4605 _reg = flush_context.register_object(state, isdelete=True) 

4606 assert _reg, "Failed to add object to the flush context!" 

4607 

4608 if not flush_context.has_work: 

4609 return 

4610 

4611 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4612 try: 

4613 self._warn_on_events = True 

4614 try: 

4615 flush_context.execute() 

4616 finally: 

4617 self._warn_on_events = False 

4618 

4619 self.dispatch.after_flush(self, flush_context) 

4620 

4621 flush_context.finalize_flush_changes() 

4622 

4623 if not objects and self.identity_map._modified: 

4624 len_ = len(self.identity_map._modified) 

4625 

4626 statelib.InstanceState._commit_all_states( 

4627 [ 

4628 (state, state.dict) 

4629 for state in self.identity_map._modified 

4630 ], 

4631 instance_dict=self.identity_map, 

4632 ) 

4633 util.warn( 

4634 "Attribute history events accumulated on %d " 

4635 "previously clean instances " 

4636 "within inner-flush event handlers have been " 

4637 "reset, and will not result in database updates. " 

4638 "Consider using set_committed_value() within " 

4639 "inner-flush event handlers to avoid this warning." % len_ 

4640 ) 

4641 

4642 # useful assertions: 

4643 # if not objects: 

4644 # assert not self.identity_map._modified 

4645 # else: 

4646 # assert self.identity_map._modified == \ 

4647 # self.identity_map._modified.difference(objects) 

4648 

4649 self.dispatch.after_flush_postexec(self, flush_context) 

4650 

4651 transaction.commit() 

4652 

4653 except: 

4654 with util.safe_reraise(): 

4655 transaction.rollback(_capture_exception=True) 

4656 

4657 def bulk_save_objects( 

4658 self, 

4659 objects: Iterable[object], 

4660 return_defaults: bool = False, 

4661 update_changed_only: bool = True, 

4662 preserve_order: bool = True, 

4663 ) -> None: 

4664 """Perform a bulk save of the given list of objects. 

4665 

4666 .. legacy:: 

4667 

4668 This method is a legacy feature as of the 2.0 series of 

4669 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4670 the sections :ref:`orm_queryguide_bulk_insert` and 

4671 :ref:`orm_queryguide_bulk_update`. 

4672 

4673 For general INSERT and UPDATE of existing ORM mapped objects, 

4674 prefer standard :term:`unit of work` data management patterns, 

4675 introduced in the :ref:`unified_tutorial` at 

4676 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4677 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4678 which solves previous issues of bulk INSERT slowness. 

4679 

4680 :param objects: a sequence of mapped object instances. The mapped 

4681 objects are persisted as is, and are **not** associated with the 

4682 :class:`.Session` afterwards. 

4683 

4684 For each object, whether the object is sent as an INSERT or an 

4685 UPDATE is dependent on the same rules used by the :class:`.Session` 

4686 in traditional operation; if the object has the 

4687 :attr:`.InstanceState.key` 

4688 attribute set, then the object is assumed to be "detached" and 

4689 will result in an UPDATE. Otherwise, an INSERT is used. 

4690 

4691 In the case of an UPDATE, statements are grouped based on which 

4692 attributes have changed, and are thus to be the subject of each 

4693 SET clause. If ``update_changed_only`` is False, then all 

4694 attributes present within each object are applied to the UPDATE 

4695 statement, which may help in allowing the statements to be grouped 

4696 together into a larger executemany(), and will also reduce the 

4697 overhead of checking history on attributes. 

4698 

4699 :param return_defaults: when True, rows that are missing values which 

4700 generate defaults, namely integer primary key defaults and sequences, 

4701 will be inserted **one at a time**, so that the primary key value 

4702 is available. In particular this will allow joined-inheritance 

4703 and other multi-table mappings to insert correctly without the need 

4704 to provide primary key values ahead of time; however, 

4705 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4706 reduces the performance gains** of the method overall. It is strongly 

4707 advised to please use the standard :meth:`_orm.Session.add_all` 

4708 approach. 

4709 

4710 :param update_changed_only: when True, UPDATE statements are rendered 

4711 based on those attributes in each state that have logged changes. 

4712 When False, all attributes present are rendered into the SET clause 

4713 with the exception of primary key attributes. 

4714 

4715 :param preserve_order: when True, the order of inserts and updates 

4716 matches exactly the order in which the objects are given. When 

4717 False, common types of objects are grouped into inserts 

4718 and updates, to allow for more batching opportunities. 

4719 

4720 .. seealso:: 

4721 

4722 :doc:`queryguide/dml` 

4723 

4724 :meth:`.Session.bulk_insert_mappings` 

4725 

4726 :meth:`.Session.bulk_update_mappings` 

4727 

4728 """ 

4729 

4730 obj_states: Iterable[InstanceState[Any]] 

4731 

4732 obj_states = (attributes.instance_state(obj) for obj in objects) 

4733 

4734 if not preserve_order: 

4735 # the purpose of this sort is just so that common mappers 

4736 # and persistence states are grouped together, so that groupby 

4737 # will return a single group for a particular type of mapper. 

4738 # it's not trying to be deterministic beyond that. 

4739 obj_states = sorted( 

4740 obj_states, 

4741 key=lambda state: (id(state.mapper), state.key is not None), 

4742 ) 

4743 

4744 def grouping_key( 

4745 state: InstanceState[_O], 

4746 ) -> Tuple[Mapper[_O], bool]: 

4747 return (state.mapper, state.key is not None) 

4748 

4749 for (mapper, isupdate), states in itertools.groupby( 

4750 obj_states, grouping_key 

4751 ): 

4752 self._bulk_save_mappings( 

4753 mapper, 

4754 states, 

4755 isupdate=isupdate, 

4756 isstates=True, 

4757 return_defaults=return_defaults, 

4758 update_changed_only=update_changed_only, 

4759 render_nulls=False, 

4760 ) 

4761 

4762 def bulk_insert_mappings( 

4763 self, 

4764 mapper: _EntityBindKey[Any], 

4765 mappings: Iterable[Dict[str, Any]], 

4766 return_defaults: bool = False, 

4767 render_nulls: bool = False, 

4768 ) -> None: 

4769 """Perform a bulk insert of the given list of mapping dictionaries. 

4770 

4771 .. legacy:: 

4772 

4773 This method is a legacy feature as of the 2.0 series of 

4774 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4775 the sections :ref:`orm_queryguide_bulk_insert` and 

4776 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4777 implementation details with this method and adds new features 

4778 as well. 

4779 

4780 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4781 object, 

4782 representing the single kind of object represented within the mapping 

4783 list. 

4784 

4785 :param mappings: a sequence of dictionaries, each one containing the 

4786 state of the mapped row to be inserted, in terms of the attribute 

4787 names on the mapped class. If the mapping refers to multiple tables, 

4788 such as a joined-inheritance mapping, each dictionary must contain all 

4789 keys to be populated into all tables. 

4790 

4791 :param return_defaults: when True, the INSERT process will be altered 

4792 to ensure that newly generated primary key values will be fetched. 

4793 The rationale for this parameter is typically to enable 

4794 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4795 be bulk inserted. 

4796 

4797 .. note:: for backends that don't support RETURNING, the 

4798 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4799 parameter can significantly decrease performance as INSERT 

4800 statements can no longer be batched. See 

4801 :ref:`engine_insertmanyvalues` 

4802 for background on which backends are affected. 

4803 

4804 :param render_nulls: When True, a value of ``None`` will result 

4805 in a NULL value being included in the INSERT statement, rather 

4806 than the column being omitted from the INSERT. This allows all 

4807 the rows being INSERTed to have the identical set of columns which 

4808 allows the full set of rows to be batched to the DBAPI. Normally, 

4809 each column-set that contains a different combination of NULL values 

4810 than the previous row must omit a different series of columns from 

4811 the rendered INSERT statement, which means it must be emitted as a 

4812 separate statement. By passing this flag, the full set of rows 

4813 are guaranteed to be batchable into one batch; the cost however is 

4814 that server-side defaults which are invoked by an omitted column will 

4815 be skipped, so care must be taken to ensure that these are not 

4816 necessary. 

4817 

4818 .. warning:: 

4819 

4820 When this flag is set, **server side default SQL values will 

4821 not be invoked** for those columns that are inserted as NULL; 

4822 the NULL value will be sent explicitly. Care must be taken 

4823 to ensure that no server-side default functions need to be 

4824 invoked for the operation as a whole. 

4825 

4826 .. seealso:: 

4827 

4828 :doc:`queryguide/dml` 

4829 

4830 :meth:`.Session.bulk_save_objects` 

4831 

4832 :meth:`.Session.bulk_update_mappings` 

4833 

4834 """ 

4835 self._bulk_save_mappings( 

4836 mapper, 

4837 mappings, 

4838 isupdate=False, 

4839 isstates=False, 

4840 return_defaults=return_defaults, 

4841 update_changed_only=False, 

4842 render_nulls=render_nulls, 

4843 ) 

4844 

4845 def bulk_update_mappings( 

4846 self, mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]] 

4847 ) -> None: 

4848 """Perform a bulk update of the given list of mapping dictionaries. 

4849 

4850 .. legacy:: 

4851 

4852 This method is a legacy feature as of the 2.0 series of 

4853 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4854 the sections :ref:`orm_queryguide_bulk_insert` and 

4855 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4856 implementation details with this method and adds new features 

4857 as well. 

4858 

4859 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4860 object, 

4861 representing the single kind of object represented within the mapping 

4862 list. 

4863 

4864 :param mappings: a sequence of dictionaries, each one containing the 

4865 state of the mapped row to be updated, in terms of the attribute names 

4866 on the mapped class. If the mapping refers to multiple tables, such 

4867 as a joined-inheritance mapping, each dictionary may contain keys 

4868 corresponding to all tables. All those keys which are present and 

4869 are not part of the primary key are applied to the SET clause of the 

4870 UPDATE statement; the primary key values, which are required, are 

4871 applied to the WHERE clause. 

4872 

4873 

4874 .. seealso:: 

4875 

4876 :doc:`queryguide/dml` 

4877 

4878 :meth:`.Session.bulk_insert_mappings` 

4879 

4880 :meth:`.Session.bulk_save_objects` 

4881 

4882 """ 

4883 self._bulk_save_mappings( 

4884 mapper, 

4885 mappings, 

4886 isupdate=True, 

4887 isstates=False, 

4888 return_defaults=False, 

4889 update_changed_only=False, 

4890 render_nulls=False, 

4891 ) 

4892 

4893 def _bulk_save_mappings( 

4894 self, 

4895 mapper: _EntityBindKey[_O], 

4896 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4897 *, 

4898 isupdate: bool, 

4899 isstates: bool, 

4900 return_defaults: bool, 

4901 update_changed_only: bool, 

4902 render_nulls: bool, 

4903 ) -> None: 

4904 mapper = _class_to_mapper(mapper) 

4905 self._flushing = True 

4906 

4907 transaction = self._autobegin_t()._begin() 

4908 try: 

4909 if isupdate: 

4910 bulk_persistence._bulk_update( 

4911 mapper, 

4912 mappings, 

4913 transaction, 

4914 isstates=isstates, 

4915 update_changed_only=update_changed_only, 

4916 ) 

4917 else: 

4918 bulk_persistence._bulk_insert( 

4919 mapper, 

4920 mappings, 

4921 transaction, 

4922 isstates=isstates, 

4923 return_defaults=return_defaults, 

4924 render_nulls=render_nulls, 

4925 ) 

4926 transaction.commit() 

4927 

4928 except: 

4929 with util.safe_reraise(): 

4930 transaction.rollback(_capture_exception=True) 

4931 finally: 

4932 self._flushing = False 

4933 

4934 def is_modified( 

4935 self, instance: object, include_collections: bool = True 

4936 ) -> bool: 

4937 r"""Return ``True`` if the given instance has locally 

4938 modified attributes. 

4939 

4940 This method retrieves the history for each instrumented 

4941 attribute on the instance and performs a comparison of the current 

4942 value to its previously flushed or committed value, if any. 

4943 

4944 It is in effect a more expensive and accurate 

4945 version of checking for the given instance in the 

4946 :attr:`.Session.dirty` collection; a full test for 

4947 each attribute's net "dirty" status is performed. 

4948 

4949 E.g.:: 

4950 

4951 return session.is_modified(someobject) 

4952 

4953 A few caveats to this method apply: 

4954 

4955 * Instances present in the :attr:`.Session.dirty` collection may 

4956 report ``False`` when tested with this method. This is because 

4957 the object may have received change events via attribute mutation, 

4958 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4959 is the same as that loaded from the database, resulting in no net 

4960 change here. 

4961 * Scalar attributes may not have recorded the previously set 

4962 value when a new value was applied, if the attribute was not loaded, 

4963 or was expired, at the time the new value was received - in these 

4964 cases, the attribute is assumed to have a change, even if there is 

4965 ultimately no net change against its database value. SQLAlchemy in 

4966 most cases does not need the "old" value when a set event occurs, so 

4967 it skips the expense of a SQL call if the old value isn't present, 

4968 based on the assumption that an UPDATE of the scalar value is 

4969 usually needed, and in those few cases where it isn't, is less 

4970 expensive on average than issuing a defensive SELECT. 

4971 

4972 The "old" value is fetched unconditionally upon set only if the 

4973 attribute container has the ``active_history`` flag set to ``True``. 

4974 This flag is set typically for primary key attributes and scalar 

4975 object references that are not a simple many-to-one. To set this 

4976 flag for any arbitrary mapped column, use the ``active_history`` 

4977 argument with :func:`.column_property`. 

4978 

4979 :param instance: mapped instance to be tested for pending changes. 

4980 :param include_collections: Indicates if multivalued collections 

4981 should be included in the operation. Setting this to ``False`` is a 

4982 way to detect only local-column based properties (i.e. scalar columns 

4983 or many-to-one foreign keys) that would result in an UPDATE for this 

4984 instance upon flush. 

4985 

4986 """ 

4987 state = object_state(instance) 

4988 

4989 if not state.modified: 

4990 return False 

4991 

4992 dict_ = state.dict 

4993 

4994 for attr in state.manager.attributes: 

4995 if ( 

4996 not include_collections 

4997 and hasattr(attr.impl, "get_collection") 

4998 ) or not hasattr(attr.impl, "get_history"): 

4999 continue 

5000 

5001 added, unchanged, deleted = attr.impl.get_history( 

5002 state, dict_, passive=PassiveFlag.NO_CHANGE 

5003 ) 

5004 

5005 if added or deleted: 

5006 return True 

5007 else: 

5008 return False 

5009 

5010 @property 

5011 def is_active(self) -> bool: 

5012 """True if this :class:`.Session` not in "partial rollback" state. 

5013 

5014 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

5015 a new transaction immediately, so this attribute will be False 

5016 when the :class:`_orm.Session` is first instantiated. 

5017 

5018 "partial rollback" state typically indicates that the flush process 

5019 of the :class:`_orm.Session` has failed, and that the 

5020 :meth:`_orm.Session.rollback` method must be emitted in order to 

5021 fully roll back the transaction. 

5022 

5023 If this :class:`_orm.Session` is not in a transaction at all, the 

5024 :class:`_orm.Session` will autobegin when it is first used, so in this 

5025 case :attr:`_orm.Session.is_active` will return True. 

5026 

5027 Otherwise, if this :class:`_orm.Session` is within a transaction, 

5028 and that transaction has not been rolled back internally, the 

5029 :attr:`_orm.Session.is_active` will also return True. 

5030 

5031 .. seealso:: 

5032 

5033 :ref:`faq_session_rollback` 

5034 

5035 :meth:`_orm.Session.in_transaction` 

5036 

5037 """ 

5038 return self._transaction is None or self._transaction.is_active 

5039 

5040 @property 

5041 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

5042 """The set of all persistent states considered dirty. 

5043 

5044 This method returns all states that were modified including 

5045 those that were possibly deleted. 

5046 

5047 """ 

5048 return self.identity_map._dirty_states() 

5049 

5050 @property 

5051 def dirty(self) -> IdentitySet: 

5052 """The set of all persistent instances considered dirty. 

5053 

5054 E.g.:: 

5055 

5056 some_mapped_object in session.dirty 

5057 

5058 Instances are considered dirty when they were modified but not 

5059 deleted. 

5060 

5061 Note that this 'dirty' calculation is 'optimistic'; most 

5062 attribute-setting or collection modification operations will 

5063 mark an instance as 'dirty' and place it in this set, even if 

5064 there is no net change to the attribute's value. At flush 

5065 time, the value of each attribute is compared to its 

5066 previously saved value, and if there's no net change, no SQL 

5067 operation will occur (this is a more expensive operation so 

5068 it's only done at flush time). 

5069 

5070 To check if an instance has actionable net changes to its 

5071 attributes, use the :meth:`.Session.is_modified` method. 

5072 

5073 """ 

5074 return IdentitySet( 

5075 [ 

5076 state.obj() 

5077 for state in self._dirty_states 

5078 if state not in self._deleted 

5079 ] 

5080 ) 

5081 

5082 @property 

5083 def deleted(self) -> IdentitySet: 

5084 "The set of all instances marked as 'deleted' within this ``Session``" 

5085 

5086 return util.IdentitySet(list(self._deleted.values())) 

5087 

5088 @property 

5089 def new(self) -> IdentitySet: 

5090 "The set of all instances marked as 'new' within this ``Session``." 

5091 

5092 return util.IdentitySet(list(self._new.values())) 

5093 

5094 

5095_S = TypeVar("_S", bound="Session") 

5096 

5097 

5098class sessionmaker(_SessionClassMethods, Generic[_S]): 

5099 """A configurable :class:`.Session` factory. 

5100 

5101 The :class:`.sessionmaker` factory generates new 

5102 :class:`.Session` objects when called, creating them given 

5103 the configurational arguments established here. 

5104 

5105 e.g.:: 

5106 

5107 from sqlalchemy import create_engine 

5108 from sqlalchemy.orm import sessionmaker 

5109 

5110 # an Engine, which the Session will use for connection 

5111 # resources 

5112 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

5113 

5114 Session = sessionmaker(engine) 

5115 

5116 with Session() as session: 

5117 session.add(some_object) 

5118 session.add(some_other_object) 

5119 session.commit() 

5120 

5121 Context manager use is optional; otherwise, the returned 

5122 :class:`_orm.Session` object may be closed explicitly via the 

5123 :meth:`_orm.Session.close` method. Using a 

5124 ``try:/finally:`` block is optional, however will ensure that the close 

5125 takes place even if there are database errors:: 

5126 

5127 session = Session() 

5128 try: 

5129 session.add(some_object) 

5130 session.add(some_other_object) 

5131 session.commit() 

5132 finally: 

5133 session.close() 

5134 

5135 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5136 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5137 for :class:`_engine.Connection` objects. In this way it also includes 

5138 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5139 manager which both begins and commits a transaction, as well as closes 

5140 out the :class:`_orm.Session` when complete, rolling back the transaction 

5141 if any errors occur:: 

5142 

5143 Session = sessionmaker(engine) 

5144 

5145 with Session.begin() as session: 

5146 session.add(some_object) 

5147 session.add(some_other_object) 

5148 # commits transaction, closes session 

5149 

5150 .. versionadded:: 1.4 

5151 

5152 When calling upon :class:`_orm.sessionmaker` to construct a 

5153 :class:`_orm.Session`, keyword arguments may also be passed to the 

5154 method; these arguments will override that of the globally configured 

5155 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5156 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5157 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5158 

5159 Session = sessionmaker(engine) 

5160 

5161 # bind an individual session to a connection 

5162 

5163 with engine.connect() as connection: 

5164 with Session(bind=connection) as session: 

5165 ... # work with session 

5166 

5167 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5168 can be used to specify additional keyword arguments to the factory, which 

5169 will take effect for subsequent :class:`.Session` objects generated. This 

5170 is usually used to associate one or more :class:`_engine.Engine` objects 

5171 with an existing 

5172 :class:`.sessionmaker` factory before it is first used:: 

5173 

5174 # application starts, sessionmaker does not have 

5175 # an engine bound yet 

5176 Session = sessionmaker() 

5177 

5178 # ... later, when an engine URL is read from a configuration 

5179 # file or other events allow the engine to be created 

5180 engine = create_engine("sqlite:///foo.db") 

5181 Session.configure(bind=engine) 

5182 

5183 sess = Session() 

5184 # work with session 

5185 

5186 .. seealso:: 

5187 

5188 :ref:`session_getting` - introductory text on creating 

5189 sessions using :class:`.sessionmaker`. 

5190 

5191 """ 

5192 

5193 class_: Type[_S] 

5194 

5195 @overload 

5196 def __init__( 

5197 self, 

5198 bind: Optional[_SessionBind] = ..., 

5199 *, 

5200 class_: Type[_S], 

5201 autoflush: bool = ..., 

5202 expire_on_commit: bool = ..., 

5203 info: Optional[_InfoType] = ..., 

5204 **kw: Any, 

5205 ): ... 

5206 

5207 @overload 

5208 def __init__( 

5209 self: "sessionmaker[Session]", 

5210 bind: Optional[_SessionBind] = ..., 

5211 *, 

5212 autoflush: bool = ..., 

5213 expire_on_commit: bool = ..., 

5214 info: Optional[_InfoType] = ..., 

5215 **kw: Any, 

5216 ): ... 

5217 

5218 def __init__( 

5219 self, 

5220 bind: Optional[_SessionBind] = None, 

5221 *, 

5222 class_: Type[_S] = Session, # type: ignore 

5223 autoflush: bool = True, 

5224 expire_on_commit: bool = True, 

5225 info: Optional[_InfoType] = None, 

5226 **kw: Any, 

5227 ): 

5228 r"""Construct a new :class:`.sessionmaker`. 

5229 

5230 All arguments here except for ``class_`` correspond to arguments 

5231 accepted by :class:`.Session` directly. See the 

5232 :meth:`.Session.__init__` docstring for more details on parameters. 

5233 

5234 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5235 with 

5236 which newly created :class:`.Session` objects will be associated. 

5237 :param class\_: class to use in order to create new :class:`.Session` 

5238 objects. Defaults to :class:`.Session`. 

5239 :param autoflush: The autoflush setting to use with newly created 

5240 :class:`.Session` objects. 

5241 

5242 .. seealso:: 

5243 

5244 :ref:`session_flushing` - additional background on autoflush 

5245 

5246 :param expire_on_commit=True: the 

5247 :paramref:`_orm.Session.expire_on_commit` setting to use 

5248 with newly created :class:`.Session` objects. 

5249 

5250 :param info: optional dictionary of information that will be available 

5251 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5252 replaced, when the ``info`` parameter is specified to the specific 

5253 :class:`.Session` construction operation. 

5254 

5255 :param \**kw: all other keyword arguments are passed to the 

5256 constructor of newly created :class:`.Session` objects. 

5257 

5258 """ 

5259 kw["bind"] = bind 

5260 kw["autoflush"] = autoflush 

5261 kw["expire_on_commit"] = expire_on_commit 

5262 if info is not None: 

5263 kw["info"] = info 

5264 self.kw = kw 

5265 # make our own subclass of the given class, so that 

5266 # events can be associated with it specifically. 

5267 self.class_ = type(class_.__name__, (class_,), {}) 

5268 

5269 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5270 """Produce a context manager that both provides a new 

5271 :class:`_orm.Session` as well as a transaction that commits. 

5272 

5273 

5274 e.g.:: 

5275 

5276 Session = sessionmaker(some_engine) 

5277 

5278 with Session.begin() as session: 

5279 session.add(some_object) 

5280 

5281 # commits transaction, closes session 

5282 

5283 .. versionadded:: 1.4 

5284 

5285 

5286 """ 

5287 

5288 session = self() 

5289 return session._maker_context_manager() 

5290 

5291 def __call__(self, **local_kw: Any) -> _S: 

5292 """Produce a new :class:`.Session` object using the configuration 

5293 established in this :class:`.sessionmaker`. 

5294 

5295 In Python, the ``__call__`` method is invoked on an object when 

5296 it is "called" in the same way as a function:: 

5297 

5298 Session = sessionmaker(some_engine) 

5299 session = Session() # invokes sessionmaker.__call__() 

5300 

5301 """ 

5302 for k, v in self.kw.items(): 

5303 if k == "info" and "info" in local_kw: 

5304 d = v.copy() 

5305 d.update(local_kw["info"]) 

5306 local_kw["info"] = d 

5307 else: 

5308 local_kw.setdefault(k, v) 

5309 return self.class_(**local_kw) 

5310 

5311 def configure(self, **new_kw: Any) -> None: 

5312 """(Re)configure the arguments for this sessionmaker. 

5313 

5314 e.g.:: 

5315 

5316 Session = sessionmaker() 

5317 

5318 Session.configure(bind=create_engine("sqlite://")) 

5319 """ 

5320 self.kw.update(new_kw) 

5321 

5322 def __repr__(self) -> str: 

5323 return "%s(class_=%r, %s)" % ( 

5324 self.__class__.__name__, 

5325 self.class_.__name__, 

5326 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5327 ) 

5328 

5329 

5330def close_all_sessions() -> None: 

5331 """Close all sessions in memory. 

5332 

5333 This function consults a global registry of all :class:`.Session` objects 

5334 and calls :meth:`.Session.close` on them, which resets them to a clean 

5335 state. 

5336 

5337 This function is not for general use but may be useful for test suites 

5338 within the teardown scheme. 

5339 

5340 """ 

5341 

5342 for sess in _sessions.values(): 

5343 sess.close() 

5344 

5345 

5346def make_transient(instance: object) -> None: 

5347 """Alter the state of the given instance so that it is :term:`transient`. 

5348 

5349 .. note:: 

5350 

5351 :func:`.make_transient` is a special-case function for 

5352 advanced use cases only. 

5353 

5354 The given mapped instance is assumed to be in the :term:`persistent` or 

5355 :term:`detached` state. The function will remove its association with any 

5356 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5357 effect is that the object will behave as though it were newly constructed, 

5358 except retaining any attribute / collection values that were loaded at the 

5359 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5360 if this object had been deleted as a result of using 

5361 :meth:`.Session.delete`. 

5362 

5363 .. warning:: 

5364 

5365 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5366 load ORM-mapped attributes that are not currently loaded at the time 

5367 the function is called. This includes attributes which: 

5368 

5369 * were expired via :meth:`.Session.expire` 

5370 

5371 * were expired as the natural effect of committing a session 

5372 transaction, e.g. :meth:`.Session.commit` 

5373 

5374 * are normally :term:`lazy loaded` but are not currently loaded 

5375 

5376 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5377 not yet loaded 

5378 

5379 * were not present in the query which loaded this object, such as that 

5380 which is common in joined table inheritance and other scenarios. 

5381 

5382 After :func:`.make_transient` is called, unloaded attributes such 

5383 as those above will normally resolve to the value ``None`` when 

5384 accessed, or an empty collection for a collection-oriented attribute. 

5385 As the object is transient and un-associated with any database 

5386 identity, it will no longer retrieve these values. 

5387 

5388 .. seealso:: 

5389 

5390 :func:`.make_transient_to_detached` 

5391 

5392 """ 

5393 state = attributes.instance_state(instance) 

5394 s = _state_session(state) 

5395 if s: 

5396 s._expunge_states([state]) 

5397 

5398 # remove expired state 

5399 state.expired_attributes.clear() 

5400 

5401 # remove deferred callables 

5402 if state.callables: 

5403 del state.callables 

5404 

5405 if state.key: 

5406 del state.key 

5407 if state._deleted: 

5408 del state._deleted 

5409 

5410 

5411def make_transient_to_detached(instance: object) -> None: 

5412 """Make the given transient instance :term:`detached`. 

5413 

5414 .. note:: 

5415 

5416 :func:`.make_transient_to_detached` is a special-case function for 

5417 advanced use cases only. 

5418 

5419 All attribute history on the given instance 

5420 will be reset as though the instance were freshly loaded 

5421 from a query. Missing attributes will be marked as expired. 

5422 The primary key attributes of the object, which are required, will be made 

5423 into the "key" of the instance. 

5424 

5425 The object can then be added to a session, or merged 

5426 possibly with the load=False flag, at which point it will look 

5427 as if it were loaded that way, without emitting SQL. 

5428 

5429 This is a special use case function that differs from a normal 

5430 call to :meth:`.Session.merge` in that a given persistent state 

5431 can be manufactured without any SQL calls. 

5432 

5433 .. seealso:: 

5434 

5435 :func:`.make_transient` 

5436 

5437 :meth:`.Session.enable_relationship_loading` 

5438 

5439 """ 

5440 state = attributes.instance_state(instance) 

5441 if state.session_id or state.key: 

5442 raise sa_exc.InvalidRequestError("Given object must be transient") 

5443 state.key = state.mapper._identity_key_from_state(state) 

5444 if state._deleted: 

5445 del state._deleted 

5446 state._commit_all(state.dict) 

5447 state._expire_attributes(state.dict, state.unloaded) 

5448 

5449 

5450def object_session(instance: object) -> Optional[Session]: 

5451 """Return the :class:`.Session` to which the given instance belongs. 

5452 

5453 This is essentially the same as the :attr:`.InstanceState.session` 

5454 accessor. See that attribute for details. 

5455 

5456 """ 

5457 

5458 try: 

5459 state = attributes.instance_state(instance) 

5460 except exc.NO_STATE as err: 

5461 raise exc.UnmappedInstanceError(instance) from err 

5462 else: 

5463 return _state_session(state) 

5464 

5465 

5466_new_sessionid = util.counter()