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

1457 statements  

1# orm/session.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

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

9 

10from __future__ import annotations 

11 

12import contextlib 

13from enum import Enum 

14import itertools 

15import sys 

16import typing 

17from typing import Any 

18from typing import Callable 

19from typing import cast 

20from typing import Dict 

21from typing import Generic 

22from typing import Iterable 

23from typing import Iterator 

24from typing import List 

25from typing import Literal 

26from typing import NoReturn 

27from typing import Optional 

28from typing import overload 

29from typing import Protocol 

30from typing import Sequence 

31from typing import Set 

32from typing import Tuple 

33from typing import Type 

34from typing import TYPE_CHECKING 

35from typing import TypeVar 

36from typing import Union 

37import weakref 

38 

39from . import attributes 

40from . import bulk_persistence 

41from . import context 

42from . import descriptor_props 

43from . import exc 

44from . import identity 

45from . import loading 

46from . import query 

47from . import state as statelib 

48from ._typing import _O 

49from ._typing import insp_is_mapper 

50from ._typing import is_composite_class 

51from ._typing import is_orm_option 

52from ._typing import is_user_defined_option 

53from .base import _class_to_mapper 

54from .base import _none_set 

55from .base import _state_mapper 

56from .base import instance_str 

57from .base import LoaderCallableStatus 

58from .base import object_mapper 

59from .base import object_state 

60from .base import PassiveFlag 

61from .base import state_str 

62from .context import _ORMCompileState 

63from .context import FromStatement 

64from .identity import IdentityMap 

65from .query import Query 

66from .state import InstanceState 

67from .state_changes import _StateChange 

68from .state_changes import _StateChangeState 

69from .state_changes import _StateChangeStates 

70from .unitofwork import UOWTransaction 

71from .. import engine 

72from .. import exc as sa_exc 

73from .. import sql 

74from .. import util 

75from ..engine import Connection 

76from ..engine import Engine 

77from ..engine.util import TransactionalContext 

78from ..event import dispatcher 

79from ..event import EventTarget 

80from ..inspection import inspect 

81from ..inspection import Inspectable 

82from ..sql import coercions 

83from ..sql import dml 

84from ..sql import roles 

85from ..sql import Select 

86from ..sql import TableClause 

87from ..sql import visitors 

88from ..sql.base import _NoArg 

89from ..sql.base import CompileState 

90from ..sql.schema import Table 

91from ..sql.selectable import ForUpdateArg 

92from ..util import deprecated_params 

93from ..util import IdentitySet 

94from ..util.typing import Never 

95from ..util.typing import TupleAny 

96from ..util.typing import TypeVarTuple 

97from ..util.typing import Unpack 

98 

99if typing.TYPE_CHECKING: 

100 from ._typing import _EntityType 

101 from ._typing import _IdentityKeyType 

102 from ._typing import _InstanceDict 

103 from ._typing import OrmExecuteOptionsParameter 

104 from .interfaces import ORMOption 

105 from .interfaces import UserDefinedOption 

106 from .mapper import Mapper 

107 from .path_registry import PathRegistry 

108 from .query import RowReturningQuery 

109 from ..engine import Result 

110 from ..engine import Row 

111 from ..engine import RowMapping 

112 from ..engine.base import Transaction 

113 from ..engine.base import TwoPhaseTransaction 

114 from ..engine.interfaces import _CoreAnyExecuteParams 

115 from ..engine.interfaces import _CoreSingleExecuteParams 

116 from ..engine.interfaces import _ExecuteOptions 

117 from ..engine.interfaces import CoreExecuteOptionsParameter 

118 from ..engine.result import ScalarResult 

119 from ..event import _InstanceLevelDispatch 

120 from ..sql._typing import _ColumnsClauseArgument 

121 from ..sql._typing import _InfoType 

122 from ..sql._typing import _T0 

123 from ..sql._typing import _T1 

124 from ..sql._typing import _T2 

125 from ..sql._typing import _T3 

126 from ..sql._typing import _T4 

127 from ..sql._typing import _T5 

128 from ..sql._typing import _T6 

129 from ..sql._typing import _T7 

130 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

131 from ..sql.base import Executable 

132 from ..sql.base import ExecutableOption 

133 from ..sql.elements import ClauseElement 

134 from ..sql.roles import TypedColumnsClauseRole 

135 from ..sql.selectable import ForUpdateParameter 

136 from ..sql.selectable import TypedReturnsRows 

137 

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

139_Ts = TypeVarTuple("_Ts") 

140 

141__all__ = [ 

142 "Session", 

143 "SessionTransaction", 

144 "sessionmaker", 

145 "ORMExecuteState", 

146 "close_all_sessions", 

147 "make_transient", 

148 "make_transient_to_detached", 

149 "object_session", 

150] 

151 

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

153 weakref.WeakValueDictionary() 

154) 

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

156""" 

157 

158statelib._sessions = _sessions 

159 

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

161 

162_BindArguments = Dict[str, Any] 

163 

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

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

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

167 

168JoinTransactionMode = Literal[ 

169 "conditional_savepoint", 

170 "rollback_only", 

171 "control_fully", 

172 "create_savepoint", 

173] 

174 

175 

176class _ConnectionCallableProto(Protocol): 

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

178 

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

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

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

182 as persistence time. 

183 

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

185 is established when using the horizontal sharding extension. 

186 

187 """ 

188 

189 def __call__( 

190 self, 

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

192 instance: Optional[object] = None, 

193 **kw: Any, 

194 ) -> Connection: ... 

195 

196 

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

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

199 associated, if any. 

200 """ 

201 return state.session 

202 

203 

204class _SessionClassMethods: 

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

206 

207 @classmethod 

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

209 def identity_key( 

210 cls, 

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

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

213 *, 

214 instance: Optional[Any] = None, 

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

216 identity_token: Optional[Any] = None, 

217 ) -> _IdentityKeyType[Any]: 

218 """Return an identity key. 

219 

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

221 

222 """ 

223 return util.preloaded.orm_util.identity_key( 

224 class_, 

225 ident, 

226 instance=instance, 

227 row=row, 

228 identity_token=identity_token, 

229 ) 

230 

231 @classmethod 

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

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

234 

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

236 

237 """ 

238 

239 return object_session(instance) 

240 

241 

242class SessionTransactionState(_StateChangeState): 

243 ACTIVE = 1 

244 PREPARED = 2 

245 COMMITTED = 3 

246 DEACTIVE = 4 

247 CLOSED = 5 

248 PROVISIONING_CONNECTION = 6 

249 

250 

251# backwards compatibility 

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

253 SessionTransactionState 

254) 

255 

256 

257class ORMExecuteState(util.MemoizedSlots): 

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

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

260 

261 .. versionadded:: 1.4 

262 

263 .. seealso:: 

264 

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

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

267 

268 """ 

269 

270 __slots__ = ( 

271 "session", 

272 "statement", 

273 "parameters", 

274 "execution_options", 

275 "local_execution_options", 

276 "bind_arguments", 

277 "identity_token", 

278 "_compile_state_cls", 

279 "_starting_event_idx", 

280 "_events_todo", 

281 "_update_execution_options", 

282 ) 

283 

284 session: Session 

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

286 

287 statement: Executable 

288 """The SQL statement being invoked. 

289 

290 For an ORM selection as would 

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

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

293 """ 

294 

295 parameters: Optional[_CoreAnyExecuteParams] 

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

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

298 

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

300 effective parameters passed to the method. 

301 

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

303 mutated or replaced. 

304 

305 """ 

306 

307 execution_options: _ExecuteOptions 

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

309 

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

311 locally passed execution options. 

312 

313 .. seealso:: 

314 

315 :attr:`_orm.ORMExecuteState.local_execution_options` 

316 

317 :meth:`_sql.Executable.execution_options` 

318 

319 :ref:`orm_queryguide_execution_options` 

320 

321 """ 

322 

323 local_execution_options: _ExecuteOptions 

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

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

326 

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

328 being invoked. 

329 

330 .. seealso:: 

331 

332 :attr:`_orm.ORMExecuteState.execution_options` 

333 

334 """ 

335 

336 bind_arguments: _BindArguments 

337 """The dictionary passed as the 

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

339 

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

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

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

343 

344 """ 

345 

346 _compile_state_cls: Optional[Type[_ORMCompileState]] 

347 _starting_event_idx: int 

348 _events_todo: List[Any] 

349 _update_execution_options: 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 """transaction 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 reuse. 

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 # special case to handle mypy issue: 

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

2415 @overload 

2416 def scalar( 

2417 self, 

2418 statement: TypedReturnsRows[Never], 

2419 params: Optional[_CoreSingleExecuteParams] = None, 

2420 *, 

2421 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2422 bind_arguments: Optional[_BindArguments] = None, 

2423 **kw: Any, 

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

2425 

2426 @overload 

2427 def scalar( 

2428 self, 

2429 statement: TypedReturnsRows[_T], 

2430 params: Optional[_CoreSingleExecuteParams] = None, 

2431 *, 

2432 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2433 bind_arguments: Optional[_BindArguments] = None, 

2434 **kw: Any, 

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

2436 

2437 @overload 

2438 def scalar( 

2439 self, 

2440 statement: Executable, 

2441 params: Optional[_CoreSingleExecuteParams] = None, 

2442 *, 

2443 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2444 bind_arguments: Optional[_BindArguments] = None, 

2445 **kw: Any, 

2446 ) -> Any: ... 

2447 

2448 def scalar( 

2449 self, 

2450 statement: Executable, 

2451 params: Optional[_CoreSingleExecuteParams] = None, 

2452 *, 

2453 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2454 bind_arguments: Optional[_BindArguments] = None, 

2455 **kw: Any, 

2456 ) -> Any: 

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

2458 

2459 Usage and parameters are the same as that of 

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

2461 value. 

2462 

2463 """ 

2464 

2465 return self._execute_internal( 

2466 statement, 

2467 params, 

2468 execution_options=execution_options, 

2469 bind_arguments=bind_arguments, 

2470 _scalar_result=True, 

2471 **kw, 

2472 ) 

2473 

2474 @overload 

