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

1450 statements  

1# orm/session.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

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

9 

10from __future__ import annotations 

11 

12import contextlib 

13from enum import Enum 

14import itertools 

15import sys 

16import typing 

17from typing import Any 

18from typing import Callable 

19from typing import cast 

20from typing import Dict 

21from typing import Generic 

22from typing import Iterable 

23from typing import Iterator 

24from typing import List 

25from typing import 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 TupleAny 

95from ..util.typing import TypeVarTuple 

96from ..util.typing import Unpack 

97 

98 

99if typing.TYPE_CHECKING: 

100 from ._typing import _EntityType 

101 from ._typing import _IdentityKeyType 

102 from ._typing import _InstanceDict 

103 from ._typing import OrmExecuteOptionsParameter 

104 from .interfaces import ORMOption 

105 from .interfaces import UserDefinedOption 

106 from .mapper import Mapper 

107 from .path_registry import PathRegistry 

108 from .query import RowReturningQuery 

109 from ..engine import 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: Optional[_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 

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

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

379 

380 def invoke_statement( 

381 self, 

382 statement: Optional[Executable] = None, 

383 params: Optional[_CoreAnyExecuteParams] = None, 

384 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

385 bind_arguments: Optional[_BindArguments] = None, 

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

387 """Execute the statement represented by this 

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

389 already proceeded. 

390 

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

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

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

394 that want to override how the ultimate 

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

396 retrieve results from an offline cache or which concatenate results 

397 from multiple executions. 

398 

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

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

401 is propagated to the calling 

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

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

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

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

406 

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

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

409 

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

411 which will be merged into the existing 

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

413 

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

415 for executemany executions. 

416 

417 :param execution_options: optional dictionary of execution options 

418 will be merged into the existing 

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

420 :class:`.ORMExecuteState`. 

421 

422 :param bind_arguments: optional dictionary of bind_arguments 

423 which will be merged amongst the current 

424 :attr:`.ORMExecuteState.bind_arguments` 

425 of this :class:`.ORMExecuteState`. 

426 

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

428 

429 .. seealso:: 

430 

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

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

433 

434 

435 """ 

436 

437 if statement is None: 

438 statement = self.statement 

439 

440 _bind_arguments = dict(self.bind_arguments) 

441 if bind_arguments: 

442 _bind_arguments.update(bind_arguments) 

443 _bind_arguments["_sa_skip_events"] = True 

444 

445 _params: Optional[_CoreAnyExecuteParams] 

446 if params: 

447 if self.is_executemany: 

448 _params = [] 

449 exec_many_parameters = cast( 

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

451 ) 

452 for _existing_params, _new_params in itertools.zip_longest( 

453 exec_many_parameters, 

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

455 ): 

456 if _existing_params is None or _new_params is None: 

457 raise sa_exc.InvalidRequestError( 

458 f"Can't apply executemany parameters to " 

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

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

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

462 f"to ORMExecuteState.invoke_statement() " 

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

464 ) 

465 _existing_params = dict(_existing_params) 

466 _existing_params.update(_new_params) 

467 _params.append(_existing_params) 

468 else: 

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

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

471 else: 

472 _params = self.parameters 

473 

474 _execution_options = self.local_execution_options 

475 if execution_options: 

476 _execution_options = _execution_options.union(execution_options) 

477 

478 return self.session._execute_internal( 

479 statement, 

480 _params, 

481 execution_options=_execution_options, 

482 bind_arguments=_bind_arguments, 

483 _parent_execute_state=self, 

484 ) 

485 

486 @property 

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

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

489 

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

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

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

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

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

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

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

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

498 would be selected. 

499 

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

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

502 way of getting this mapper. 

503 

504 .. versionadded:: 1.4.0b2 

505 

506 .. seealso:: 

507 

508 :attr:`_orm.ORMExecuteState.all_mappers` 

509 

510 

511 """ 

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

513 return mp 

514 

515 @property 

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

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

518 involved at the top level of this statement. 

519 

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

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

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

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

524 

525 .. versionadded:: 1.4.0b2 

526 

527 .. seealso:: 

528 

529 :attr:`_orm.ORMExecuteState.bind_mapper` 

530 

531 

532 

533 """ 

534 if not self.is_orm_statement: 

535 return [] 

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

537 result = [] 

538 seen = set() 

539 for d in self.statement.column_descriptions: 

540 ent = d["entity"] 

541 if ent: 

542 insp = inspect(ent, raiseerr=False) 

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

544 seen.add(insp.mapper) 

545 result.append(insp.mapper) 

546 return result 

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

548 return [self.bind_mapper] 

549 else: 

550 return [] 

551 

552 @property 

553 def is_orm_statement(self) -> bool: 

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

555 

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

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

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

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

560 and no ORM-level automation takes place. 

561 

562 """ 

563 return self._compile_state_cls is not None 

564 

565 @property 

566 def is_executemany(self) -> bool: 

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

568 dictionaries with more than one dictionary. 

569 

570 .. versionadded:: 2.0 

571 

572 """ 

573 return isinstance(self.parameters, list) 

574 

575 @property 

576 def is_select(self) -> bool: 

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

578 

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

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

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

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

583 

584 """ 

585 return self.statement.is_select 

586 

587 @property 

588 def is_from_statement(self) -> bool: 

589 """return True if this operation is a 

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

591 

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

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

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

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

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

597 :class:`_sql.Select` construct. 

598 

599 .. versionadded:: 2.0.30 

600 

601 """ 

602 return self.statement.is_from_statement 

603 

604 @property 

605 def is_insert(self) -> bool: 

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

607 

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

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

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

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

612 

613 """ 

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

615 

616 @property 

617 def is_update(self) -> bool: 

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

619 

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

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

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

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

624 

625 """ 

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

627 

628 @property 

629 def is_delete(self) -> bool: 

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

631 

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

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

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

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

636 

637 """ 

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

639 

640 @property 

641 def _is_crud(self) -> bool: 

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

643 

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

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

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

647 

648 def _orm_compile_options( 

649 self, 

650 ) -> Optional[ 

651 Union[ 

652 context._ORMCompileState.default_compile_options, 

653 Type[context._ORMCompileState.default_compile_options], 

654 ] 

655 ]: 

656 if not self.is_select: 

657 return None 

658 try: 

659 opts = self.statement._compile_options 

660 except AttributeError: 

661 return None 

662 

663 if opts is not None and opts.isinstance( 

664 context._ORMCompileState.default_compile_options 

665 ): 

666 return opts # type: ignore 

667 else: 

668 return None 

669 

670 @property 

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

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

673 for a lazy load operation. 

674 

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

676 sharding extension, where it is available within specific query 

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

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

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

680 compilation time. 

681 

682 """ 

683 return self.load_options._lazy_loaded_from 

684 

685 @property 

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

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

688 

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

690 when a particular object or collection is being loaded. 

691 

692 """ 

693 opts = self._orm_compile_options() 

694 if opts is not None: 

695 return opts._current_path 

696 else: 

697 return None 

698 

699 @property 

700 def is_column_load(self) -> bool: 

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

702 attributes on an existing ORM object. 

703 

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

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

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

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

708 loaded. 

709 

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

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

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

713 and loader options travelling with the instance 

714 will have already been added to the query. 

715 

716 .. versionadded:: 1.4.0b2 

717 

718 .. seealso:: 

719 

720 :attr:`_orm.ORMExecuteState.is_relationship_load` 

721 

722 """ 

723 opts = self._orm_compile_options() 

724 return opts is not None and opts._for_refresh_state 

725 

726 @property 

727 def is_relationship_load(self) -> bool: 

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

729 relationship. 

730 

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

732 SelectInLoader, SubqueryLoader, or similar, and the entire 

733 SELECT statement being emitted is on behalf of a relationship 

734 load. 

735 

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

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

738 capable of being propagated to relationship loaders and should 

739 be already present. 

740 

741 .. seealso:: 

742 

743 :attr:`_orm.ORMExecuteState.is_column_load` 

744 

745 """ 

746 opts = self._orm_compile_options() 

747 if opts is None: 

748 return False 

749 path = self.loader_strategy_path 

750 return path is not None and not path.is_root 

751 

752 @property 

753 def load_options( 

754 self, 

755 ) -> Union[ 

756 context.QueryContext.default_load_options, 

757 Type[context.QueryContext.default_load_options], 

758 ]: 

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

760 

761 if not self.is_select: 

762 raise sa_exc.InvalidRequestError( 

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

764 "so there are no load options." 

765 ) 

766 

767 lo: Union[ 

768 context.QueryContext.default_load_options, 

769 Type[context.QueryContext.default_load_options], 

770 ] = self.execution_options.get( 

771 "_sa_orm_load_options", context.QueryContext.default_load_options 

772 ) 

773 return lo 

774 

775 @property 

776 def update_delete_options( 

777 self, 

778 ) -> Union[ 

779 bulk_persistence._BulkUDCompileState.default_update_options, 

780 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

781 ]: 

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

783 execution.""" 

784 

785 if not self._is_crud: 

786 raise sa_exc.InvalidRequestError( 

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

788 "statement so there are no update options." 

789 ) 

790 uo: Union[ 

791 bulk_persistence._BulkUDCompileState.default_update_options, 

792 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

793 ] = self.execution_options.get( 

794 "_sa_orm_update_options", 

795 bulk_persistence._BulkUDCompileState.default_update_options, 

796 ) 

797 return uo 

798 

799 @property 

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

801 return [ 

802 opt 

803 for opt in self.statement._with_options 

804 if is_orm_option(opt) and not opt._is_compile_state 

805 ] 

806 

807 @property 

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

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

810 associated with the statement being invoked. 

811 

812 """ 

813 return [ 

814 opt 

815 for opt in self.statement._with_options 

816 if is_user_defined_option(opt) 

817 ] 

818 

819 

820class SessionTransactionOrigin(Enum): 

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

822 

823 This enumeration is present on the 

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

825 :class:`.SessionTransaction` object. 

826 

827 .. versionadded:: 2.0 

828 

829 """ 

830 

831 AUTOBEGIN = 0 

832 """transaction were started by autobegin""" 

833 

834 BEGIN = 1 

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

836 

837 BEGIN_NESTED = 2 

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

839 

840 SUBTRANSACTION = 3 

841 """transaction is an internal "subtransaction" """ 

842 

843 

844class SessionTransaction(_StateChange, TransactionalContext): 

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

846 

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

848 :meth:`_orm.Session.begin` 

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

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

851 transactions. 

852 

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

854 at: :ref:`unitofwork_transaction`. 

855 

856 

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

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

859 

860 .. seealso:: 

861 

862 :ref:`unitofwork_transaction` 

863 

864 :meth:`.Session.begin` 

865 

866 :meth:`.Session.begin_nested` 

867 

868 :meth:`.Session.rollback` 

869 

870 :meth:`.Session.commit` 

871 

872 :meth:`.Session.in_transaction` 

873 

874 :meth:`.Session.in_nested_transaction` 

875 

876 :meth:`.Session.get_transaction` 

877 

878 :meth:`.Session.get_nested_transaction` 

879 

880 

881 """ 

882 

883 _rollback_exception: Optional[BaseException] = None 

884 

885 _connections: Dict[ 

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

887 ] 

888 session: Session 

889 _parent: Optional[SessionTransaction] 

890 

891 _state: SessionTransactionState 

892 

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

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

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

896 _key_switches: weakref.WeakKeyDictionary[ 

897 InstanceState[Any], Tuple[Any, Any] 

898 ] 

899 

900 origin: SessionTransactionOrigin 

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

902 

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

904 enumeration indicating the source event that led to constructing 

905 this :class:`_orm.SessionTransaction`. 

906 

907 .. versionadded:: 2.0 

908 

909 """ 

910 

911 nested: bool = False 

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

913 

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

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

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

917 

918 .. seealso:: 

919 

920 :attr:`.SessionTransaction.origin` 

921 

922 """ 

923 

924 def __init__( 

925 self, 

926 session: Session, 

927 origin: SessionTransactionOrigin, 

928 parent: Optional[SessionTransaction] = None, 

929 ): 

930 TransactionalContext._trans_ctx_check(session) 

931 

932 self.session = session 

933 self._connections = {} 

934 self._parent = parent 

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

936 self.origin = origin 

937 

938 if session._close_state is _SessionCloseState.CLOSED: 

939 raise sa_exc.InvalidRequestError( 

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

941 "to handle any more transaction requests." 

942 ) 

943 

944 if nested: 

945 if not parent: 

946 raise sa_exc.InvalidRequestError( 

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

948 "transaction is in progress" 

949 ) 

950 

951 self._previous_nested_transaction = session._nested_transaction 

952 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

953 assert parent is not None 

954 else: 

955 assert parent is None 

956 

957 self._state = SessionTransactionState.ACTIVE 

958 

959 self._take_snapshot() 

960 

961 # make sure transaction is assigned before we call the 

962 # dispatch 

963 self.session._transaction = self 

964 

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

966 

967 def _raise_for_prerequisite_state( 

968 self, operation_name: str, state: _StateChangeState 

969 ) -> NoReturn: 

970 if state is SessionTransactionState.DEACTIVE: 

971 if self._rollback_exception: 

972 raise sa_exc.PendingRollbackError( 

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

974 "due to a previous exception during flush." 

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

976 "first issue Session.rollback()." 

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

978 code="7s2a", 

979 ) 

980 else: 

981 raise sa_exc.InvalidRequestError( 

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

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

984 "can be emitted within this transaction." 

985 ) 

986 elif state is SessionTransactionState.CLOSED: 

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

988 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

989 raise sa_exc.InvalidRequestError( 

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

991 "operations are not permitted", 

992 code="isce", 

993 ) 

994 else: 

995 raise sa_exc.InvalidRequestError( 

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

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

998 ) 

999 

1000 @property 

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

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

1003 :class:`.SessionTransaction`. 

1004 

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

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

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

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

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

1010 "nested" / SAVEPOINT transaction. If the 

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

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

1013 

1014 """ 

1015 return self._parent 

1016 

1017 @property 

1018 def is_active(self) -> bool: 

1019 return ( 

1020 self.session is not None 

1021 and self._state is SessionTransactionState.ACTIVE 

1022 ) 

1023 

1024 @property 

1025 def _is_transaction_boundary(self) -> bool: 

1026 return self.nested or not self._parent 

1027 

1028 @_StateChange.declare_states( 

1029 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1030 ) 

1031 def connection( 

1032 self, 

1033 bindkey: Optional[Mapper[Any]], 

1034 execution_options: Optional[_ExecuteOptions] = None, 

1035 **kwargs: Any, 

1036 ) -> Connection: 

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

1038 return self._connection_for_bind(bind, execution_options) 

1039 

1040 @_StateChange.declare_states( 

1041 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1042 ) 

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

1044 return SessionTransaction( 

1045 self.session, 

1046 ( 

1047 SessionTransactionOrigin.BEGIN_NESTED 

1048 if nested 

1049 else SessionTransactionOrigin.SUBTRANSACTION 

1050 ), 

1051 self, 

1052 ) 

1053 

1054 def _iterate_self_and_parents( 

1055 self, upto: Optional[SessionTransaction] = None 

1056 ) -> Iterable[SessionTransaction]: 

1057 current = self 

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

1059 while current: 

1060 result += (current,) 

1061 if current._parent is upto: 

1062 break 

1063 elif current._parent is None: 

1064 raise sa_exc.InvalidRequestError( 

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

1066 % (upto) 

1067 ) 

1068 else: 

1069 current = current._parent 

1070 

1071 return result 

1072 

1073 def _take_snapshot(self) -> None: 

1074 if not self._is_transaction_boundary: 

1075 parent = self._parent 

1076 assert parent is not None 

1077 self._new = parent._new 

1078 self._deleted = parent._deleted 

1079 self._dirty = parent._dirty 

1080 self._key_switches = parent._key_switches 

1081 return 

1082 

1083 is_begin = self.origin in ( 

1084 SessionTransactionOrigin.BEGIN, 

1085 SessionTransactionOrigin.AUTOBEGIN, 

1086 ) 

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

1088 self.session.flush() 

1089 

1090 self._new = weakref.WeakKeyDictionary() 

1091 self._deleted = weakref.WeakKeyDictionary() 

1092 self._dirty = weakref.WeakKeyDictionary() 

1093 self._key_switches = weakref.WeakKeyDictionary() 

1094 

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

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

1097 

1098 Corresponds to a rollback. 

1099 

1100 """ 

1101 assert self._is_transaction_boundary 

1102 

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

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

1105 

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

1107 # we probably can do this conditionally based on 

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

1109 self.session.identity_map.safe_discard(s) 

1110 

1111 # restore the old key 

1112 s.key = oldkey 

1113 

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

1115 if s not in to_expunge: 

1116 self.session.identity_map.replace(s) 

1117 

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

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

1120 

1121 assert not self.session._deleted 

1122 

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

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

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

1126 

1127 def _remove_snapshot(self) -> None: 

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

1129 

1130 Corresponds to a commit. 

1131 

1132 """ 

1133 assert self._is_transaction_boundary 

1134 

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

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

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

1138 

1139 statelib.InstanceState._detach_states( 

1140 list(self._deleted), self.session 

1141 ) 

1142 self._deleted.clear() 

1143 elif self.nested: 

1144 parent = self._parent 

1145 assert parent is not None 

1146 parent._new.update(self._new) 

1147 parent._dirty.update(self._dirty) 

1148 parent._deleted.update(self._deleted) 

1149 parent._key_switches.update(self._key_switches) 

1150 

1151 @_StateChange.declare_states( 

1152 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1153 ) 

1154 def _connection_for_bind( 

1155 self, 

1156 bind: _SessionBind, 

1157 execution_options: Optional[CoreExecuteOptionsParameter], 

1158 ) -> Connection: 

1159 if bind in self._connections: 

1160 if execution_options: 

1161 util.warn( 

1162 "Connection is already established for the " 

1163 "given bind; execution_options ignored" 

1164 ) 

1165 return self._connections[bind][0] 

1166 

1167 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1168 

1169 local_connect = False 

1170 should_commit = True 

1171 

1172 try: 

1173 if self._parent: 

1174 conn = self._parent._connection_for_bind( 

1175 bind, execution_options 

1176 ) 

1177 if not self.nested: 

1178 return conn 

1179 else: 

1180 if isinstance(bind, engine.Connection): 

1181 conn = bind 

1182 if conn.engine in self._connections: 

1183 raise sa_exc.InvalidRequestError( 

1184 "Session already has a Connection associated " 

1185 "for the given Connection's Engine" 

1186 ) 

1187 else: 

1188 conn = bind.connect() 

1189 local_connect = True 

1190 

1191 try: 

1192 if execution_options: 

1193 conn = conn.execution_options(**execution_options) 

1194 

1195 transaction: Transaction 

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

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

1198 # conn.in_transaction() ? 

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

1200 # that it is in fact twophase. 

1201 transaction = conn.begin_twophase() 

1202 elif self.nested: 

1203 transaction = conn.begin_nested() 

1204 elif conn.in_transaction(): 

1205 

1206 if local_connect: 

1207 _trans = conn.get_transaction() 

1208 assert _trans is not None 

1209 transaction = _trans 

1210 else: 

1211 join_transaction_mode = ( 

1212 self.session.join_transaction_mode 

1213 ) 

1214 

1215 if join_transaction_mode == "conditional_savepoint": 

1216 if conn.in_nested_transaction(): 

1217 join_transaction_mode = "create_savepoint" 

1218 else: 

1219 join_transaction_mode = "rollback_only" 

1220 

1221 if join_transaction_mode in ( 

1222 "control_fully", 

1223 "rollback_only", 

1224 ): 

1225 if conn.in_nested_transaction(): 

1226 transaction = ( 

1227 conn._get_required_nested_transaction() 

1228 ) 

1229 else: 

1230 transaction = conn._get_required_transaction() 

1231 if join_transaction_mode == "rollback_only": 

1232 should_commit = False 

1233 elif join_transaction_mode == "create_savepoint": 

1234 transaction = conn.begin_nested() 

1235 else: 

1236 assert False, join_transaction_mode 

1237 else: 

1238 transaction = conn.begin() 

1239 except: 

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

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

1242 if local_connect: 

1243 conn.close() 

1244 raise 

1245 else: 

1246 bind_is_connection = isinstance(bind, engine.Connection) 

1247 

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

1249 conn, 

1250 transaction, 

1251 should_commit, 

1252 not bind_is_connection, 

1253 ) 

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

1255 return conn 

1256 finally: 

1257 self._state = SessionTransactionState.ACTIVE 

1258 

1259 def prepare(self) -> None: 

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

1261 raise sa_exc.InvalidRequestError( 

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

1263 "can't prepare." 

1264 ) 

1265 self._prepare_impl() 

1266 

1267 @_StateChange.declare_states( 

1268 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1269 ) 

1270 def _prepare_impl(self) -> None: 

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

1272 self.session.dispatch.before_commit(self.session) 

1273 

1274 stx = self.session._transaction 

1275 assert stx is not None 

1276 if stx is not self: 

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

1278 subtransaction.commit() 

1279 

1280 if not self.session._flushing: 

1281 for _flush_guard in range(100): 

1282 if self.session._is_clean(): 

1283 break 

1284 self.session.flush() 

1285 else: 

1286 raise exc.FlushError( 

1287 "Over 100 subsequent flushes have occurred within " 

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

1289 "creating new objects?" 

1290 ) 

1291 

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

1293 try: 

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

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

1296 except: 

1297 with util.safe_reraise(): 

1298 self.rollback() 

1299 

1300 self._state = SessionTransactionState.PREPARED 

1301 

1302 @_StateChange.declare_states( 

1303 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1304 SessionTransactionState.CLOSED, 

1305 ) 

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

1307 if self._state is not SessionTransactionState.PREPARED: 

1308 with self._expect_state(SessionTransactionState.PREPARED): 

1309 self._prepare_impl() 

1310 

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

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

1313 self._connections.values() 

1314 ): 

1315 if should_commit: 

1316 trans.commit() 

1317 

1318 self._state = SessionTransactionState.COMMITTED 

1319 self.session.dispatch.after_commit(self.session) 

1320 

1321 self._remove_snapshot() 

1322 

1323 with self._expect_state(SessionTransactionState.CLOSED): 

1324 self.close() 

1325 

1326 if _to_root and self._parent: 

1327 self._parent.commit(_to_root=True) 

1328 

1329 @_StateChange.declare_states( 

1330 ( 

1331 SessionTransactionState.ACTIVE, 

1332 SessionTransactionState.DEACTIVE, 

1333 SessionTransactionState.PREPARED, 

1334 ), 

1335 SessionTransactionState.CLOSED, 

1336 ) 

1337 def rollback( 

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

1339 ) -> None: 

1340 stx = self.session._transaction 

1341 assert stx is not None 

1342 if stx is not self: 

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

1344 subtransaction.close() 

1345 

1346 boundary = self 

1347 rollback_err = None 

1348 if self._state in ( 

1349 SessionTransactionState.ACTIVE, 

1350 SessionTransactionState.PREPARED, 

1351 ): 

1352 for transaction in self._iterate_self_and_parents(): 

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

1354 try: 

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

1356 t[1].rollback() 

1357 

1358 transaction._state = SessionTransactionState.DEACTIVE 

1359 self.session.dispatch.after_rollback(self.session) 

1360 except: 

1361 rollback_err = sys.exc_info() 

1362 finally: 

1363 transaction._state = SessionTransactionState.DEACTIVE 

1364 transaction._restore_snapshot( 

1365 dirty_only=transaction.nested 

1366 ) 

1367 boundary = transaction 

1368 break 

1369 else: 

1370 transaction._state = SessionTransactionState.DEACTIVE 

1371 

1372 sess = self.session 

1373 

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

1375 # if items were added, deleted, or mutated 

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

1377 util.warn( 

1378 "Session's state has been changed on " 

1379 "a non-active transaction - this state " 

1380 "will be discarded." 

1381 ) 

1382 boundary._restore_snapshot(dirty_only=boundary.nested) 

1383 

1384 with self._expect_state(SessionTransactionState.CLOSED): 

1385 self.close() 

1386 

1387 if self._parent and _capture_exception: 

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

1389 

1390 if rollback_err and rollback_err[1]: 

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

1392 

1393 sess.dispatch.after_soft_rollback(sess, self) 

1394 

1395 if _to_root and self._parent: 

1396 self._parent.rollback(_to_root=True) 

1397 

1398 @_StateChange.declare_states( 

1399 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1400 ) 

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

1402 if self.nested: 

1403 self.session._nested_transaction = ( 

1404 self._previous_nested_transaction 

1405 ) 

1406 

1407 self.session._transaction = self._parent 

1408 

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

1410 self._connections.values() 

1411 ): 

1412 if invalidate and self._parent is None: 

1413 connection.invalidate() 

1414 if should_commit and transaction.is_active: 

1415 transaction.close() 

1416 if autoclose and self._parent is None: 

1417 connection.close() 

1418 

1419 self._state = SessionTransactionState.CLOSED 

1420 sess = self.session 

1421 

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

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

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

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

1426 # passes with these commented out. 

1427 # self.session = None # type: ignore 

1428 # self._connections = None # type: ignore 

1429 

1430 sess.dispatch.after_transaction_end(sess, self) 

1431 

1432 def _get_subject(self) -> Session: 

1433 return self.session 

1434 

1435 def _transaction_is_active(self) -> bool: 

1436 return self._state is SessionTransactionState.ACTIVE 

1437 

1438 def _transaction_is_closed(self) -> bool: 

1439 return self._state is SessionTransactionState.CLOSED 

1440 

1441 def _rollback_can_be_called(self) -> bool: 

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

1443 

1444 

1445class _SessionCloseState(Enum): 

1446 ACTIVE = 1 

1447 CLOSED = 2 

1448 CLOSE_IS_RESET = 3 

1449 

1450 

1451class Session(_SessionClassMethods, EventTarget): 

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

1453 

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

1455 See :ref:`session_faq_threadsafe` for background. 

1456 

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

1458 

1459 

1460 """ 

1461 

1462 _is_asyncio = False 

1463 

1464 dispatch: dispatcher[Session] 

1465 

1466 identity_map: IdentityMap 

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

1468 

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

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

1471 that have row identity) currently in the session. 

1472 

1473 .. seealso:: 

1474 

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

1476 in this dictionary. 

1477 

1478 """ 

1479 

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

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

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

1483 __binds: Dict[_SessionBindKey, _SessionBind] 

1484 _flushing: bool 

1485 _warn_on_events: bool 

1486 _transaction: Optional[SessionTransaction] 

1487 _nested_transaction: Optional[SessionTransaction] 

1488 hash_key: int 

1489 autoflush: bool 

1490 expire_on_commit: bool 

1491 enable_baked_queries: bool 

1492 twophase: bool 

1493 join_transaction_mode: JoinTransactionMode 

1494 execution_options: _ExecuteOptions = util.EMPTY_DICT 

1495 _query_cls: Type[Query[Any]] 

1496 _close_state: _SessionCloseState 

1497 

1498 def __init__( 

1499 self, 

1500 bind: Optional[_SessionBind] = None, 

1501 *, 

1502 autoflush: bool = True, 

1503 future: Literal[True] = True, 

1504 expire_on_commit: bool = True, 

1505 autobegin: bool = True, 

1506 twophase: bool = False, 

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

1508 enable_baked_queries: bool = True, 

1509 info: Optional[_InfoType] = None, 

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

1511 autocommit: Literal[False] = False, 

1512 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1514 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

1515 ): 

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

1517 

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

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

1520 set of arguments. 

1521 

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

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

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

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

1526 results. 

1527 

1528 .. seealso:: 

1529 

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

1531 

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

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

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

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

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

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

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

1539 

1540 .. versionadded:: 2.0 

1541 

1542 .. seealso:: 

1543 

1544 :ref:`session_autobegin_disable` 

1545 

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

1547 :class:`_engine.Connection` to 

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

1549 operations performed by this session will execute via this 

1550 connectable. 

1551 

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

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

1554 objects as the source of 

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

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

1557 arbitrary Python classes that are bases for mapped classes, 

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

1559 The 

1560 values of the dictionary are then instances of 

1561 :class:`_engine.Engine` 

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

1563 Operations which 

1564 proceed relative to a particular mapped class will consult this 

1565 dictionary for the closest matching entity in order to determine 

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

1567 operation. The complete heuristics for resolution are 

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

1569 

1570 Session = sessionmaker( 

1571 binds={ 

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

1573 SomeDeclarativeBase: create_engine( 

1574 "postgresql+psycopg2://engine2" 

1575 ), 

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

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

1578 } 

1579 ) 

1580 

1581 .. seealso:: 

1582 

1583 :ref:`session_partitioning` 

1584 

1585 :meth:`.Session.bind_mapper` 

1586 

1587 :meth:`.Session.bind_table` 

1588 

1589 :meth:`.Session.get_bind` 

1590 

1591 

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

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

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

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

1596 constructor for ``Session``. 

1597 

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

1599 A parameter consumed 

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

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

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

1603 this particular extension is disabled. 

1604 

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

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

1607 flag therefore only affects applications that are making explicit 

1608 use of this extension within their own code. 

1609 

1610 :param execution_options: optional dictionary of execution options 

1611 that will be applied to all calls to :meth:`_orm.Session.execute`, 

1612 :meth:`_orm.Session.scalars`, and similar. Execution options 

1613 present in statements as well as options passed to methods like 

1614 :meth:`_orm.Session.execute` explicitly take precedence over 

1615 the session-wide options. 

1616 

1617 .. versionadded:: 2.1 

1618 

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

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

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

1622 transaction will load from the most recent database state. 

1623 

1624 .. seealso:: 

1625 

1626 :ref:`session_committing` 

1627 

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

1629 

1630 .. seealso:: 

1631 

1632 :ref:`migration_20_toplevel` 

1633 

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

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

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

1637 construction time so that modifications to the per- 

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

1639 :class:`.Session`. 

1640 

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

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

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

1644 

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

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

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

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

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

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

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

1652 transaction, before each transaction is committed. 

1653 

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

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

1656 

1657 :param join_transaction_mode: Describes the transactional behavior to 

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

1659 has already begun a transaction outside the scope of this 

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

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

1662 

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

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

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

1666 etc. are actually invoked: 

1667 

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

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

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

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

1672 a SAVEPOINT, in other words 

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

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

1675 

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

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

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

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

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

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

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

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

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

1685 

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

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

1688 its own transaction. This transaction by its nature rides 

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

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

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

1692 external transaction will remain unaffected throughout the 

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

1694 

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

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

1697 initiated transaction should remain unaffected; however, it relies 

1698 on proper SAVEPOINT support from the underlying driver and 

1699 database. 

1700 

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

1702 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1703 without workarounds. See the sections 

1704 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1705 for details on current workarounds. 

1706 

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

1708 control of the given transaction as its own; 

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

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

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

1712 call ``.rollback`` on the transaction. 

1713 

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

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

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

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

1718 SAVEPOINT. 

1719 

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

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

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

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

1724 given transaction. 

1725 

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

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

1728 regular database transaction (i.e. 

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

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

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

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

1733 

1734 .. versionadded:: 2.0.0rc1 

1735 

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

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

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

1739 

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

1741 A future SQLAlchemy version may change the default value of 

1742 this flag to ``False``. 

1743 

1744 .. seealso:: 

1745 

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

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

1748 

1749 """ # noqa 

1750 

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

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

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

1754 # of cases including in our own test suite 

1755 if autocommit: 

1756 raise sa_exc.ArgumentError( 

1757 "autocommit=True is no longer supported" 

1758 ) 

1759 self.identity_map = identity._WeakInstanceDict() 

1760 

1761 if not future: 

1762 raise sa_exc.ArgumentError( 

1763 "The 'future' parameter passed to " 

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

1765 ) 

1766 

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

1768 self._deleted = {} # same 

1769 self.bind = bind 

1770 self.__binds = {} 

1771 self._flushing = False 

1772 self._warn_on_events = False 

1773 self._transaction = None 

1774 self._nested_transaction = None 

1775 self.hash_key = _new_sessionid() 

1776 self.autobegin = autobegin 

1777 self.autoflush = autoflush 

1778 self.expire_on_commit = expire_on_commit 

1779 self.enable_baked_queries = enable_baked_queries 

1780 if execution_options: 

1781 self.execution_options = self.execution_options.union( 

1782 execution_options 

1783 ) 

1784 

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

1786 # the default will switch to close_resets_only=False. 

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

1788 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1789 else: 

1790 self._close_state = _SessionCloseState.ACTIVE 

1791 if ( 

1792 join_transaction_mode 

1793 and join_transaction_mode 

1794 not in JoinTransactionMode.__args__ # type: ignore 

1795 ): 

1796 raise sa_exc.ArgumentError( 

1797 f"invalid selection for join_transaction_mode: " 

1798 f'"{join_transaction_mode}"' 

1799 ) 

1800 self.join_transaction_mode = join_transaction_mode 

1801 

1802 self.twophase = twophase 

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

1804 if info: 

1805 self.info.update(info) 

1806 

1807 if binds is not None: 

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

1809 self._add_bind(key, bind) 

1810 

1811 _sessions[self.hash_key] = self 

1812 

1813 # used by sqlalchemy.engine.util.TransactionalContext 

1814 _trans_context_manager: Optional[TransactionalContext] = None 

1815 

1816 connection_callable: Optional[_ConnectionCallableProto] = None 

1817 

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

1819 return self 

1820 

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

1822 self.close() 

1823 

1824 @contextlib.contextmanager 

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

1826 with self: 

1827 with self.begin(): 

1828 yield self 

1829 

1830 def in_transaction(self) -> bool: 

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

1832 

1833 .. versionadded:: 1.4 

1834 

1835 .. seealso:: 

1836 

1837 :attr:`_orm.Session.is_active` 

1838 

1839 

1840 """ 

1841 return self._transaction is not None 

1842 

1843 def in_nested_transaction(self) -> bool: 

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

1845 transaction, e.g. SAVEPOINT. 

1846 

1847 .. versionadded:: 1.4 

1848 

1849 """ 

1850 return self._nested_transaction is not None 

1851 

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

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

1854 

1855 .. versionadded:: 1.4 

1856 

1857 """ 

1858 trans = self._transaction 

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

1860 trans = trans._parent 

1861 return trans 

1862 

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

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

1865 

1866 .. versionadded:: 1.4 

1867 

1868 """ 

1869 

1870 return self._nested_transaction 

1871 

1872 @util.memoized_property 

1873 def info(self) -> _InfoType: 

1874 """A user-modifiable dictionary. 

1875 

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

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

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

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

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

1881 

1882 """ 

1883 return {} 

1884 

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

1886 if self._transaction is None: 

1887 if not begin and not self.autobegin: 

1888 raise sa_exc.InvalidRequestError( 

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

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

1891 ) 

1892 trans = SessionTransaction( 

1893 self, 

1894 ( 

1895 SessionTransactionOrigin.BEGIN 

1896 if begin 

1897 else SessionTransactionOrigin.AUTOBEGIN 

1898 ), 

1899 ) 

1900 assert self._transaction is trans 

1901 return trans 

1902 

1903 return self._transaction 

1904 

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

1906 """Begin a transaction, or nested transaction, 

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

1908 

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

1910 so that normally it is not necessary to call the 

1911 :meth:`_orm.Session.begin` 

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

1913 the scope of when the transactional state is begun. 

1914 

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

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

1917 

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

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

1920 documentation on SAVEPOINT transactions, please see 

1921 :ref:`session_begin_nested`. 

1922 

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

1924 :class:`.SessionTransaction` 

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

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

1927 an example. 

1928 

1929 .. seealso:: 

1930 

1931 :ref:`session_autobegin` 

1932 

1933 :ref:`unitofwork_transaction` 

1934 

1935 :meth:`.Session.begin_nested` 

1936 

1937 

1938 """ 

1939 

1940 trans = self._transaction 

1941 if trans is None: 

1942 trans = self._autobegin_t(begin=True) 

1943 

1944 if not nested: 

1945 return trans 

1946 

1947 assert trans is not None 

1948 

1949 if nested: 

1950 trans = trans._begin(nested=nested) 

1951 assert self._transaction is trans 

1952 self._nested_transaction = trans 

1953 else: 

1954 raise sa_exc.InvalidRequestError( 

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

1956 ) 

1957 

1958 return trans # needed for __enter__/__exit__ hook 

1959 

1960 def begin_nested(self) -> SessionTransaction: 

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

1962 

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

1964 SAVEPOINT for this method to function correctly. 

1965 

1966 For documentation on SAVEPOINT 

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

1968 

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

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

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

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

1973 

1974 .. seealso:: 

1975 

1976 :ref:`session_begin_nested` 

1977 

1978 :ref:`pysqlite_serializable` - special workarounds required 

1979 with the SQLite driver in order for SAVEPOINT to work 

1980 correctly. For asyncio use cases, see the section 

1981 :ref:`aiosqlite_serializable`. 

1982 

1983 """ 

1984 return self.begin(nested=True) 

1985 

1986 def rollback(self) -> None: 

1987 """Rollback the current transaction in progress. 

1988 

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

1990 

1991 The method always rolls back 

1992 the topmost database transaction, discarding any nested 

1993 transactions that may be in progress. 

1994 

1995 .. seealso:: 

1996 

1997 :ref:`session_rollback` 

1998 

1999 :ref:`unitofwork_transaction` 

2000 

2001 """ 

2002 if self._transaction is None: 

2003 pass 

2004 else: 

2005 self._transaction.rollback(_to_root=True) 

2006 

2007 def commit(self) -> None: 

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

2009 

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

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

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

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

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

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

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

2017 to disable this behavior. 

2018 

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

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

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

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

2023 normally affect the database unless pending flush changes were 

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

2025 rules. 

2026 

2027 The outermost database transaction is committed unconditionally, 

2028 automatically releasing any SAVEPOINTs in effect. 

2029 

2030 .. seealso:: 

2031 

2032 :ref:`session_committing` 

2033 

2034 :ref:`unitofwork_transaction` 

2035 

2036 :ref:`asyncio_orm_avoid_lazyloads` 

2037 

2038 """ 

2039 trans = self._transaction 

2040 if trans is None: 

2041 trans = self._autobegin_t() 

2042 

2043 trans.commit(_to_root=True) 

2044 

2045 def prepare(self) -> None: 

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

2047 

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

2049 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2050 

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

2052 current transaction is not such, an 

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

2054 

2055 """ 

2056 trans = self._transaction 

2057 if trans is None: 

2058 trans = self._autobegin_t() 

2059 

2060 trans.prepare() 

2061 

2062 def connection( 

2063 self, 

2064 bind_arguments: Optional[_BindArguments] = None, 

2065 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2066 ) -> Connection: 

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

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

2069 

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

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

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

2073 returned (note that no 

2074 transactional state is established with the DBAPI until the first 

2075 SQL statement is emitted). 

2076 

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

2078 resolved through any of the optional keyword arguments. This 

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

2080 

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

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

2083 to :meth:`.Session.get_bind`. 

2084 

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

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

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

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

2089 the arguments are ignored. 

2090 

2091 .. seealso:: 

2092 

2093 :ref:`session_transaction_isolation` 

2094 

2095 """ 

2096 

2097 if bind_arguments: 

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

2099 

2100 if bind is None: 

2101 bind = self.get_bind(**bind_arguments) 

2102 else: 

2103 bind = self.get_bind() 

2104 

2105 return self._connection_for_bind( 

2106 bind, 

2107 execution_options=execution_options, 

2108 ) 

2109 

2110 def _connection_for_bind( 

2111 self, 

2112 engine: _SessionBind, 

2113 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2114 **kw: Any, 

2115 ) -> Connection: 

2116 TransactionalContext._trans_ctx_check(self) 

2117 

2118 trans = self._transaction 

2119 if trans is None: 

2120 trans = self._autobegin_t() 

2121 return trans._connection_for_bind(engine, execution_options) 

2122 

2123 @overload 

2124 def _execute_internal( 

2125 self, 

2126 statement: Executable, 

2127 params: Optional[_CoreSingleExecuteParams] = None, 

2128 *, 

2129 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2130 bind_arguments: Optional[_BindArguments] = None, 

2131 _parent_execute_state: Optional[Any] = None, 

2132 _add_event: Optional[Any] = None, 

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

2134 ) -> Any: ... 

2135 

2136 @overload 

2137 def _execute_internal( 

2138 self, 

2139 statement: Executable, 

2140 params: Optional[_CoreAnyExecuteParams] = None, 

2141 *, 

2142 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2143 bind_arguments: Optional[_BindArguments] = None, 

2144 _parent_execute_state: Optional[Any] = None, 

2145 _add_event: Optional[Any] = None, 

2146 _scalar_result: bool = ..., 

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

2148 

2149 def _execute_internal( 

2150 self, 

2151 statement: Executable, 

2152 params: Optional[_CoreAnyExecuteParams] = None, 

2153 *, 

2154 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2155 bind_arguments: Optional[_BindArguments] = None, 

2156 _parent_execute_state: Optional[Any] = None, 

2157 _add_event: Optional[Any] = None, 

2158 _scalar_result: bool = False, 

2159 ) -> Any: 

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

2161 

2162 if not bind_arguments: 

2163 bind_arguments = {} 

2164 else: 

2165 bind_arguments = dict(bind_arguments) 

2166 

2167 if ( 

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

2169 == "orm" 

2170 ): 

2171 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2172 statement, "orm" 

2173 ) 

2174 if TYPE_CHECKING: 

2175 assert isinstance( 

2176 compile_state_cls, context._AbstractORMCompileState 

2177 ) 

2178 else: 

2179 compile_state_cls = None 

2180 bind_arguments.setdefault("clause", statement) 

2181 

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

2183 util.coerce_to_immutabledict(execution_options) 

2184 ) 

2185 if self.execution_options: 

2186 # merge given execution options with session-wide execution 

2187 # options. if the statement also has execution_options, 

2188 # maintain priority of session.execution_options -> 

2189 # statement.execution_options -> method passed execution_options 

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

2191 # will come from the statement 

2192 if statement._execution_options: 

2193 combined_execution_options = util.immutabledict( 

2194 { 

2195 k: v 

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

2197 if k not in statement._execution_options 

2198 } 

2199 ).union(combined_execution_options) 

2200 else: 

2201 combined_execution_options = self.execution_options.union( 

2202 combined_execution_options 

2203 ) 

2204 

2205 if _parent_execute_state: 

2206 events_todo = _parent_execute_state._remaining_events() 

2207 else: 

2208 events_todo = self.dispatch.do_orm_execute 

2209 if _add_event: 

2210 events_todo = list(events_todo) + [_add_event] 

2211 

2212 if events_todo: 

2213 if compile_state_cls is not None: 

2214 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2220 ( 

2221 statement, 

2222 combined_execution_options, 

2223 params, 

2224 ) = compile_state_cls.orm_pre_session_exec( 

2225 self, 

2226 statement, 

2227 params, 

2228 combined_execution_options, 

2229 bind_arguments, 

2230 True, 

2231 ) 