2475 def scalars( 

2476 self, 

2477 statement: TypedReturnsRows[_T], 

2478 params: Optional[_CoreAnyExecuteParams] = None, 

2479 *, 

2480 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2481 bind_arguments: Optional[_BindArguments] = None, 

2482 **kw: Any, 

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

2484 

2485 @overload 

2486 def scalars( 

2487 self, 

2488 statement: Executable, 

2489 params: Optional[_CoreAnyExecuteParams] = None, 

2490 *, 

2491 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2492 bind_arguments: Optional[_BindArguments] = None, 

2493 **kw: Any, 

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

2495 

2496 def scalars( 

2497 self, 

2498 statement: Executable, 

2499 params: Optional[_CoreAnyExecuteParams] = None, 

2500 *, 

2501 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2502 bind_arguments: Optional[_BindArguments] = None, 

2503 **kw: Any, 

2504 ) -> ScalarResult[Any]: 

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

2506 

2507 Usage and parameters are the same as that of 

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

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

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

2511 

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

2513 

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

2515 

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

2517 

2518 .. seealso:: 

2519 

2520 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2522 

2523 """ 

2524 

2525 return self._execute_internal( 

2526 statement, 

2527 params=params, 

2528 execution_options=execution_options, 

2529 bind_arguments=bind_arguments, 

2530 _scalar_result=False, # mypy appreciates this 

2531 **kw, 

2532 ).scalars() 

2533 

2534 def close(self) -> None: 

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

2536 :class:`_orm.Session`. 

2537 

2538 This expunges all ORM objects associated with this 

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

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

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

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

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

2544 

2545 .. tip:: 

2546 

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

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

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

2550 distinct "closed" state; it merely means 

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

2552 and ORM objects. 

2553 

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

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

2556 any further action on the session will be forbidden. 

2557 

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

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

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

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

2562 

2563 .. seealso:: 

2564 

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

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

2567 

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

2569 ``close()`` with the parameter 

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

2571 

2572 """ 

2573 self._close_impl(invalidate=False) 

2574 

2575 def reset(self) -> None: 

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

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

2578 

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

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

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

2582 brand new, and ready to be used again. 

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

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

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

2586 

2587 .. versionadded:: 2.0.22 

2588 

2589 .. seealso:: 

2590 

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

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

2593 

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

2595 prevent reuse of the Session when the parameter 

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

2597 """ 

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

2599 

2600 def invalidate(self) -> None: 

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

2602 

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

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

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

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

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

2608 multiple engines). 

2609 

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

2611 the connections are no longer safe to be used. 

2612 

2613 Below illustrates a scenario when using `gevent 

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

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

2616 

2617 import gevent 

2618 

2619 try: 

2620 sess = Session() 

2621 sess.add(User()) 

2622 sess.commit() 

2623 except gevent.Timeout: 

2624 sess.invalidate() 

2625 raise 

2626 except: 

2627 sess.rollback() 

2628 raise 

2629 

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

2631 does, including that all ORM objects are expunged. 

2632 

2633 """ 

2634 self._close_impl(invalidate=True) 

2635 

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

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

2638 self._close_state = _SessionCloseState.CLOSED 

2639 self.expunge_all() 

2640 if self._transaction is not None: 

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

2642 transaction.close(invalidate) 

2643 

2644 def expunge_all(self) -> None: 

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

2646 

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

2648 ``Session``. 

2649 

2650 """ 

2651 

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

2653 self.identity_map._kill() 

2654 self.identity_map = identity._WeakInstanceDict() 

2655 self._new = {} 

2656 self._deleted = {} 

2657 

2658 statelib.InstanceState._detach_states(all_states, self) 

2659 

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

2661 try: 

2662 insp = inspect(key) 

2663 except sa_exc.NoInspectionAvailable as err: 

2664 if not isinstance(key, type): 

2665 raise sa_exc.ArgumentError( 

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

2667 ) from err 

2668 else: 

2669 self.__binds[key] = bind 

2670 else: 

2671 if TYPE_CHECKING: 

2672 assert isinstance(insp, Inspectable) 

2673 

2674 if isinstance(insp, TableClause): 

2675 self.__binds[insp] = bind 

2676 elif insp_is_mapper(insp): 

2677 self.__binds[insp.class_] = bind 

2678 for _selectable in insp._all_tables: 

2679 self.__binds[_selectable] = bind 

2680 else: 

2681 raise sa_exc.ArgumentError( 

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

2683 ) 

2684 

2685 def bind_mapper( 

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

2687 ) -> None: 

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

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

2690 :class:`_engine.Connection`. 

2691 

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

2693 :meth:`.Session.get_bind` method. 

2694 

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

2696 or an instance of a mapped 

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

2698 classes. 

2699 

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

2701 object. 

2702 

2703 .. seealso:: 

2704 

2705 :ref:`session_partitioning` 

2706 

2707 :paramref:`.Session.binds` 

2708 

2709 :meth:`.Session.bind_table` 

2710 

2711 

2712 """ 

2713 self._add_bind(mapper, bind) 

2714 

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

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

2717 :class:`_engine.Engine` 

2718 or :class:`_engine.Connection`. 

2719 

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

2721 :meth:`.Session.get_bind` method. 

2722 

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

2724 which is typically the target 

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

2726 mapped. 

2727 

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

2729 object. 

2730 

2731 .. seealso:: 

2732 

2733 :ref:`session_partitioning` 

2734 

2735 :paramref:`.Session.binds` 

2736 

2737 :meth:`.Session.bind_mapper` 

2738 

2739 

2740 """ 

2741 self._add_bind(table, bind) 

2742 

2743 def get_bind( 

2744 self, 

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

2746 *, 

2747 clause: Optional[ClauseElement] = None, 

2748 bind: Optional[_SessionBind] = None, 

2749 _sa_skip_events: Optional[bool] = None, 

2750 _sa_skip_for_implicit_returning: bool = False, 

2751 **kw: Any, 

2752 ) -> Union[Engine, Connection]: 

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

2754 

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

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

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

2758 

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

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

2761 appropriate bind to return. 

2762 

2763 Note that the "mapper" argument is usually present 

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

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

2766 individual INSERT/UPDATE/DELETE operation within a 

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

2768 

2769 The order of resolution is: 

2770 

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

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

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

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

2775 superclasses to more general. 

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

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

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

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

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

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

2782 associated with the clause. 

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

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

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

2786 selectable to which the mapper is mapped. 

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

2788 is raised. 

2789 

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

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

2792 of bind resolution scheme. See the example at 

2793 :ref:`session_custom_partitioning`. 

2794 

2795 :param mapper: 

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

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

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

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

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

2801 mapped for a bind. 

2802 

2803 :param clause: 

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

2805 :func:`_expression.select`, 

2806 :func:`_expression.text`, 

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

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

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

2810 associated with 

2811 bound :class:`_schema.MetaData`. 

2812 

2813 .. seealso:: 

2814 

2815 :ref:`session_partitioning` 

2816 

2817 :paramref:`.Session.binds` 

2818 

2819 :meth:`.Session.bind_mapper` 

2820 

2821 :meth:`.Session.bind_table` 

2822 

2823 """ 

2824 

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

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

2827 if bind: 

2828 return bind 

2829 elif not self.__binds and self.bind: 

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

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

2832 return self.bind 

2833 

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

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

2836 # mapper and the clause 

2837 if mapper is None and clause is None: 

2838 if self.bind: 

2839 return self.bind 

2840 else: 

2841 raise sa_exc.UnboundExecutionError( 

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

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

2844 "a binding." 

2845 ) 

2846 

2847 # look more closely at the mapper. 

2848 if mapper is not None: 

2849 try: 

2850 inspected_mapper = inspect(mapper) 

2851 except sa_exc.NoInspectionAvailable as err: 

2852 if isinstance(mapper, type): 

2853 raise exc.UnmappedClassError(mapper) from err 

2854 else: 

2855 raise 

2856 else: 

2857 inspected_mapper = None 

2858 

2859 # match up the mapper or clause in the __binds 

2860 if self.__binds: 

2861 # matching mappers and selectables to entries in the 

2862 # binds dictionary; supported use case. 

2863 if inspected_mapper: 

2864 for cls in inspected_mapper.class_.__mro__: 

2865 if cls in self.__binds: 

2866 return self.__binds[cls] 

2867 if clause is None: 

2868 clause = inspected_mapper.persist_selectable 

2869 

2870 if clause is not None: 

2871 plugin_subject = clause._propagate_attrs.get( 

2872 "plugin_subject", None 

2873 ) 

2874 

2875 if plugin_subject is not None: 

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

2877 if cls in self.__binds: 

2878 return self.__binds[cls] 

2879 

2880 for obj in visitors.iterate(clause): 

2881 if obj in self.__binds: 

2882 if TYPE_CHECKING: 

2883 assert isinstance(obj, Table) 

2884 return self.__binds[obj] 

2885 

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

2887 # return that 

2888 if self.bind: 

2889 return self.bind 

2890 

2891 context = [] 

2892 if inspected_mapper is not None: 

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

2894 if clause is not None: 

2895 context.append("SQL expression") 

2896 

2897 raise sa_exc.UnboundExecutionError( 

2898 f"Could not locate a bind configured on " 

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

2900 ) 

2901 

2902 @overload 

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

2904 

2905 @overload 

2906 def query( 

2907 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2909 

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

2911 

2912 # code within this block is **programmatically, 

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

2914 

2915 @overload 

2916 def query( 

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

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

2919 

2920 @overload 

2921 def query( 

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

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

2924 

2925 @overload 

2926 def query( 

2927 self, 

2928 __ent0: _TCCA[_T0], 

2929 __ent1: _TCCA[_T1], 

2930 __ent2: _TCCA[_T2], 

2931 __ent3: _TCCA[_T3], 

2932 /, 

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

2934 

2935 @overload 

2936 def query( 

2937 self, 

2938 __ent0: _TCCA[_T0], 

2939 __ent1: _TCCA[_T1], 

2940 __ent2: _TCCA[_T2], 

2941 __ent3: _TCCA[_T3], 

2942 __ent4: _TCCA[_T4], 

2943 /, 

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

2945 

2946 @overload 

2947 def query( 

2948 self, 

2949 __ent0: _TCCA[_T0], 

2950 __ent1: _TCCA[_T1], 

2951 __ent2: _TCCA[_T2], 

2952 __ent3: _TCCA[_T3], 

2953 __ent4: _TCCA[_T4], 

2954 __ent5: _TCCA[_T5], 

2955 /, 

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

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 /, 

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

2970 

2971 @overload 

2972 def query( 

2973 self, 

2974 __ent0: _TCCA[_T0], 

2975 __ent1: _TCCA[_T1], 

2976 __ent2: _TCCA[_T2], 

2977 __ent3: _TCCA[_T3], 

2978 __ent4: _TCCA[_T4], 

2979 __ent5: _TCCA[_T5], 

2980 __ent6: _TCCA[_T6], 

2981 __ent7: _TCCA[_T7], 

2982 /, 

2983 *entities: _ColumnsClauseArgument[Any], 

2984 ) -> RowReturningQuery[ 

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

2986 ]: ... 

2987 

2988 # END OVERLOADED FUNCTIONS self.query 

2989 

2990 @overload 

2991 def query( 

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

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

2994 

2995 def query( 

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

2997 ) -> Query[Any]: 

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

2999 :class:`_orm.Session`. 

3000 

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

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

3003 to construct ORM queries. 

3004 

3005 .. seealso:: 

3006 

3007 :ref:`unified_tutorial` 

3008 

3009 :ref:`queryguide_toplevel` 

3010 

3011 :ref:`query_api_toplevel` - legacy API doc 

3012 

3013 """ 

3014 

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

3016 

3017 def _identity_lookup( 

3018 self, 

3019 mapper: Mapper[_O], 

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

3021 identity_token: Any = None, 

3022 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

3024 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3025 bind_arguments: Optional[_BindArguments] = None, 

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

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

3028 

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

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

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

3032 check if was deleted). 

3033 

3034 e.g.:: 

3035 

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

3037 

3038 :param mapper: mapper in use 

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

3040 a tuple. 

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

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

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

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

3045 :param passive: passive load flag passed to 

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

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

3048 if the flag allows for SQL to be emitted. 

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

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

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

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

3053 relationship-loaded). 

3054 

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

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

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

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

3059 

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

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

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

3063 :class:`_query.Query` object. 

3064 

3065 

3066 """ 

3067 

3068 key = mapper.identity_key_from_primary_key( 

3069 primary_key_identity, identity_token=identity_token 

3070 ) 

3071 

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

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

3074 return return_value 

3075 

3076 @util.non_memoized_property 

3077 @contextlib.contextmanager 

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

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

3080 

3081 e.g.:: 

3082 

3083 with session.no_autoflush: 

3084 

3085 some_object = SomeClass() 

3086 session.add(some_object) 

3087 # won't autoflush 

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

3089 

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

3091 will not be subject to flushes occurring upon query 

3092 access. This is useful when initializing a series 

3093 of objects which involve existing database queries, 

3094 where the uncompleted object should not yet be flushed. 

3095 

3096 """ 

3097 autoflush = self.autoflush 

3098 self.autoflush = False 

3099 try: 

3100 yield self 

3101 finally: 

3102 self.autoflush = autoflush 

3103 

3104 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3108 "warning happened while initializing objects.", 

3109 sa_exc.SAWarning, 

3110 ) 

3111 def _autoflush(self) -> None: 

3112 if self.autoflush and not self._flushing: 

3113 try: 

3114 self.flush() 

3115 except sa_exc.StatementError as e: 

3116 # note we are reraising StatementError as opposed to 

3117 # raising FlushError with "chaining" to remain compatible 

3118 # with code that catches StatementError, IntegrityError, 

3119 # etc. 

3120 e.add_detail( 

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

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

3123 "flush is occurring prematurely" 

3124 ) 

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

3126 

3127 def refresh( 

3128 self, 

3129 instance: object, 

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

3131 with_for_update: ForUpdateParameter = None, 

3132 ) -> None: 

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

3134 

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

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

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

3138 value available in the current transaction. 

3139 

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

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

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

3143 

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

3145 can also refresh eagerly loaded attributes. 

3146 

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

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

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

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

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

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

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

3154 refreshed. 

3155 

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

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

3158 attributes for those which are named explicitly in the 

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

3160 

3161 .. tip:: 

3162 

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

3164 refreshing both column and relationship oriented attributes, its 

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

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

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

3168 once while having explicit control over relationship loader 

3169 strategies, use the 

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

3171 instead. 

3172 

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

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

3175 in database state outside of that transaction. Refreshing 

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

3177 where database rows have not yet been accessed. 

3178 

3179 :param attribute_names: optional. An iterable collection of 

3180 string attribute names indicating a subset of attributes to 

3181 be refreshed. 

3182 

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

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

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

3186 flags should match the parameters of 

3187 :meth:`_query.Query.with_for_update`. 

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

3189 

3190 .. seealso:: 

3191 

3192 :ref:`session_expire` - introductory material 

3193 

3194 :meth:`.Session.expire` 

3195 

3196 :meth:`.Session.expire_all` 

3197 

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

3199 to refresh objects as they would be loaded normally. 

3200 

3201 """ 

3202 try: 

3203 state = attributes.instance_state(instance) 

3204 except exc.NO_STATE as err: 

3205 raise exc.UnmappedInstanceError(instance) from err 

3206 

3207 self._expire_state(state, attribute_names) 

3208 

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

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

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

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

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

3214 # load_on_ident. 

3215 self._autoflush() 

3216 

3217 if with_for_update == {}: 

3218 raise sa_exc.ArgumentError( 

3219 "with_for_update should be the boolean value " 

3220 "True, or a dictionary with options. " 

3221 "A blank dictionary is ambiguous." 

3222 ) 

3223 

3224 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3225 

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

3227 if ( 

3228 loading._load_on_ident( 

3229 self, 

3230 stmt, 

3231 state.key, 

3232 refresh_state=state, 

3233 with_for_update=with_for_update, 

3234 only_load_props=attribute_names, 

3235 require_pk_cols=True, 

3236 # technically unnecessary as we just did autoflush 

3237 # above, however removes the additional unnecessary 

3238 # call to _autoflush() 

3239 no_autoflush=True, 

3240 is_user_refresh=True, 

3241 ) 

3242 is None 

3243 ): 

3244 raise sa_exc.InvalidRequestError( 

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

3246 ) 

3247 

3248 def expire_all(self) -> None: 

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

3250 

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

3252 a query will be issued using the 

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

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

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

3256 previously read in that same transaction, regardless of changes 

3257 in database state outside of that transaction. 

3258 

3259 To expire individual objects and individual attributes 

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

3261 

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

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

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

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

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

3267 assuming the transaction is isolated. 

3268 

3269 .. seealso:: 

3270 

3271 :ref:`session_expire` - introductory material 

3272 

3273 :meth:`.Session.expire` 

3274 

3275 :meth:`.Session.refresh` 

3276 

3277 :meth:`_orm.Query.populate_existing` 

3278 

3279 """ 

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

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

3282 

3283 def expire( 

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

3285 ) -> None: 

3286 """Expire the attributes on an instance. 

3287 

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

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

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

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

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

3293 previously read in that same transaction, regardless of changes 

3294 in database state outside of that transaction. 

3295 

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

3297 use :meth:`Session.expire_all`. 

3298 

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

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

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

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

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

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

3305 transaction. 

3306 

3307 :param instance: The instance to be refreshed. 

3308 :param attribute_names: optional list of string attribute names 

3309 indicating a subset of attributes to be expired. 

3310 

3311 .. seealso:: 

3312 

3313 :ref:`session_expire` - introductory material 

3314 

3315 :meth:`.Session.expire` 

3316 

3317 :meth:`.Session.refresh` 

3318 

3319 :meth:`_orm.Query.populate_existing` 

3320 

3321 """ 

3322 try: 

3323 state = attributes.instance_state(instance) 

3324 except exc.NO_STATE as err: 

3325 raise exc.UnmappedInstanceError(instance) from err 

3326 self._expire_state(state, attribute_names) 

3327 

3328 def _expire_state( 

3329 self, 

3330 state: InstanceState[Any], 

3331 attribute_names: Optional[Iterable[str]], 

3332 ) -> None: 

3333 self._validate_persistent(state) 

3334 if attribute_names: 

3335 state._expire_attributes(state.dict, attribute_names) 

3336 else: 

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

3338 # remove associations 

3339 cascaded = list( 

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

3341 ) 

3342 self._conditional_expire(state) 

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

3344 self._conditional_expire(st_) 

3345 

3346 def _conditional_expire( 

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

3348 ) -> None: 

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

3350 

3351 if state.key: 

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

3353 elif state in self._new: 

3354 self._new.pop(state) 

3355 state._detach(self) 

3356 

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

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

3359 

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

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

3362 

3363 """ 

3364 try: 

3365 state = attributes.instance_state(instance) 

3366 except exc.NO_STATE as err: 

3367 raise exc.UnmappedInstanceError(instance) from err 

3368 if state.session_id is not self.hash_key: 

3369 raise sa_exc.InvalidRequestError( 

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

3371 ) 

3372 

3373 cascaded = list( 

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

3375 ) 

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

3377 

3378 def _expunge_states( 

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

3380 ) -> None: 

3381 for state in states: 

3382 if state in self._new: 

3383 self._new.pop(state) 

3384 elif self.identity_map.contains_state(state): 

3385 self.identity_map.safe_discard(state) 

3386 self._deleted.pop(state, None) 

3387 elif self._transaction: 

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

3389 # in the transaction snapshot 

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

3391 statelib.InstanceState._detach_states( 

3392 states, self, to_transient=to_transient 

3393 ) 

3394 

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

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

3397 

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

3399 state as well as already persistent objects. 

3400 

3401 """ 

3402 

3403 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3404 for state in states: 

3405 mapper = _state_mapper(state) 

3406 

3407 # prevent against last minute dereferences of the object 

3408 obj = state.obj() 

3409 if obj is not None: 

3410 instance_key = mapper._identity_key_from_state(state) 

3411 

3412 if ( 

3413 _none_set.intersection(instance_key[1]) 

3414 and not mapper.allow_partial_pks 

3415 or _none_set.issuperset(instance_key[1]) 

3416 ): 

3417 raise exc.FlushError( 

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

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

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

3421 "that the mapped Column object is configured to " 

3422 "expect these generated values. Ensure also that " 

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

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

3425 % state_str(state) 

3426 ) 

3427 

3428 if state.key is None: 

3429 state.key = instance_key 

3430 elif state.key != instance_key: 

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

3432 # state has already replaced this one in the identity 

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

3434 self.identity_map.safe_discard(state) 

3435 trans = self._transaction 

3436 assert trans is not None 

3437 if state in trans._key_switches: 

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

3439 else: 

3440 orig_key = state.key 

3441 trans._key_switches[state] = ( 

3442 orig_key, 

3443 instance_key, 

3444 ) 

3445 state.key = instance_key 

3446 

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

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

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

3450 old = self.identity_map.replace(state) 

3451 if ( 

3452 old is not None 

3453 and mapper._identity_key_from_state(old) == instance_key 

3454 and old.obj() is not None 

3455 ): 

3456 util.warn( 

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

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

3459 "load operations occurring inside of an event handler " 

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

3461 ) 

3462 state._orphaned_outside_of_session = False 

3463 

3464 statelib.InstanceState._commit_all_states( 

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

3466 ) 

3467 

3468 self._register_altered(states) 

3469 

3470 if pending_to_persistent is not None: 

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

3472 pending_to_persistent(self, state) 

3473 

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

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

3476 self._new.pop(state) 

3477 

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

3479 if self._transaction: 

3480 for state in states: 

3481 if state in self._new: 

3482 self._transaction._new[state] = True 

3483 else: 

3484 self._transaction._dirty[state] = True 

3485 

3486 def _remove_newly_deleted( 

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

3488 ) -> None: 

3489 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3490 for state in states: 

3491 if self._transaction: 

3492 self._transaction._deleted[state] = True 

3493 

3494 if persistent_to_deleted is not None: 

3495 # get a strong reference before we pop out of 

3496 # self._deleted 

3497 obj = state.obj() # noqa 

3498 

3499 self.identity_map.safe_discard(state) 

3500 self._deleted.pop(state, None) 

3501 state._deleted = True 

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

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

3504 # tracked as part of that 

3505 if persistent_to_deleted is not None: 

3506 persistent_to_deleted(self, state) 

3507 

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

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

3510 

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

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

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

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

3515 

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

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

3518 state directly. 

3519 

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

3521 objects which were transient when they were passed to 

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

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

3524 :class:`_orm.Session`. 

3525 

3526 .. seealso:: 

3527 

3528 :meth:`_orm.Session.add_all` 

3529 

3530 :ref:`session_adding` - at :ref:`session_basics` 

3531 

3532 """ 

3533 if _warn and self._warn_on_events: 

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

3535 

3536 try: 

3537 state = attributes.instance_state(instance) 

3538 except exc.NO_STATE as err: 

3539 raise exc.UnmappedInstanceError(instance) from err 

3540 

3541 self._save_or_update_state(state) 

3542 

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

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

3545 

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

3547 behavioral description. 

3548 

3549 .. seealso:: 

3550 

3551 :meth:`_orm.Session.add` 

3552 

3553 :ref:`session_adding` - at :ref:`session_basics` 

3554 

3555 """ 

3556 

3557 if self._warn_on_events: 

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

3559 

3560 for instance in instances: 

3561 self.add(instance, _warn=False) 

3562 

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

3564 state._orphaned_outside_of_session = False 

3565 self._save_or_update_impl(state) 

3566 

3567 mapper = _state_mapper(state) 

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

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

3570 ): 

3571 self._save_or_update_impl(st_) 

3572 

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

3574 """Mark an instance as deleted. 

3575 

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

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

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

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

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

3581 

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

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

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

3585 is successfully committed, 

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

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

3588 

3589 .. seealso:: 

3590 

3591 :ref:`session_deleting` - at :ref:`session_basics` 

3592 

3593 :meth:`.Session.delete_all` - multiple instance version 

3594 

3595 """ 

3596 if self._warn_on_events: 

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

3598 

3599 self._delete_impl(object_state(instance), instance, head=True) 

3600 

3601 def delete_all(self, instances: Iterable[object]) -> None: 

3602 """Calls :meth:`.Session.delete` on multiple instances. 

3603 

3604 .. seealso:: 

3605 

3606 :meth:`.Session.delete` - main documentation on delete 

3607 

3608 .. versionadded:: 2.1 

3609 

3610 """ 

3611 

3612 if self._warn_on_events: 

3613 self._flush_warning("Session.delete_all()") 

3614 

3615 for instance in instances: 

3616 self._delete_impl(object_state(instance), instance, head=True) 

3617 

3618 def _delete_impl( 

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

3620 ) -> None: 

3621 if state.key is None: 

3622 if head: 

3623 raise sa_exc.InvalidRequestError( 

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

3625 ) 

3626 else: 

3627 return 

3628 

3629 to_attach = self._before_attach(state, obj) 

3630 

3631 if state in self._deleted: 

3632 return 

3633 

3634 self.identity_map.add(state) 

3635 

3636 if to_attach: 

3637 self._after_attach(state, obj) 

3638 

3639 if head: 

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

3641 # so that autoflush does not delete the item 

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

3643 cascade_states = list( 

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

3645 ) 

3646 else: 

3647 cascade_states = None 

3648 

3649 self._deleted[state] = obj 

3650 

3651 if head: 

3652 if TYPE_CHECKING: 

3653 assert cascade_states is not None 

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

3655 self._delete_impl(st_, o, False) 

3656 

3657 def get( 

3658 self, 

3659 entity: _EntityBindKey[_O], 

3660 ident: _PKIdentityArgument, 

3661 *, 

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

3663 populate_existing: bool | None = None, 

3664 with_for_update: ForUpdateParameter = None, 

3665 identity_token: Optional[Any] = None, 

3666 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3667 bind_arguments: Optional[_BindArguments] = None, 

3668 ) -> Optional[_O]: 

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

3670 or ``None`` if not found. 

3671 

3672 E.g.:: 

3673 

3674 my_user = session.get(User, 5) 

3675 

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

3677 

3678 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3679 

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

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

3682 

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

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

3685 If the given primary key identifier is present 

3686 in the local identity map, the object is returned 

3687 directly from this collection and no SQL is emitted, 

3688 unless the object has been marked fully expired. 

3689 If not present, 

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

3691 

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

3693 the object is present in the identity map and 

3694 marked as expired - a SELECT 

3695 is emitted to refresh the object as well as to 

3696 ensure that the row is still present. 

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

3698 

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

3700 type of entity to be loaded. 

3701 

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

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

3704 a tuple or dictionary should be passed. 

3705 

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

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

3708 the call looks like:: 

3709 

3710 my_object = session.get(SomeClass, 5) 

3711 

3712 The tuple form contains primary key values typically in 

3713 the order in which they correspond to the mapped 

3714 :class:`_schema.Table` 

3715 object's primary key columns, or if the 

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

3717 used, in 

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

3719 of a row is represented by the integer 

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

3721 

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

3723 

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

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

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

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

3728 

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

3730 

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

3732 applied to the query, if one is emitted. 

3733 

3734 :param populate_existing: causes the method to unconditionally emit 

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

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

3737 Setting this flag takes precedence over passing it as an 

3738 execution option. 

3739 

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

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

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

3743 flags should match the parameters of 

3744 :meth:`_query.Query.with_for_update`. 

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

3746 

3747 :param execution_options: optional dictionary of execution options, 

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

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

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

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

3752 

3753 .. versionadded:: 1.4.29 

3754 

3755 .. seealso:: 

3756 

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

3758 options 

3759 

3760 :param bind_arguments: dictionary of additional arguments to determine 

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

3762 Contents of this dictionary are passed to the 

3763 :meth:`.Session.get_bind` method. 

3764 

3765 .. versionadded:: 2.0.0rc1 

3766 

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

3768 

3769 """ # noqa: E501 

3770 return self._get_impl( 

3771 entity, 

3772 ident, 

3773 loading._load_on_pk_identity, 

3774 options=options, 

3775 populate_existing=populate_existing, 

3776 with_for_update=with_for_update, 

3777 identity_token=identity_token, 

3778 execution_options=execution_options, 

3779 bind_arguments=bind_arguments, 

3780 ) 

3781 

3782 def get_one( 

3783 self, 

3784 entity: _EntityBindKey[_O], 

3785 ident: _PKIdentityArgument, 

3786 *, 

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

3788 populate_existing: bool | None = None, 

3789 with_for_update: ForUpdateParameter = None, 

3790 identity_token: Optional[Any] = None, 

3791 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3792 bind_arguments: Optional[_BindArguments] = None, 

3793 ) -> _O: 

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

3795 identifier, or raise an exception if not found. 

3796 

3797 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3798 

3799 For a detailed documentation of the arguments see the 

3800 method :meth:`.Session.get`. 

3801 

3802 .. versionadded:: 2.0.22 

3803 

3804 :return: The object instance. 

3805 

3806 .. seealso:: 

3807 

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

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

3810 key 

3811 

3812 """ 

3813 

3814 instance = self.get( 

3815 entity, 

3816 ident, 

3817 options=options, 

3818 populate_existing=populate_existing, 

3819 with_for_update=with_for_update, 

3820 identity_token=identity_token, 

3821 execution_options=execution_options, 

3822 bind_arguments=bind_arguments, 

3823 ) 

3824 

3825 if instance is None: 

3826 raise sa_exc.NoResultFound( 

3827 "No row was found when one was required" 

3828 ) 

3829 

3830 return instance 

3831 

3832 def _get_impl( 

3833 self, 

3834 entity: _EntityBindKey[_O], 

3835 primary_key_identity: _PKIdentityArgument, 

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

3837 *, 

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

3839 populate_existing: bool | None = None, 

3840 with_for_update: ForUpdateParameter = None, 

3841 identity_token: Optional[Any] = None, 

3842 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3843 bind_arguments: Optional[_BindArguments] = None, 

3844 ) -> Optional[_O]: 

3845 # set populate_existing value; direct parameter 

3846 # takes precedence over execution_options 

3847 if populate_existing is not None: 

3848 execution_options = { 

3849 **execution_options, # type: ignore[typeddict-item] 

3850 "populate_existing": populate_existing, 

3851 } 

3852 else: 

3853 populate_existing = execution_options.get( 

3854 "populate_existing", False 

3855 ) 

3856 

3857 # convert composite types to individual args 

3858 if ( 

3859 is_composite_class(primary_key_identity) 

3860 and type(primary_key_identity) 

3861 in descriptor_props._composite_getters 

3862 ): 

3863 getter = descriptor_props._composite_getters[ 

3864 type(primary_key_identity) 

3865 ] 

3866 primary_key_identity = getter(primary_key_identity) 

3867 

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

3869 

3870 if mapper is None or not mapper.is_mapper: 

3871 raise sa_exc.ArgumentError( 

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

3873 ) 

3874 

3875 is_dict = isinstance(primary_key_identity, dict) 

3876 if not is_dict: 

3877 primary_key_identity = util.to_list( 

3878 primary_key_identity, default=[None] 

3879 ) 

3880 

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

3882 raise sa_exc.InvalidRequestError( 

3883 "Incorrect number of values in identifier to formulate " 

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

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

3886 ) 

3887 

3888 if is_dict: 

3889 pk_synonyms = mapper._pk_synonyms 

3890 

3891 if pk_synonyms: 

3892 correct_keys = set(pk_synonyms).intersection( 

3893 primary_key_identity 

3894 ) 

3895 

3896 if correct_keys: 

3897 primary_key_identity = dict(primary_key_identity) 

3898 for k in correct_keys: 

3899 primary_key_identity[pk_synonyms[k]] = ( 

3900 primary_key_identity[k] 

3901 ) 

3902 

3903 try: 

3904 primary_key_identity = list( 

3905 primary_key_identity[prop.key] 

3906 for prop in mapper._identity_key_props 

3907 ) 

3908 

3909 except KeyError as err: 

3910 raise sa_exc.InvalidRequestError( 

3911 "Incorrect names of values in identifier to formulate " 

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

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

3914 % ",".join( 

3915 "'%s'" % prop.key 

3916 for prop in mapper._identity_key_props 

3917 ) 

3918 ) from err 

3919 

3920 for_update_arg = ForUpdateArg._from_argument(with_for_update) 

3921 

3922 if ( 

3923 not populate_existing 

3924 and not mapper.always_refresh 

3925 and for_update_arg is None 

3926 ): 

3927 instance = self._identity_lookup( 

3928 mapper, 

3929 primary_key_identity, 

3930 identity_token=identity_token, 

3931 execution_options=execution_options, 

3932 bind_arguments=bind_arguments, 

3933 ) 

3934 

3935 if instance is not None: 

3936 # reject calls for id in identity map but class 

3937 # mismatch. 

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

3939 return None 

3940 return instance 

3941 

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

3943 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3944 

3945 load_options = context.QueryContext.default_load_options 

3946 

3947 if populate_existing: 

3948 load_options += {"_populate_existing": populate_existing} 

3949 statement = sql.select(mapper) 

3950 if for_update_arg is not None: 

3951 statement._for_update_arg = for_update_arg 

3952 

3953 if options: 

3954 statement = statement.options(*options) 

3955 if self.execution_options: 

3956 execution_options = self.execution_options.union(execution_options) 

3957 return db_load_fn( 

3958 self, 

3959 statement, 

3960 primary_key_identity, 

3961 load_options=load_options, 

3962 identity_token=identity_token, 

3963 execution_options=execution_options, 

3964 bind_arguments=bind_arguments, 

3965 ) 

3966 

3967 def merge( 

3968 self, 

3969 instance: _O, 

3970 *, 

3971 load: bool = True, 

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

3973 ) -> _O: 

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

3975 within this :class:`.Session`. 

3976 

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

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

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

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

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

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

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

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

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

3986 

3987 This operation cascades to associated instances if the association is 

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

3989 

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

3991 

3992 :param instance: Instance to be merged. 

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

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

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

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

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

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

3999 without re-querying the database. 

4000 

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

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

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

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

4005 the merge operation populates local attributes and 

4006 cascades to related objects and 

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

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

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

4010 any existing related objects or collections that might not 

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

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

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

4014 method. 

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

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

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

4018 

4019 .. versionadded:: 1.4.24 

4020 

4021 

4022 .. seealso:: 

4023 

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

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

4026 

4027 :meth:`.Session.merge_all` - multiple instance version 

4028 

4029 """ 

4030 

4031 if self._warn_on_events: 

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

4033 

4034 if load: 

4035 # flush current contents if we expect to load data 

4036 self._autoflush() 

4037 

4038 with self.no_autoflush: 

4039 return self._merge( 

4040 object_state(instance), 

4041 attributes.instance_dict(instance), 

4042 load=load, 

4043 options=options, 

4044 _recursive={}, 

4045 _resolve_conflict_map={}, 

4046 ) 

4047 

4048 def merge_all( 

4049 self, 

4050 instances: Iterable[_O], 

4051 *, 

4052 load: bool = True, 

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

4054 ) -> Sequence[_O]: 

4055 """Calls :meth:`.Session.merge` on multiple instances. 

4056 

4057 .. seealso:: 

4058 

4059 :meth:`.Session.merge` - main documentation on merge 

4060 

4061 .. versionadded:: 2.1 

4062 

4063 """ 

4064 

4065 if self._warn_on_events: 

4066 self._flush_warning("Session.merge_all()") 

4067 

4068 if load: 

4069 # flush current contents if we expect to load data 

4070 self._autoflush() 

4071 

4072 return [ 

4073 self._merge( 

4074 object_state(instance), 

4075 attributes.instance_dict(instance), 

4076 load=load, 

4077 options=options, 

4078 _recursive={}, 

4079 _resolve_conflict_map={}, 

4080 ) 

4081 for instance in instances 

4082 ] 

4083 

4084 def _merge( 

4085 self, 

4086 state: InstanceState[_O], 

4087 state_dict: _InstanceDict, 

4088 *, 

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

4090 load: bool, 

4091 _recursive: Dict[Any, object], 

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

4093 ) -> _O: 

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

4095 if state in _recursive: 

4096 return cast(_O, _recursive[state]) 

4097 

4098 new_instance = False 

4099 key = state.key 

4100 

4101 merged: Optional[_O] 

4102 

4103 if key is None: 

4104 if state in self._new: 

4105 util.warn( 

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

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

4108 "to do" % state_str(state) 

4109 ) 

4110 

4111 if not load: 

4112 raise sa_exc.InvalidRequestError( 

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

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

4115 "all changes on mapped instances before merging with " 

4116 "load=False." 

4117 ) 

4118 key = mapper._identity_key_from_state(state) 

4119 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4120 1 

4121 ] and ( 

4122 not _none_set.intersection(key[1]) 

4123 or ( 

4124 mapper.allow_partial_pks 

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

4126 ) 

4127 ) 

4128 else: 

4129 key_is_persistent = True 

4130 

4131 merged = self.identity_map.get(key) 

4132 

4133 if merged is None: 

4134 if key_is_persistent and key in _resolve_conflict_map: 

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

4136 

4137 elif not load: 

4138 if state.modified: 

4139 raise sa_exc.InvalidRequestError( 

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

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

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

4143 ) 

4144 merged = mapper.class_manager.new_instance() 

4145 merged_state = attributes.instance_state(merged) 

4146 merged_state.key = key 

4147 self._update_impl(merged_state) 

4148 new_instance = True 

4149 

4150 elif key_is_persistent: 

4151 merged = self.get( 

4152 mapper.class_, 

4153 key[1], 

4154 identity_token=key[2], 

4155 options=options, 

4156 ) 

4157 

4158 if merged is None: 

4159 merged = mapper.class_manager.new_instance() 

4160 merged_state = attributes.instance_state(merged) 

4161 merged_dict = attributes.instance_dict(merged) 

4162 new_instance = True 

4163 self._save_or_update_state(merged_state) 

4164 else: 

4165 merged_state = attributes.instance_state(merged) 

4166 merged_dict = attributes.instance_dict(merged) 

4167 

4168 _recursive[state] = merged 

4169 _resolve_conflict_map[key] = merged 

4170 

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

4172 # state out. 

4173 if state is not merged_state: 

4174 # version check if applicable 

4175 if mapper.version_id_col is not None: 

4176 existing_version = mapper._get_state_attr_by_column( 

4177 state, 

4178 state_dict, 

4179 mapper.version_id_col, 

4180 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4181 ) 

4182 

4183 merged_version = mapper._get_state_attr_by_column( 

4184 merged_state, 

4185 merged_dict, 

4186 mapper.version_id_col, 

4187 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4188 ) 

4189 

4190 if ( 

4191 existing_version 

4192 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4193 and merged_version 

4194 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4195 and existing_version != merged_version 

4196 ): 

4197 raise exc.StaleDataError( 

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

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

4200 "Leave the version attribute unset when " 

4201 "merging to update the most recent version." 

4202 % ( 

4203 existing_version, 

4204 state_str(merged_state), 

4205 merged_version, 

4206 ) 

4207 ) 

4208 

4209 merged_state.load_path = state.load_path 

4210 merged_state.load_options = state.load_options 

4211 

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

4213 # the callables_ that would have been generated by those 

4214 # load_options. 

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

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

4217 merged_state._copy_callables(state) 

4218 

4219 for prop in mapper.iterate_properties: 

4220 prop.merge( 

4221 self, 

4222 state, 

4223 state_dict, 

4224 merged_state, 

4225 merged_dict, 

4226 load, 

4227 _recursive, 

4228 _resolve_conflict_map, 

4229 ) 

4230 

4231 if not load: 

4232 # remove any history 

4233 merged_state._commit_all(merged_dict, self.identity_map) 

4234 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4235 merged_state, None 

4236 ) 

4237 

4238 if new_instance: 

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

4240 

4241 return merged 

4242 

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

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

4245 raise sa_exc.InvalidRequestError( 

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

4247 % state_str(state) 

4248 ) 

4249 

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

4251 if state.key is not None: 

4252 raise sa_exc.InvalidRequestError( 

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

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

4255 ) 

4256 

4257 obj = state.obj() 

4258 to_attach = self._before_attach(state, obj) 

4259 if state not in self._new: 

4260 self._new[state] = obj 

4261 state.insert_order = len(self._new) 

4262 if to_attach: 

4263 self._after_attach(state, obj) 

4264 

4265 def _update_impl( 

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

4267 ) -> None: 

4268 if state.key is None: 

4269 raise sa_exc.InvalidRequestError( 

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

4271 ) 

4272 

4273 if state._deleted: 

4274 if revert_deletion: 

4275 if not state._attached: 

4276 return 

4277 del state._deleted 

4278 else: 

4279 raise sa_exc.InvalidRequestError( 

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

4281 "Use the make_transient() " 

4282 "function to send this object back " 

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

4284 ) 

4285 

4286 obj = state.obj() 

4287 

4288 # check for late gc 

4289 if obj is None: 

4290 return 

4291 

4292 to_attach = self._before_attach(state, obj) 

4293 

4294 self._deleted.pop(state, None) 

4295 if revert_deletion: 

4296 self.identity_map.replace(state) 

4297 else: 

4298 self.identity_map.add(state) 

4299 

4300 if to_attach: 

4301 self._after_attach(state, obj) 

4302 elif revert_deletion: 

4303 self.dispatch.deleted_to_persistent(self, state) 

4304 

4305 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4306 if state.key is None: 

4307 self._save_impl(state) 

4308 else: 

4309 self._update_impl(state) 

4310 

4311 def enable_relationship_loading(self, obj: object) -> None: 

4312 """Associate an object with this :class:`.Session` for related 

4313 object loading. 

4314 

4315 .. warning:: 

4316 

4317 :meth:`.enable_relationship_loading` exists to serve special 

4318 use cases and is not recommended for general use. 

4319 

4320 Accesses of attributes mapped with :func:`_orm.relationship` 

4321 will attempt to load a value from the database using this 

4322 :class:`.Session` as the source of connectivity. The values 

4323 will be loaded based on foreign key and primary key values 

4324 present on this object - if not present, then those relationships 

4325 will be unavailable. 

4326 

4327 The object will be attached to this session, but will 

4328 **not** participate in any persistence operations; its state 

4329 for almost all purposes will remain either "transient" or 

4330 "detached", except for the case of relationship loading. 

4331 

4332 Also note that backrefs will often not work as expected. 

4333 Altering a relationship-bound attribute on the target object 

4334 may not fire off a backref event, if the effective value 

4335 is what was already loaded from a foreign-key-holding value. 

4336 

4337 The :meth:`.Session.enable_relationship_loading` method is 

4338 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4339 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4340 an object to remain transient while still being able to load 

4341 related items. 

4342 

4343 To make a transient object associated with a :class:`.Session` 

4344 via :meth:`.Session.enable_relationship_loading` pending, add 

4345 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4346 If the object instead represents an existing identity in the database, 

4347 it should be merged using :meth:`.Session.merge`. 

4348 

4349 :meth:`.Session.enable_relationship_loading` does not improve 

4350 behavior when the ORM is used normally - object references should be 

4351 constructed at the object level, not at the foreign key level, so 

4352 that they are present in an ordinary way before flush() 

4353 proceeds. This method is not intended for general use. 

4354 

4355 .. seealso:: 

4356 

4357 :paramref:`_orm.relationship.load_on_pending` - this flag 

4358 allows per-relationship loading of many-to-ones on items that 

4359 are pending. 

4360 

4361 :func:`.make_transient_to_detached` - allows for an object to 

4362 be added to a :class:`.Session` without SQL emitted, which then 

4363 will unexpire attributes on access. 

4364 

4365 """ 

4366 try: 

4367 state = attributes.instance_state(obj) 

4368 except exc.NO_STATE as err: 

4369 raise exc.UnmappedInstanceError(obj) from err 

4370 

4371 to_attach = self._before_attach(state, obj) 

4372 state._load_pending = True 

4373 if to_attach: 

4374 self._after_attach(state, obj) 

4375 

4376 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4377 self._autobegin_t() 

4378 

4379 if state.session_id == self.hash_key: 

4380 return False 

4381 

4382 if state.session_id and state.session_id in _sessions: 

4383 raise sa_exc.InvalidRequestError( 

4384 "Object '%s' is already attached to session '%s' " 

4385 "(this is '%s')" 

4386 % (state_str(state), state.session_id, self.hash_key) 

4387 ) 

4388 

4389 self.dispatch.before_attach(self, state) 

4390 

4391 return True 

4392 

4393 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4394 state.session_id = self.hash_key 

4395 if state.modified and state._strong_obj is None: 

4396 state._strong_obj = obj 

4397 self.dispatch.after_attach(self, state) 

4398 

4399 if state.key: 

4400 self.dispatch.detached_to_persistent(self, state) 

4401 else: 

4402 self.dispatch.transient_to_pending(self, state) 

4403 

4404 def __contains__(self, instance: object) -> bool: 

4405 """Return True if the instance is associated with this session. 

4406 

4407 The instance may be pending or persistent within the Session for a 

4408 result of True. 

4409 

4410 """ 

4411 try: 

4412 state = attributes.instance_state(instance) 

4413 except exc.NO_STATE as err: 

4414 raise exc.UnmappedInstanceError(instance) from err 

4415 return self._contains_state(state) 

4416 

4417 def __iter__(self) -> Iterator[object]: 

4418 """Iterate over all pending or persistent instances within this 

4419 Session. 

4420 

4421 """ 

4422 return iter( 

4423 list(self._new.values()) + list(self.identity_map.values()) 

4424 ) 

4425 

4426 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4427 return state in self._new or self.identity_map.contains_state(state) 

4428 

4429 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4430 """Flush all the object changes to the database. 

4431 

4432 Writes out all pending object creations, deletions and modifications 

4433 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4434 automatically ordered by the Session's unit of work dependency 

4435 solver. 

4436 

4437 Database operations will be issued in the current transactional 

4438 context and do not affect the state of the transaction, unless an 

4439 error occurs, in which case the entire transaction is rolled back. 

4440 You may flush() as often as you like within a transaction to move 

4441 changes from Python to the database's transaction buffer. 

4442 

4443 :param objects: Optional; restricts the flush operation to operate 

4444 only on elements that are in the given collection. 

4445 

4446 This feature is for an extremely narrow set of use cases where 

4447 particular objects may need to be operated upon before the 

4448 full flush() occurs. It is not intended for general use. 

4449 

4450 .. deprecated:: 2.1 

4451 

4452 """ 

4453 

4454 if self._flushing: 

4455 raise sa_exc.InvalidRequestError("Session is already flushing") 

4456 

4457 if self._is_clean(): 

4458 return 

4459 try: 

4460 self._flushing = True 

4461 self._flush(objects) 

4462 finally: 

4463 self._flushing = False 

4464 

4465 def _flush_warning(self, method: Any) -> None: 

4466 util.warn( 

4467 "Usage of the '%s' operation is not currently supported " 

4468 "within the execution stage of the flush process. " 

4469 "Results may not be consistent. Consider using alternative " 

4470 "event listeners or connection-level operations instead." % method 

4471 ) 

4472 

4473 def _is_clean(self) -> bool: 

4474 return ( 

4475 not self.identity_map.check_modified() 

4476 and not self._deleted 

4477 and not self._new 

4478 ) 

4479 

4480 # have this here since it otherwise causes issues with the proxy 

4481 # method generation 

4482 @deprecated_params( 

4483 objects=( 

4484 "2.1", 

4485 "The `objects` parameter of `Session.flush` is deprecated", 

4486 ) 

4487 ) 

4488 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4489 dirty = self._dirty_states 

4490 if not dirty and not self._deleted and not self._new: 

4491 self.identity_map._modified.clear() 

4492 return 

4493 

4494 flush_context = UOWTransaction(self) 

4495 

4496 if self.dispatch.before_flush: 

4497 self.dispatch.before_flush(self, flush_context, objects) 

4498 # re-establish "dirty states" in case the listeners 

4499 # added 

4500 dirty = self._dirty_states 

4501 

4502 deleted = set(self._deleted) 

4503 new = set(self._new) 

4504 

4505 dirty = set(dirty).difference(deleted) 

4506 

4507 # create the set of all objects we want to operate upon 

4508 if objects: 

4509 # specific list passed in 

4510 objset = set() 

4511 for o in objects: 

4512 try: 

4513 state = attributes.instance_state(o) 

4514 

4515 except exc.NO_STATE as err: 

4516 raise exc.UnmappedInstanceError(o) from err 

4517 objset.add(state) 

4518 else: 

4519 objset = None 

4520 

4521 # store objects whose fate has been decided 

4522 processed = set() 

4523 

4524 # put all saves/updates into the flush context. detect top-level 

4525 # orphans and throw them into deleted. 

4526 if objset: 

4527 proc = new.union(dirty).intersection(objset).difference(deleted) 

4528 else: 

4529 proc = new.union(dirty).difference(deleted) 

4530 

4531 for state in proc: 

4532 is_orphan = _state_mapper(state)._is_orphan(state) 

4533 

4534 is_persistent_orphan = is_orphan and state.has_identity 

4535 

4536 if ( 

4537 is_orphan 

4538 and not is_persistent_orphan 

4539 and state._orphaned_outside_of_session 

4540 ): 

4541 self._expunge_states([state]) 

4542 else: 

4543 _reg = flush_context.register_object( 

4544 state, isdelete=is_persistent_orphan 

4545 ) 

4546 assert _reg, "Failed to add object to the flush context!" 

4547 processed.add(state) 

4548 

4549 # put all remaining deletes into the flush context. 

4550 if objset: 

4551 proc = deleted.intersection(objset).difference(processed) 

4552 else: 

4553 proc = deleted.difference(processed) 

4554 for state in proc: 

4555 _reg = flush_context.register_object(state, isdelete=True) 

4556 assert _reg, "Failed to add object to the flush context!" 

4557 

4558 if not flush_context.has_work: 

4559 return 

4560 

4561 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4562 try: 

4563 self._warn_on_events = True 

4564 try: 

4565 flush_context.execute() 

4566 finally: 

4567 self._warn_on_events = False 

4568 

4569 self.dispatch.after_flush(self, flush_context) 

4570 

4571 flush_context.finalize_flush_changes() 

4572 

4573 if not objects and self.identity_map._modified: 

4574 len_ = len(self.identity_map._modified) 

4575 

4576 statelib.InstanceState._commit_all_states( 

4577 [ 

4578 (state, state.dict) 

4579 for state in self.identity_map._modified 

4580 ], 

4581 instance_dict=self.identity_map, 

4582 ) 

4583 util.warn( 

4584 "Attribute history events accumulated on %d " 

4585 "previously clean instances " 

4586 "within inner-flush event handlers have been " 

4587 "reset, and will not result in database updates. " 

4588 "Consider using set_committed_value() within " 

4589 "inner-flush event handlers to avoid this warning." % len_ 

4590 ) 

4591 

4592 # useful assertions: 

4593 # if not objects: 

4594 # assert not self.identity_map._modified 

4595 # else: 

4596 # assert self.identity_map._modified == \ 

4597 # self.identity_map._modified.difference(objects) 

4598 

4599 self.dispatch.after_flush_postexec(self, flush_context) 

4600 

4601 transaction.commit() 

4602 

4603 except: 

4604 with util.safe_reraise(): 

4605 transaction.rollback(_capture_exception=True) 

4606 

4607 def bulk_save_objects( 

4608 self, 

4609 objects: Iterable[object], 

4610 return_defaults: bool = False, 

4611 update_changed_only: bool = True, 

4612 preserve_order: bool = True, 

4613 ) -> None: 

4614 """Perform a bulk save of the given list of objects. 

4615 

4616 .. legacy:: 

4617 

4618 This method is a legacy feature as of the 2.0 series of 

4619 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4620 the sections :ref:`orm_queryguide_bulk_insert` and 

4621 :ref:`orm_queryguide_bulk_update`. 

4622 

4623 For general INSERT and UPDATE of existing ORM mapped objects, 

4624 prefer standard :term:`unit of work` data management patterns, 

4625 introduced in the :ref:`unified_tutorial` at 

4626 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4627 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4628 which solves previous issues of bulk INSERT slowness. 

4629 

4630 :param objects: a sequence of mapped object instances. The mapped 

4631 objects are persisted as is, and are **not** associated with the 

4632 :class:`.Session` afterwards. 

4633 

4634 For each object, whether the object is sent as an INSERT or an 

4635 UPDATE is dependent on the same rules used by the :class:`.Session` 

4636 in traditional operation; if the object has the 

4637 :attr:`.InstanceState.key` 

4638 attribute set, then the object is assumed to be "detached" and 

4639 will result in an UPDATE. Otherwise, an INSERT is used. 

4640 

4641 In the case of an UPDATE, statements are grouped based on which 

4642 attributes have changed, and are thus to be the subject of each 

4643 SET clause. If ``update_changed_only`` is False, then all 

4644 attributes present within each object are applied to the UPDATE 

4645 statement, which may help in allowing the statements to be grouped 

4646 together into a larger executemany(), and will also reduce the 

4647 overhead of checking history on attributes. 

4648 

4649 :param return_defaults: when True, rows that are missing values which 

4650 generate defaults, namely integer primary key defaults and sequences, 

4651 will be inserted **one at a time**, so that the primary key value 

4652 is available. In particular this will allow joined-inheritance 

4653 and other multi-table mappings to insert correctly without the need 

4654 to provide primary key values ahead of time; however, 

4655 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4656 reduces the performance gains** of the method overall. It is strongly 

4657 advised to please use the standard :meth:`_orm.Session.add_all` 

4658 approach. 

4659 

4660 :param update_changed_only: when True, UPDATE statements are rendered 

4661 based on those attributes in each state that have logged changes. 

4662 When False, all attributes present are rendered into the SET clause 

4663 with the exception of primary key attributes. 

4664 

4665 :param preserve_order: when True, the order of inserts and updates 

4666 matches exactly the order in which the objects are given. When 

4667 False, common types of objects are grouped into inserts 

4668 and updates, to allow for more batching opportunities. 

4669 

4670 .. seealso:: 

4671 

4672 :doc:`queryguide/dml` 

4673 

4674 :meth:`.Session.bulk_insert_mappings` 

4675 

4676 :meth:`.Session.bulk_update_mappings` 

4677 

4678 """ 

4679 

4680 obj_states: Iterable[InstanceState[Any]] 

4681 

4682 obj_states = (attributes.instance_state(obj) for obj in objects) 

4683 

4684 if not preserve_order: 

4685 # the purpose of this sort is just so that common mappers 

4686 # and persistence states are grouped together, so that groupby 

4687 # will return a single group for a particular type of mapper. 

4688 # it's not trying to be deterministic beyond that. 

4689 obj_states = sorted( 

4690 obj_states, 

4691 key=lambda state: (id(state.mapper), state.key is not None), 

4692 ) 

4693 

4694 def grouping_key( 

4695 state: InstanceState[_O], 

4696 ) -> Tuple[Mapper[_O], bool]: 

4697 return (state.mapper, state.key is not None) 

4698 

4699 for (mapper, isupdate), states in itertools.groupby( 

4700 obj_states, grouping_key 

4701 ): 

4702 self._bulk_save_mappings( 

4703 mapper, 

4704 states, 

4705 isupdate=isupdate, 

4706 isstates=True, 

4707 return_defaults=return_defaults, 

4708 update_changed_only=update_changed_only, 

4709 render_nulls=False, 

4710 ) 

4711 

4712 def bulk_insert_mappings( 

4713 self, 

4714 mapper: Mapper[Any], 

4715 mappings: Iterable[Dict[str, Any]], 

4716 return_defaults: bool = False, 

4717 render_nulls: bool = False, 

4718 ) -> None: 

4719 """Perform a bulk insert of the given list of mapping dictionaries. 

4720 

4721 .. legacy:: 

4722 

4723 This method is a legacy feature as of the 2.0 series of 

4724 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4725 the sections :ref:`orm_queryguide_bulk_insert` and 

4726 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4727 implementation details with this method and adds new features 

4728 as well. 

4729 

4730 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4731 object, 

4732 representing the single kind of object represented within the mapping 

4733 list. 

4734 

4735 :param mappings: a sequence of dictionaries, each one containing the 

4736 state of the mapped row to be inserted, in terms of the attribute 

4737 names on the mapped class. If the mapping refers to multiple tables, 

4738 such as a joined-inheritance mapping, each dictionary must contain all 

4739 keys to be populated into all tables. 

4740 

4741 :param return_defaults: when True, the INSERT process will be altered 

4742 to ensure that newly generated primary key values will be fetched. 

4743 The rationale for this parameter is typically to enable 

4744 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4745 be bulk inserted. 

4746 

4747 .. note:: for backends that don't support RETURNING, the 

4748 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4749 parameter can significantly decrease performance as INSERT 

4750 statements can no longer be batched. See 

4751 :ref:`engine_insertmanyvalues` 

4752 for background on which backends are affected. 

4753 

4754 :param render_nulls: When True, a value of ``None`` will result 

4755 in a NULL value being included in the INSERT statement, rather 

4756 than the column being omitted from the INSERT. This allows all 

4757 the rows being INSERTed to have the identical set of columns which 

4758 allows the full set of rows to be batched to the DBAPI. Normally, 

4759 each column-set that contains a different combination of NULL values 

4760 than the previous row must omit a different series of columns from 

4761 the rendered INSERT statement, which means it must be emitted as a 

4762 separate statement. By passing this flag, the full set of rows 

4763 are guaranteed to be batchable into one batch; the cost however is 

4764 that server-side defaults which are invoked by an omitted column will 

4765 be skipped, so care must be taken to ensure that these are not 

4766 necessary. 

4767 

4768 .. warning:: 

4769 

4770 When this flag is set, **server side default SQL values will 

4771 not be invoked** for those columns that are inserted as NULL; 

4772 the NULL value will be sent explicitly. Care must be taken 

4773 to ensure that no server-side default functions need to be 

4774 invoked for the operation as a whole. 

4775 

4776 .. seealso:: 

4777 

4778 :doc:`queryguide/dml` 

4779 

4780 :meth:`.Session.bulk_save_objects` 

4781 

4782 :meth:`.Session.bulk_update_mappings` 

4783 

4784 """ 

4785 self._bulk_save_mappings( 

4786 mapper, 

4787 mappings, 

4788 isupdate=False, 

4789 isstates=False, 

4790 return_defaults=return_defaults, 

4791 update_changed_only=False, 

4792 render_nulls=render_nulls, 

4793 ) 

4794 

4795 def bulk_update_mappings( 

4796 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4797 ) -> None: 

4798 """Perform a bulk update of the given list of mapping dictionaries. 

4799 

4800 .. legacy:: 

4801 

4802 This method is a legacy feature as of the 2.0 series of 

4803 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4804 the sections :ref:`orm_queryguide_bulk_insert` and 

4805 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4806 implementation details with this method and adds new features 

4807 as well. 

4808 

4809 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4810 object, 

4811 representing the single kind of object represented within the mapping 

4812 list. 

4813 

4814 :param mappings: a sequence of dictionaries, each one containing the 

4815 state of the mapped row to be updated, in terms of the attribute names 

4816 on the mapped class. If the mapping refers to multiple tables, such 

4817 as a joined-inheritance mapping, each dictionary may contain keys 

4818 corresponding to all tables. All those keys which are present and 

4819 are not part of the primary key are applied to the SET clause of the 

4820 UPDATE statement; the primary key values, which are required, are 

4821 applied to the WHERE clause. 

4822 

4823 

4824 .. seealso:: 

4825 

4826 :doc:`queryguide/dml` 

4827 

4828 :meth:`.Session.bulk_insert_mappings` 

4829 

4830 :meth:`.Session.bulk_save_objects` 

4831 

4832 """ 

4833 self._bulk_save_mappings( 

4834 mapper, 

4835 mappings, 

4836 isupdate=True, 

4837 isstates=False, 

4838 return_defaults=False, 

4839 update_changed_only=False, 

4840 render_nulls=False, 

4841 ) 

4842 

4843 def _bulk_save_mappings( 

4844 self, 

4845 mapper: Mapper[_O], 

4846 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4847 *, 

4848 isupdate: bool, 

4849 isstates: bool, 

4850 return_defaults: bool, 

4851 update_changed_only: bool, 

4852 render_nulls: bool, 

4853 ) -> None: 

4854 mapper = _class_to_mapper(mapper) 

4855 self._flushing = True 

4856 

4857 transaction = self._autobegin_t()._begin() 

4858 try: 

4859 if isupdate: 

4860 bulk_persistence._bulk_update( 

4861 mapper, 

4862 mappings, 

4863 transaction, 

4864 isstates=isstates, 

4865 update_changed_only=update_changed_only, 

4866 ) 

4867 else: 

4868 bulk_persistence._bulk_insert( 

4869 mapper, 

4870 mappings, 

4871 transaction, 

4872 isstates=isstates, 

4873 return_defaults=return_defaults, 

4874 render_nulls=render_nulls, 

4875 ) 

4876 transaction.commit() 

4877 

4878 except: 

4879 with util.safe_reraise(): 

4880 transaction.rollback(_capture_exception=True) 

4881 finally: 

4882 self._flushing = False 

4883 

4884 def is_modified( 

4885 self, instance: object, include_collections: bool = True 

4886 ) -> bool: 

4887 r"""Return ``True`` if the given instance has locally 

4888 modified attributes. 

4889 

4890 This method retrieves the history for each instrumented 

4891 attribute on the instance and performs a comparison of the current 

4892 value to its previously flushed or committed value, if any. 

4893 

4894 It is in effect a more expensive and accurate 

4895 version of checking for the given instance in the 

4896 :attr:`.Session.dirty` collection; a full test for 

4897 each attribute's net "dirty" status is performed. 

4898 

4899 E.g.:: 

4900 

4901 return session.is_modified(someobject) 

4902 

4903 A few caveats to this method apply: 

4904 

4905 * Instances present in the :attr:`.Session.dirty` collection may 

4906 report ``False`` when tested with this method. This is because 

4907 the object may have received change events via attribute mutation, 

4908 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4909 is the same as that loaded from the database, resulting in no net 

4910 change here. 

4911 * Scalar attributes may not have recorded the previously set 

4912 value when a new value was applied, if the attribute was not loaded, 

4913 or was expired, at the time the new value was received - in these 

4914 cases, the attribute is assumed to have a change, even if there is 

4915 ultimately no net change against its database value. SQLAlchemy in 

4916 most cases does not need the "old" value when a set event occurs, so 

4917 it skips the expense of a SQL call if the old value isn't present, 

4918 based on the assumption that an UPDATE of the scalar value is 

4919 usually needed, and in those few cases where it isn't, is less 

4920 expensive on average than issuing a defensive SELECT. 

4921 

4922 The "old" value is fetched unconditionally upon set only if the 

4923 attribute container has the ``active_history`` flag set to ``True``. 

4924 This flag is set typically for primary key attributes and scalar 

4925 object references that are not a simple many-to-one. To set this 

4926 flag for any arbitrary mapped column, use the ``active_history`` 

4927 argument with :func:`.column_property`. 

4928 

4929 :param instance: mapped instance to be tested for pending changes. 

4930 :param include_collections: Indicates if multivalued collections 

4931 should be included in the operation. Setting this to ``False`` is a 

4932 way to detect only local-column based properties (i.e. scalar columns 

4933 or many-to-one foreign keys) that would result in an UPDATE for this 

4934 instance upon flush. 

4935 

4936 """ 

4937 state = object_state(instance) 

4938 

4939 if not state.modified: 

4940 return False 

4941 

4942 dict_ = state.dict 

4943 

4944 for attr in state.manager.attributes: 

4945 if ( 

4946 not include_collections 

4947 and hasattr(attr.impl, "get_collection") 

4948 ) or not hasattr(attr.impl, "get_history"): 

4949 continue 

4950 

4951 added, unchanged, deleted = attr.impl.get_history( 

4952 state, dict_, passive=PassiveFlag.NO_CHANGE 

4953 ) 

4954 

4955 if added or deleted: 

4956 return True 

4957 else: 

4958 return False 

4959 

4960 @property 

4961 def is_active(self) -> bool: 

4962 """True if this :class:`.Session` not in "partial rollback" state. 

4963 

4964 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4965 a new transaction immediately, so this attribute will be False 

4966 when the :class:`_orm.Session` is first instantiated. 

4967 

4968 "partial rollback" state typically indicates that the flush process 

4969 of the :class:`_orm.Session` has failed, and that the 

4970 :meth:`_orm.Session.rollback` method must be emitted in order to 

4971 fully roll back the transaction. 

4972 

4973 If this :class:`_orm.Session` is not in a transaction at all, the 

4974 :class:`_orm.Session` will autobegin when it is first used, so in this 

4975 case :attr:`_orm.Session.is_active` will return True. 

4976 

4977 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4978 and that transaction has not been rolled back internally, the 

4979 :attr:`_orm.Session.is_active` will also return True. 

4980 

4981 .. seealso:: 

4982 

4983 :ref:`faq_session_rollback` 

4984 

4985 :meth:`_orm.Session.in_transaction` 

4986 

4987 """ 

4988 return self._transaction is None or self._transaction.is_active 

4989 

4990 @property 

4991 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4992 """The set of all persistent states considered dirty. 

4993 

4994 This method returns all states that were modified including 

4995 those that were possibly deleted. 

4996 

4997 """ 

4998 return self.identity_map._dirty_states() 

4999 

5000 @property 

5001 def dirty(self) -> IdentitySet: 

5002 """The set of all persistent instances considered dirty. 

5003 

5004 E.g.:: 

5005 

5006 some_mapped_object in session.dirty 

5007 

5008 Instances are considered dirty when they were modified but not 

5009 deleted. 

5010 

5011 Note that this 'dirty' calculation is 'optimistic'; most 

5012 attribute-setting or collection modification operations will 

5013 mark an instance as 'dirty' and place it in this set, even if 

5014 there is no net change to the attribute's value. At flush 

5015 time, the value of each attribute is compared to its 

5016 previously saved value, and if there's no net change, no SQL 

5017 operation will occur (this is a more expensive operation so 

5018 it's only done at flush time). 

5019 

5020 To check if an instance has actionable net changes to its 

5021 attributes, use the :meth:`.Session.is_modified` method. 

5022 

5023 """ 

5024 return IdentitySet( 

5025 [ 

5026 state.obj() 

5027 for state in self._dirty_states 

5028 if state not in self._deleted 

5029 ] 

5030 ) 

5031 

5032 @property 

5033 def deleted(self) -> IdentitySet: 

5034 "The set of all instances marked as 'deleted' within this ``Session``" 

5035 

5036 return util.IdentitySet(list(self._deleted.values())) 

5037 

5038 @property 

5039 def new(self) -> IdentitySet: 

5040 "The set of all instances marked as 'new' within this ``Session``." 

5041 

5042 return util.IdentitySet(list(self._new.values())) 

5043 

5044 

5045_S = TypeVar("_S", bound="Session") 

5046 

5047 

5048class sessionmaker(_SessionClassMethods, Generic[_S]): 

5049 """A configurable :class:`.Session` factory. 

5050 

5051 The :class:`.sessionmaker` factory generates new 

5052 :class:`.Session` objects when called, creating them given 

5053 the configurational arguments established here. 

5054 

5055 e.g.:: 

5056 

5057 from sqlalchemy import create_engine 

5058 from sqlalchemy.orm import sessionmaker 

5059 

5060 # an Engine, which the Session will use for connection 

5061 # resources 

5062 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

5063 

5064 Session = sessionmaker(engine) 

5065 

5066 with Session() as session: 

5067 session.add(some_object) 

5068 session.add(some_other_object) 

5069 session.commit() 

5070 

5071 Context manager use is optional; otherwise, the returned 

5072 :class:`_orm.Session` object may be closed explicitly via the 

5073 :meth:`_orm.Session.close` method. Using a 

5074 ``try:/finally:`` block is optional, however will ensure that the close 

5075 takes place even if there are database errors:: 

5076 

5077 session = Session() 

5078 try: 

5079 session.add(some_object) 

5080 session.add(some_other_object) 

5081 session.commit() 

5082 finally: 

5083 session.close() 

5084 

5085 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5086 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5087 for :class:`_engine.Connection` objects. In this way it also includes 

5088 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5089 manager which both begins and commits a transaction, as well as closes 

5090 out the :class:`_orm.Session` when complete, rolling back the transaction 

5091 if any errors occur:: 

5092 

5093 Session = sessionmaker(engine) 

5094 

5095 with Session.begin() as session: 

5096 session.add(some_object) 

5097 session.add(some_other_object) 

5098 # commits transaction, closes session 

5099 

5100 .. versionadded:: 1.4 

5101 

5102 When calling upon :class:`_orm.sessionmaker` to construct a 

5103 :class:`_orm.Session`, keyword arguments may also be passed to the 

5104 method; these arguments will override that of the globally configured 

5105 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5106 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5107 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5108 

5109 Session = sessionmaker(engine) 

5110 

5111 # bind an individual session to a connection 

5112 

5113 with engine.connect() as connection: 

5114 with Session(bind=connection) as session: 

5115 ... # work with session 

5116 

5117 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5118 can be used to specify additional keyword arguments to the factory, which 

5119 will take effect for subsequent :class:`.Session` objects generated. This 

5120 is usually used to associate one or more :class:`_engine.Engine` objects 

5121 with an existing 

5122 :class:`.sessionmaker` factory before it is first used:: 

5123 

5124 # application starts, sessionmaker does not have 

5125 # an engine bound yet 

5126 Session = sessionmaker() 

5127 

5128 # ... later, when an engine URL is read from a configuration 

5129 # file or other events allow the engine to be created 

5130 engine = create_engine("sqlite:///foo.db") 

5131 Session.configure(bind=engine) 

5132 

5133 sess = Session() 

5134 # work with session 

5135 

5136 .. seealso:: 

5137 

5138 :ref:`session_getting` - introductory text on creating 

5139 sessions using :class:`.sessionmaker`. 

5140 

5141 """ 

5142 

5143 class_: Type[_S] 

5144 

5145 @overload 

5146 def __init__( 

5147 self, 

5148 bind: Optional[_SessionBind] = ..., 

5149 *, 

5150 class_: Type[_S], 

5151 autoflush: bool = ..., 

5152 expire_on_commit: bool = ..., 

5153 info: Optional[_InfoType] = ..., 

5154 **kw: Any, 

5155 ): ... 

5156 

5157 @overload 

5158 def __init__( 

5159 self: "sessionmaker[Session]", 

5160 bind: Optional[_SessionBind] = ..., 

5161 *, 

5162 autoflush: bool = ..., 

5163 expire_on_commit: bool = ..., 

5164 info: Optional[_InfoType] = ..., 

5165 **kw: Any, 

5166 ): ... 

5167 

5168 def __init__( 

5169 self, 

5170 bind: Optional[_SessionBind] = None, 

5171 *, 

5172 class_: Type[_S] = Session, # type: ignore 

5173 autoflush: bool = True, 

5174 expire_on_commit: bool = True, 

5175 info: Optional[_InfoType] = None, 

5176 **kw: Any, 

5177 ): 

5178 r"""Construct a new :class:`.sessionmaker`. 

5179 

5180 All arguments here except for ``class_`` correspond to arguments 

5181 accepted by :class:`.Session` directly. See the 

5182 :meth:`.Session.__init__` docstring for more details on parameters. 

5183 

5184 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5185 with 

5186 which newly created :class:`.Session` objects will be associated. 

5187 :param class\_: class to use in order to create new :class:`.Session` 

5188 objects. Defaults to :class:`.Session`. 

5189 :param autoflush: The autoflush setting to use with newly created 

5190 :class:`.Session` objects. 

5191 

5192 .. seealso:: 

5193 

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

5195 

5196 :param expire_on_commit=True: the 

5197 :paramref:`_orm.Session.expire_on_commit` setting to use 

5198 with newly created :class:`.Session` objects. 

5199 

5200 :param info: optional dictionary of information that will be available 

5201 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5202 replaced, when the ``info`` parameter is specified to the specific 

5203 :class:`.Session` construction operation. 

5204 

5205 :param \**kw: all other keyword arguments are passed to the 

5206 constructor of newly created :class:`.Session` objects. 

5207 

5208 """ 

5209 kw["bind"] = bind 

5210 kw["autoflush"] = autoflush 

5211 kw["expire_on_commit"] = expire_on_commit 

5212 if info is not None: 

5213 kw["info"] = info 

5214 self.kw = kw 

5215 # make our own subclass of the given class, so that 

5216 # events can be associated with it specifically. 

5217 self.class_ = type(class_.__name__, (class_,), {}) 

5218 

5219 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5220 """Produce a context manager that both provides a new 

5221 :class:`_orm.Session` as well as a transaction that commits. 

5222 

5223 

5224 e.g.:: 

5225 

5226 Session = sessionmaker(some_engine) 

5227 

5228 with Session.begin() as session: 

5229 session.add(some_object) 

5230 

5231 # commits transaction, closes session 

5232 

5233 .. versionadded:: 1.4 

5234 

5235 

5236 """ 

5237 

5238 session = self() 

5239 return session._maker_context_manager() 

5240 

5241 def __call__(self, **local_kw: Any) -> _S: 

5242 """Produce a new :class:`.Session` object using the configuration 

5243 established in this :class:`.sessionmaker`. 

5244 

5245 In Python, the ``__call__`` method is invoked on an object when 

5246 it is "called" in the same way as a function:: 

5247 

5248 Session = sessionmaker(some_engine) 

5249 session = Session() # invokes sessionmaker.__call__() 

5250 

5251 """ 

5252 for k, v in self.kw.items(): 

5253 if k == "info" and "info" in local_kw: 

5254 d = v.copy() 

5255 d.update(local_kw["info"]) 

5256 local_kw["info"] = d 

5257 else: 

5258 local_kw.setdefault(k, v) 

5259 return self.class_(**local_kw) 

5260 

5261 def configure(self, **new_kw: Any) -> None: 

5262 """(Re)configure the arguments for this sessionmaker. 

5263 

5264 e.g.:: 

5265 

5266 Session = sessionmaker() 

5267 

5268 Session.configure(bind=create_engine("sqlite://")) 

5269 """ 

5270 self.kw.update(new_kw) 

5271 

5272 def __repr__(self) -> str: 

5273 return "%s(class_=%r, %s)" % ( 

5274 self.__class__.__name__, 

5275 self.class_.__name__, 

5276 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5277 ) 

5278 

5279 

5280def close_all_sessions() -> None: 

5281 """Close all sessions in memory. 

5282 

5283 This function consults a global registry of all :class:`.Session` objects 

5284 and calls :meth:`.Session.close` on them, which resets them to a clean 

5285 state. 

5286 

5287 This function is not for general use but may be useful for test suites 

5288 within the teardown scheme. 

5289 

5290 """ 

5291 

5292 for sess in _sessions.values(): 

5293 sess.close() 

5294 

5295 

5296def make_transient(instance: object) -> None: 

5297 """Alter the state of the given instance so that it is :term:`transient`. 

5298 

5299 .. note:: 

5300 

5301 :func:`.make_transient` is a special-case function for 

5302 advanced use cases only. 

5303 

5304 The given mapped instance is assumed to be in the :term:`persistent` or 

5305 :term:`detached` state. The function will remove its association with any 

5306 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5307 effect is that the object will behave as though it were newly constructed, 

5308 except retaining any attribute / collection values that were loaded at the 

5309 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5310 if this object had been deleted as a result of using 

5311 :meth:`.Session.delete`. 

5312 

5313 .. warning:: 

5314 

5315 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5316 load ORM-mapped attributes that are not currently loaded at the time 

5317 the function is called. This includes attributes which: 

5318 

5319 * were expired via :meth:`.Session.expire` 

5320 

5321 * were expired as the natural effect of committing a session 

5322 transaction, e.g. :meth:`.Session.commit` 

5323 

5324 * are normally :term:`lazy loaded` but are not currently loaded 

5325 

5326 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5327 not yet loaded 

5328 

5329 * were not present in the query which loaded this object, such as that 

5330 which is common in joined table inheritance and other scenarios. 

5331 

5332 After :func:`.make_transient` is called, unloaded attributes such 

5333 as those above will normally resolve to the value ``None`` when 

5334 accessed, or an empty collection for a collection-oriented attribute. 

5335 As the object is transient and un-associated with any database 

5336 identity, it will no longer retrieve these values. 

5337 

5338 .. seealso:: 

5339 

5340 :func:`.make_transient_to_detached` 

5341 

5342 """ 

5343 state = attributes.instance_state(instance) 

5344 s = _state_session(state) 

5345 if s: 

5346 s._expunge_states([state]) 

5347 

5348 # remove expired state 

5349 state.expired_attributes.clear() 

5350 

5351 # remove deferred callables 

5352 if state.callables: 

5353 del state.callables 

5354 

5355 if state.key: 

5356 del state.key 

5357 if state._deleted: 

5358 del state._deleted 

5359 

5360 

5361def make_transient_to_detached(instance: object) -> None: 

5362 """Make the given transient instance :term:`detached`. 

5363 

5364 .. note:: 

5365 

5366 :func:`.make_transient_to_detached` is a special-case function for 

5367 advanced use cases only. 

5368 

5369 All attribute history on the given instance 

5370 will be reset as though the instance were freshly loaded 

5371 from a query. Missing attributes will be marked as expired. 

5372 The primary key attributes of the object, which are required, will be made 

5373 into the "key" of the instance. 

5374 

5375 The object can then be added to a session, or merged 

5376 possibly with the load=False flag, at which point it will look 

5377 as if it were loaded that way, without emitting SQL. 

5378 

5379 This is a special use case function that differs from a normal 

5380 call to :meth:`.Session.merge` in that a given persistent state 

5381 can be manufactured without any SQL calls. 

5382 

5383 .. seealso:: 

5384 

5385 :func:`.make_transient` 

5386 

5387 :meth:`.Session.enable_relationship_loading` 

5388 

5389 """ 

5390 state = attributes.instance_state(instance) 

5391 if state.session_id or state.key: 

5392 raise sa_exc.InvalidRequestError("Given object must be transient") 

5393 state.key = state.mapper._identity_key_from_state(state) 

5394 if state._deleted: 

5395 del state._deleted 

5396 state._commit_all(state.dict) 

5397 state._expire_attributes(state.dict, state.unloaded) 

5398 

5399 

5400def object_session(instance: object) -> Optional[Session]: 

5401 """Return the :class:`.Session` to which the given instance belongs. 

5402 

5403 This is essentially the same as the :attr:`.InstanceState.session` 

5404 accessor. See that attribute for details. 

5405 

5406 """ 

5407 

5408 try: 

5409 state = attributes.instance_state(instance) 

5410 except exc.NO_STATE as err: 

5411 raise exc.UnmappedInstanceError(instance) from err 

5412 else: 

5413 return _state_session(state) 

5414 

5415 

5416_new_sessionid = util.counter()