2232 

2233 orm_exec_state = ORMExecuteState( 

2234 self, 

2235 statement, 

2236 params, 

2237 combined_execution_options, 

2238 bind_arguments, 

2239 compile_state_cls, 

2240 events_todo, 

2241 ) 

2242 for idx, fn in enumerate(events_todo): 

2243 orm_exec_state._starting_event_idx = idx 

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

2245 orm_exec_state 

2246 ) 

2247 if fn_result: 

2248 if _scalar_result: 

2249 return fn_result.scalar() 

2250 else: 

2251 return fn_result 

2252 

2253 statement = orm_exec_state.statement 

2254 combined_execution_options = orm_exec_state.local_execution_options 

2255 params = orm_exec_state.parameters 

2256 

2257 if compile_state_cls is not None: 

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

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

2260 # new execution_options into load_options / update_delete_options, 

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

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

2263 ( 

2264 statement, 

2265 combined_execution_options, 

2266 params, 

2267 ) = compile_state_cls.orm_pre_session_exec( 

2268 self, 

2269 statement, 

2270 params, 

2271 combined_execution_options, 

2272 bind_arguments, 

2273 False, 

2274 ) 

2275 else: 

2276 # Issue #9809: unconditionally autoflush for Core statements 

2277 self._autoflush() 

2278 

2279 bind = self.get_bind(**bind_arguments) 

2280 

2281 conn = self._connection_for_bind(bind) 

2282 

2283 if _scalar_result and not compile_state_cls: 

2284 if TYPE_CHECKING: 

2285 params = cast(_CoreSingleExecuteParams, params) 

2286 return conn.scalar( 

2287 statement, 

2288 params or {}, 

2289 execution_options=combined_execution_options, 

2290 ) 

2291 

2292 if compile_state_cls: 

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

2294 compile_state_cls.orm_execute_statement( 

2295 self, 

2296 statement, 

2297 params or {}, 

2298 combined_execution_options, 

2299 bind_arguments, 

2300 conn, 

2301 ) 

2302 ) 

2303 else: 

2304 result = conn.execute( 

2305 statement, params, execution_options=combined_execution_options 

2306 ) 

2307 

2308 if _scalar_result: 

2309 return result.scalar() 

2310 else: 

2311 return result 

2312 

2313 @overload 

2314 def execute( 

2315 self, 

2316 statement: TypedReturnsRows[Unpack[_Ts]], 

2317 params: Optional[_CoreAnyExecuteParams] = None, 

2318 *, 

2319 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2320 bind_arguments: Optional[_BindArguments] = None, 

2321 _parent_execute_state: Optional[Any] = None, 

2322 _add_event: Optional[Any] = None, 

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

2324 

2325 @overload 

2326 def execute( 

2327 self, 

2328 statement: Executable, 

2329 params: Optional[_CoreAnyExecuteParams] = None, 

2330 *, 

2331 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2332 bind_arguments: Optional[_BindArguments] = None, 

2333 _parent_execute_state: Optional[Any] = None, 

2334 _add_event: Optional[Any] = None, 

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

2336 

2337 def execute( 

2338 self, 

2339 statement: Executable, 

2340 params: Optional[_CoreAnyExecuteParams] = None, 

2341 *, 

2342 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2343 bind_arguments: Optional[_BindArguments] = None, 

2344 _parent_execute_state: Optional[Any] = None, 

2345 _add_event: Optional[Any] = None, 

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

2347 r"""Execute a SQL expression construct. 

2348 

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

2350 results of the statement execution. 

2351 

2352 E.g.:: 

2353 

2354 from sqlalchemy import select 

2355 

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

2357 

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

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

2360 of :class:`_engine.Connection`. 

2361 

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

2363 now the primary point of ORM statement execution when using 

2364 :term:`2.0 style` ORM usage. 

2365 

2366 :param statement: 

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

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

2369 

2370 :param params: 

2371 Optional dictionary, or list of dictionaries, containing 

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

2373 execution occurs; if a list of dictionaries, an 

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

2375 must correspond to parameter names present in the statement. 

2376 

2377 :param execution_options: optional dictionary of execution options, 

2378 which will be associated with the statement execution. This 

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

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

2381 provide additional options understood only in an ORM context. 

2382 

2383 The execution_options are passed along to methods like 

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

2385 highest priority to execution_options that are passed to this 

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

2387 statement object if any, and finally those options present 

2388 session-wide. 

2389 

2390 .. seealso:: 

2391 

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

2393 options 

2394 

2395 :param bind_arguments: dictionary of additional arguments to determine 

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

2397 Contents of this dictionary are passed to the 

2398 :meth:`.Session.get_bind` method. 

2399 

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

2401 

2402 

2403 """ 

2404 return self._execute_internal( 

2405 statement, 

2406 params, 

2407 execution_options=execution_options, 

2408 bind_arguments=bind_arguments, 

2409 _parent_execute_state=_parent_execute_state, 

2410 _add_event=_add_event, 

2411 ) 

2412 

2413 @overload 

2414 def scalar( 

2415 self, 

2416 statement: TypedReturnsRows[_T], 

2417 params: Optional[_CoreSingleExecuteParams] = None, 

2418 *, 

2419 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2420 bind_arguments: Optional[_BindArguments] = None, 

2421 **kw: Any, 

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

2423 

2424 @overload 

2425 def scalar( 

2426 self, 

2427 statement: Executable, 

2428 params: Optional[_CoreSingleExecuteParams] = None, 

2429 *, 

2430 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2431 bind_arguments: Optional[_BindArguments] = None, 

2432 **kw: Any, 

2433 ) -> Any: ... 

2434 

2435 def scalar( 

2436 self, 

2437 statement: Executable, 

2438 params: Optional[_CoreSingleExecuteParams] = None, 

2439 *, 

2440 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2441 bind_arguments: Optional[_BindArguments] = None, 

2442 **kw: Any, 

2443 ) -> Any: 

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

2445 

2446 Usage and parameters are the same as that of 

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

2448 value. 

2449 

2450 """ 

2451 

2452 return self._execute_internal( 

2453 statement, 

2454 params, 

2455 execution_options=execution_options, 

2456 bind_arguments=bind_arguments, 

2457 _scalar_result=True, 

2458 **kw, 

2459 ) 

2460 

2461 @overload 

2462 def scalars( 

2463 self, 

2464 statement: TypedReturnsRows[_T], 

2465 params: Optional[_CoreAnyExecuteParams] = None, 

2466 *, 

2467 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2468 bind_arguments: Optional[_BindArguments] = None, 

2469 **kw: Any, 

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

2471 

2472 @overload 

2473 def scalars( 

2474 self, 

2475 statement: Executable, 

2476 params: Optional[_CoreAnyExecuteParams] = None, 

2477 *, 

2478 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2479 bind_arguments: Optional[_BindArguments] = None, 

2480 **kw: Any, 

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

2482 

2483 def scalars( 

2484 self, 

2485 statement: Executable, 

2486 params: Optional[_CoreAnyExecuteParams] = None, 

2487 *, 

2488 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2489 bind_arguments: Optional[_BindArguments] = None, 

2490 **kw: Any, 

2491 ) -> ScalarResult[Any]: 

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

2493 

2494 Usage and parameters are the same as that of 

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

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

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

2498 

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

2500 

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

2502 

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

2504 

2505 .. seealso:: 

2506 

2507 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2509 

2510 """ 

2511 

2512 return self._execute_internal( 

2513 statement, 

2514 params=params, 

2515 execution_options=execution_options, 

2516 bind_arguments=bind_arguments, 

2517 _scalar_result=False, # mypy appreciates this 

2518 **kw, 

2519 ).scalars() 

2520 

2521 def close(self) -> None: 

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

2523 :class:`_orm.Session`. 

2524 

2525 This expunges all ORM objects associated with this 

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

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

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

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

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

2531 

2532 .. tip:: 

2533 

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

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

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

2537 distinct "closed" state; it merely means 

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

2539 and ORM objects. 

2540 

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

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

2543 any further action on the session will be forbidden. 

2544 

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

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

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

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

2549 

2550 .. seealso:: 

2551 

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

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

2554 

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

2556 ``close()`` with the parameter 

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

2558 

2559 """ 

2560 self._close_impl(invalidate=False) 

2561 

2562 def reset(self) -> None: 

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

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

2565 

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

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

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

2569 brand new, and ready to be used again. 

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

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

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

2573 

2574 .. versionadded:: 2.0.22 

2575 

2576 .. seealso:: 

2577 

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

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

2580 

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

2582 prevent re-use of the Session when the parameter 

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

2584 """ 

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

2586 

2587 def invalidate(self) -> None: 

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

2589 

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

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

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

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

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

2595 multiple engines). 

2596 

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

2598 the connections are no longer safe to be used. 

2599 

2600 Below illustrates a scenario when using `gevent 

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

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

2603 

2604 import gevent 

2605 

2606 try: 

2607 sess = Session() 

2608 sess.add(User()) 

2609 sess.commit() 

2610 except gevent.Timeout: 

2611 sess.invalidate() 

2612 raise 

2613 except: 

2614 sess.rollback() 

2615 raise 

2616 

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

2618 does, including that all ORM objects are expunged. 

2619 

2620 """ 

2621 self._close_impl(invalidate=True) 

2622 

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

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

2625 self._close_state = _SessionCloseState.CLOSED 

2626 self.expunge_all() 

2627 if self._transaction is not None: 

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

2629 transaction.close(invalidate) 

2630 

2631 def expunge_all(self) -> None: 

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

2633 

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

2635 ``Session``. 

2636 

2637 """ 

2638 

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

2640 self.identity_map._kill() 

2641 self.identity_map = identity._WeakInstanceDict() 

2642 self._new = {} 

2643 self._deleted = {} 

2644 

2645 statelib.InstanceState._detach_states(all_states, self) 

2646 

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

2648 try: 

2649 insp = inspect(key) 

2650 except sa_exc.NoInspectionAvailable as err: 

2651 if not isinstance(key, type): 

2652 raise sa_exc.ArgumentError( 

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

2654 ) from err 

2655 else: 

2656 self.__binds[key] = bind 

2657 else: 

2658 if TYPE_CHECKING: 

2659 assert isinstance(insp, Inspectable) 

2660 

2661 if isinstance(insp, TableClause): 

2662 self.__binds[insp] = bind 

2663 elif insp_is_mapper(insp): 

2664 self.__binds[insp.class_] = bind 

2665 for _selectable in insp._all_tables: 

2666 self.__binds[_selectable] = bind 

2667 else: 

2668 raise sa_exc.ArgumentError( 

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

2670 ) 

2671 

2672 def bind_mapper( 

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

2674 ) -> None: 

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

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

2677 :class:`_engine.Connection`. 

2678 

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

2680 :meth:`.Session.get_bind` method. 

2681 

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

2683 or an instance of a mapped 

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

2685 classes. 

2686 

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

2688 object. 

2689 

2690 .. seealso:: 

2691 

2692 :ref:`session_partitioning` 

2693 

2694 :paramref:`.Session.binds` 

2695 

2696 :meth:`.Session.bind_table` 

2697 

2698 

2699 """ 

2700 self._add_bind(mapper, bind) 

2701 

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

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

2704 :class:`_engine.Engine` 

2705 or :class:`_engine.Connection`. 

2706 

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

2708 :meth:`.Session.get_bind` method. 

2709 

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

2711 which is typically the target 

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

2713 mapped. 

2714 

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

2716 object. 

2717 

2718 .. seealso:: 

2719 

2720 :ref:`session_partitioning` 

2721 

2722 :paramref:`.Session.binds` 

2723 

2724 :meth:`.Session.bind_mapper` 

2725 

2726 

2727 """ 

2728 self._add_bind(table, bind) 

2729 

2730 def get_bind( 

2731 self, 

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

2733 *, 

2734 clause: Optional[ClauseElement] = None, 

2735 bind: Optional[_SessionBind] = None, 

2736 _sa_skip_events: Optional[bool] = None, 

2737 _sa_skip_for_implicit_returning: bool = False, 

2738 **kw: Any, 

2739 ) -> Union[Engine, Connection]: 

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

2741 

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

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

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

2745 

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

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

2748 appropriate bind to return. 

2749 

2750 Note that the "mapper" argument is usually present 

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

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

2753 individual INSERT/UPDATE/DELETE operation within a 

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

2755 

2756 The order of resolution is: 

2757 

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

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

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

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

2762 superclasses to more general. 

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

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

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

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

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

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

2769 associated with the clause. 

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

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

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

2773 selectable to which the mapper is mapped. 

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

2775 is raised. 

2776 

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

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

2779 of bind resolution scheme. See the example at 

2780 :ref:`session_custom_partitioning`. 

2781 

2782 :param mapper: 

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

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

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

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

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

2788 mapped for a bind. 

2789 

2790 :param clause: 

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

2792 :func:`_expression.select`, 

2793 :func:`_expression.text`, 

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

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

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

2797 associated with 

2798 bound :class:`_schema.MetaData`. 

2799 

2800 .. seealso:: 

2801 

2802 :ref:`session_partitioning` 

2803 

2804 :paramref:`.Session.binds` 

2805 

2806 :meth:`.Session.bind_mapper` 

2807 

2808 :meth:`.Session.bind_table` 

2809 

2810 """ 

2811 

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

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

2814 if bind: 

2815 return bind 

2816 elif not self.__binds and self.bind: 

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

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

2819 return self.bind 

2820 

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

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

2823 # mapper and the clause 

2824 if mapper is None and clause is None: 

2825 if self.bind: 

2826 return self.bind 

2827 else: 

2828 raise sa_exc.UnboundExecutionError( 

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

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

2831 "a binding." 

2832 ) 

2833 

2834 # look more closely at the mapper. 

2835 if mapper is not None: 

2836 try: 

2837 inspected_mapper = inspect(mapper) 

2838 except sa_exc.NoInspectionAvailable as err: 

2839 if isinstance(mapper, type): 

2840 raise exc.UnmappedClassError(mapper) from err 

2841 else: 

2842 raise 

2843 else: 

2844 inspected_mapper = None 

2845 

2846 # match up the mapper or clause in the __binds 

2847 if self.__binds: 

2848 # matching mappers and selectables to entries in the 

2849 # binds dictionary; supported use case. 

2850 if inspected_mapper: 

2851 for cls in inspected_mapper.class_.__mro__: 

2852 if cls in self.__binds: 

2853 return self.__binds[cls] 

2854 if clause is None: 

2855 clause = inspected_mapper.persist_selectable 

2856 

2857 if clause is not None: 

2858 plugin_subject = clause._propagate_attrs.get( 

2859 "plugin_subject", None 

2860 ) 

2861 

2862 if plugin_subject is not None: 

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

2864 if cls in self.__binds: 

2865 return self.__binds[cls] 

2866 

2867 for obj in visitors.iterate(clause): 

2868 if obj in self.__binds: 

2869 if TYPE_CHECKING: 

2870 assert isinstance(obj, Table) 

2871 return self.__binds[obj] 

2872 

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

2874 # return that 

2875 if self.bind: 

2876 return self.bind 

2877 

2878 context = [] 

2879 if inspected_mapper is not None: 

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

2881 if clause is not None: 

2882 context.append("SQL expression") 

2883 

2884 raise sa_exc.UnboundExecutionError( 

2885 f"Could not locate a bind configured on " 

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

2887 ) 

2888 

2889 @overload 

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

2891 

2892 @overload 

2893 def query( 

2894 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2896 

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

2898 

2899 # code within this block is **programmatically, 

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

2901 

2902 @overload 

2903 def query( 

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

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

2906 

2907 @overload 

2908 def query( 

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

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

2911 

2912 @overload 

2913 def query( 

2914 self, 

2915 __ent0: _TCCA[_T0], 

2916 __ent1: _TCCA[_T1], 

2917 __ent2: _TCCA[_T2], 

2918 __ent3: _TCCA[_T3], 

2919 /, 

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

2921 

2922 @overload 

2923 def query( 

2924 self, 

2925 __ent0: _TCCA[_T0], 

2926 __ent1: _TCCA[_T1], 

2927 __ent2: _TCCA[_T2], 

2928 __ent3: _TCCA[_T3], 

2929 __ent4: _TCCA[_T4], 

2930 /, 

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

2932 

2933 @overload 

2934 def query( 

2935 self, 

2936 __ent0: _TCCA[_T0], 

2937 __ent1: _TCCA[_T1], 

2938 __ent2: _TCCA[_T2], 

2939 __ent3: _TCCA[_T3], 

2940 __ent4: _TCCA[_T4], 

2941 __ent5: _TCCA[_T5], 

2942 /, 

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

2944 

2945 @overload 

2946 def query( 

2947 self, 

2948 __ent0: _TCCA[_T0], 

2949 __ent1: _TCCA[_T1], 

2950 __ent2: _TCCA[_T2], 

2951 __ent3: _TCCA[_T3], 

2952 __ent4: _TCCA[_T4], 

2953 __ent5: _TCCA[_T5], 

2954 __ent6: _TCCA[_T6], 

2955 /, 

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

2957 

2958 @overload 

2959 def query( 

2960 self, 

2961 __ent0: _TCCA[_T0], 

2962 __ent1: _TCCA[_T1], 

2963 __ent2: _TCCA[_T2], 

2964 __ent3: _TCCA[_T3], 

2965 __ent4: _TCCA[_T4], 

2966 __ent5: _TCCA[_T5], 

2967 __ent6: _TCCA[_T6], 

2968 __ent7: _TCCA[_T7], 

2969 /, 

2970 *entities: _ColumnsClauseArgument[Any], 

2971 ) -> RowReturningQuery[ 

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

2973 ]: ... 

2974 

2975 # END OVERLOADED FUNCTIONS self.query 

2976 

2977 @overload 

2978 def query( 

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

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

2981 

2982 def query( 

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

2984 ) -> Query[Any]: 

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

2986 :class:`_orm.Session`. 

2987 

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

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

2990 to construct ORM queries. 

2991 

2992 .. seealso:: 

2993 

2994 :ref:`unified_tutorial` 

2995 

2996 :ref:`queryguide_toplevel` 

2997 

2998 :ref:`query_api_toplevel` - legacy API doc 

2999 

3000 """ 

3001 

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

3003 

3004 def _identity_lookup( 

3005 self, 

3006 mapper: Mapper[_O], 

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

3008 identity_token: Any = None, 

3009 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

3011 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3012 bind_arguments: Optional[_BindArguments] = None, 

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

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

3015 

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

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

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

3019 check if was deleted). 

3020 

3021 e.g.:: 

3022 

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

3024 

3025 :param mapper: mapper in use 

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

3027 a tuple. 

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

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

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

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

3032 :param passive: passive load flag passed to 

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

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

3035 if the flag allows for SQL to be emitted. 

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

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

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

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

3040 relationship-loaded). 

3041 

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

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

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

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

3046 

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

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

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

3050 :class:`_query.Query` object. 

3051 

3052 

3053 """ 

3054 

3055 key = mapper.identity_key_from_primary_key( 

3056 primary_key_identity, identity_token=identity_token 

3057 ) 

3058 

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

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

3061 return return_value 

3062 

3063 @util.non_memoized_property 

3064 @contextlib.contextmanager 

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

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

3067 

3068 e.g.:: 

3069 

3070 with session.no_autoflush: 

3071 

3072 some_object = SomeClass() 

3073 session.add(some_object) 

3074 # won't autoflush 

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

3076 

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

3078 will not be subject to flushes occurring upon query 

3079 access. This is useful when initializing a series 

3080 of objects which involve existing database queries, 

3081 where the uncompleted object should not yet be flushed. 

3082 

3083 """ 

3084 autoflush = self.autoflush 

3085 self.autoflush = False 

3086 try: 

3087 yield self 

3088 finally: 

3089 self.autoflush = autoflush 

3090 

3091 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3095 "warning happened while initializing objects.", 

3096 sa_exc.SAWarning, 

3097 ) 

3098 def _autoflush(self) -> None: 

3099 if self.autoflush and not self._flushing: 

3100 try: 

3101 self.flush() 

3102 except sa_exc.StatementError as e: 

3103 # note we are reraising StatementError as opposed to 

3104 # raising FlushError with "chaining" to remain compatible 

3105 # with code that catches StatementError, IntegrityError, 

3106 # etc. 

3107 e.add_detail( 

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

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

3110 "flush is occurring prematurely" 

3111 ) 

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

3113 

3114 def refresh( 

3115 self, 

3116 instance: object, 

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

3118 with_for_update: ForUpdateParameter = None, 

3119 ) -> None: 

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

3121 

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

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

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

3125 value available in the current transaction. 

3126 

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

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

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

3130 

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

3132 can also refresh eagerly loaded attributes. 

3133 

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

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

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

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

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

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

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

3141 refreshed. 

3142 

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

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

3145 attributes for those which are named explicitly in the 

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

3147 

3148 .. tip:: 

3149 

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

3151 refreshing both column and relationship oriented attributes, its 

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

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

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

3155 once while having explicit control over relationship loader 

3156 strategies, use the 

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

3158 instead. 

3159 

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

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

3162 in database state outside of that transaction. Refreshing 

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

3164 where database rows have not yet been accessed. 

3165 

3166 :param attribute_names: optional. An iterable collection of 

3167 string attribute names indicating a subset of attributes to 

3168 be refreshed. 

3169 

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

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

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

3173 flags should match the parameters of 

3174 :meth:`_query.Query.with_for_update`. 

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

3176 

3177 .. seealso:: 

3178 

3179 :ref:`session_expire` - introductory material 

3180 

3181 :meth:`.Session.expire` 

3182 

3183 :meth:`.Session.expire_all` 

3184 

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

3186 to refresh objects as they would be loaded normally. 

3187 

3188 """ 

3189 try: 

3190 state = attributes.instance_state(instance) 

3191 except exc.NO_STATE as err: 

3192 raise exc.UnmappedInstanceError(instance) from err 

3193 

3194 self._expire_state(state, attribute_names) 

3195 

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

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

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

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

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

3201 # load_on_ident. 

3202 self._autoflush() 

3203 

3204 if with_for_update == {}: 

3205 raise sa_exc.ArgumentError( 

3206 "with_for_update should be the boolean value " 

3207 "True, or a dictionary with options. " 

3208 "A blank dictionary is ambiguous." 

3209 ) 

3210 

3211 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3212 

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

3214 if ( 

3215 loading._load_on_ident( 

3216 self, 

3217 stmt, 

3218 state.key, 

3219 refresh_state=state, 

3220 with_for_update=with_for_update, 

3221 only_load_props=attribute_names, 

3222 require_pk_cols=True, 

3223 # technically unnecessary as we just did autoflush 

3224 # above, however removes the additional unnecessary 

3225 # call to _autoflush() 

3226 no_autoflush=True, 

3227 is_user_refresh=True, 

3228 ) 

3229 is None 

3230 ): 

3231 raise sa_exc.InvalidRequestError( 

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

3233 ) 

3234 

3235 def expire_all(self) -> None: 

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

3237 

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

3239 a query will be issued using the 

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

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

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

3243 previously read in that same transaction, regardless of changes 

3244 in database state outside of that transaction. 

3245 

3246 To expire individual objects and individual attributes 

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

3248 

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

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

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

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

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

3254 assuming the transaction is isolated. 

3255 

3256 .. seealso:: 

3257 

3258 :ref:`session_expire` - introductory material 

3259 

3260 :meth:`.Session.expire` 

3261 

3262 :meth:`.Session.refresh` 

3263 

3264 :meth:`_orm.Query.populate_existing` 

3265 

3266 """ 

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

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

3269 

3270 def expire( 

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

3272 ) -> None: 

3273 """Expire the attributes on an instance. 

3274 

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

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

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

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

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

3280 previously read in that same transaction, regardless of changes 

3281 in database state outside of that transaction. 

3282 

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

3284 use :meth:`Session.expire_all`. 

3285 

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

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

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

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

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

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

3292 transaction. 

3293 

3294 :param instance: The instance to be refreshed. 

3295 :param attribute_names: optional list of string attribute names 

3296 indicating a subset of attributes to be expired. 

3297 

3298 .. seealso:: 

3299 

3300 :ref:`session_expire` - introductory material 

3301 

3302 :meth:`.Session.expire` 

3303 

3304 :meth:`.Session.refresh` 

3305 

3306 :meth:`_orm.Query.populate_existing` 

3307 

3308 """ 

3309 try: 

3310 state = attributes.instance_state(instance) 

3311 except exc.NO_STATE as err: 

3312 raise exc.UnmappedInstanceError(instance) from err 

3313 self._expire_state(state, attribute_names) 

3314 

3315 def _expire_state( 

3316 self, 

3317 state: InstanceState[Any], 

3318 attribute_names: Optional[Iterable[str]], 

3319 ) -> None: 

3320 self._validate_persistent(state) 

3321 if attribute_names: 

3322 state._expire_attributes(state.dict, attribute_names) 

3323 else: 

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

3325 # remove associations 

3326 cascaded = list( 

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

3328 ) 

3329 self._conditional_expire(state) 

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

3331 self._conditional_expire(st_) 

3332 

3333 def _conditional_expire( 

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

3335 ) -> None: 

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

3337 

3338 if state.key: 

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

3340 elif state in self._new: 

3341 self._new.pop(state) 

3342 state._detach(self) 

3343 

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

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

3346 

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

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

3349 

3350 """ 

3351 try: 

3352 state = attributes.instance_state(instance) 

3353 except exc.NO_STATE as err: 

3354 raise exc.UnmappedInstanceError(instance) from err 

3355 if state.session_id is not self.hash_key: 

3356 raise sa_exc.InvalidRequestError( 

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

3358 ) 

3359 

3360 cascaded = list( 

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

3362 ) 

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

3364 

3365 def _expunge_states( 

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

3367 ) -> None: 

3368 for state in states: 

3369 if state in self._new: 

3370 self._new.pop(state) 

3371 elif self.identity_map.contains_state(state): 

3372 self.identity_map.safe_discard(state) 

3373 self._deleted.pop(state, None) 

3374 elif self._transaction: 

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

3376 # in the transaction snapshot 

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

3378 statelib.InstanceState._detach_states( 

3379 states, self, to_transient=to_transient 

3380 ) 

3381 

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

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

3384 

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

3386 state as well as already persistent objects. 

3387 

3388 """ 

3389 

3390 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3391 for state in states: 

3392 mapper = _state_mapper(state) 

3393 

3394 # prevent against last minute dereferences of the object 

3395 obj = state.obj() 

3396 if obj is not None: 

3397 instance_key = mapper._identity_key_from_state(state) 

3398 

3399 if ( 

3400 _none_set.intersection(instance_key[1]) 

3401 and not mapper.allow_partial_pks 

3402 or _none_set.issuperset(instance_key[1]) 

3403 ): 

3404 raise exc.FlushError( 

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

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

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

3408 "that the mapped Column object is configured to " 

3409 "expect these generated values. Ensure also that " 

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

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

3412 % state_str(state) 

3413 ) 

3414 

3415 if state.key is None: 

3416 state.key = instance_key 

3417 elif state.key != instance_key: 

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

3419 # state has already replaced this one in the identity 

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

3421 self.identity_map.safe_discard(state) 

3422 trans = self._transaction 

3423 assert trans is not None 

3424 if state in trans._key_switches: 

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

3426 else: 

3427 orig_key = state.key 

3428 trans._key_switches[state] = ( 

3429 orig_key, 

3430 instance_key, 

3431 ) 

3432 state.key = instance_key 

3433 

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

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

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

3437 old = self.identity_map.replace(state) 

3438 if ( 

3439 old is not None 

3440 and mapper._identity_key_from_state(old) == instance_key 

3441 and old.obj() is not None 

3442 ): 

3443 util.warn( 

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

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

3446 "load operations occurring inside of an event handler " 

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

3448 ) 

3449 state._orphaned_outside_of_session = False 

3450 

3451 statelib.InstanceState._commit_all_states( 

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

3453 ) 

3454 

3455 self._register_altered(states) 

3456 

3457 if pending_to_persistent is not None: 

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

3459 pending_to_persistent(self, state) 

3460 

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

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

3463 self._new.pop(state) 

3464 

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

3466 if self._transaction: 

3467 for state in states: 

3468 if state in self._new: 

3469 self._transaction._new[state] = True 

3470 else: 

3471 self._transaction._dirty[state] = True 

3472 

3473 def _remove_newly_deleted( 

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

3475 ) -> None: 

3476 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3477 for state in states: 

3478 if self._transaction: 

3479 self._transaction._deleted[state] = True 

3480 

3481 if persistent_to_deleted is not None: 

3482 # get a strong reference before we pop out of 

3483 # self._deleted 

3484 obj = state.obj() # noqa 

3485 

3486 self.identity_map.safe_discard(state) 

3487 self._deleted.pop(state, None) 

3488 state._deleted = True 

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

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

3491 # tracked as part of that 

3492 if persistent_to_deleted is not None: 

3493 persistent_to_deleted(self, state) 

3494 

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

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

3497 

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

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

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

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

3502 

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

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

3505 state directly. 

3506 

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

3508 objects which were transient when they were passed to 

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

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

3511 :class:`_orm.Session`. 

3512 

3513 .. seealso:: 

3514 

3515 :meth:`_orm.Session.add_all` 

3516 

3517 :ref:`session_adding` - at :ref:`session_basics` 

3518 

3519 """ 

3520 if _warn and self._warn_on_events: 

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

3522 

3523 try: 

3524 state = attributes.instance_state(instance) 

3525 except exc.NO_STATE as err: 

3526 raise exc.UnmappedInstanceError(instance) from err 

3527 

3528 self._save_or_update_state(state) 

3529 

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

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

3532 

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

3534 behavioral description. 

3535 

3536 .. seealso:: 

3537 

3538 :meth:`_orm.Session.add` 

3539 

3540 :ref:`session_adding` - at :ref:`session_basics` 

3541 

3542 """ 

3543 

3544 if self._warn_on_events: 

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

3546 

3547 for instance in instances: 

3548 self.add(instance, _warn=False) 

3549 

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

3551 state._orphaned_outside_of_session = False 

3552 self._save_or_update_impl(state) 

3553 

3554 mapper = _state_mapper(state) 

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

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

3557 ): 

3558 self._save_or_update_impl(st_) 

3559 

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

3561 """Mark an instance as deleted. 

3562 

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

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

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

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

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

3568 

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

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

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

3572 is successfully committed, 

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

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

3575 

3576 .. seealso:: 

3577 

3578 :ref:`session_deleting` - at :ref:`session_basics` 

3579 

3580 :meth:`.Session.delete_all` - multiple instance version 

3581 

3582 """ 

3583 if self._warn_on_events: 

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

3585 

3586 self._delete_impl(object_state(instance), instance, head=True) 

3587 

3588 def delete_all(self, instances: Iterable[object]) -> None: 

3589 """Calls :meth:`.Session.delete` on multiple instances. 

3590 

3591 .. seealso:: 

3592 

3593 :meth:`.Session.delete` - main documentation on delete 

3594 

3595 .. versionadded:: 2.1 

3596 

3597 """ 

3598 

3599 if self._warn_on_events: 

3600 self._flush_warning("Session.delete_all()") 

3601 

3602 for instance in instances: 

3603 self._delete_impl(object_state(instance), instance, head=True) 

3604 

3605 def _delete_impl( 

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

3607 ) -> None: 

3608 if state.key is None: 

3609 if head: 

3610 raise sa_exc.InvalidRequestError( 

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

3612 ) 

3613 else: 

3614 return 

3615 

3616 to_attach = self._before_attach(state, obj) 

3617 

3618 if state in self._deleted: 

3619 return 

3620 

3621 self.identity_map.add(state) 

3622 

3623 if to_attach: 

3624 self._after_attach(state, obj) 

3625 

3626 if head: 

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

3628 # so that autoflush does not delete the item 

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

3630 cascade_states = list( 

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

3632 ) 

3633 else: 

3634 cascade_states = None 

3635 

3636 self._deleted[state] = obj 

3637 

3638 if head: 

3639 if TYPE_CHECKING: 

3640 assert cascade_states is not None 

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

3642 self._delete_impl(st_, o, False) 

3643 

3644 def get( 

3645 self, 

3646 entity: _EntityBindKey[_O], 

3647 ident: _PKIdentityArgument, 

3648 *, 

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

3650 populate_existing: bool = False, 

3651 with_for_update: ForUpdateParameter = None, 

3652 identity_token: Optional[Any] = None, 

3653 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3654 bind_arguments: Optional[_BindArguments] = None, 

3655 ) -> Optional[_O]: 

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

3657 or ``None`` if not found. 

3658 

3659 E.g.:: 

3660 

3661 my_user = session.get(User, 5) 

3662 

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

3664 

3665 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3666 

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

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

3669 

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

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

3672 If the given primary key identifier is present 

3673 in the local identity map, the object is returned 

3674 directly from this collection and no SQL is emitted, 

3675 unless the object has been marked fully expired. 

3676 If not present, 

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

3678 

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

3680 the object is present in the identity map and 

3681 marked as expired - a SELECT 

3682 is emitted to refresh the object as well as to 

3683 ensure that the row is still present. 

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

3685 

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

3687 type of entity to be loaded. 

3688 

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

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

3691 a tuple or dictionary should be passed. 

3692 

3693 For a single-column primary key, the scalar calling form is typically 

3694 the most expedient. If the primary key of a row is the value "5", 

3695 the call looks like:: 

3696 

3697 my_object = session.get(SomeClass, 5) 

3698 

3699 The tuple form contains primary key values typically in 

3700 the order in which they correspond to the mapped 

3701 :class:`_schema.Table` 

3702 object's primary key columns, or if the 

3703 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3704 used, in 

3705 the order used for that parameter. For example, if the primary key 

3706 of a row is represented by the integer 

3707 digits "5, 10" the call would look like:: 

3708 

3709 my_object = session.get(SomeClass, (5, 10)) 

3710 

3711 The dictionary form should include as keys the mapped attribute names 

3712 corresponding to each element of the primary key. If the mapped class 

3713 has the attributes ``id``, ``version_id`` as the attributes which 

3714 store the object's primary key value, the call would look like:: 

3715 

3716 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3717 

3718 :param options: optional sequence of loader options which will be 

3719 applied to the query, if one is emitted. 

3720 

3721 :param populate_existing: causes the method to unconditionally emit 

3722 a SQL query and refresh the object with the newly loaded data, 

3723 regardless of whether or not the object is already present. 

3724 

3725 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE 

3726 should be used, or may be a dictionary containing flags to 

3727 indicate a more specific set of FOR UPDATE flags for the SELECT; 

3728 flags should match the parameters of 

3729 :meth:`_query.Query.with_for_update`. 

3730 Supersedes the :paramref:`.Session.refresh.lockmode` parameter. 

3731 

3732 :param execution_options: optional dictionary of execution options, 

3733 which will be associated with the query execution if one is emitted. 

3734 This dictionary can provide a subset of the options that are 

3735 accepted by :meth:`_engine.Connection.execution_options`, and may 

3736 also provide additional options understood only in an ORM context. 

3737 

3738 .. versionadded:: 1.4.29 

3739 

3740 .. seealso:: 

3741 

3742 :ref:`orm_queryguide_execution_options` - ORM-specific execution 

3743 options 

3744 

3745 :param bind_arguments: dictionary of additional arguments to determine 

3746 the bind. May include "mapper", "bind", or other custom arguments. 

3747 Contents of this dictionary are passed to the 

3748 :meth:`.Session.get_bind` method. 

3749 

3750 .. versionadded:: 2.0.0rc1 

3751 

3752 :return: The object instance, or ``None``. 

3753 

3754 """ # noqa: E501 

3755 return self._get_impl( 

3756 entity, 

3757 ident, 

3758 loading._load_on_pk_identity, 

3759 options=options, 

3760 populate_existing=populate_existing, 

3761 with_for_update=with_for_update, 

3762 identity_token=identity_token, 

3763 execution_options=execution_options, 

3764 bind_arguments=bind_arguments, 

3765 ) 

3766 

3767 def get_one( 

3768 self, 

3769 entity: _EntityBindKey[_O], 

3770 ident: _PKIdentityArgument, 

3771 *, 

3772 options: Optional[Sequence[ORMOption]] = None, 

3773 populate_existing: bool = False, 

3774 with_for_update: ForUpdateParameter = None, 

3775 identity_token: Optional[Any] = None, 

3776 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3777 bind_arguments: Optional[_BindArguments] = None, 

3778 ) -> _O: 

3779 """Return exactly one instance based on the given primary key 

3780 identifier, or raise an exception if not found. 

3781 

3782 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3783 

3784 For a detailed documentation of the arguments see the 

3785 method :meth:`.Session.get`. 

3786 

3787 .. versionadded:: 2.0.22 

3788 

3789 :return: The object instance. 

3790 

3791 .. seealso:: 

3792 

3793 :meth:`.Session.get` - equivalent method that instead 

3794 returns ``None`` if no row was found with the provided primary 

3795 key 

3796 

3797 """ 

3798 

3799 instance = self.get( 

3800 entity, 

3801 ident, 

3802 options=options, 

3803 populate_existing=populate_existing, 

3804 with_for_update=with_for_update, 

3805 identity_token=identity_token, 

3806 execution_options=execution_options, 

3807 bind_arguments=bind_arguments, 

3808 ) 

3809 

3810 if instance is None: 

3811 raise sa_exc.NoResultFound( 

3812 "No row was found when one was required" 

3813 ) 

3814 

3815 return instance 

3816 

3817 def _get_impl( 

3818 self, 

3819 entity: _EntityBindKey[_O], 

3820 primary_key_identity: _PKIdentityArgument, 

3821 db_load_fn: Callable[..., _O], 

3822 *, 

3823 options: Optional[Sequence[ExecutableOption]] = None, 

3824 populate_existing: bool = False, 

3825 with_for_update: ForUpdateParameter = None, 

3826 identity_token: Optional[Any] = None, 

3827 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3828 bind_arguments: Optional[_BindArguments] = None, 

3829 ) -> Optional[_O]: 

3830 # convert composite types to individual args 

3831 if ( 

3832 is_composite_class(primary_key_identity) 

3833 and type(primary_key_identity) 

3834 in descriptor_props._composite_getters 

3835 ): 

3836 getter = descriptor_props._composite_getters[ 

3837 type(primary_key_identity) 

3838 ] 

3839 primary_key_identity = getter(primary_key_identity) 

3840 

3841 mapper: Optional[Mapper[_O]] = inspect(entity) 

3842 

3843 if mapper is None or not mapper.is_mapper: 

3844 raise sa_exc.ArgumentError( 

3845 "Expected mapped class or mapper, got: %r" % entity 

3846 ) 

3847 

3848 is_dict = isinstance(primary_key_identity, dict) 

3849 if not is_dict: 

3850 primary_key_identity = util.to_list( 

3851 primary_key_identity, default=[None] 

3852 ) 

3853 

3854 if len(primary_key_identity) != len(mapper.primary_key): 

3855 raise sa_exc.InvalidRequestError( 

3856 "Incorrect number of values in identifier to formulate " 

3857 "primary key for session.get(); primary key columns " 

3858 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3859 ) 

3860 

3861 if is_dict: 

3862 pk_synonyms = mapper._pk_synonyms 

3863 

3864 if pk_synonyms: 

3865 correct_keys = set(pk_synonyms).intersection( 

3866 primary_key_identity 

3867 ) 

3868 

3869 if correct_keys: 

3870 primary_key_identity = dict(primary_key_identity) 

3871 for k in correct_keys: 

3872 primary_key_identity[pk_synonyms[k]] = ( 

3873 primary_key_identity[k] 

3874 ) 

3875 

3876 try: 

3877 primary_key_identity = list( 

3878 primary_key_identity[prop.key] 

3879 for prop in mapper._identity_key_props 

3880 ) 

3881 

3882 except KeyError as err: 

3883 raise sa_exc.InvalidRequestError( 

3884 "Incorrect names of values in identifier to formulate " 

3885 "primary key for session.get(); primary key attribute " 

3886 "names are %s (synonym names are also accepted)" 

3887 % ",".join( 

3888 "'%s'" % prop.key 

3889 for prop in mapper._identity_key_props 

3890 ) 

3891 ) from err 

3892 

3893 if ( 

3894 not populate_existing 

3895 and not mapper.always_refresh 

3896 and with_for_update is None 

3897 ): 

3898 instance = self._identity_lookup( 

3899 mapper, 

3900 primary_key_identity, 

3901 identity_token=identity_token, 

3902 execution_options=execution_options, 

3903 bind_arguments=bind_arguments, 

3904 ) 

3905 

3906 if instance is not None: 

3907 # reject calls for id in identity map but class 

3908 # mismatch. 

3909 if not isinstance(instance, mapper.class_): 

3910 return None 

3911 return instance 

3912 

3913 # TODO: this was being tested before, but this is not possible 

3914 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3915 

3916 load_options = context.QueryContext.default_load_options 

3917 

3918 if populate_existing: 

3919 load_options += {"_populate_existing": populate_existing} 

3920 statement = sql.select(mapper) 

3921 if with_for_update is not None: 

3922 statement._for_update_arg = ForUpdateArg._from_argument( 

3923 with_for_update 

3924 ) 

3925 

3926 if options: 

3927 statement = statement.options(*options) 

3928 if self.execution_options: 

3929 execution_options = self.execution_options.union(execution_options) 

3930 return db_load_fn( 

3931 self, 

3932 statement, 

3933 primary_key_identity, 

3934 load_options=load_options, 

3935 identity_token=identity_token, 

3936 execution_options=execution_options, 

3937 bind_arguments=bind_arguments, 

3938 ) 

3939 

3940 def merge( 

3941 self, 

3942 instance: _O, 

3943 *, 

3944 load: bool = True, 

3945 options: Optional[Sequence[ORMOption]] = None, 

3946 ) -> _O: 

3947 """Copy the state of a given instance into a corresponding instance 

3948 within this :class:`.Session`. 

3949 

3950 :meth:`.Session.merge` examines the primary key attributes of the 

3951 source instance, and attempts to reconcile it with an instance of the 

3952 same primary key in the session. If not found locally, it attempts 

3953 to load the object from the database based on primary key, and if 

3954 none can be located, creates a new instance. The state of each 

3955 attribute on the source instance is then copied to the target 

3956 instance. The resulting target instance is then returned by the 

3957 method; the original source instance is left unmodified, and 

3958 un-associated with the :class:`.Session` if not already. 

3959 

3960 This operation cascades to associated instances if the association is 

3961 mapped with ``cascade="merge"``. 

3962 

3963 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3964 

3965 :param instance: Instance to be merged. 

3966 :param load: Boolean, when False, :meth:`.merge` switches into 

3967 a "high performance" mode which causes it to forego emitting history 

3968 events as well as all database access. This flag is used for 

3969 cases such as transferring graphs of objects into a :class:`.Session` 

3970 from a second level cache, or to transfer just-loaded objects 

3971 into the :class:`.Session` owned by a worker thread or process 

3972 without re-querying the database. 

3973 

3974 The ``load=False`` use case adds the caveat that the given 

3975 object has to be in a "clean" state, that is, has no pending changes 

3976 to be flushed - even if the incoming object is detached from any 

3977 :class:`.Session`. This is so that when 

3978 the merge operation populates local attributes and 

3979 cascades to related objects and 

3980 collections, the values can be "stamped" onto the 

3981 target object as is, without generating any history or attribute 

3982 events, and without the need to reconcile the incoming data with 

3983 any existing related objects or collections that might not 

3984 be loaded. The resulting objects from ``load=False`` are always 

3985 produced as "clean", so it is only appropriate that the given objects 

3986 should be "clean" as well, else this suggests a mis-use of the 

3987 method. 

3988 :param options: optional sequence of loader options which will be 

3989 applied to the :meth:`_orm.Session.get` method when the merge 

3990 operation loads the existing version of the object from the database. 

3991 

3992 .. versionadded:: 1.4.24 

3993 

3994 

3995 .. seealso:: 

3996 

3997 :func:`.make_transient_to_detached` - provides for an alternative 

3998 means of "merging" a single object into the :class:`.Session` 

3999 

4000 :meth:`.Session.merge_all` - multiple instance version 

4001 

4002 """ 

4003 

4004 if self._warn_on_events: 

4005 self._flush_warning("Session.merge()") 

4006 

4007 if load: 

4008 # flush current contents if we expect to load data 

4009 self._autoflush() 

4010 

4011 with self.no_autoflush: 

4012 return self._merge( 

4013 object_state(instance), 

4014 attributes.instance_dict(instance), 

4015 load=load, 

4016 options=options, 

4017 _recursive={}, 

4018 _resolve_conflict_map={}, 

4019 ) 

4020 

4021 def merge_all( 

4022 self, 

4023 instances: Iterable[_O], 

4024 *, 

4025 load: bool = True, 

4026 options: Optional[Sequence[ORMOption]] = None, 

4027 ) -> Sequence[_O]: 

4028 """Calls :meth:`.Session.merge` on multiple instances. 

4029 

4030 .. seealso:: 

4031 

4032 :meth:`.Session.merge` - main documentation on merge 

4033 

4034 .. versionadded:: 2.1 

4035 

4036 """ 

4037 

4038 if self._warn_on_events: 

4039 self._flush_warning("Session.merge_all()") 

4040 

4041 if load: 

4042 # flush current contents if we expect to load data 

4043 self._autoflush() 

4044 

4045 return [ 

4046 self._merge( 

4047 object_state(instance), 

4048 attributes.instance_dict(instance), 

4049 load=load, 

4050 options=options, 

4051 _recursive={}, 

4052 _resolve_conflict_map={}, 

4053 ) 

4054 for instance in instances 

4055 ] 

4056 

4057 def _merge( 

4058 self, 

4059 state: InstanceState[_O], 

4060 state_dict: _InstanceDict, 

4061 *, 

4062 options: Optional[Sequence[ORMOption]] = None, 

4063 load: bool, 

4064 _recursive: Dict[Any, object], 

4065 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4066 ) -> _O: 

4067 mapper: Mapper[_O] = _state_mapper(state) 

4068 if state in _recursive: 

4069 return cast(_O, _recursive[state]) 

4070 

4071 new_instance = False 

4072 key = state.key 

4073 

4074 merged: Optional[_O] 

4075 

4076 if key is None: 

4077 if state in self._new: 

4078 util.warn( 

4079 "Instance %s is already pending in this Session yet is " 

4080 "being merged again; this is probably not what you want " 

4081 "to do" % state_str(state) 

4082 ) 

4083 

4084 if not load: 

4085 raise sa_exc.InvalidRequestError( 

4086 "merge() with load=False option does not support " 

4087 "objects transient (i.e. unpersisted) objects. flush() " 

4088 "all changes on mapped instances before merging with " 

4089 "load=False." 

4090 ) 

4091 key = mapper._identity_key_from_state(state) 

4092 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4093 1 

4094 ] and ( 

4095 not _none_set.intersection(key[1]) 

4096 or ( 

4097 mapper.allow_partial_pks 

4098 and not _none_set.issuperset(key[1]) 

4099 ) 

4100 ) 

4101 else: 

4102 key_is_persistent = True 

4103 

4104 merged = self.identity_map.get(key) 

4105 

4106 if merged is None: 

4107 if key_is_persistent and key in _resolve_conflict_map: 

4108 merged = cast(_O, _resolve_conflict_map[key]) 

4109 

4110 elif not load: 

4111 if state.modified: 

4112 raise sa_exc.InvalidRequestError( 

4113 "merge() with load=False option does not support " 

4114 "objects marked as 'dirty'. flush() all changes on " 

4115 "mapped instances before merging with load=False." 

4116 ) 

4117 merged = mapper.class_manager.new_instance() 

4118 merged_state = attributes.instance_state(merged) 

4119 merged_state.key = key 

4120 self._update_impl(merged_state) 

4121 new_instance = True 

4122 

4123 elif key_is_persistent: 

4124 merged = self.get( 

4125 mapper.class_, 

4126 key[1], 

4127 identity_token=key[2], 

4128 options=options, 

4129 ) 

4130 

4131 if merged is None: 

4132 merged = mapper.class_manager.new_instance() 

4133 merged_state = attributes.instance_state(merged) 

4134 merged_dict = attributes.instance_dict(merged) 

4135 new_instance = True 

4136 self._save_or_update_state(merged_state) 

4137 else: 

4138 merged_state = attributes.instance_state(merged) 

4139 merged_dict = attributes.instance_dict(merged) 

4140 

4141 _recursive[state] = merged 

4142 _resolve_conflict_map[key] = merged 

4143 

4144 # check that we didn't just pull the exact same 

4145 # state out. 

4146 if state is not merged_state: 

4147 # version check if applicable 

4148 if mapper.version_id_col is not None: 

4149 existing_version = mapper._get_state_attr_by_column( 

4150 state, 

4151 state_dict, 

4152 mapper.version_id_col, 

4153 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4154 ) 

4155 

4156 merged_version = mapper._get_state_attr_by_column( 

4157 merged_state, 

4158 merged_dict, 

4159 mapper.version_id_col, 

4160 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4161 ) 

4162 

4163 if ( 

4164 existing_version 

4165 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4166 and merged_version 

4167 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4168 and existing_version != merged_version 

4169 ): 

4170 raise exc.StaleDataError( 

4171 "Version id '%s' on merged state %s " 

4172 "does not match existing version '%s'. " 

4173 "Leave the version attribute unset when " 

4174 "merging to update the most recent version." 

4175 % ( 

4176 existing_version, 

4177 state_str(merged_state), 

4178 merged_version, 

4179 ) 

4180 ) 

4181 

4182 merged_state.load_path = state.load_path 

4183 merged_state.load_options = state.load_options 

4184 

4185 # since we are copying load_options, we need to copy 

4186 # the callables_ that would have been generated by those 

4187 # load_options. 

4188 # assumes that the callables we put in state.callables_ 

4189 # are not instance-specific (which they should not be) 

4190 merged_state._copy_callables(state) 

4191 

4192 for prop in mapper.iterate_properties: 

4193 prop.merge( 

4194 self, 

4195 state, 

4196 state_dict, 

4197 merged_state, 

4198 merged_dict, 

4199 load, 

4200 _recursive, 

4201 _resolve_conflict_map, 

4202 ) 

4203 

4204 if not load: 

4205 # remove any history 

4206 merged_state._commit_all(merged_dict, self.identity_map) 

4207 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4208 merged_state, None 

4209 ) 

4210 

4211 if new_instance: 

4212 merged_state.manager.dispatch.load(merged_state, None) 

4213 

4214 return merged 

4215 

4216 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4217 if not self.identity_map.contains_state(state): 

4218 raise sa_exc.InvalidRequestError( 

4219 "Instance '%s' is not persistent within this Session" 

4220 % state_str(state) 

4221 ) 

4222 

4223 def _save_impl(self, state: InstanceState[Any]) -> None: 

4224 if state.key is not None: 

4225 raise sa_exc.InvalidRequestError( 

4226 "Object '%s' already has an identity - " 

4227 "it can't be registered as pending" % state_str(state) 

4228 ) 

4229 

4230 obj = state.obj() 

4231 to_attach = self._before_attach(state, obj) 

4232 if state not in self._new: 

4233 self._new[state] = obj 

4234 state.insert_order = len(self._new) 

4235 if to_attach: 

4236 self._after_attach(state, obj) 

4237 

4238 def _update_impl( 

4239 self, state: InstanceState[Any], revert_deletion: bool = False 

4240 ) -> None: 

4241 if state.key is None: 

4242 raise sa_exc.InvalidRequestError( 

4243 "Instance '%s' is not persisted" % state_str(state) 

4244 ) 

4245 

4246 if state._deleted: 

4247 if revert_deletion: 

4248 if not state._attached: 

4249 return 

4250 del state._deleted 

4251 else: 

4252 raise sa_exc.InvalidRequestError( 

4253 "Instance '%s' has been deleted. " 

4254 "Use the make_transient() " 

4255 "function to send this object back " 

4256 "to the transient state." % state_str(state) 

4257 ) 

4258 

4259 obj = state.obj() 

4260 

4261 # check for late gc 

4262 if obj is None: 

4263 return 

4264 

4265 to_attach = self._before_attach(state, obj) 

4266 

4267 self._deleted.pop(state, None) 

4268 if revert_deletion: 

4269 self.identity_map.replace(state) 

4270 else: 

4271 self.identity_map.add(state) 

4272 

4273 if to_attach: 

4274 self._after_attach(state, obj) 

4275 elif revert_deletion: 

4276 self.dispatch.deleted_to_persistent(self, state) 

4277 

4278 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4279 if state.key is None: 

4280 self._save_impl(state) 

4281 else: 

4282 self._update_impl(state) 

4283 

4284 def enable_relationship_loading(self, obj: object) -> None: 

4285 """Associate an object with this :class:`.Session` for related 

4286 object loading. 

4287 

4288 .. warning:: 

4289 

4290 :meth:`.enable_relationship_loading` exists to serve special 

4291 use cases and is not recommended for general use. 

4292 

4293 Accesses of attributes mapped with :func:`_orm.relationship` 

4294 will attempt to load a value from the database using this 

4295 :class:`.Session` as the source of connectivity. The values 

4296 will be loaded based on foreign key and primary key values 

4297 present on this object - if not present, then those relationships 

4298 will be unavailable. 

4299 

4300 The object will be attached to this session, but will 

4301 **not** participate in any persistence operations; its state 

4302 for almost all purposes will remain either "transient" or 

4303 "detached", except for the case of relationship loading. 

4304 

4305 Also note that backrefs will often not work as expected. 

4306 Altering a relationship-bound attribute on the target object 

4307 may not fire off a backref event, if the effective value 

4308 is what was already loaded from a foreign-key-holding value. 

4309 

4310 The :meth:`.Session.enable_relationship_loading` method is 

4311 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4312 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4313 an object to remain transient while still being able to load 

4314 related items. 

4315 

4316 To make a transient object associated with a :class:`.Session` 

4317 via :meth:`.Session.enable_relationship_loading` pending, add 

4318 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4319 If the object instead represents an existing identity in the database, 

4320 it should be merged using :meth:`.Session.merge`. 

4321 

4322 :meth:`.Session.enable_relationship_loading` does not improve 

4323 behavior when the ORM is used normally - object references should be 

4324 constructed at the object level, not at the foreign key level, so 

4325 that they are present in an ordinary way before flush() 

4326 proceeds. This method is not intended for general use. 

4327 

4328 .. seealso:: 

4329 

4330 :paramref:`_orm.relationship.load_on_pending` - this flag 

4331 allows per-relationship loading of many-to-ones on items that 

4332 are pending. 

4333 

4334 :func:`.make_transient_to_detached` - allows for an object to 

4335 be added to a :class:`.Session` without SQL emitted, which then 

4336 will unexpire attributes on access. 

4337 

4338 """ 

4339 try: 

4340 state = attributes.instance_state(obj) 

4341 except exc.NO_STATE as err: 

4342 raise exc.UnmappedInstanceError(obj) from err 

4343 

4344 to_attach = self._before_attach(state, obj) 

4345 state._load_pending = True 

4346 if to_attach: 

4347 self._after_attach(state, obj) 

4348 

4349 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4350 self._autobegin_t() 

4351 

4352 if state.session_id == self.hash_key: 

4353 return False 

4354 

4355 if state.session_id and state.session_id in _sessions: 

4356 raise sa_exc.InvalidRequestError( 

4357 "Object '%s' is already attached to session '%s' " 

4358 "(this is '%s')" 

4359 % (state_str(state), state.session_id, self.hash_key) 

4360 ) 

4361 

4362 self.dispatch.before_attach(self, state) 

4363 

4364 return True 

4365 

4366 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4367 state.session_id = self.hash_key 

4368 if state.modified and state._strong_obj is None: 

4369 state._strong_obj = obj 

4370 self.dispatch.after_attach(self, state) 

4371 

4372 if state.key: 

4373 self.dispatch.detached_to_persistent(self, state) 

4374 else: 

4375 self.dispatch.transient_to_pending(self, state) 

4376 

4377 def __contains__(self, instance: object) -> bool: 

4378 """Return True if the instance is associated with this session. 

4379 

4380 The instance may be pending or persistent within the Session for a 

4381 result of True. 

4382 

4383 """ 

4384 try: 

4385 state = attributes.instance_state(instance) 

4386 except exc.NO_STATE as err: 

4387 raise exc.UnmappedInstanceError(instance) from err 

4388 return self._contains_state(state) 

4389 

4390 def __iter__(self) -> Iterator[object]: 

4391 """Iterate over all pending or persistent instances within this 

4392 Session. 

4393 

4394 """ 

4395 return iter( 

4396 list(self._new.values()) + list(self.identity_map.values()) 

4397 ) 

4398 

4399 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4400 return state in self._new or self.identity_map.contains_state(state) 

4401 

4402 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4403 """Flush all the object changes to the database. 

4404 

4405 Writes out all pending object creations, deletions and modifications 

4406 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4407 automatically ordered by the Session's unit of work dependency 

4408 solver. 

4409 

4410 Database operations will be issued in the current transactional 

4411 context and do not affect the state of the transaction, unless an 

4412 error occurs, in which case the entire transaction is rolled back. 

4413 You may flush() as often as you like within a transaction to move 

4414 changes from Python to the database's transaction buffer. 

4415 

4416 :param objects: Optional; restricts the flush operation to operate 

4417 only on elements that are in the given collection. 

4418 

4419 This feature is for an extremely narrow set of use cases where 

4420 particular objects may need to be operated upon before the 

4421 full flush() occurs. It is not intended for general use. 

4422 

4423 .. deprecated:: 2.1 

4424 

4425 """ 

4426 

4427 if self._flushing: 

4428 raise sa_exc.InvalidRequestError("Session is already flushing") 

4429 

4430 if self._is_clean(): 

4431 return 

4432 try: 

4433 self._flushing = True 

4434 self._flush(objects) 

4435 finally: 

4436 self._flushing = False 

4437 

4438 def _flush_warning(self, method: Any) -> None: 

4439 util.warn( 

4440 "Usage of the '%s' operation is not currently supported " 

4441 "within the execution stage of the flush process. " 

4442 "Results may not be consistent. Consider using alternative " 

4443 "event listeners or connection-level operations instead." % method 

4444 ) 

4445 

4446 def _is_clean(self) -> bool: 

4447 return ( 

4448 not self.identity_map.check_modified() 

4449 and not self._deleted 

4450 and not self._new 

4451 ) 

4452 

4453 # have this here since it otherwise causes issues with the proxy 

4454 # method generation 

4455 @deprecated_params( 

4456 objects=( 

4457 "2.1", 

4458 "The `objects` parameter of `Session.flush` is deprecated", 

4459 ) 

4460 ) 

4461 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4462 dirty = self._dirty_states 

4463 if not dirty and not self._deleted and not self._new: 

4464 self.identity_map._modified.clear() 

4465 return 

4466 

4467 flush_context = UOWTransaction(self) 

4468 

4469 if self.dispatch.before_flush: 

4470 self.dispatch.before_flush(self, flush_context, objects) 

4471 # re-establish "dirty states" in case the listeners 

4472 # added 

4473 dirty = self._dirty_states 

4474 

4475 deleted = set(self._deleted) 

4476 new = set(self._new) 

4477 

4478 dirty = set(dirty).difference(deleted) 

4479 

4480 # create the set of all objects we want to operate upon 

4481 if objects: 

4482 # specific list passed in 

4483 objset = set() 

4484 for o in objects: 

4485 try: 

4486 state = attributes.instance_state(o) 

4487 

4488 except exc.NO_STATE as err: 

4489 raise exc.UnmappedInstanceError(o) from err 

4490 objset.add(state) 

4491 else: 

4492 objset = None 

4493 

4494 # store objects whose fate has been decided 

4495 processed = set() 

4496 

4497 # put all saves/updates into the flush context. detect top-level 

4498 # orphans and throw them into deleted. 

4499 if objset: 

4500 proc = new.union(dirty).intersection(objset).difference(deleted) 

4501 else: 

4502 proc = new.union(dirty).difference(deleted) 

4503 

4504 for state in proc: 

4505 is_orphan = _state_mapper(state)._is_orphan(state) 

4506 

4507 is_persistent_orphan = is_orphan and state.has_identity 

4508 

4509 if ( 

4510 is_orphan 

4511 and not is_persistent_orphan 

4512 and state._orphaned_outside_of_session 

4513 ): 

4514 self._expunge_states([state]) 

4515 else: 

4516 _reg = flush_context.register_object( 

4517 state, isdelete=is_persistent_orphan 

4518 ) 

4519 assert _reg, "Failed to add object to the flush context!" 

4520 processed.add(state) 

4521 

4522 # put all remaining deletes into the flush context. 

4523 if objset: 

4524 proc = deleted.intersection(objset).difference(processed) 

4525 else: 

4526 proc = deleted.difference(processed) 

4527 for state in proc: 

4528 _reg = flush_context.register_object(state, isdelete=True) 

4529 assert _reg, "Failed to add object to the flush context!" 

4530 

4531 if not flush_context.has_work: 

4532 return 

4533 

4534 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4535 try: 

4536 self._warn_on_events = True 

4537 try: 

4538 flush_context.execute() 

4539 finally: 

4540 self._warn_on_events = False 

4541 

4542 self.dispatch.after_flush(self, flush_context) 

4543 

4544 flush_context.finalize_flush_changes() 

4545 

4546 if not objects and self.identity_map._modified: 

4547 len_ = len(self.identity_map._modified) 

4548 

4549 statelib.InstanceState._commit_all_states( 

4550 [ 

4551 (state, state.dict) 

4552 for state in self.identity_map._modified 

4553 ], 

4554 instance_dict=self.identity_map, 

4555 ) 

4556 util.warn( 

4557 "Attribute history events accumulated on %d " 

4558 "previously clean instances " 

4559 "within inner-flush event handlers have been " 

4560 "reset, and will not result in database updates. " 

4561 "Consider using set_committed_value() within " 

4562 "inner-flush event handlers to avoid this warning." % len_ 

4563 ) 

4564 

4565 # useful assertions: 

4566 # if not objects: 

4567 # assert not self.identity_map._modified 

4568 # else: 

4569 # assert self.identity_map._modified == \ 

4570 # self.identity_map._modified.difference(objects) 

4571 

4572 self.dispatch.after_flush_postexec(self, flush_context) 

4573 

4574 transaction.commit() 

4575 

4576 except: 

4577 with util.safe_reraise(): 

4578 transaction.rollback(_capture_exception=True) 

4579 

4580 def bulk_save_objects( 

4581 self, 

4582 objects: Iterable[object], 

4583 return_defaults: bool = False, 

4584 update_changed_only: bool = True, 

4585 preserve_order: bool = True, 

4586 ) -> None: 

4587 """Perform a bulk save of the given list of objects. 

4588 

4589 .. legacy:: 

4590 

4591 This method is a legacy feature as of the 2.0 series of 

4592 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4593 the sections :ref:`orm_queryguide_bulk_insert` and 

4594 :ref:`orm_queryguide_bulk_update`. 

4595 

4596 For general INSERT and UPDATE of existing ORM mapped objects, 

4597 prefer standard :term:`unit of work` data management patterns, 

4598 introduced in the :ref:`unified_tutorial` at 

4599 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4600 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4601 which solves previous issues of bulk INSERT slowness. 

4602 

4603 :param objects: a sequence of mapped object instances. The mapped 

4604 objects are persisted as is, and are **not** associated with the 

4605 :class:`.Session` afterwards. 

4606 

4607 For each object, whether the object is sent as an INSERT or an 

4608 UPDATE is dependent on the same rules used by the :class:`.Session` 

4609 in traditional operation; if the object has the 

4610 :attr:`.InstanceState.key` 

4611 attribute set, then the object is assumed to be "detached" and 

4612 will result in an UPDATE. Otherwise, an INSERT is used. 

4613 

4614 In the case of an UPDATE, statements are grouped based on which 

4615 attributes have changed, and are thus to be the subject of each 

4616 SET clause. If ``update_changed_only`` is False, then all 

4617 attributes present within each object are applied to the UPDATE 

4618 statement, which may help in allowing the statements to be grouped 

4619 together into a larger executemany(), and will also reduce the 

4620 overhead of checking history on attributes. 

4621 

4622 :param return_defaults: when True, rows that are missing values which 

4623 generate defaults, namely integer primary key defaults and sequences, 

4624 will be inserted **one at a time**, so that the primary key value 

4625 is available. In particular this will allow joined-inheritance 

4626 and other multi-table mappings to insert correctly without the need 

4627 to provide primary key values ahead of time; however, 

4628 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4629 reduces the performance gains** of the method overall. It is strongly 

4630 advised to please use the standard :meth:`_orm.Session.add_all` 

4631 approach. 

4632 

4633 :param update_changed_only: when True, UPDATE statements are rendered 

4634 based on those attributes in each state that have logged changes. 

4635 When False, all attributes present are rendered into the SET clause 

4636 with the exception of primary key attributes. 

4637 

4638 :param preserve_order: when True, the order of inserts and updates 

4639 matches exactly the order in which the objects are given. When 

4640 False, common types of objects are grouped into inserts 

4641 and updates, to allow for more batching opportunities. 

4642 

4643 .. seealso:: 

4644 

4645 :doc:`queryguide/dml` 

4646 

4647 :meth:`.Session.bulk_insert_mappings` 

4648 

4649 :meth:`.Session.bulk_update_mappings` 

4650 

4651 """ 

4652 

4653 obj_states: Iterable[InstanceState[Any]] 

4654 

4655 obj_states = (attributes.instance_state(obj) for obj in objects) 

4656 

4657 if not preserve_order: 

4658 # the purpose of this sort is just so that common mappers 

4659 # and persistence states are grouped together, so that groupby 

4660 # will return a single group for a particular type of mapper. 

4661 # it's not trying to be deterministic beyond that. 

4662 obj_states = sorted( 

4663 obj_states, 

4664 key=lambda state: (id(state.mapper), state.key is not None), 

4665 ) 

4666 

4667 def grouping_key( 

4668 state: InstanceState[_O], 

4669 ) -> Tuple[Mapper[_O], bool]: 

4670 return (state.mapper, state.key is not None) 

4671 

4672 for (mapper, isupdate), states in itertools.groupby( 

4673 obj_states, grouping_key 

4674 ): 

4675 self._bulk_save_mappings( 

4676 mapper, 

4677 states, 

4678 isupdate=isupdate, 

4679 isstates=True, 

4680 return_defaults=return_defaults, 

4681 update_changed_only=update_changed_only, 

4682 render_nulls=False, 

4683 ) 

4684 

4685 def bulk_insert_mappings( 

4686 self, 

4687 mapper: Mapper[Any], 

4688 mappings: Iterable[Dict[str, Any]], 

4689 return_defaults: bool = False, 

4690 render_nulls: bool = False, 

4691 ) -> None: 

4692 """Perform a bulk insert of the given list of mapping dictionaries. 

4693 

4694 .. legacy:: 

4695 

4696 This method is a legacy feature as of the 2.0 series of 

4697 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4698 the sections :ref:`orm_queryguide_bulk_insert` and 

4699 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4700 implementation details with this method and adds new features 

4701 as well. 

4702 

4703 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4704 object, 

4705 representing the single kind of object represented within the mapping 

4706 list. 

4707 

4708 :param mappings: a sequence of dictionaries, each one containing the 

4709 state of the mapped row to be inserted, in terms of the attribute 

4710 names on the mapped class. If the mapping refers to multiple tables, 

4711 such as a joined-inheritance mapping, each dictionary must contain all 

4712 keys to be populated into all tables. 

4713 

4714 :param return_defaults: when True, the INSERT process will be altered 

4715 to ensure that newly generated primary key values will be fetched. 

4716 The rationale for this parameter is typically to enable 

4717 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4718 be bulk inserted. 

4719 

4720 .. note:: for backends that don't support RETURNING, the 

4721 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4722 parameter can significantly decrease performance as INSERT 

4723 statements can no longer be batched. See 

4724 :ref:`engine_insertmanyvalues` 

4725 for background on which backends are affected. 

4726 

4727 :param render_nulls: When True, a value of ``None`` will result 

4728 in a NULL value being included in the INSERT statement, rather 

4729 than the column being omitted from the INSERT. This allows all 

4730 the rows being INSERTed to have the identical set of columns which 

4731 allows the full set of rows to be batched to the DBAPI. Normally, 

4732 each column-set that contains a different combination of NULL values 

4733 than the previous row must omit a different series of columns from 

4734 the rendered INSERT statement, which means it must be emitted as a 

4735 separate statement. By passing this flag, the full set of rows 

4736 are guaranteed to be batchable into one batch; the cost however is 

4737 that server-side defaults which are invoked by an omitted column will 

4738 be skipped, so care must be taken to ensure that these are not 

4739 necessary. 

4740 

4741 .. warning:: 

4742 

4743 When this flag is set, **server side default SQL values will 

4744 not be invoked** for those columns that are inserted as NULL; 

4745 the NULL value will be sent explicitly. Care must be taken 

4746 to ensure that no server-side default functions need to be 

4747 invoked for the operation as a whole. 

4748 

4749 .. seealso:: 

4750 

4751 :doc:`queryguide/dml` 

4752 

4753 :meth:`.Session.bulk_save_objects` 

4754 

4755 :meth:`.Session.bulk_update_mappings` 

4756 

4757 """ 

4758 self._bulk_save_mappings( 

4759 mapper, 

4760 mappings, 

4761 isupdate=False, 

4762 isstates=False, 

4763 return_defaults=return_defaults, 

4764 update_changed_only=False, 

4765 render_nulls=render_nulls, 

4766 ) 

4767 

4768 def bulk_update_mappings( 

4769 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4770 ) -> None: 

4771 """Perform a bulk update of the given list of mapping dictionaries. 

4772 

4773 .. legacy:: 

4774 

4775 This method is a legacy feature as of the 2.0 series of 

4776 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4777 the sections :ref:`orm_queryguide_bulk_insert` and 

4778 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4779 implementation details with this method and adds new features 

4780 as well. 

4781 

4782 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4783 object, 

4784 representing the single kind of object represented within the mapping 

4785 list. 

4786 

4787 :param mappings: a sequence of dictionaries, each one containing the 

4788 state of the mapped row to be updated, in terms of the attribute names 

4789 on the mapped class. If the mapping refers to multiple tables, such 

4790 as a joined-inheritance mapping, each dictionary may contain keys 

4791 corresponding to all tables. All those keys which are present and 

4792 are not part of the primary key are applied to the SET clause of the 

4793 UPDATE statement; the primary key values, which are required, are 

4794 applied to the WHERE clause. 

4795 

4796 

4797 .. seealso:: 

4798 

4799 :doc:`queryguide/dml` 

4800 

4801 :meth:`.Session.bulk_insert_mappings` 

4802 

4803 :meth:`.Session.bulk_save_objects` 

4804 

4805 """ 

4806 self._bulk_save_mappings( 

4807 mapper, 

4808 mappings, 

4809 isupdate=True, 

4810 isstates=False, 

4811 return_defaults=False, 

4812 update_changed_only=False, 

4813 render_nulls=False, 

4814 ) 

4815 

4816 def _bulk_save_mappings( 

4817 self, 

4818 mapper: Mapper[_O], 

4819 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4820 *, 

4821 isupdate: bool, 

4822 isstates: bool, 

4823 return_defaults: bool, 

4824 update_changed_only: bool, 

4825 render_nulls: bool, 

4826 ) -> None: 

4827 mapper = _class_to_mapper(mapper) 

4828 self._flushing = True 

4829 

4830 transaction = self._autobegin_t()._begin() 

4831 try: 

4832 if isupdate: 

4833 bulk_persistence._bulk_update( 

4834 mapper, 

4835 mappings, 

4836 transaction, 

4837 isstates=isstates, 

4838 update_changed_only=update_changed_only, 

4839 ) 

4840 else: 

4841 bulk_persistence._bulk_insert( 

4842 mapper, 

4843 mappings, 

4844 transaction, 

4845 isstates=isstates, 

4846 return_defaults=return_defaults, 

4847 render_nulls=render_nulls, 

4848 ) 

4849 transaction.commit() 

4850 

4851 except: 

4852 with util.safe_reraise(): 

4853 transaction.rollback(_capture_exception=True) 

4854 finally: 

4855 self._flushing = False 

4856 

4857 def is_modified( 

4858 self, instance: object, include_collections: bool = True 

4859 ) -> bool: 

4860 r"""Return ``True`` if the given instance has locally 

4861 modified attributes. 

4862 

4863 This method retrieves the history for each instrumented 

4864 attribute on the instance and performs a comparison of the current 

4865 value to its previously flushed or committed value, if any. 

4866 

4867 It is in effect a more expensive and accurate 

4868 version of checking for the given instance in the 

4869 :attr:`.Session.dirty` collection; a full test for 

4870 each attribute's net "dirty" status is performed. 

4871 

4872 E.g.:: 

4873 

4874 return session.is_modified(someobject) 

4875 

4876 A few caveats to this method apply: 

4877 

4878 * Instances present in the :attr:`.Session.dirty` collection may 

4879 report ``False`` when tested with this method. This is because 

4880 the object may have received change events via attribute mutation, 

4881 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4882 is the same as that loaded from the database, resulting in no net 

4883 change here. 

4884 * Scalar attributes may not have recorded the previously set 

4885 value when a new value was applied, if the attribute was not loaded, 

4886 or was expired, at the time the new value was received - in these 

4887 cases, the attribute is assumed to have a change, even if there is 

4888 ultimately no net change against its database value. SQLAlchemy in 

4889 most cases does not need the "old" value when a set event occurs, so 

4890 it skips the expense of a SQL call if the old value isn't present, 

4891 based on the assumption that an UPDATE of the scalar value is 

4892 usually needed, and in those few cases where it isn't, is less 

4893 expensive on average than issuing a defensive SELECT. 

4894 

4895 The "old" value is fetched unconditionally upon set only if the 

4896 attribute container has the ``active_history`` flag set to ``True``. 

4897 This flag is set typically for primary key attributes and scalar 

4898 object references that are not a simple many-to-one. To set this 

4899 flag for any arbitrary mapped column, use the ``active_history`` 

4900 argument with :func:`.column_property`. 

4901 

4902 :param instance: mapped instance to be tested for pending changes. 

4903 :param include_collections: Indicates if multivalued collections 

4904 should be included in the operation. Setting this to ``False`` is a 

4905 way to detect only local-column based properties (i.e. scalar columns 

4906 or many-to-one foreign keys) that would result in an UPDATE for this 

4907 instance upon flush. 

4908 

4909 """ 

4910 state = object_state(instance) 

4911 

4912 if not state.modified: 

4913 return False 

4914 

4915 dict_ = state.dict 

4916 

4917 for attr in state.manager.attributes: 

4918 if ( 

4919 not include_collections 

4920 and hasattr(attr.impl, "get_collection") 

4921 ) or not hasattr(attr.impl, "get_history"): 

4922 continue 

4923 

4924 (added, unchanged, deleted) = attr.impl.get_history( 

4925 state, dict_, passive=PassiveFlag.NO_CHANGE 

4926 ) 

4927 

4928 if added or deleted: 

4929 return True 

4930 else: 

4931 return False 

4932 

4933 @property 

4934 def is_active(self) -> bool: 

4935 """True if this :class:`.Session` not in "partial rollback" state. 

4936 

4937 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4938 a new transaction immediately, so this attribute will be False 

4939 when the :class:`_orm.Session` is first instantiated. 

4940 

4941 "partial rollback" state typically indicates that the flush process 

4942 of the :class:`_orm.Session` has failed, and that the 

4943 :meth:`_orm.Session.rollback` method must be emitted in order to 

4944 fully roll back the transaction. 

4945 

4946 If this :class:`_orm.Session` is not in a transaction at all, the 

4947 :class:`_orm.Session` will autobegin when it is first used, so in this 

4948 case :attr:`_orm.Session.is_active` will return True. 

4949 

4950 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4951 and that transaction has not been rolled back internally, the 

4952 :attr:`_orm.Session.is_active` will also return True. 

4953 

4954 .. seealso:: 

4955 

4956 :ref:`faq_session_rollback` 

4957 

4958 :meth:`_orm.Session.in_transaction` 

4959 

4960 """ 

4961 return self._transaction is None or self._transaction.is_active 

4962 

4963 @property 

4964 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4965 """The set of all persistent states considered dirty. 

4966 

4967 This method returns all states that were modified including 

4968 those that were possibly deleted. 

4969 

4970 """ 

4971 return self.identity_map._dirty_states() 

4972 

4973 @property 

4974 def dirty(self) -> IdentitySet: 

4975 """The set of all persistent instances considered dirty. 

4976 

4977 E.g.:: 

4978 

4979 some_mapped_object in session.dirty 

4980 

4981 Instances are considered dirty when they were modified but not 

4982 deleted. 

4983 

4984 Note that this 'dirty' calculation is 'optimistic'; most 

4985 attribute-setting or collection modification operations will 

4986 mark an instance as 'dirty' and place it in this set, even if 

4987 there is no net change to the attribute's value. At flush 

4988 time, the value of each attribute is compared to its 

4989 previously saved value, and if there's no net change, no SQL 

4990 operation will occur (this is a more expensive operation so 

4991 it's only done at flush time). 

4992 

4993 To check if an instance has actionable net changes to its 

4994 attributes, use the :meth:`.Session.is_modified` method. 

4995 

4996 """ 

4997 return IdentitySet( 

4998 [ 

4999 state.obj() 

5000 for state in self._dirty_states 

5001 if state not in self._deleted 

5002 ] 

5003 ) 

5004 

5005 @property 

5006 def deleted(self) -> IdentitySet: 

5007 "The set of all instances marked as 'deleted' within this ``Session``" 

5008 

5009 return util.IdentitySet(list(self._deleted.values())) 

5010 

5011 @property 

5012 def new(self) -> IdentitySet: 

5013 "The set of all instances marked as 'new' within this ``Session``." 

5014 

5015 return util.IdentitySet(list(self._new.values())) 

5016 

5017 

5018_S = TypeVar("_S", bound="Session") 

5019 

5020 

5021class sessionmaker(_SessionClassMethods, Generic[_S]): 

5022 """A configurable :class:`.Session` factory. 

5023 

5024 The :class:`.sessionmaker` factory generates new 

5025 :class:`.Session` objects when called, creating them given 

5026 the configurational arguments established here. 

5027 

5028 e.g.:: 

5029 

5030 from sqlalchemy import create_engine 

5031 from sqlalchemy.orm import sessionmaker 

5032 

5033 # an Engine, which the Session will use for connection 

5034 # resources 

5035 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

5036 

5037 Session = sessionmaker(engine) 

5038 

5039 with Session() as session: 

5040 session.add(some_object) 

5041 session.add(some_other_object) 

5042 session.commit() 

5043 

5044 Context manager use is optional; otherwise, the returned 

5045 :class:`_orm.Session` object may be closed explicitly via the 

5046 :meth:`_orm.Session.close` method. Using a 

5047 ``try:/finally:`` block is optional, however will ensure that the close 

5048 takes place even if there are database errors:: 

5049 

5050 session = Session() 

5051 try: 

5052 session.add(some_object) 

5053 session.add(some_other_object) 

5054 session.commit() 

5055 finally: 

5056 session.close() 

5057 

5058 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5059 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5060 for :class:`_engine.Connection` objects. In this way it also includes 

5061 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5062 manager which both begins and commits a transaction, as well as closes 

5063 out the :class:`_orm.Session` when complete, rolling back the transaction 

5064 if any errors occur:: 

5065 

5066 Session = sessionmaker(engine) 

5067 

5068 with Session.begin() as session: 

5069 session.add(some_object) 

5070 session.add(some_other_object) 

5071 # commits transaction, closes session 

5072 

5073 .. versionadded:: 1.4 

5074 

5075 When calling upon :class:`_orm.sessionmaker` to construct a 

5076 :class:`_orm.Session`, keyword arguments may also be passed to the 

5077 method; these arguments will override that of the globally configured 

5078 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5079 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5080 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5081 

5082 Session = sessionmaker(engine) 

5083 

5084 # bind an individual session to a connection 

5085 

5086 with engine.connect() as connection: 

5087 with Session(bind=connection) as session: 

5088 ... # work with session 

5089 

5090 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5091 can be used to specify additional keyword arguments to the factory, which 

5092 will take effect for subsequent :class:`.Session` objects generated. This 

5093 is usually used to associate one or more :class:`_engine.Engine` objects 

5094 with an existing 

5095 :class:`.sessionmaker` factory before it is first used:: 

5096 

5097 # application starts, sessionmaker does not have 

5098 # an engine bound yet 

5099 Session = sessionmaker() 

5100 

5101 # ... later, when an engine URL is read from a configuration 

5102 # file or other events allow the engine to be created 

5103 engine = create_engine("sqlite:///foo.db") 

5104 Session.configure(bind=engine) 

5105 

5106 sess = Session() 

5107 # work with session 

5108 

5109 .. seealso:: 

5110 

5111 :ref:`session_getting` - introductory text on creating 

5112 sessions using :class:`.sessionmaker`. 

5113 

5114 """ 

5115 

5116 class_: Type[_S] 

5117 

5118 @overload 

5119 def __init__( 

5120 self, 

5121 bind: Optional[_SessionBind] = ..., 

5122 *, 

5123 class_: Type[_S], 

5124 autoflush: bool = ..., 

5125 expire_on_commit: bool = ..., 

5126 info: Optional[_InfoType] = ..., 

5127 **kw: Any, 

5128 ): ... 

5129 

5130 @overload 

5131 def __init__( 

5132 self: "sessionmaker[Session]", 

5133 bind: Optional[_SessionBind] = ..., 

5134 *, 

5135 autoflush: bool = ..., 

5136 expire_on_commit: bool = ..., 

5137 info: Optional[_InfoType] = ..., 

5138 **kw: Any, 

5139 ): ... 

5140 

5141 def __init__( 

5142 self, 

5143 bind: Optional[_SessionBind] = None, 

5144 *, 

5145 class_: Type[_S] = Session, # type: ignore 

5146 autoflush: bool = True, 

5147 expire_on_commit: bool = True, 

5148 info: Optional[_InfoType] = None, 

5149 **kw: Any, 

5150 ): 

5151 r"""Construct a new :class:`.sessionmaker`. 

5152 

5153 All arguments here except for ``class_`` correspond to arguments 

5154 accepted by :class:`.Session` directly. See the 

5155 :meth:`.Session.__init__` docstring for more details on parameters. 

5156 

5157 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5158 with 

5159 which newly created :class:`.Session` objects will be associated. 

5160 :param class\_: class to use in order to create new :class:`.Session` 

5161 objects. Defaults to :class:`.Session`. 

5162 :param autoflush: The autoflush setting to use with newly created 

5163 :class:`.Session` objects. 

5164 

5165 .. seealso:: 

5166 

5167 :ref:`session_flushing` - additional background on autoflush 

5168 

5169 :param expire_on_commit=True: the 

5170 :paramref:`_orm.Session.expire_on_commit` setting to use 

5171 with newly created :class:`.Session` objects. 

5172 

5173 :param info: optional dictionary of information that will be available 

5174 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5175 replaced, when the ``info`` parameter is specified to the specific 

5176 :class:`.Session` construction operation. 

5177 

5178 :param \**kw: all other keyword arguments are passed to the 

5179 constructor of newly created :class:`.Session` objects. 

5180 

5181 """ 

5182 kw["bind"] = bind 

5183 kw["autoflush"] = autoflush 

5184 kw["expire_on_commit"] = expire_on_commit 

5185 if info is not None: 

5186 kw["info"] = info 

5187 self.kw = kw 

5188 # make our own subclass of the given class, so that 

5189 # events can be associated with it specifically. 

5190 self.class_ = type(class_.__name__, (class_,), {}) 

5191 

5192 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5193 """Produce a context manager that both provides a new 

5194 :class:`_orm.Session` as well as a transaction that commits. 

5195 

5196 

5197 e.g.:: 

5198 

5199 Session = sessionmaker(some_engine) 

5200 

5201 with Session.begin() as session: 

5202 session.add(some_object) 

5203 

5204 # commits transaction, closes session 

5205 

5206 .. versionadded:: 1.4 

5207 

5208 

5209 """ 

5210 

5211 session = self() 

5212 return session._maker_context_manager() 

5213 

5214 def __call__(self, **local_kw: Any) -> _S: 

5215 """Produce a new :class:`.Session` object using the configuration 

5216 established in this :class:`.sessionmaker`. 

5217 

5218 In Python, the ``__call__`` method is invoked on an object when 

5219 it is "called" in the same way as a function:: 

5220 

5221 Session = sessionmaker(some_engine) 

5222 session = Session() # invokes sessionmaker.__call__() 

5223 

5224 """ 

5225 for k, v in self.kw.items(): 

5226 if k == "info" and "info" in local_kw: 

5227 d = v.copy() 

5228 d.update(local_kw["info"]) 

5229 local_kw["info"] = d 

5230 else: 

5231 local_kw.setdefault(k, v) 

5232 return self.class_(**local_kw) 

5233 

5234 def configure(self, **new_kw: Any) -> None: 

5235 """(Re)configure the arguments for this sessionmaker. 

5236 

5237 e.g.:: 

5238 

5239 Session = sessionmaker() 

5240 

5241 Session.configure(bind=create_engine("sqlite://")) 

5242 """ 

5243 self.kw.update(new_kw) 

5244 

5245 def __repr__(self) -> str: 

5246 return "%s(class_=%r, %s)" % ( 

5247 self.__class__.__name__, 

5248 self.class_.__name__, 

5249 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5250 ) 

5251 

5252 

5253def close_all_sessions() -> None: 

5254 """Close all sessions in memory. 

5255 

5256 This function consults a global registry of all :class:`.Session` objects 

5257 and calls :meth:`.Session.close` on them, which resets them to a clean 

5258 state. 

5259 

5260 This function is not for general use but may be useful for test suites 

5261 within the teardown scheme. 

5262 

5263 """ 

5264 

5265 for sess in _sessions.values(): 

5266 sess.close() 

5267 

5268 

5269def make_transient(instance: object) -> None: 

5270 """Alter the state of the given instance so that it is :term:`transient`. 

5271 

5272 .. note:: 

5273 

5274 :func:`.make_transient` is a special-case function for 

5275 advanced use cases only. 

5276 

5277 The given mapped instance is assumed to be in the :term:`persistent` or 

5278 :term:`detached` state. The function will remove its association with any 

5279 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5280 effect is that the object will behave as though it were newly constructed, 

5281 except retaining any attribute / collection values that were loaded at the 

5282 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5283 if this object had been deleted as a result of using 

5284 :meth:`.Session.delete`. 

5285 

5286 .. warning:: 

5287 

5288 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5289 load ORM-mapped attributes that are not currently loaded at the time 

5290 the function is called. This includes attributes which: 

5291 

5292 * were expired via :meth:`.Session.expire` 

5293 

5294 * were expired as the natural effect of committing a session 

5295 transaction, e.g. :meth:`.Session.commit` 

5296 

5297 * are normally :term:`lazy loaded` but are not currently loaded 

5298 

5299 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5300 not yet loaded 

5301 

5302 * were not present in the query which loaded this object, such as that 

5303 which is common in joined table inheritance and other scenarios. 

5304 

5305 After :func:`.make_transient` is called, unloaded attributes such 

5306 as those above will normally resolve to the value ``None`` when 

5307 accessed, or an empty collection for a collection-oriented attribute. 

5308 As the object is transient and un-associated with any database 

5309 identity, it will no longer retrieve these values. 

5310 

5311 .. seealso:: 

5312 

5313 :func:`.make_transient_to_detached` 

5314 

5315 """ 

5316 state = attributes.instance_state(instance) 

5317 s = _state_session(state) 

5318 if s: 

5319 s._expunge_states([state]) 

5320 

5321 # remove expired state 

5322 state.expired_attributes.clear() 

5323 

5324 # remove deferred callables 

5325 if state.callables: 

5326 del state.callables 

5327 

5328 if state.key: 

5329 del state.key 

5330 if state._deleted: 

5331 del state._deleted 

5332 

5333 

5334def make_transient_to_detached(instance: object) -> None: 

5335 """Make the given transient instance :term:`detached`. 

5336 

5337 .. note:: 

5338 

5339 :func:`.make_transient_to_detached` is a special-case function for 

5340 advanced use cases only. 

5341 

5342 All attribute history on the given instance 

5343 will be reset as though the instance were freshly loaded 

5344 from a query. Missing attributes will be marked as expired. 

5345 The primary key attributes of the object, which are required, will be made 

5346 into the "key" of the instance. 

5347 

5348 The object can then be added to a session, or merged 

5349 possibly with the load=False flag, at which point it will look 

5350 as if it were loaded that way, without emitting SQL. 

5351 

5352 This is a special use case function that differs from a normal 

5353 call to :meth:`.Session.merge` in that a given persistent state 

5354 can be manufactured without any SQL calls. 

5355 

5356 .. seealso:: 

5357 

5358 :func:`.make_transient` 

5359 

5360 :meth:`.Session.enable_relationship_loading` 

5361 

5362 """ 

5363 state = attributes.instance_state(instance) 

5364 if state.session_id or state.key: 

5365 raise sa_exc.InvalidRequestError("Given object must be transient") 

5366 state.key = state.mapper._identity_key_from_state(state) 

5367 if state._deleted: 

5368 del state._deleted 

5369 state._commit_all(state.dict) 

5370 state._expire_attributes(state.dict, state.unloaded) 

5371 

5372 

5373def object_session(instance: object) -> Optional[Session]: 

5374 """Return the :class:`.Session` to which the given instance belongs. 

5375 

5376 This is essentially the same as the :attr:`.InstanceState.session` 

5377 accessor. See that attribute for details. 

5378 

5379 """ 

5380 

5381 try: 

5382 state = attributes.instance_state(instance) 

5383 except exc.NO_STATE as err: 

5384 raise exc.UnmappedInstanceError(instance) from err 

5385 else: 

5386 return _state_session(state) 

5387 

5388 

5389_new_sessionid = util.counter()