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

1453 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 

99 

100if typing.TYPE_CHECKING: 

101 from ._typing import _EntityType 

102 from ._typing import _IdentityKeyType 

103 from ._typing import _InstanceDict 

104 from ._typing import OrmExecuteOptionsParameter 

105 from .interfaces import ORMOption 

106 from .interfaces import UserDefinedOption 

107 from .mapper import Mapper 

108 from .path_registry import PathRegistry 

109 from .query import RowReturningQuery 

110 from ..engine import Result 

111 from ..engine import Row 

112 from ..engine import RowMapping 

113 from ..engine.base import Transaction 

114 from ..engine.base import TwoPhaseTransaction 

115 from ..engine.interfaces import _CoreAnyExecuteParams 

116 from ..engine.interfaces import _CoreSingleExecuteParams 

117 from ..engine.interfaces import _ExecuteOptions 

118 from ..engine.interfaces import CoreExecuteOptionsParameter 

119 from ..engine.result import ScalarResult 

120 from ..event import _InstanceLevelDispatch 

121 from ..sql._typing import _ColumnsClauseArgument 

122 from ..sql._typing import _InfoType 

123 from ..sql._typing import _T0 

124 from ..sql._typing import _T1 

125 from ..sql._typing import _T2 

126 from ..sql._typing import _T3 

127 from ..sql._typing import _T4 

128 from ..sql._typing import _T5 

129 from ..sql._typing import _T6 

130 from ..sql._typing import _T7 

131 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

132 from ..sql.base import Executable 

133 from ..sql.base import ExecutableOption 

134 from ..sql.elements import ClauseElement 

135 from ..sql.roles import TypedColumnsClauseRole 

136 from ..sql.selectable import ForUpdateParameter 

137 from ..sql.selectable import TypedReturnsRows 

138 

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

140_Ts = TypeVarTuple("_Ts") 

141 

142__all__ = [ 

143 "Session", 

144 "SessionTransaction", 

145 "sessionmaker", 

146 "ORMExecuteState", 

147 "close_all_sessions", 

148 "make_transient", 

149 "make_transient_to_detached", 

150 "object_session", 

151] 

152 

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

154 weakref.WeakValueDictionary() 

155) 

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

157""" 

158 

159statelib._sessions = _sessions 

160 

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

162 

163_BindArguments = Dict[str, Any] 

164 

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

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

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

168 

169JoinTransactionMode = Literal[ 

170 "conditional_savepoint", 

171 "rollback_only", 

172 "control_fully", 

173 "create_savepoint", 

174] 

175 

176 

177class _ConnectionCallableProto(Protocol): 

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

179 

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

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

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

183 as persistence time. 

184 

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

186 is established when using the horizontal sharding extension. 

187 

188 """ 

189 

190 def __call__( 

191 self, 

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

193 instance: Optional[object] = None, 

194 **kw: Any, 

195 ) -> Connection: ... 

196 

197 

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

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

200 associated, if any. 

201 """ 

202 return state.session 

203 

204 

205class _SessionClassMethods: 

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

207 

208 @classmethod 

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

210 def identity_key( 

211 cls, 

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

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

214 *, 

215 instance: Optional[Any] = None, 

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

217 identity_token: Optional[Any] = None, 

218 ) -> _IdentityKeyType[Any]: 

219 """Return an identity key. 

220 

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

222 

223 """ 

224 return util.preloaded.orm_util.identity_key( 

225 class_, 

226 ident, 

227 instance=instance, 

228 row=row, 

229 identity_token=identity_token, 

230 ) 

231 

232 @classmethod 

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

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

235 

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

237 

238 """ 

239 

240 return object_session(instance) 

241 

242 

243class SessionTransactionState(_StateChangeState): 

244 ACTIVE = 1 

245 PREPARED = 2 

246 COMMITTED = 3 

247 DEACTIVE = 4 

248 CLOSED = 5 

249 PROVISIONING_CONNECTION = 6 

250 

251 

252# backwards compatibility 

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

254 SessionTransactionState 

255) 

256 

257 

258class ORMExecuteState(util.MemoizedSlots): 

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

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

261 

262 .. versionadded:: 1.4 

263 

264 .. seealso:: 

265 

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

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

268 

269 """ 

270 

271 __slots__ = ( 

272 "session", 

273 "statement", 

274 "parameters", 

275 "execution_options", 

276 "local_execution_options", 

277 "bind_arguments", 

278 "identity_token", 

279 "_compile_state_cls", 

280 "_starting_event_idx", 

281 "_events_todo", 

282 "_update_execution_options", 

283 ) 

284 

285 session: Session 

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

287 

288 statement: Executable 

289 """The SQL statement being invoked. 

290 

291 For an ORM selection as would 

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

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

294 """ 

295 

296 parameters: Optional[_CoreAnyExecuteParams] 

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

298 :meth:`_orm.Session.execute`. 

299 

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

301 effective parameters passed to the method. 

302 

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

304 mutated or replaced. 

305 

306 """ 

307 

308 execution_options: _ExecuteOptions 

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

310 

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

312 locally passed execution options. 

313 

314 .. seealso:: 

315 

316 :attr:`_orm.ORMExecuteState.local_execution_options` 

317 

318 :meth:`_sql.Executable.execution_options` 

319 

320 :ref:`orm_queryguide_execution_options` 

321 

322 """ 

323 

324 local_execution_options: _ExecuteOptions 

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

326 :meth:`.Session.execute` method. 

327 

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

329 being invoked. 

330 

331 .. seealso:: 

332 

333 :attr:`_orm.ORMExecuteState.execution_options` 

334 

335 """ 

336 

337 bind_arguments: _BindArguments 

338 """The dictionary passed as the 

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

340 

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

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

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

344 

345 """ 

346 

347 _compile_state_cls: Optional[Type[_ORMCompileState]] 

348 _starting_event_idx: int 

349 _events_todo: List[Any] 

350 _update_execution_options: Optional[_ExecuteOptions] 

351 

352 def __init__( 

353 self, 

354 session: Session, 

355 statement: Executable, 

356 parameters: Optional[_CoreAnyExecuteParams], 

357 execution_options: _ExecuteOptions, 

358 bind_arguments: _BindArguments, 

359 compile_state_cls: Optional[Type[_ORMCompileState]], 

360 events_todo: List[_InstanceLevelDispatch[Session]], 

361 ): 

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

363 

364 this object is constructed internally. 

365 

366 """ 

367 self.session = session 

368 self.statement = statement 

369 self.parameters = parameters 

370 self.local_execution_options = execution_options 

371 self.execution_options = statement._execution_options.union( 

372 execution_options 

373 ) 

374 self.bind_arguments = bind_arguments 

375 self._compile_state_cls = compile_state_cls 

376 self._events_todo = list(events_todo) 

377 

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

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

380 

381 def invoke_statement( 

382 self, 

383 statement: Optional[Executable] = None, 

384 params: Optional[_CoreAnyExecuteParams] = None, 

385 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

386 bind_arguments: Optional[_BindArguments] = None, 

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

388 """Execute the statement represented by this 

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

390 already proceeded. 

391 

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

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

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

395 that want to override how the ultimate 

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

397 retrieve results from an offline cache or which concatenate results 

398 from multiple executions. 

399 

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

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

402 is propagated to the calling 

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

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

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

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

407 

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

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

410 

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

412 which will be merged into the existing 

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

414 

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

416 for executemany executions. 

417 

418 :param execution_options: optional dictionary of execution options 

419 will be merged into the existing 

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

421 :class:`.ORMExecuteState`. 

422 

423 :param bind_arguments: optional dictionary of bind_arguments 

424 which will be merged amongst the current 

425 :attr:`.ORMExecuteState.bind_arguments` 

426 of this :class:`.ORMExecuteState`. 

427 

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

429 

430 .. seealso:: 

431 

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

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

434 

435 

436 """ 

437 

438 if statement is None: 

439 statement = self.statement 

440 

441 _bind_arguments = dict(self.bind_arguments) 

442 if bind_arguments: 

443 _bind_arguments.update(bind_arguments) 

444 _bind_arguments["_sa_skip_events"] = True 

445 

446 _params: Optional[_CoreAnyExecuteParams] 

447 if params: 

448 if self.is_executemany: 

449 _params = [] 

450 exec_many_parameters = cast( 

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

452 ) 

453 for _existing_params, _new_params in itertools.zip_longest( 

454 exec_many_parameters, 

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

456 ): 

457 if _existing_params is None or _new_params is None: 

458 raise sa_exc.InvalidRequestError( 

459 f"Can't apply executemany parameters to " 

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

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

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

463 f"to ORMExecuteState.invoke_statement() " 

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

465 ) 

466 _existing_params = dict(_existing_params) 

467 _existing_params.update(_new_params) 

468 _params.append(_existing_params) 

469 else: 

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

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

472 else: 

473 _params = self.parameters 

474 

475 _execution_options = self.local_execution_options 

476 if execution_options: 

477 _execution_options = _execution_options.union(execution_options) 

478 

479 return self.session._execute_internal( 

480 statement, 

481 _params, 

482 execution_options=_execution_options, 

483 bind_arguments=_bind_arguments, 

484 _parent_execute_state=self, 

485 ) 

486 

487 @property 

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

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

490 

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

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

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

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

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

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

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

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

499 would be selected. 

500 

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

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

503 way of getting this mapper. 

504 

505 .. versionadded:: 1.4.0b2 

506 

507 .. seealso:: 

508 

509 :attr:`_orm.ORMExecuteState.all_mappers` 

510 

511 

512 """ 

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

514 return mp 

515 

516 @property 

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

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

519 involved at the top level of this statement. 

520 

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

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

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

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

525 

526 .. versionadded:: 1.4.0b2 

527 

528 .. seealso:: 

529 

530 :attr:`_orm.ORMExecuteState.bind_mapper` 

531 

532 

533 

534 """ 

535 if not self.is_orm_statement: 

536 return [] 

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

538 result = [] 

539 seen = set() 

540 for d in self.statement.column_descriptions: 

541 ent = d["entity"] 

542 if ent: 

543 insp = inspect(ent, raiseerr=False) 

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

545 seen.add(insp.mapper) 

546 result.append(insp.mapper) 

547 return result 

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

549 return [self.bind_mapper] 

550 else: 

551 return [] 

552 

553 @property 

554 def is_orm_statement(self) -> bool: 

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

556 

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

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

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

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

561 and no ORM-level automation takes place. 

562 

563 """ 

564 return self._compile_state_cls is not None 

565 

566 @property 

567 def is_executemany(self) -> bool: 

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

569 dictionaries with more than one dictionary. 

570 

571 .. versionadded:: 2.0 

572 

573 """ 

574 return isinstance(self.parameters, list) 

575 

576 @property 

577 def is_select(self) -> bool: 

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

579 

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

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

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

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

584 

585 """ 

586 return self.statement.is_select 

587 

588 @property 

589 def is_from_statement(self) -> bool: 

590 """return True if this operation is a 

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

592 

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

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

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

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

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

598 :class:`_sql.Select` construct. 

599 

600 .. versionadded:: 2.0.30 

601 

602 """ 

603 return self.statement.is_from_statement 

604 

605 @property 

606 def is_insert(self) -> bool: 

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

608 

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

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

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

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

613 

614 """ 

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

616 

617 @property 

618 def is_update(self) -> bool: 

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

620 

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

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

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

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

625 

626 """ 

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

628 

629 @property 

630 def is_delete(self) -> bool: 

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

632 

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

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

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

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

637 

638 """ 

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

640 

641 @property 

642 def _is_crud(self) -> bool: 

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

644 

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

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

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

648 

649 def _orm_compile_options( 

650 self, 

651 ) -> Optional[ 

652 Union[ 

653 context._ORMCompileState.default_compile_options, 

654 Type[context._ORMCompileState.default_compile_options], 

655 ] 

656 ]: 

657 if not self.is_select: 

658 return None 

659 try: 

660 opts = self.statement._compile_options 

661 except AttributeError: 

662 return None 

663 

664 if opts is not None and opts.isinstance( 

665 context._ORMCompileState.default_compile_options 

666 ): 

667 return opts # type: ignore 

668 else: 

669 return None 

670 

671 @property 

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

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

674 for a lazy load operation. 

675 

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

677 sharding extension, where it is available within specific query 

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

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

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

681 compilation time. 

682 

683 """ 

684 return self.load_options._lazy_loaded_from 

685 

686 @property 

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

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

689 

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

691 when a particular object or collection is being loaded. 

692 

693 """ 

694 opts = self._orm_compile_options() 

695 if opts is not None: 

696 return opts._current_path 

697 else: 

698 return None 

699 

700 @property 

701 def is_column_load(self) -> bool: 

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

703 attributes on an existing ORM object. 

704 

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

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

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

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

709 loaded. 

710 

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

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

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

714 and loader options travelling with the instance 

715 will have already been added to the query. 

716 

717 .. versionadded:: 1.4.0b2 

718 

719 .. seealso:: 

720 

721 :attr:`_orm.ORMExecuteState.is_relationship_load` 

722 

723 """ 

724 opts = self._orm_compile_options() 

725 return opts is not None and opts._for_refresh_state 

726 

727 @property 

728 def is_relationship_load(self) -> bool: 

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

730 relationship. 

731 

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

733 SelectInLoader, SubqueryLoader, or similar, and the entire 

734 SELECT statement being emitted is on behalf of a relationship 

735 load. 

736 

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

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

739 capable of being propagated to relationship loaders and should 

740 be already present. 

741 

742 .. seealso:: 

743 

744 :attr:`_orm.ORMExecuteState.is_column_load` 

745 

746 """ 

747 opts = self._orm_compile_options() 

748 if opts is None: 

749 return False 

750 path = self.loader_strategy_path 

751 return path is not None and not path.is_root 

752 

753 @property 

754 def load_options( 

755 self, 

756 ) -> Union[ 

757 context.QueryContext.default_load_options, 

758 Type[context.QueryContext.default_load_options], 

759 ]: 

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

761 

762 if not self.is_select: 

763 raise sa_exc.InvalidRequestError( 

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

765 "so there are no load options." 

766 ) 

767 

768 lo: Union[ 

769 context.QueryContext.default_load_options, 

770 Type[context.QueryContext.default_load_options], 

771 ] = self.execution_options.get( 

772 "_sa_orm_load_options", context.QueryContext.default_load_options 

773 ) 

774 return lo 

775 

776 @property 

777 def update_delete_options( 

778 self, 

779 ) -> Union[ 

780 bulk_persistence._BulkUDCompileState.default_update_options, 

781 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

782 ]: 

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

784 execution.""" 

785 

786 if not self._is_crud: 

787 raise sa_exc.InvalidRequestError( 

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

789 "statement so there are no update options." 

790 ) 

791 uo: Union[ 

792 bulk_persistence._BulkUDCompileState.default_update_options, 

793 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

794 ] = self.execution_options.get( 

795 "_sa_orm_update_options", 

796 bulk_persistence._BulkUDCompileState.default_update_options, 

797 ) 

798 return uo 

799 

800 @property 

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

802 return [ 

803 opt 

804 for opt in self.statement._with_options 

805 if is_orm_option(opt) and not opt._is_compile_state 

806 ] 

807 

808 @property 

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

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

811 associated with the statement being invoked. 

812 

813 """ 

814 return [ 

815 opt 

816 for opt in self.statement._with_options 

817 if is_user_defined_option(opt) 

818 ] 

819 

820 

821class SessionTransactionOrigin(Enum): 

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

823 

824 This enumeration is present on the 

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

826 :class:`.SessionTransaction` object. 

827 

828 .. versionadded:: 2.0 

829 

830 """ 

831 

832 AUTOBEGIN = 0 

833 """transaction were started by autobegin""" 

834 

835 BEGIN = 1 

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

837 

838 BEGIN_NESTED = 2 

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

840 

841 SUBTRANSACTION = 3 

842 """transaction is an internal "subtransaction" """ 

843 

844 

845class SessionTransaction(_StateChange, TransactionalContext): 

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

847 

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

849 :meth:`_orm.Session.begin` 

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

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

852 transactions. 

853 

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

855 at: :ref:`unitofwork_transaction`. 

856 

857 

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

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

860 

861 .. seealso:: 

862 

863 :ref:`unitofwork_transaction` 

864 

865 :meth:`.Session.begin` 

866 

867 :meth:`.Session.begin_nested` 

868 

869 :meth:`.Session.rollback` 

870 

871 :meth:`.Session.commit` 

872 

873 :meth:`.Session.in_transaction` 

874 

875 :meth:`.Session.in_nested_transaction` 

876 

877 :meth:`.Session.get_transaction` 

878 

879 :meth:`.Session.get_nested_transaction` 

880 

881 

882 """ 

883 

884 _rollback_exception: Optional[BaseException] = None 

885 

886 _connections: Dict[ 

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

888 ] 

889 session: Session 

890 _parent: Optional[SessionTransaction] 

891 

892 _state: SessionTransactionState 

893 

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

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

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

897 _key_switches: weakref.WeakKeyDictionary[ 

898 InstanceState[Any], Tuple[Any, Any] 

899 ] 

900 

901 origin: SessionTransactionOrigin 

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

903 

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

905 enumeration indicating the source event that led to constructing 

906 this :class:`_orm.SessionTransaction`. 

907 

908 .. versionadded:: 2.0 

909 

910 """ 

911 

912 nested: bool = False 

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

914 

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

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

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

918 

919 .. seealso:: 

920 

921 :attr:`.SessionTransaction.origin` 

922 

923 """ 

924 

925 def __init__( 

926 self, 

927 session: Session, 

928 origin: SessionTransactionOrigin, 

929 parent: Optional[SessionTransaction] = None, 

930 ): 

931 TransactionalContext._trans_ctx_check(session) 

932 

933 self.session = session 

934 self._connections = {} 

935 self._parent = parent 

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

937 self.origin = origin 

938 

939 if session._close_state is _SessionCloseState.CLOSED: 

940 raise sa_exc.InvalidRequestError( 

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

942 "to handle any more transaction requests." 

943 ) 

944 

945 if nested: 

946 if not parent: 

947 raise sa_exc.InvalidRequestError( 

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

949 "transaction is in progress" 

950 ) 

951 

952 self._previous_nested_transaction = session._nested_transaction 

953 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

954 assert parent is not None 

955 else: 

956 assert parent is None 

957 

958 self._state = SessionTransactionState.ACTIVE 

959 

960 self._take_snapshot() 

961 

962 # make sure transaction is assigned before we call the 

963 # dispatch 

964 self.session._transaction = self 

965 

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

967 

968 def _raise_for_prerequisite_state( 

969 self, operation_name: str, state: _StateChangeState 

970 ) -> NoReturn: 

971 if state is SessionTransactionState.DEACTIVE: 

972 if self._rollback_exception: 

973 raise sa_exc.PendingRollbackError( 

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

975 "due to a previous exception during flush." 

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

977 "first issue Session.rollback()." 

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

979 code="7s2a", 

980 ) 

981 else: 

982 raise sa_exc.InvalidRequestError( 

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

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

985 "can be emitted within this transaction." 

986 ) 

987 elif state is SessionTransactionState.CLOSED: 

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

989 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

990 raise sa_exc.InvalidRequestError( 

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

992 "operations are not permitted", 

993 code="isce", 

994 ) 

995 else: 

996 raise sa_exc.InvalidRequestError( 

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

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

999 ) 

1000 

1001 @property 

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

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

1004 :class:`.SessionTransaction`. 

1005 

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

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

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

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

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

1011 "nested" / SAVEPOINT transaction. If the 

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

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

1014 

1015 """ 

1016 return self._parent 

1017 

1018 @property 

1019 def is_active(self) -> bool: 

1020 return ( 

1021 self.session is not None 

1022 and self._state is SessionTransactionState.ACTIVE 

1023 ) 

1024 

1025 @property 

1026 def _is_transaction_boundary(self) -> bool: 

1027 return self.nested or not self._parent 

1028 

1029 @_StateChange.declare_states( 

1030 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1031 ) 

1032 def connection( 

1033 self, 

1034 bindkey: Optional[Mapper[Any]], 

1035 execution_options: Optional[_ExecuteOptions] = None, 

1036 **kwargs: Any, 

1037 ) -> Connection: 

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

1039 return self._connection_for_bind(bind, execution_options) 

1040 

1041 @_StateChange.declare_states( 

1042 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1043 ) 

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

1045 return SessionTransaction( 

1046 self.session, 

1047 ( 

1048 SessionTransactionOrigin.BEGIN_NESTED 

1049 if nested 

1050 else SessionTransactionOrigin.SUBTRANSACTION 

1051 ), 

1052 self, 

1053 ) 

1054 

1055 def _iterate_self_and_parents( 

1056 self, upto: Optional[SessionTransaction] = None 

1057 ) -> Iterable[SessionTransaction]: 

1058 current = self 

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

1060 while current: 

1061 result += (current,) 

1062 if current._parent is upto: 

1063 break 

1064 elif current._parent is None: 

1065 raise sa_exc.InvalidRequestError( 

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

1067 % (upto) 

1068 ) 

1069 else: 

1070 current = current._parent 

1071 

1072 return result 

1073 

1074 def _take_snapshot(self) -> None: 

1075 if not self._is_transaction_boundary: 

1076 parent = self._parent 

1077 assert parent is not None 

1078 self._new = parent._new 

1079 self._deleted = parent._deleted 

1080 self._dirty = parent._dirty 

1081 self._key_switches = parent._key_switches 

1082 return 

1083 

1084 is_begin = self.origin in ( 

1085 SessionTransactionOrigin.BEGIN, 

1086 SessionTransactionOrigin.AUTOBEGIN, 

1087 ) 

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

1089 self.session.flush() 

1090 

1091 self._new = weakref.WeakKeyDictionary() 

1092 self._deleted = weakref.WeakKeyDictionary() 

1093 self._dirty = weakref.WeakKeyDictionary() 

1094 self._key_switches = weakref.WeakKeyDictionary() 

1095 

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

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

1098 

1099 Corresponds to a rollback. 

1100 

1101 """ 

1102 assert self._is_transaction_boundary 

1103 

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

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

1106 

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

1108 # we probably can do this conditionally based on 

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

1110 self.session.identity_map.safe_discard(s) 

1111 

1112 # restore the old key 

1113 s.key = oldkey 

1114 

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

1116 if s not in to_expunge: 

1117 self.session.identity_map.replace(s) 

1118 

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

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

1121 

1122 assert not self.session._deleted 

1123 

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

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

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

1127 

1128 def _remove_snapshot(self) -> None: 

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

1130 

1131 Corresponds to a commit. 

1132 

1133 """ 

1134 assert self._is_transaction_boundary 

1135 

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

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

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

1139 

1140 statelib.InstanceState._detach_states( 

1141 list(self._deleted), self.session 

1142 ) 

1143 self._deleted.clear() 

1144 elif self.nested: 

1145 parent = self._parent 

1146 assert parent is not None 

1147 parent._new.update(self._new) 

1148 parent._dirty.update(self._dirty) 

1149 parent._deleted.update(self._deleted) 

1150 parent._key_switches.update(self._key_switches) 

1151 

1152 @_StateChange.declare_states( 

1153 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1154 ) 

1155 def _connection_for_bind( 

1156 self, 

1157 bind: _SessionBind, 

1158 execution_options: Optional[CoreExecuteOptionsParameter], 

1159 ) -> Connection: 

1160 if bind in self._connections: 

1161 if execution_options: 

1162 util.warn( 

1163 "Connection is already established for the " 

1164 "given bind; execution_options ignored" 

1165 ) 

1166 return self._connections[bind][0] 

1167 

1168 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1169 

1170 local_connect = False 

1171 should_commit = True 

1172 

1173 try: 

1174 if self._parent: 

1175 conn = self._parent._connection_for_bind( 

1176 bind, execution_options 

1177 ) 

1178 if not self.nested: 

1179 return conn 

1180 else: 

1181 if isinstance(bind, engine.Connection): 

1182 conn = bind 

1183 if conn.engine in self._connections: 

1184 raise sa_exc.InvalidRequestError( 

1185 "Session already has a Connection associated " 

1186 "for the given Connection's Engine" 

1187 ) 

1188 else: 

1189 conn = bind.connect() 

1190 local_connect = True 

1191 

1192 try: 

1193 if execution_options: 

1194 conn = conn.execution_options(**execution_options) 

1195 

1196 transaction: Transaction 

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

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

1199 # conn.in_transaction() ? 

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

1201 # that it is in fact twophase. 

1202 transaction = conn.begin_twophase() 

1203 elif self.nested: 

1204 transaction = conn.begin_nested() 

1205 elif conn.in_transaction(): 

1206 

1207 if local_connect: 

1208 _trans = conn.get_transaction() 

1209 assert _trans is not None 

1210 transaction = _trans 

1211 else: 

1212 join_transaction_mode = ( 

1213 self.session.join_transaction_mode 

1214 ) 

1215 

1216 if join_transaction_mode == "conditional_savepoint": 

1217 if conn.in_nested_transaction(): 

1218 join_transaction_mode = "create_savepoint" 

1219 else: 

1220 join_transaction_mode = "rollback_only" 

1221 

1222 if join_transaction_mode in ( 

1223 "control_fully", 

1224 "rollback_only", 

1225 ): 

1226 if conn.in_nested_transaction(): 

1227 transaction = ( 

1228 conn._get_required_nested_transaction() 

1229 ) 

1230 else: 

1231 transaction = conn._get_required_transaction() 

1232 if join_transaction_mode == "rollback_only": 

1233 should_commit = False 

1234 elif join_transaction_mode == "create_savepoint": 

1235 transaction = conn.begin_nested() 

1236 else: 

1237 assert False, join_transaction_mode 

1238 else: 

1239 transaction = conn.begin() 

1240 except: 

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

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

1243 if local_connect: 

1244 conn.close() 

1245 raise 

1246 else: 

1247 bind_is_connection = isinstance(bind, engine.Connection) 

1248 

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

1250 conn, 

1251 transaction, 

1252 should_commit, 

1253 not bind_is_connection, 

1254 ) 

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

1256 return conn 

1257 finally: 

1258 self._state = SessionTransactionState.ACTIVE 

1259 

1260 def prepare(self) -> None: 

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

1262 raise sa_exc.InvalidRequestError( 

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

1264 "can't prepare." 

1265 ) 

1266 self._prepare_impl() 

1267 

1268 @_StateChange.declare_states( 

1269 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1270 ) 

1271 def _prepare_impl(self) -> None: 

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

1273 self.session.dispatch.before_commit(self.session) 

1274 

1275 stx = self.session._transaction 

1276 assert stx is not None 

1277 if stx is not self: 

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

1279 subtransaction.commit() 

1280 

1281 if not self.session._flushing: 

1282 for _flush_guard in range(100): 

1283 if self.session._is_clean(): 

1284 break 

1285 self.session.flush() 

1286 else: 

1287 raise exc.FlushError( 

1288 "Over 100 subsequent flushes have occurred within " 

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

1290 "creating new objects?" 

1291 ) 

1292 

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

1294 try: 

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

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

1297 except: 

1298 with util.safe_reraise(): 

1299 self.rollback() 

1300 

1301 self._state = SessionTransactionState.PREPARED 

1302 

1303 @_StateChange.declare_states( 

1304 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1305 SessionTransactionState.CLOSED, 

1306 ) 

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

1308 if self._state is not SessionTransactionState.PREPARED: 

1309 with self._expect_state(SessionTransactionState.PREPARED): 

1310 self._prepare_impl() 

1311 

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

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

1314 self._connections.values() 

1315 ): 

1316 if should_commit: 

1317 trans.commit() 

1318 

1319 self._state = SessionTransactionState.COMMITTED 

1320 self.session.dispatch.after_commit(self.session) 

1321 

1322 self._remove_snapshot() 

1323 

1324 with self._expect_state(SessionTransactionState.CLOSED): 

1325 self.close() 

1326 

1327 if _to_root and self._parent: 

1328 self._parent.commit(_to_root=True) 

1329 

1330 @_StateChange.declare_states( 

1331 ( 

1332 SessionTransactionState.ACTIVE, 

1333 SessionTransactionState.DEACTIVE, 

1334 SessionTransactionState.PREPARED, 

1335 ), 

1336 SessionTransactionState.CLOSED, 

1337 ) 

1338 def rollback( 

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

1340 ) -> None: 

1341 stx = self.session._transaction 

1342 assert stx is not None 

1343 if stx is not self: 

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

1345 subtransaction.close() 

1346 

1347 boundary = self 

1348 rollback_err = None 

1349 if self._state in ( 

1350 SessionTransactionState.ACTIVE, 

1351 SessionTransactionState.PREPARED, 

1352 ): 

1353 for transaction in self._iterate_self_and_parents(): 

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

1355 try: 

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

1357 t[1].rollback() 

1358 

1359 transaction._state = SessionTransactionState.DEACTIVE 

1360 self.session.dispatch.after_rollback(self.session) 

1361 except: 

1362 rollback_err = sys.exc_info() 

1363 finally: 

1364 transaction._state = SessionTransactionState.DEACTIVE 

1365 transaction._restore_snapshot( 

1366 dirty_only=transaction.nested 

1367 ) 

1368 boundary = transaction 

1369 break 

1370 else: 

1371 transaction._state = SessionTransactionState.DEACTIVE 

1372 

1373 sess = self.session 

1374 

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

1376 # if items were added, deleted, or mutated 

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

1378 util.warn( 

1379 "Session's state has been changed on " 

1380 "a non-active transaction - this state " 

1381 "will be discarded." 

1382 ) 

1383 boundary._restore_snapshot(dirty_only=boundary.nested) 

1384 

1385 with self._expect_state(SessionTransactionState.CLOSED): 

1386 self.close() 

1387 

1388 if self._parent and _capture_exception: 

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

1390 

1391 if rollback_err and rollback_err[1]: 

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

1393 

1394 sess.dispatch.after_soft_rollback(sess, self) 

1395 

1396 if _to_root and self._parent: 

1397 self._parent.rollback(_to_root=True) 

1398 

1399 @_StateChange.declare_states( 

1400 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1401 ) 

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

1403 if self.nested: 

1404 self.session._nested_transaction = ( 

1405 self._previous_nested_transaction 

1406 ) 

1407 

1408 self.session._transaction = self._parent 

1409 

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

1411 self._connections.values() 

1412 ): 

1413 if invalidate and self._parent is None: 

1414 connection.invalidate() 

1415 if should_commit and transaction.is_active: 

1416 transaction.close() 

1417 if autoclose and self._parent is None: 

1418 connection.close() 

1419 

1420 self._state = SessionTransactionState.CLOSED 

1421 sess = self.session 

1422 

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

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

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

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

1427 # passes with these commented out. 

1428 # self.session = None # type: ignore 

1429 # self._connections = None # type: ignore 

1430 

1431 sess.dispatch.after_transaction_end(sess, self) 

1432 

1433 def _get_subject(self) -> Session: 

1434 return self.session 

1435 

1436 def _transaction_is_active(self) -> bool: 

1437 return self._state is SessionTransactionState.ACTIVE 

1438 

1439 def _transaction_is_closed(self) -> bool: 

1440 return self._state is SessionTransactionState.CLOSED 

1441 

1442 def _rollback_can_be_called(self) -> bool: 

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

1444 

1445 

1446class _SessionCloseState(Enum): 

1447 ACTIVE = 1 

1448 CLOSED = 2 

1449 CLOSE_IS_RESET = 3 

1450 

1451 

1452class Session(_SessionClassMethods, EventTarget): 

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

1454 

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

1456 See :ref:`session_faq_threadsafe` for background. 

1457 

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

1459 

1460 

1461 """ 

1462 

1463 _is_asyncio = False 

1464 

1465 dispatch: dispatcher[Session] 

1466 

1467 identity_map: IdentityMap 

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

1469 

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

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

1472 that have row identity) currently in the session. 

1473 

1474 .. seealso:: 

1475 

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

1477 in this dictionary. 

1478 

1479 """ 

1480 

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

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

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

1484 __binds: Dict[_SessionBindKey, _SessionBind] 

1485 _flushing: bool 

1486 _warn_on_events: bool 

1487 _transaction: Optional[SessionTransaction] 

1488 _nested_transaction: Optional[SessionTransaction] 

1489 hash_key: int 

1490 autoflush: bool 

1491 expire_on_commit: bool 

1492 enable_baked_queries: bool 

1493 twophase: bool 

1494 join_transaction_mode: JoinTransactionMode 

1495 execution_options: _ExecuteOptions = util.EMPTY_DICT 

1496 _query_cls: Type[Query[Any]] 

1497 _close_state: _SessionCloseState 

1498 

1499 def __init__( 

1500 self, 

1501 bind: Optional[_SessionBind] = None, 

1502 *, 

1503 autoflush: bool = True, 

1504 future: Literal[True] = True, 

1505 expire_on_commit: bool = True, 

1506 autobegin: bool = True, 

1507 twophase: bool = False, 

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

1509 enable_baked_queries: bool = True, 

1510 info: Optional[_InfoType] = None, 

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

1512 autocommit: Literal[False] = False, 

1513 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1515 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

1516 ): 

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

1518 

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

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

1521 set of arguments. 

1522 

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

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

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

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

1527 results. 

1528 

1529 .. seealso:: 

1530 

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

1532 

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

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

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

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

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

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

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

1540 

1541 .. versionadded:: 2.0 

1542 

1543 .. seealso:: 

1544 

1545 :ref:`session_autobegin_disable` 

1546 

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

1548 :class:`_engine.Connection` to 

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

1550 operations performed by this session will execute via this 

1551 connectable. 

1552 

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

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

1555 objects as the source of 

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

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

1558 arbitrary Python classes that are bases for mapped classes, 

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

1560 The 

1561 values of the dictionary are then instances of 

1562 :class:`_engine.Engine` 

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

1564 Operations which 

1565 proceed relative to a particular mapped class will consult this 

1566 dictionary for the closest matching entity in order to determine 

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

1568 operation. The complete heuristics for resolution are 

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

1570 

1571 Session = sessionmaker( 

1572 binds={ 

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

1574 SomeDeclarativeBase: create_engine( 

1575 "postgresql+psycopg2://engine2" 

1576 ), 

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

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

1579 } 

1580 ) 

1581 

1582 .. seealso:: 

1583 

1584 :ref:`session_partitioning` 

1585 

1586 :meth:`.Session.bind_mapper` 

1587 

1588 :meth:`.Session.bind_table` 

1589 

1590 :meth:`.Session.get_bind` 

1591 

1592 

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

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

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

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

1597 constructor for ``Session``. 

1598 

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

1600 A parameter consumed 

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

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

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

1604 this particular extension is disabled. 

1605 

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

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

1608 flag therefore only affects applications that are making explicit 

1609 use of this extension within their own code. 

1610 

1611 :param execution_options: optional dictionary of execution options 

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

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

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

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

1616 the session-wide options. 

1617 

1618 .. versionadded:: 2.1 

1619 

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

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

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

1623 transaction will load from the most recent database state. 

1624 

1625 .. seealso:: 

1626 

1627 :ref:`session_committing` 

1628 

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

1630 

1631 .. seealso:: 

1632 

1633 :ref:`migration_20_toplevel` 

1634 

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

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

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

1638 construction time so that modifications to the per- 

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

1640 :class:`.Session`. 

1641 

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

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

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

1645 

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

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

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

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

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

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

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

1653 transaction, before each transaction is committed. 

1654 

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

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

1657 

1658 :param join_transaction_mode: Describes the transactional behavior to 

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

1660 has already begun a transaction outside the scope of this 

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

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

1663 

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

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

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

1667 etc. are actually invoked: 

1668 

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

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

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

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

1673 a SAVEPOINT, in other words 

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

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

1676 

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

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

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

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

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

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

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

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

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

1686 

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

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

1689 its own transaction. This transaction by its nature rides 

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

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

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

1693 external transaction will remain unaffected throughout the 

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

1695 

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

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

1698 initiated transaction should remain unaffected; however, it relies 

1699 on proper SAVEPOINT support from the underlying driver and 

1700 database. 

1701 

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

1703 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1704 without workarounds. See the sections 

1705 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1706 for details on current workarounds. 

1707 

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

1709 control of the given transaction as its own; 

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

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

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

1713 call ``.rollback`` on the transaction. 

1714 

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

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

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

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

1719 SAVEPOINT. 

1720 

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

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

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

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

1725 given transaction. 

1726 

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

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

1729 regular database transaction (i.e. 

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

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

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

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

1734 

1735 .. versionadded:: 2.0.0rc1 

1736 

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

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

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

1740 

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

1742 A future SQLAlchemy version may change the default value of 

1743 this flag to ``False``. 

1744 

1745 .. seealso:: 

1746 

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

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

1749 

1750 """ # noqa 

1751 

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

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

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

1755 # of cases including in our own test suite 

1756 if autocommit: 

1757 raise sa_exc.ArgumentError( 

1758 "autocommit=True is no longer supported" 

1759 ) 

1760 self.identity_map = identity._WeakInstanceDict() 

1761 

1762 if not future: 

1763 raise sa_exc.ArgumentError( 

1764 "The 'future' parameter passed to " 

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

1766 ) 

1767 

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

1769 self._deleted = {} # same 

1770 self.bind = bind 

1771 self.__binds = {} 

1772 self._flushing = False 

1773 self._warn_on_events = False 

1774 self._transaction = None 

1775 self._nested_transaction = None 

1776 self.hash_key = _new_sessionid() 

1777 self.autobegin = autobegin 

1778 self.autoflush = autoflush 

1779 self.expire_on_commit = expire_on_commit 

1780 self.enable_baked_queries = enable_baked_queries 

1781 if execution_options: 

1782 self.execution_options = self.execution_options.union( 

1783 execution_options 

1784 ) 

1785 

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

1787 # the default will switch to close_resets_only=False. 

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

1789 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1790 else: 

1791 self._close_state = _SessionCloseState.ACTIVE 

1792 if ( 

1793 join_transaction_mode 

1794 and join_transaction_mode 

1795 not in JoinTransactionMode.__args__ # type: ignore 

1796 ): 

1797 raise sa_exc.ArgumentError( 

1798 f"invalid selection for join_transaction_mode: " 

1799 f'"{join_transaction_mode}"' 

1800 ) 

1801 self.join_transaction_mode = join_transaction_mode 

1802 

1803 self.twophase = twophase 

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

1805 if info: 

1806 self.info.update(info) 

1807 

1808 if binds is not None: 

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

1810 self._add_bind(key, bind) 

1811 

1812 _sessions[self.hash_key] = self 

1813 

1814 # used by sqlalchemy.engine.util.TransactionalContext 

1815 _trans_context_manager: Optional[TransactionalContext] = None 

1816 

1817 connection_callable: Optional[_ConnectionCallableProto] = None 

1818 

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

1820 return self 

1821 

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

1823 self.close() 

1824 

1825 @contextlib.contextmanager 

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

1827 with self: 

1828 with self.begin(): 

1829 yield self 

1830 

1831 def in_transaction(self) -> bool: 

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

1833 

1834 .. versionadded:: 1.4 

1835 

1836 .. seealso:: 

1837 

1838 :attr:`_orm.Session.is_active` 

1839 

1840 

1841 """ 

1842 return self._transaction is not None 

1843 

1844 def in_nested_transaction(self) -> bool: 

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

1846 transaction, e.g. SAVEPOINT. 

1847 

1848 .. versionadded:: 1.4 

1849 

1850 """ 

1851 return self._nested_transaction is not None 

1852 

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

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

1855 

1856 .. versionadded:: 1.4 

1857 

1858 """ 

1859 trans = self._transaction 

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

1861 trans = trans._parent 

1862 return trans 

1863 

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

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

1866 

1867 .. versionadded:: 1.4 

1868 

1869 """ 

1870 

1871 return self._nested_transaction 

1872 

1873 @util.memoized_property 

1874 def info(self) -> _InfoType: 

1875 """A user-modifiable dictionary. 

1876 

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

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

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

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

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

1882 

1883 """ 

1884 return {} 

1885 

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

1887 if self._transaction is None: 

1888 if not begin and not self.autobegin: 

1889 raise sa_exc.InvalidRequestError( 

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

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

1892 ) 

1893 trans = SessionTransaction( 

1894 self, 

1895 ( 

1896 SessionTransactionOrigin.BEGIN 

1897 if begin 

1898 else SessionTransactionOrigin.AUTOBEGIN 

1899 ), 

1900 ) 

1901 assert self._transaction is trans 

1902 return trans 

1903 

1904 return self._transaction 

1905 

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

1907 """Begin a transaction, or nested transaction, 

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

1909 

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

1911 so that normally it is not necessary to call the 

1912 :meth:`_orm.Session.begin` 

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

1914 the scope of when the transactional state is begun. 

1915 

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

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

1918 

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

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

1921 documentation on SAVEPOINT transactions, please see 

1922 :ref:`session_begin_nested`. 

1923 

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

1925 :class:`.SessionTransaction` 

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

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

1928 an example. 

1929 

1930 .. seealso:: 

1931 

1932 :ref:`session_autobegin` 

1933 

1934 :ref:`unitofwork_transaction` 

1935 

1936 :meth:`.Session.begin_nested` 

1937 

1938 

1939 """ 

1940 

1941 trans = self._transaction 

1942 if trans is None: 

1943 trans = self._autobegin_t(begin=True) 

1944 

1945 if not nested: 

1946 return trans 

1947 

1948 assert trans is not None 

1949 

1950 if nested: 

1951 trans = trans._begin(nested=nested) 

1952 assert self._transaction is trans 

1953 self._nested_transaction = trans 

1954 else: 

1955 raise sa_exc.InvalidRequestError( 

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

1957 ) 

1958 

1959 return trans # needed for __enter__/__exit__ hook 

1960 

1961 def begin_nested(self) -> SessionTransaction: 

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

1963 

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

1965 SAVEPOINT for this method to function correctly. 

1966 

1967 For documentation on SAVEPOINT 

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

1969 

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

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

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

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

1974 

1975 .. seealso:: 

1976 

1977 :ref:`session_begin_nested` 

1978 

1979 :ref:`pysqlite_serializable` - special workarounds required 

1980 with the SQLite driver in order for SAVEPOINT to work 

1981 correctly. For asyncio use cases, see the section 

1982 :ref:`aiosqlite_serializable`. 

1983 

1984 """ 

1985 return self.begin(nested=True) 

1986 

1987 def rollback(self) -> None: 

1988 """Rollback the current transaction in progress. 

1989 

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

1991 

1992 The method always rolls back 

1993 the topmost database transaction, discarding any nested 

1994 transactions that may be in progress. 

1995 

1996 .. seealso:: 

1997 

1998 :ref:`session_rollback` 

1999 

2000 :ref:`unitofwork_transaction` 

2001 

2002 """ 

2003 if self._transaction is None: 

2004 pass 

2005 else: 

2006 self._transaction.rollback(_to_root=True) 

2007 

2008 def commit(self) -> None: 

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

2010 

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

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

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

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

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

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

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

2018 to disable this behavior. 

2019 

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

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

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

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

2024 normally affect the database unless pending flush changes were 

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

2026 rules. 

2027 

2028 The outermost database transaction is committed unconditionally, 

2029 automatically releasing any SAVEPOINTs in effect. 

2030 

2031 .. seealso:: 

2032 

2033 :ref:`session_committing` 

2034 

2035 :ref:`unitofwork_transaction` 

2036 

2037 :ref:`asyncio_orm_avoid_lazyloads` 

2038 

2039 """ 

2040 trans = self._transaction 

2041 if trans is None: 

2042 trans = self._autobegin_t() 

2043 

2044 trans.commit(_to_root=True) 

2045 

2046 def prepare(self) -> None: 

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

2048 

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

2050 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2051 

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

2053 current transaction is not such, an 

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

2055 

2056 """ 

2057 trans = self._transaction 

2058 if trans is None: 

2059 trans = self._autobegin_t() 

2060 

2061 trans.prepare() 

2062 

2063 def connection( 

2064 self, 

2065 bind_arguments: Optional[_BindArguments] = None, 

2066 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2067 ) -> Connection: 

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

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

2070 

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

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

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

2074 returned (note that no 

2075 transactional state is established with the DBAPI until the first 

2076 SQL statement is emitted). 

2077 

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

2079 resolved through any of the optional keyword arguments. This 

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

2081 

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

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

2084 to :meth:`.Session.get_bind`. 

2085 

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

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

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

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

2090 the arguments are ignored. 

2091 

2092 .. seealso:: 

2093 

2094 :ref:`session_transaction_isolation` 

2095 

2096 """ 

2097 

2098 if bind_arguments: 

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

2100 

2101 if bind is None: 

2102 bind = self.get_bind(**bind_arguments) 

2103 else: 

2104 bind = self.get_bind() 

2105 

2106 return self._connection_for_bind( 

2107 bind, 

2108 execution_options=execution_options, 

2109 ) 

2110 

2111 def _connection_for_bind( 

2112 self, 

2113 engine: _SessionBind, 

2114 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2115 **kw: Any, 

2116 ) -> Connection: 

2117 TransactionalContext._trans_ctx_check(self) 

2118 

2119 trans = self._transaction 

2120 if trans is None: 

2121 trans = self._autobegin_t() 

2122 return trans._connection_for_bind(engine, execution_options) 

2123 

2124 @overload 

2125 def _execute_internal( 

2126 self, 

2127 statement: Executable, 

2128 params: Optional[_CoreSingleExecuteParams] = None, 

2129 *, 

2130 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2131 bind_arguments: Optional[_BindArguments] = None, 

2132 _parent_execute_state: Optional[Any] = None, 

2133 _add_event: Optional[Any] = None, 

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

2135 ) -> Any: ... 

2136 

2137 @overload 

2138 def _execute_internal( 

2139 self, 

2140 statement: Executable, 

2141 params: Optional[_CoreAnyExecuteParams] = None, 

2142 *, 

2143 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2144 bind_arguments: Optional[_BindArguments] = None, 

2145 _parent_execute_state: Optional[Any] = None, 

2146 _add_event: Optional[Any] = None, 

2147 _scalar_result: bool = ..., 

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

2149 

2150 def _execute_internal( 

2151 self, 

2152 statement: Executable, 

2153 params: Optional[_CoreAnyExecuteParams] = None, 

2154 *, 

2155 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2156 bind_arguments: Optional[_BindArguments] = None, 

2157 _parent_execute_state: Optional[Any] = None, 

2158 _add_event: Optional[Any] = None, 

2159 _scalar_result: bool = False, 

2160 ) -> Any: 

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

2162 

2163 if not bind_arguments: 

2164 bind_arguments = {} 

2165 else: 

2166 bind_arguments = dict(bind_arguments) 

2167 

2168 if ( 

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

2170 == "orm" 

2171 ): 

2172 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2173 statement, "orm" 

2174 ) 

2175 if TYPE_CHECKING: 

2176 assert isinstance( 

2177 compile_state_cls, context._AbstractORMCompileState 

2178 ) 

2179 else: 

2180 compile_state_cls = None 

2181 bind_arguments.setdefault("clause", statement) 

2182 

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

2184 util.coerce_to_immutabledict(execution_options) 

2185 ) 

2186 if self.execution_options: 

2187 # merge given execution options with session-wide execution 

2188 # options. if the statement also has execution_options, 

2189 # maintain priority of session.execution_options -> 

2190 # statement.execution_options -> method passed execution_options 

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

2192 # will come from the statement 

2193 if statement._execution_options: 

2194 combined_execution_options = util.immutabledict( 

2195 { 

2196 k: v 

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

2198 if k not in statement._execution_options 

2199 } 

2200 ).union(combined_execution_options) 

2201 else: 

2202 combined_execution_options = self.execution_options.union( 

2203 combined_execution_options 

2204 ) 

2205 

2206 if _parent_execute_state: 

2207 events_todo = _parent_execute_state._remaining_events() 

2208 else: 

2209 events_todo = self.dispatch.do_orm_execute 

2210 if _add_event: 

2211 events_todo = list(events_todo) + [_add_event] 

2212 

2213 if events_todo: 

2214 if compile_state_cls is not None: 

2215 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2221 ( 

2222 statement, 

2223 combined_execution_options, 

2224 params, 

2225 ) = compile_state_cls.orm_pre_session_exec( 

2226 self, 

2227 statement, 

2228 params, 

2229 combined_execution_options, 

2230 bind_arguments, 

2231 True, 

2232 ) 

2233 

2234 orm_exec_state = ORMExecuteState( 

2235 self, 

2236 statement, 

2237 params, 

2238 combined_execution_options, 

2239 bind_arguments, 

2240 compile_state_cls, 

2241 events_todo, 

2242 ) 

2243 for idx, fn in enumerate(events_todo): 

2244 orm_exec_state._starting_event_idx = idx 

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

2246 orm_exec_state 

2247 ) 

2248 if fn_result: 

2249 if _scalar_result: 

2250 return fn_result.scalar() 

2251 else: 

2252 return fn_result 

2253 

2254 statement = orm_exec_state.statement 

2255 combined_execution_options = orm_exec_state.local_execution_options 

2256 params = orm_exec_state.parameters 

2257 

2258 if compile_state_cls is not None: 

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

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

2261 # new execution_options into load_options / update_delete_options, 

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

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

2264 ( 

2265 statement, 

2266 combined_execution_options, 

2267 params, 

2268 ) = compile_state_cls.orm_pre_session_exec( 

2269 self, 

2270 statement, 

2271 params, 

2272 combined_execution_options, 

2273 bind_arguments, 

2274 False, 

2275 ) 

2276 else: 

2277 # Issue #9809: unconditionally autoflush for Core statements 

2278 self._autoflush() 

2279 

2280 bind = self.get_bind(**bind_arguments) 

2281 

2282 conn = self._connection_for_bind(bind) 

2283 

2284 if _scalar_result and not compile_state_cls: 

2285 if TYPE_CHECKING: 

2286 params = cast(_CoreSingleExecuteParams, params) 

2287 return conn.scalar( 

2288 statement, 

2289 params or {}, 

2290 execution_options=combined_execution_options, 

2291 ) 

2292 

2293 if compile_state_cls: 

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

2295 compile_state_cls.orm_execute_statement( 

2296 self, 

2297 statement, 

2298 params or {}, 

2299 combined_execution_options, 

2300 bind_arguments, 

2301 conn, 

2302 ) 

2303 ) 

2304 else: 

2305 result = conn.execute( 

2306 statement, params, execution_options=combined_execution_options 

2307 ) 

2308 

2309 if _scalar_result: 

2310 return result.scalar() 

2311 else: 

2312 return result 

2313 

2314 @overload 

2315 def execute( 

2316 self, 

2317 statement: TypedReturnsRows[Unpack[_Ts]], 

2318 params: Optional[_CoreAnyExecuteParams] = None, 

2319 *, 

2320 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2321 bind_arguments: Optional[_BindArguments] = None, 

2322 _parent_execute_state: Optional[Any] = None, 

2323 _add_event: Optional[Any] = None, 

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

2325 

2326 @overload 

2327 def execute( 

2328 self, 

2329 statement: Executable, 

2330 params: Optional[_CoreAnyExecuteParams] = None, 

2331 *, 

2332 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2333 bind_arguments: Optional[_BindArguments] = None, 

2334 _parent_execute_state: Optional[Any] = None, 

2335 _add_event: Optional[Any] = None, 

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

2337 

2338 def execute( 

2339 self, 

2340 statement: Executable, 

2341 params: Optional[_CoreAnyExecuteParams] = None, 

2342 *, 

2343 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2344 bind_arguments: Optional[_BindArguments] = None, 

2345 _parent_execute_state: Optional[Any] = None, 

2346 _add_event: Optional[Any] = None, 

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

2348 r"""Execute a SQL expression construct. 

2349 

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

2351 results of the statement execution. 

2352 

2353 E.g.:: 

2354 

2355 from sqlalchemy import select 

2356 

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

2358 

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

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

2361 of :class:`_engine.Connection`. 

2362 

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

2364 now the primary point of ORM statement execution when using 

2365 :term:`2.0 style` ORM usage. 

2366 

2367 :param statement: 

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

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

2370 

2371 :param params: 

2372 Optional dictionary, or list of dictionaries, containing 

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

2374 execution occurs; if a list of dictionaries, an 

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

2376 must correspond to parameter names present in the statement. 

2377 

2378 :param execution_options: optional dictionary of execution options, 

2379 which will be associated with the statement execution. This 

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

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

2382 provide additional options understood only in an ORM context. 

2383 

2384 The execution_options are passed along to methods like 

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

2386 highest priority to execution_options that are passed to this 

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

2388 statement object if any, and finally those options present 

2389 session-wide. 

2390 

2391 .. seealso:: 

2392 

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

2394 options 

2395 

2396 :param bind_arguments: dictionary of additional arguments to determine 

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

2398 Contents of this dictionary are passed to the 

2399 :meth:`.Session.get_bind` method. 

2400 

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

2402 

2403 

2404 """ 

2405 return self._execute_internal( 

2406 statement, 

2407 params, 

2408 execution_options=execution_options, 

2409 bind_arguments=bind_arguments, 

2410 _parent_execute_state=_parent_execute_state, 

2411 _add_event=_add_event, 

2412 ) 

2413 

2414 # special case to handle mypy issue: 

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

2416 @overload 

2417 def scalar( 

2418 self, 

2419 statement: TypedReturnsRows[Never], 

2420 params: Optional[_CoreSingleExecuteParams] = None, 

2421 *, 

2422 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2423 bind_arguments: Optional[_BindArguments] = None, 

2424 **kw: Any, 

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

2426 

2427 @overload 

2428 def scalar( 

2429 self, 

2430 statement: TypedReturnsRows[_T], 

2431 params: Optional[_CoreSingleExecuteParams] = None, 

2432 *, 

2433 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2434 bind_arguments: Optional[_BindArguments] = None, 

2435 **kw: Any, 

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

2437 

2438 @overload 

2439 def scalar( 

2440 self, 

2441 statement: Executable, 

2442 params: Optional[_CoreSingleExecuteParams] = None, 

2443 *, 

2444 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2445 bind_arguments: Optional[_BindArguments] = None, 

2446 **kw: Any, 

2447 ) -> Any: ... 

2448 

2449 def scalar( 

2450 self, 

2451 statement: Executable, 

2452 params: Optional[_CoreSingleExecuteParams] = None, 

2453 *, 

2454 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2455 bind_arguments: Optional[_BindArguments] = None, 

2456 **kw: Any, 

2457 ) -> Any: 

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

2459 

2460 Usage and parameters are the same as that of 

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

2462 value. 

2463 

2464 """ 

2465 

2466 return self._execute_internal( 

2467 statement, 

2468 params, 

2469 execution_options=execution_options, 

2470 bind_arguments=bind_arguments, 

2471 _scalar_result=True, 

2472 **kw, 

2473 ) 

2474 

2475 @overload 

2476 def scalars( 

2477 self, 

2478 statement: TypedReturnsRows[_T], 

2479 params: Optional[_CoreAnyExecuteParams] = None, 

2480 *, 

2481 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2482 bind_arguments: Optional[_BindArguments] = None, 

2483 **kw: Any, 

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

2485 

2486 @overload 

2487 def scalars( 

2488 self, 

2489 statement: Executable, 

2490 params: Optional[_CoreAnyExecuteParams] = None, 

2491 *, 

2492 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2493 bind_arguments: Optional[_BindArguments] = None, 

2494 **kw: Any, 

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

2496 

2497 def scalars( 

2498 self, 

2499 statement: Executable, 

2500 params: Optional[_CoreAnyExecuteParams] = None, 

2501 *, 

2502 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2503 bind_arguments: Optional[_BindArguments] = None, 

2504 **kw: Any, 

2505 ) -> ScalarResult[Any]: 

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

2507 

2508 Usage and parameters are the same as that of 

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

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

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

2512 

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

2514 

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

2516 

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

2518 

2519 .. seealso:: 

2520 

2521 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2523 

2524 """ 

2525 

2526 return self._execute_internal( 

2527 statement, 

2528 params=params, 

2529 execution_options=execution_options, 

2530 bind_arguments=bind_arguments, 

2531 _scalar_result=False, # mypy appreciates this 

2532 **kw, 

2533 ).scalars() 

2534 

2535 def close(self) -> None: 

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

2537 :class:`_orm.Session`. 

2538 

2539 This expunges all ORM objects associated with this 

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

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

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

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

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

2545 

2546 .. tip:: 

2547 

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

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

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

2551 distinct "closed" state; it merely means 

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

2553 and ORM objects. 

2554 

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

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

2557 any further action on the session will be forbidden. 

2558 

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

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

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

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

2563 

2564 .. seealso:: 

2565 

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

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

2568 

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

2570 ``close()`` with the parameter 

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

2572 

2573 """ 

2574 self._close_impl(invalidate=False) 

2575 

2576 def reset(self) -> None: 

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

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

2579 

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

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

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

2583 brand new, and ready to be used again. 

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

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

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

2587 

2588 .. versionadded:: 2.0.22 

2589 

2590 .. seealso:: 

2591 

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

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

2594 

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

2596 prevent reuse of the Session when the parameter 

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

2598 """ 

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

2600 

2601 def invalidate(self) -> None: 

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

2603 

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

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

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

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

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

2609 multiple engines). 

2610 

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

2612 the connections are no longer safe to be used. 

2613 

2614 Below illustrates a scenario when using `gevent 

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

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

2617 

2618 import gevent 

2619 

2620 try: 

2621 sess = Session() 

2622 sess.add(User()) 

2623 sess.commit() 

2624 except gevent.Timeout: 

2625 sess.invalidate() 

2626 raise 

2627 except: 

2628 sess.rollback() 

2629 raise 

2630 

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

2632 does, including that all ORM objects are expunged. 

2633 

2634 """ 

2635 self._close_impl(invalidate=True) 

2636 

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

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

2639 self._close_state = _SessionCloseState.CLOSED 

2640 self.expunge_all() 

2641 if self._transaction is not None: 

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

2643 transaction.close(invalidate) 

2644 

2645 def expunge_all(self) -> None: 

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

2647 

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

2649 ``Session``. 

2650 

2651 """ 

2652 

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

2654 self.identity_map._kill() 

2655 self.identity_map = identity._WeakInstanceDict() 

2656 self._new = {} 

2657 self._deleted = {} 

2658 

2659 statelib.InstanceState._detach_states(all_states, self) 

2660 

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

2662 try: 

2663 insp = inspect(key) 

2664 except sa_exc.NoInspectionAvailable as err: 

2665 if not isinstance(key, type): 

2666 raise sa_exc.ArgumentError( 

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

2668 ) from err 

2669 else: 

2670 self.__binds[key] = bind 

2671 else: 

2672 if TYPE_CHECKING: 

2673 assert isinstance(insp, Inspectable) 

2674 

2675 if isinstance(insp, TableClause): 

2676 self.__binds[insp] = bind 

2677 elif insp_is_mapper(insp): 

2678 self.__binds[insp.class_] = bind 

2679 for _selectable in insp._all_tables: 

2680 self.__binds[_selectable] = bind 

2681 else: 

2682 raise sa_exc.ArgumentError( 

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

2684 ) 

2685 

2686 def bind_mapper( 

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

2688 ) -> None: 

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

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

2691 :class:`_engine.Connection`. 

2692 

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

2694 :meth:`.Session.get_bind` method. 

2695 

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

2697 or an instance of a mapped 

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

2699 classes. 

2700 

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

2702 object. 

2703 

2704 .. seealso:: 

2705 

2706 :ref:`session_partitioning` 

2707 

2708 :paramref:`.Session.binds` 

2709 

2710 :meth:`.Session.bind_table` 

2711 

2712 

2713 """ 

2714 self._add_bind(mapper, bind) 

2715 

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

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

2718 :class:`_engine.Engine` 

2719 or :class:`_engine.Connection`. 

2720 

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

2722 :meth:`.Session.get_bind` method. 

2723 

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

2725 which is typically the target 

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

2727 mapped. 

2728 

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

2730 object. 

2731 

2732 .. seealso:: 

2733 

2734 :ref:`session_partitioning` 

2735 

2736 :paramref:`.Session.binds` 

2737 

2738 :meth:`.Session.bind_mapper` 

2739 

2740 

2741 """ 

2742 self._add_bind(table, bind) 

2743 

2744 def get_bind( 

2745 self, 

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

2747 *, 

2748 clause: Optional[ClauseElement] = None, 

2749 bind: Optional[_SessionBind] = None, 

2750 _sa_skip_events: Optional[bool] = None, 

2751 _sa_skip_for_implicit_returning: bool = False, 

2752 **kw: Any, 

2753 ) -> Union[Engine, Connection]: 

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

2755 

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

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

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

2759 

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

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

2762 appropriate bind to return. 

2763 

2764 Note that the "mapper" argument is usually present 

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

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

2767 individual INSERT/UPDATE/DELETE operation within a 

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

2769 

2770 The order of resolution is: 

2771 

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

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

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

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

2776 superclasses to more general. 

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

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

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

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

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

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

2783 associated with the clause. 

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

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

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

2787 selectable to which the mapper is mapped. 

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

2789 is raised. 

2790 

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

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

2793 of bind resolution scheme. See the example at 

2794 :ref:`session_custom_partitioning`. 

2795 

2796 :param mapper: 

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

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

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

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

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

2802 mapped for a bind. 

2803 

2804 :param clause: 

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

2806 :func:`_expression.select`, 

2807 :func:`_expression.text`, 

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

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

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

2811 associated with 

2812 bound :class:`_schema.MetaData`. 

2813 

2814 .. seealso:: 

2815 

2816 :ref:`session_partitioning` 

2817 

2818 :paramref:`.Session.binds` 

2819 

2820 :meth:`.Session.bind_mapper` 

2821 

2822 :meth:`.Session.bind_table` 

2823 

2824 """ 

2825 

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

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

2828 if bind: 

2829 return bind 

2830 elif not self.__binds and self.bind: 

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

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

2833 return self.bind 

2834 

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

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

2837 # mapper and the clause 

2838 if mapper is None and clause is None: 

2839 if self.bind: 

2840 return self.bind 

2841 else: 

2842 raise sa_exc.UnboundExecutionError( 

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

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

2845 "a binding." 

2846 ) 

2847 

2848 # look more closely at the mapper. 

2849 if mapper is not None: 

2850 try: 

2851 inspected_mapper = inspect(mapper) 

2852 except sa_exc.NoInspectionAvailable as err: 

2853 if isinstance(mapper, type): 

2854 raise exc.UnmappedClassError(mapper) from err 

2855 else: 

2856 raise 

2857 else: 

2858 inspected_mapper = None 

2859 

2860 # match up the mapper or clause in the __binds 

2861 if self.__binds: 

2862 # matching mappers and selectables to entries in the 

2863 # binds dictionary; supported use case. 

2864 if inspected_mapper: 

2865 for cls in inspected_mapper.class_.__mro__: 

2866 if cls in self.__binds: 

2867 return self.__binds[cls] 

2868 if clause is None: 

2869 clause = inspected_mapper.persist_selectable 

2870 

2871 if clause is not None: 

2872 plugin_subject = clause._propagate_attrs.get( 

2873 "plugin_subject", None 

2874 ) 

2875 

2876 if plugin_subject is not None: 

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

2878 if cls in self.__binds: 

2879 return self.__binds[cls] 

2880 

2881 for obj in visitors.iterate(clause): 

2882 if obj in self.__binds: 

2883 if TYPE_CHECKING: 

2884 assert isinstance(obj, Table) 

2885 return self.__binds[obj] 

2886 

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

2888 # return that 

2889 if self.bind: 

2890 return self.bind 

2891 

2892 context = [] 

2893 if inspected_mapper is not None: 

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

2895 if clause is not None: 

2896 context.append("SQL expression") 

2897 

2898 raise sa_exc.UnboundExecutionError( 

2899 f"Could not locate a bind configured on " 

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

2901 ) 

2902 

2903 @overload 

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

2905 

2906 @overload 

2907 def query( 

2908 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2910 

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

2912 

2913 # code within this block is **programmatically, 

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

2915 

2916 @overload 

2917 def query( 

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

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

2920 

2921 @overload 

2922 def query( 

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

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

2925 

2926 @overload 

2927 def query( 

2928 self, 

2929 __ent0: _TCCA[_T0], 

2930 __ent1: _TCCA[_T1], 

2931 __ent2: _TCCA[_T2], 

2932 __ent3: _TCCA[_T3], 

2933 /, 

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

2935 

2936 @overload 

2937 def query( 

2938 self, 

2939 __ent0: _TCCA[_T0], 

2940 __ent1: _TCCA[_T1], 

2941 __ent2: _TCCA[_T2], 

2942 __ent3: _TCCA[_T3], 

2943 __ent4: _TCCA[_T4], 

2944 /, 

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

2946 

2947 @overload 

2948 def query( 

2949 self, 

2950 __ent0: _TCCA[_T0], 

2951 __ent1: _TCCA[_T1], 

2952 __ent2: _TCCA[_T2], 

2953 __ent3: _TCCA[_T3], 

2954 __ent4: _TCCA[_T4], 

2955 __ent5: _TCCA[_T5], 

2956 /, 

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

2958 

2959 @overload 

2960 def query( 

2961 self, 

2962 __ent0: _TCCA[_T0], 

2963 __ent1: _TCCA[_T1], 

2964 __ent2: _TCCA[_T2], 

2965 __ent3: _TCCA[_T3], 

2966 __ent4: _TCCA[_T4], 

2967 __ent5: _TCCA[_T5], 

2968 __ent6: _TCCA[_T6], 

2969 /, 

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

2971 

2972 @overload 

2973 def query( 

2974 self, 

2975 __ent0: _TCCA[_T0], 

2976 __ent1: _TCCA[_T1], 

2977 __ent2: _TCCA[_T2], 

2978 __ent3: _TCCA[_T3], 

2979 __ent4: _TCCA[_T4], 

2980 __ent5: _TCCA[_T5], 

2981 __ent6: _TCCA[_T6], 

2982 __ent7: _TCCA[_T7], 

2983 /, 

2984 *entities: _ColumnsClauseArgument[Any], 

2985 ) -> RowReturningQuery[ 

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

2987 ]: ... 

2988 

2989 # END OVERLOADED FUNCTIONS self.query 

2990 

2991 @overload 

2992 def query( 

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

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

2995 

2996 def query( 

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

2998 ) -> Query[Any]: 

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

3000 :class:`_orm.Session`. 

3001 

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

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

3004 to construct ORM queries. 

3005 

3006 .. seealso:: 

3007 

3008 :ref:`unified_tutorial` 

3009 

3010 :ref:`queryguide_toplevel` 

3011 

3012 :ref:`query_api_toplevel` - legacy API doc 

3013 

3014 """ 

3015 

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

3017 

3018 def _identity_lookup( 

3019 self, 

3020 mapper: Mapper[_O], 

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

3022 identity_token: Any = None, 

3023 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

3025 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3026 bind_arguments: Optional[_BindArguments] = None, 

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

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

3029 

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

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

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

3033 check if was deleted). 

3034 

3035 e.g.:: 

3036 

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

3038 

3039 :param mapper: mapper in use 

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

3041 a tuple. 

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

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

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

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

3046 :param passive: passive load flag passed to 

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

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

3049 if the flag allows for SQL to be emitted. 

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

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

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

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

3054 relationship-loaded). 

3055 

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

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

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

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

3060 

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

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

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

3064 :class:`_query.Query` object. 

3065 

3066 

3067 """ 

3068 

3069 key = mapper.identity_key_from_primary_key( 

3070 primary_key_identity, identity_token=identity_token 

3071 ) 

3072 

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

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

3075 return return_value 

3076 

3077 @util.non_memoized_property 

3078 @contextlib.contextmanager 

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

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

3081 

3082 e.g.:: 

3083 

3084 with session.no_autoflush: 

3085 

3086 some_object = SomeClass() 

3087 session.add(some_object) 

3088 # won't autoflush 

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

3090 

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

3092 will not be subject to flushes occurring upon query 

3093 access. This is useful when initializing a series 

3094 of objects which involve existing database queries, 

3095 where the uncompleted object should not yet be flushed. 

3096 

3097 """ 

3098 autoflush = self.autoflush 

3099 self.autoflush = False 

3100 try: 

3101 yield self 

3102 finally: 

3103 self.autoflush = autoflush 

3104 

3105 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3109 "warning happened while initializing objects.", 

3110 sa_exc.SAWarning, 

3111 ) 

3112 def _autoflush(self) -> None: 

3113 if self.autoflush and not self._flushing: 

3114 try: 

3115 self.flush() 

3116 except sa_exc.StatementError as e: 

3117 # note we are reraising StatementError as opposed to 

3118 # raising FlushError with "chaining" to remain compatible 

3119 # with code that catches StatementError, IntegrityError, 

3120 # etc. 

3121 e.add_detail( 

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

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

3124 "flush is occurring prematurely" 

3125 ) 

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

3127 

3128 def refresh( 

3129 self, 

3130 instance: object, 

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

3132 with_for_update: ForUpdateParameter = None, 

3133 ) -> None: 

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

3135 

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

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

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

3139 value available in the current transaction. 

3140 

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

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

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

3144 

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

3146 can also refresh eagerly loaded attributes. 

3147 

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

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

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

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

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

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

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

3155 refreshed. 

3156 

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

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

3159 attributes for those which are named explicitly in the 

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

3161 

3162 .. tip:: 

3163 

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

3165 refreshing both column and relationship oriented attributes, its 

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

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

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

3169 once while having explicit control over relationship loader 

3170 strategies, use the 

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

3172 instead. 

3173 

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

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

3176 in database state outside of that transaction. Refreshing 

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

3178 where database rows have not yet been accessed. 

3179 

3180 :param attribute_names: optional. An iterable collection of 

3181 string attribute names indicating a subset of attributes to 

3182 be refreshed. 

3183 

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

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

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

3187 flags should match the parameters of 

3188 :meth:`_query.Query.with_for_update`. 

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

3190 

3191 .. seealso:: 

3192 

3193 :ref:`session_expire` - introductory material 

3194 

3195 :meth:`.Session.expire` 

3196 

3197 :meth:`.Session.expire_all` 

3198 

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

3200 to refresh objects as they would be loaded normally. 

3201 

3202 """ 

3203 try: 

3204 state = attributes.instance_state(instance) 

3205 except exc.NO_STATE as err: 

3206 raise exc.UnmappedInstanceError(instance) from err 

3207 

3208 self._expire_state(state, attribute_names) 

3209 

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

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

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

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

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

3215 # load_on_ident. 

3216 self._autoflush() 

3217 

3218 if with_for_update == {}: 

3219 raise sa_exc.ArgumentError( 

3220 "with_for_update should be the boolean value " 

3221 "True, or a dictionary with options. " 

3222 "A blank dictionary is ambiguous." 

3223 ) 

3224 

3225 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3226 

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

3228 if ( 

3229 loading._load_on_ident( 

3230 self, 

3231 stmt, 

3232 state.key, 

3233 refresh_state=state, 

3234 with_for_update=with_for_update, 

3235 only_load_props=attribute_names, 

3236 require_pk_cols=True, 

3237 # technically unnecessary as we just did autoflush 

3238 # above, however removes the additional unnecessary 

3239 # call to _autoflush() 

3240 no_autoflush=True, 

3241 is_user_refresh=True, 

3242 ) 

3243 is None 

3244 ): 

3245 raise sa_exc.InvalidRequestError( 

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

3247 ) 

3248 

3249 def expire_all(self) -> None: 

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

3251 

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

3253 a query will be issued using the 

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

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

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

3257 previously read in that same transaction, regardless of changes 

3258 in database state outside of that transaction. 

3259 

3260 To expire individual objects and individual attributes 

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

3262 

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

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

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

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

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

3268 assuming the transaction is isolated. 

3269 

3270 .. seealso:: 

3271 

3272 :ref:`session_expire` - introductory material 

3273 

3274 :meth:`.Session.expire` 

3275 

3276 :meth:`.Session.refresh` 

3277 

3278 :meth:`_orm.Query.populate_existing` 

3279 

3280 """ 

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

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

3283 

3284 def expire( 

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

3286 ) -> None: 

3287 """Expire the attributes on an instance. 

3288 

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

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

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

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

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

3294 previously read in that same transaction, regardless of changes 

3295 in database state outside of that transaction. 

3296 

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

3298 use :meth:`Session.expire_all`. 

3299 

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

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

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

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

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

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

3306 transaction. 

3307 

3308 :param instance: The instance to be refreshed. 

3309 :param attribute_names: optional list of string attribute names 

3310 indicating a subset of attributes to be expired. 

3311 

3312 .. seealso:: 

3313 

3314 :ref:`session_expire` - introductory material 

3315 

3316 :meth:`.Session.expire` 

3317 

3318 :meth:`.Session.refresh` 

3319 

3320 :meth:`_orm.Query.populate_existing` 

3321 

3322 """ 

3323 try: 

3324 state = attributes.instance_state(instance) 

3325 except exc.NO_STATE as err: 

3326 raise exc.UnmappedInstanceError(instance) from err 

3327 self._expire_state(state, attribute_names) 

3328 

3329 def _expire_state( 

3330 self, 

3331 state: InstanceState[Any], 

3332 attribute_names: Optional[Iterable[str]], 

3333 ) -> None: 

3334 self._validate_persistent(state) 

3335 if attribute_names: 

3336 state._expire_attributes(state.dict, attribute_names) 

3337 else: 

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

3339 # remove associations 

3340 cascaded = list( 

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

3342 ) 

3343 self._conditional_expire(state) 

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

3345 self._conditional_expire(st_) 

3346 

3347 def _conditional_expire( 

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

3349 ) -> None: 

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

3351 

3352 if state.key: 

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

3354 elif state in self._new: 

3355 self._new.pop(state) 

3356 state._detach(self) 

3357 

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

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

3360 

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

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

3363 

3364 """ 

3365 try: 

3366 state = attributes.instance_state(instance) 

3367 except exc.NO_STATE as err: 

3368 raise exc.UnmappedInstanceError(instance) from err 

3369 if state.session_id is not self.hash_key: 

3370 raise sa_exc.InvalidRequestError( 

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

3372 ) 

3373 

3374 cascaded = list( 

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

3376 ) 

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

3378 

3379 def _expunge_states( 

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

3381 ) -> None: 

3382 for state in states: 

3383 if state in self._new: 

3384 self._new.pop(state) 

3385 elif self.identity_map.contains_state(state): 

3386 self.identity_map.safe_discard(state) 

3387 self._deleted.pop(state, None) 

3388 elif self._transaction: 

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

3390 # in the transaction snapshot 

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

3392 statelib.InstanceState._detach_states( 

3393 states, self, to_transient=to_transient 

3394 ) 

3395 

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

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

3398 

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

3400 state as well as already persistent objects. 

3401 

3402 """ 

3403 

3404 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3405 for state in states: 

3406 mapper = _state_mapper(state) 

3407 

3408 # prevent against last minute dereferences of the object 

3409 obj = state.obj() 

3410 if obj is not None: 

3411 instance_key = mapper._identity_key_from_state(state) 

3412 

3413 if ( 

3414 _none_set.intersection(instance_key[1]) 

3415 and not mapper.allow_partial_pks 

3416 or _none_set.issuperset(instance_key[1]) 

3417 ): 

3418 raise exc.FlushError( 

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

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

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

3422 "that the mapped Column object is configured to " 

3423 "expect these generated values. Ensure also that " 

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

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

3426 % state_str(state) 

3427 ) 

3428 

3429 if state.key is None: 

3430 state.key = instance_key 

3431 elif state.key != instance_key: 

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

3433 # state has already replaced this one in the identity 

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

3435 self.identity_map.safe_discard(state) 

3436 trans = self._transaction 

3437 assert trans is not None 

3438 if state in trans._key_switches: 

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

3440 else: 

3441 orig_key = state.key 

3442 trans._key_switches[state] = ( 

3443 orig_key, 

3444 instance_key, 

3445 ) 

3446 state.key = instance_key 

3447 

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

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

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

3451 old = self.identity_map.replace(state) 

3452 if ( 

3453 old is not None 

3454 and mapper._identity_key_from_state(old) == instance_key 

3455 and old.obj() is not None 

3456 ): 

3457 util.warn( 

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

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

3460 "load operations occurring inside of an event handler " 

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

3462 ) 

3463 state._orphaned_outside_of_session = False 

3464 

3465 statelib.InstanceState._commit_all_states( 

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

3467 ) 

3468 

3469 self._register_altered(states) 

3470 

3471 if pending_to_persistent is not None: 

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

3473 pending_to_persistent(self, state) 

3474 

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

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

3477 self._new.pop(state) 

3478 

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

3480 if self._transaction: 

3481 for state in states: 

3482 if state in self._new: 

3483 self._transaction._new[state] = True 

3484 else: 

3485 self._transaction._dirty[state] = True 

3486 

3487 def _remove_newly_deleted( 

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

3489 ) -> None: 

3490 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3491 for state in states: 

3492 if self._transaction: 

3493 self._transaction._deleted[state] = True 

3494 

3495 if persistent_to_deleted is not None: 

3496 # get a strong reference before we pop out of 

3497 # self._deleted 

3498 obj = state.obj() # noqa 

3499 

3500 self.identity_map.safe_discard(state) 

3501 self._deleted.pop(state, None) 

3502 state._deleted = True 

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

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

3505 # tracked as part of that 

3506 if persistent_to_deleted is not None: 

3507 persistent_to_deleted(self, state) 

3508 

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

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

3511 

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

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

3514 :term:`pending` state, until the next flush, at which point they 

3515 will move to the :term:`persistent` state. 

3516 

3517 Objects that are in the :term:`detached` state when passed to the 

3518 :meth:`_orm.Session.add` method will move to the :term:`persistent` 

3519 state directly. 

3520 

3521 If the transaction used by the :class:`_orm.Session` is rolled back, 

3522 objects which were transient when they were passed to 

3523 :meth:`_orm.Session.add` will be moved back to the 

3524 :term:`transient` state, and will no longer be present within this 

3525 :class:`_orm.Session`. 

3526 

3527 .. seealso:: 

3528 

3529 :meth:`_orm.Session.add_all` 

3530 

3531 :ref:`session_adding` - at :ref:`session_basics` 

3532 

3533 """ 

3534 if _warn and self._warn_on_events: 

3535 self._flush_warning("Session.add()") 

3536 

3537 try: 

3538 state = attributes.instance_state(instance) 

3539 except exc.NO_STATE as err: 

3540 raise exc.UnmappedInstanceError(instance) from err 

3541 

3542 self._save_or_update_state(state) 

3543 

3544 def add_all(self, instances: Iterable[object]) -> None: 

3545 """Add the given collection of instances to this :class:`_orm.Session`. 

3546 

3547 See the documentation for :meth:`_orm.Session.add` for a general 

3548 behavioral description. 

3549 

3550 .. seealso:: 

3551 

3552 :meth:`_orm.Session.add` 

3553 

3554 :ref:`session_adding` - at :ref:`session_basics` 

3555 

3556 """ 

3557 

3558 if self._warn_on_events: 

3559 self._flush_warning("Session.add_all()") 

3560 

3561 for instance in instances: 

3562 self.add(instance, _warn=False) 

3563 

3564 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3565 state._orphaned_outside_of_session = False 

3566 self._save_or_update_impl(state) 

3567 

3568 mapper = _state_mapper(state) 

3569 for o, m, st_, dct_ in mapper.cascade_iterator( 

3570 "save-update", state, halt_on=self._contains_state 

3571 ): 

3572 self._save_or_update_impl(st_) 

3573 

3574 def delete(self, instance: object) -> None: 

3575 """Mark an instance as deleted. 

3576 

3577 The object is assumed to be either :term:`persistent` or 

3578 :term:`detached` when passed; after the method is called, the 

3579 object will remain in the :term:`persistent` state until the next 

3580 flush proceeds. During this time, the object will also be a member 

3581 of the :attr:`_orm.Session.deleted` collection. 

3582 

3583 When the next flush proceeds, the object will move to the 

3584 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3585 for its row within the current transaction. When the transaction 

3586 is successfully committed, 

3587 the deleted object is moved to the :term:`detached` state and is 

3588 no longer present within this :class:`_orm.Session`. 

3589 

3590 .. seealso:: 

3591 

3592 :ref:`session_deleting` - at :ref:`session_basics` 

3593 

3594 :meth:`.Session.delete_all` - multiple instance version 

3595 

3596 """ 

3597 if self._warn_on_events: 

3598 self._flush_warning("Session.delete()") 

3599 

3600 self._delete_impl(object_state(instance), instance, head=True) 

3601 

3602 def delete_all(self, instances: Iterable[object]) -> None: 

3603 """Calls :meth:`.Session.delete` on multiple instances. 

3604 

3605 .. seealso:: 

3606 

3607 :meth:`.Session.delete` - main documentation on delete 

3608 

3609 .. versionadded:: 2.1 

3610 

3611 """ 

3612 

3613 if self._warn_on_events: 

3614 self._flush_warning("Session.delete_all()") 

3615 

3616 for instance in instances: 

3617 self._delete_impl(object_state(instance), instance, head=True) 

3618 

3619 def _delete_impl( 

3620 self, state: InstanceState[Any], obj: object, head: bool 

3621 ) -> None: 

3622 if state.key is None: 

3623 if head: 

3624 raise sa_exc.InvalidRequestError( 

3625 "Instance '%s' is not persisted" % state_str(state) 

3626 ) 

3627 else: 

3628 return 

3629 

3630 to_attach = self._before_attach(state, obj) 

3631 

3632 if state in self._deleted: 

3633 return 

3634 

3635 self.identity_map.add(state) 

3636 

3637 if to_attach: 

3638 self._after_attach(state, obj) 

3639 

3640 if head: 

3641 # grab the cascades before adding the item to the deleted list 

3642 # so that autoflush does not delete the item 

3643 # the strong reference to the instance itself is significant here 

3644 cascade_states = list( 

3645 state.manager.mapper.cascade_iterator("delete", state) 

3646 ) 

3647 else: 

3648 cascade_states = None 

3649 

3650 self._deleted[state] = obj 

3651 

3652 if head: 

3653 if TYPE_CHECKING: 

3654 assert cascade_states is not None 

3655 for o, m, st_, dct_ in cascade_states: 

3656 self._delete_impl(st_, o, False) 

3657 

3658 def get( 

3659 self, 

3660 entity: _EntityBindKey[_O], 

3661 ident: _PKIdentityArgument, 

3662 *, 

3663 options: Optional[Sequence[ORMOption]] = None, 

3664 populate_existing: bool = False, 

3665 with_for_update: ForUpdateParameter = None, 

3666 identity_token: Optional[Any] = None, 

3667 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3668 bind_arguments: Optional[_BindArguments] = None, 

3669 ) -> Optional[_O]: 

3670 """Return an instance based on the given primary key identifier, 

3671 or ``None`` if not found. 

3672 

3673 E.g.:: 

3674 

3675 my_user = session.get(User, 5) 

3676 

3677 some_object = session.get(VersionedFoo, (5, 10)) 

3678 

3679 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3680 

3681 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3682 from the now legacy :meth:`_orm.Query.get` method. 

3683 

3684 :meth:`_orm.Session.get` is special in that it provides direct 

3685 access to the identity map of the :class:`.Session`. 

3686 If the given primary key identifier is present 

3687 in the local identity map, the object is returned 

3688 directly from this collection and no SQL is emitted, 

3689 unless the object has been marked fully expired. 

3690 If not present, 

3691 a SELECT is performed in order to locate the object. 

3692 

3693 :meth:`_orm.Session.get` also will perform a check if 

3694 the object is present in the identity map and 

3695 marked as expired - a SELECT 

3696 is emitted to refresh the object as well as to 

3697 ensure that the row is still present. 

3698 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3699 

3700 :param entity: a mapped class or :class:`.Mapper` indicating the 

3701 type of entity to be loaded. 

3702 

3703 :param ident: A scalar, tuple, or dictionary representing the 

3704 primary key. For a composite (e.g. multiple column) primary key, 

3705 a tuple or dictionary should be passed. 

3706 

3707 For a single-column primary key, the scalar calling form is typically 

3708 the most expedient. If the primary key of a row is the value "5", 

3709 the call looks like:: 

3710 

3711 my_object = session.get(SomeClass, 5) 

3712 

3713 The tuple form contains primary key values typically in 

3714 the order in which they correspond to the mapped 

3715 :class:`_schema.Table` 

3716 object's primary key columns, or if the 

3717 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3718 used, in 

3719 the order used for that parameter. For example, if the primary key 

3720 of a row is represented by the integer 

3721 digits "5, 10" the call would look like:: 

3722 

3723 my_object = session.get(SomeClass, (5, 10)) 

3724 

3725 The dictionary form should include as keys the mapped attribute names 

3726 corresponding to each element of the primary key. If the mapped class 

3727 has the attributes ``id``, ``version_id`` as the attributes which 

3728 store the object's primary key value, the call would look like:: 

3729 

3730 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3731 

3732 :param options: optional sequence of loader options which will be 

3733 applied to the query, if one is emitted. 

3734 

3735 :param populate_existing: causes the method to unconditionally emit 

3736 a SQL query and refresh the object with the newly loaded data, 

3737 regardless of whether or not the object is already present. 

3738 

3739 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE 

3740 should be used, or may be a dictionary containing flags to 

3741 indicate a more specific set of FOR UPDATE flags for the SELECT; 

3742 flags should match the parameters of 

3743 :meth:`_query.Query.with_for_update`. 

3744 Supersedes the :paramref:`.Session.refresh.lockmode` parameter. 

3745 

3746 :param execution_options: optional dictionary of execution options, 

3747 which will be associated with the query execution if one is emitted. 

3748 This dictionary can provide a subset of the options that are 

3749 accepted by :meth:`_engine.Connection.execution_options`, and may 

3750 also provide additional options understood only in an ORM context. 

3751 

3752 .. versionadded:: 1.4.29 

3753 

3754 .. seealso:: 

3755 

3756 :ref:`orm_queryguide_execution_options` - ORM-specific execution 

3757 options 

3758 

3759 :param bind_arguments: dictionary of additional arguments to determine 

3760 the bind. May include "mapper", "bind", or other custom arguments. 

3761 Contents of this dictionary are passed to the 

3762 :meth:`.Session.get_bind` method. 

3763 

3764 .. versionadded:: 2.0.0rc1 

3765 

3766 :return: The object instance, or ``None``. 

3767 

3768 """ # noqa: E501 

3769 return self._get_impl( 

3770 entity, 

3771 ident, 

3772 loading._load_on_pk_identity, 

3773 options=options, 

3774 populate_existing=populate_existing, 

3775 with_for_update=with_for_update, 

3776 identity_token=identity_token, 

3777 execution_options=execution_options, 

3778 bind_arguments=bind_arguments, 

3779 ) 

3780 

3781 def get_one( 

3782 self, 

3783 entity: _EntityBindKey[_O], 

3784 ident: _PKIdentityArgument, 

3785 *, 

3786 options: Optional[Sequence[ORMOption]] = None, 

3787 populate_existing: bool = False, 

3788 with_for_update: ForUpdateParameter = None, 

3789 identity_token: Optional[Any] = None, 

3790 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3791 bind_arguments: Optional[_BindArguments] = None, 

3792 ) -> _O: 

3793 """Return exactly one instance based on the given primary key 

3794 identifier, or raise an exception if not found. 

3795 

3796 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3797 

3798 For a detailed documentation of the arguments see the 

3799 method :meth:`.Session.get`. 

3800 

3801 .. versionadded:: 2.0.22 

3802 

3803 :return: The object instance. 

3804 

3805 .. seealso:: 

3806 

3807 :meth:`.Session.get` - equivalent method that instead 

3808 returns ``None`` if no row was found with the provided primary 

3809 key 

3810 

3811 """ 

3812 

3813 instance = self.get( 

3814 entity, 

3815 ident, 

3816 options=options, 

3817 populate_existing=populate_existing, 

3818 with_for_update=with_for_update, 

3819 identity_token=identity_token, 

3820 execution_options=execution_options, 

3821 bind_arguments=bind_arguments, 

3822 ) 

3823 

3824 if instance is None: 

3825 raise sa_exc.NoResultFound( 

3826 "No row was found when one was required" 

3827 ) 

3828 

3829 return instance 

3830 

3831 def _get_impl( 

3832 self, 

3833 entity: _EntityBindKey[_O], 

3834 primary_key_identity: _PKIdentityArgument, 

3835 db_load_fn: Callable[..., _O], 

3836 *, 

3837 options: Optional[Sequence[ExecutableOption]] = None, 

3838 populate_existing: bool = False, 

3839 with_for_update: ForUpdateParameter = None, 

3840 identity_token: Optional[Any] = None, 

3841 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3842 bind_arguments: Optional[_BindArguments] = None, 

3843 ) -> Optional[_O]: 

3844 # convert composite types to individual args 

3845 if ( 

3846 is_composite_class(primary_key_identity) 

3847 and type(primary_key_identity) 

3848 in descriptor_props._composite_getters 

3849 ): 

3850 getter = descriptor_props._composite_getters[ 

3851 type(primary_key_identity) 

3852 ] 

3853 primary_key_identity = getter(primary_key_identity) 

3854 

3855 mapper: Optional[Mapper[_O]] = inspect(entity) 

3856 

3857 if mapper is None or not mapper.is_mapper: 

3858 raise sa_exc.ArgumentError( 

3859 "Expected mapped class or mapper, got: %r" % entity 

3860 ) 

3861 

3862 is_dict = isinstance(primary_key_identity, dict) 

3863 if not is_dict: 

3864 primary_key_identity = util.to_list( 

3865 primary_key_identity, default=[None] 

3866 ) 

3867 

3868 if len(primary_key_identity) != len(mapper.primary_key): 

3869 raise sa_exc.InvalidRequestError( 

3870 "Incorrect number of values in identifier to formulate " 

3871 "primary key for session.get(); primary key columns " 

3872 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3873 ) 

3874 

3875 if is_dict: 

3876 pk_synonyms = mapper._pk_synonyms 

3877 

3878 if pk_synonyms: 

3879 correct_keys = set(pk_synonyms).intersection( 

3880 primary_key_identity 

3881 ) 

3882 

3883 if correct_keys: 

3884 primary_key_identity = dict(primary_key_identity) 

3885 for k in correct_keys: 

3886 primary_key_identity[pk_synonyms[k]] = ( 

3887 primary_key_identity[k] 

3888 ) 

3889 

3890 try: 

3891 primary_key_identity = list( 

3892 primary_key_identity[prop.key] 

3893 for prop in mapper._identity_key_props 

3894 ) 

3895 

3896 except KeyError as err: 

3897 raise sa_exc.InvalidRequestError( 

3898 "Incorrect names of values in identifier to formulate " 

3899 "primary key for session.get(); primary key attribute " 

3900 "names are %s (synonym names are also accepted)" 

3901 % ",".join( 

3902 "'%s'" % prop.key 

3903 for prop in mapper._identity_key_props 

3904 ) 

3905 ) from err 

3906 

3907 if ( 

3908 not populate_existing 

3909 and not mapper.always_refresh 

3910 and with_for_update is None 

3911 ): 

3912 instance = self._identity_lookup( 

3913 mapper, 

3914 primary_key_identity, 

3915 identity_token=identity_token, 

3916 execution_options=execution_options, 

3917 bind_arguments=bind_arguments, 

3918 ) 

3919 

3920 if instance is not None: 

3921 # reject calls for id in identity map but class 

3922 # mismatch. 

3923 if not isinstance(instance, mapper.class_): 

3924 return None 

3925 return instance 

3926 

3927 # TODO: this was being tested before, but this is not possible 

3928 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3929 

3930 load_options = context.QueryContext.default_load_options 

3931 

3932 if populate_existing: 

3933 load_options += {"_populate_existing": populate_existing} 

3934 statement = sql.select(mapper) 

3935 if with_for_update is not None: 

3936 statement._for_update_arg = ForUpdateArg._from_argument( 

3937 with_for_update 

3938 ) 

3939 

3940 if options: 

3941 statement = statement.options(*options) 

3942 if self.execution_options: 

3943 execution_options = self.execution_options.union(execution_options) 

3944 return db_load_fn( 

3945 self, 

3946 statement, 

3947 primary_key_identity, 

3948 load_options=load_options, 

3949 identity_token=identity_token, 

3950 execution_options=execution_options, 

3951 bind_arguments=bind_arguments, 

3952 ) 

3953 

3954 def merge( 

3955 self, 

3956 instance: _O, 

3957 *, 

3958 load: bool = True, 

3959 options: Optional[Sequence[ORMOption]] = None, 

3960 ) -> _O: 

3961 """Copy the state of a given instance into a corresponding instance 

3962 within this :class:`.Session`. 

3963 

3964 :meth:`.Session.merge` examines the primary key attributes of the 

3965 source instance, and attempts to reconcile it with an instance of the 

3966 same primary key in the session. If not found locally, it attempts 

3967 to load the object from the database based on primary key, and if 

3968 none can be located, creates a new instance. The state of each 

3969 attribute on the source instance is then copied to the target 

3970 instance. The resulting target instance is then returned by the 

3971 method; the original source instance is left unmodified, and 

3972 un-associated with the :class:`.Session` if not already. 

3973 

3974 This operation cascades to associated instances if the association is 

3975 mapped with ``cascade="merge"``. 

3976 

3977 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3978 

3979 :param instance: Instance to be merged. 

3980 :param load: Boolean, when False, :meth:`.merge` switches into 

3981 a "high performance" mode which causes it to forego emitting history 

3982 events as well as all database access. This flag is used for 

3983 cases such as transferring graphs of objects into a :class:`.Session` 

3984 from a second level cache, or to transfer just-loaded objects 

3985 into the :class:`.Session` owned by a worker thread or process 

3986 without re-querying the database. 

3987 

3988 The ``load=False`` use case adds the caveat that the given 

3989 object has to be in a "clean" state, that is, has no pending changes 

3990 to be flushed - even if the incoming object is detached from any 

3991 :class:`.Session`. This is so that when 

3992 the merge operation populates local attributes and 

3993 cascades to related objects and 

3994 collections, the values can be "stamped" onto the 

3995 target object as is, without generating any history or attribute 

3996 events, and without the need to reconcile the incoming data with 

3997 any existing related objects or collections that might not 

3998 be loaded. The resulting objects from ``load=False`` are always 

3999 produced as "clean", so it is only appropriate that the given objects 

4000 should be "clean" as well, else this suggests a mis-use of the 

4001 method. 

4002 :param options: optional sequence of loader options which will be 

4003 applied to the :meth:`_orm.Session.get` method when the merge 

4004 operation loads the existing version of the object from the database. 

4005 

4006 .. versionadded:: 1.4.24 

4007 

4008 

4009 .. seealso:: 

4010 

4011 :func:`.make_transient_to_detached` - provides for an alternative 

4012 means of "merging" a single object into the :class:`.Session` 

4013 

4014 :meth:`.Session.merge_all` - multiple instance version 

4015 

4016 """ 

4017 

4018 if self._warn_on_events: 

4019 self._flush_warning("Session.merge()") 

4020 

4021 if load: 

4022 # flush current contents if we expect to load data 

4023 self._autoflush() 

4024 

4025 with self.no_autoflush: 

4026 return self._merge( 

4027 object_state(instance), 

4028 attributes.instance_dict(instance), 

4029 load=load, 

4030 options=options, 

4031 _recursive={}, 

4032 _resolve_conflict_map={}, 

4033 ) 

4034 

4035 def merge_all( 

4036 self, 

4037 instances: Iterable[_O], 

4038 *, 

4039 load: bool = True, 

4040 options: Optional[Sequence[ORMOption]] = None, 

4041 ) -> Sequence[_O]: 

4042 """Calls :meth:`.Session.merge` on multiple instances. 

4043 

4044 .. seealso:: 

4045 

4046 :meth:`.Session.merge` - main documentation on merge 

4047 

4048 .. versionadded:: 2.1 

4049 

4050 """ 

4051 

4052 if self._warn_on_events: 

4053 self._flush_warning("Session.merge_all()") 

4054 

4055 if load: 

4056 # flush current contents if we expect to load data 

4057 self._autoflush() 

4058 

4059 return [ 

4060 self._merge( 

4061 object_state(instance), 

4062 attributes.instance_dict(instance), 

4063 load=load, 

4064 options=options, 

4065 _recursive={}, 

4066 _resolve_conflict_map={}, 

4067 ) 

4068 for instance in instances 

4069 ] 

4070 

4071 def _merge( 

4072 self, 

4073 state: InstanceState[_O], 

4074 state_dict: _InstanceDict, 

4075 *, 

4076 options: Optional[Sequence[ORMOption]] = None, 

4077 load: bool, 

4078 _recursive: Dict[Any, object], 

4079 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4080 ) -> _O: 

4081 mapper: Mapper[_O] = _state_mapper(state) 

4082 if state in _recursive: 

4083 return cast(_O, _recursive[state]) 

4084 

4085 new_instance = False 

4086 key = state.key 

4087 

4088 merged: Optional[_O] 

4089 

4090 if key is None: 

4091 if state in self._new: 

4092 util.warn( 

4093 "Instance %s is already pending in this Session yet is " 

4094 "being merged again; this is probably not what you want " 

4095 "to do" % state_str(state) 

4096 ) 

4097 

4098 if not load: 

4099 raise sa_exc.InvalidRequestError( 

4100 "merge() with load=False option does not support " 

4101 "objects transient (i.e. unpersisted) objects. flush() " 

4102 "all changes on mapped instances before merging with " 

4103 "load=False." 

4104 ) 

4105 key = mapper._identity_key_from_state(state) 

4106 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4107 1 

4108 ] and ( 

4109 not _none_set.intersection(key[1]) 

4110 or ( 

4111 mapper.allow_partial_pks 

4112 and not _none_set.issuperset(key[1]) 

4113 ) 

4114 ) 

4115 else: 

4116 key_is_persistent = True 

4117 

4118 merged = self.identity_map.get(key) 

4119 

4120 if merged is None: 

4121 if key_is_persistent and key in _resolve_conflict_map: 

4122 merged = cast(_O, _resolve_conflict_map[key]) 

4123 

4124 elif not load: 

4125 if state.modified: 

4126 raise sa_exc.InvalidRequestError( 

4127 "merge() with load=False option does not support " 

4128 "objects marked as 'dirty'. flush() all changes on " 

4129 "mapped instances before merging with load=False." 

4130 ) 

4131 merged = mapper.class_manager.new_instance() 

4132 merged_state = attributes.instance_state(merged) 

4133 merged_state.key = key 

4134 self._update_impl(merged_state) 

4135 new_instance = True 

4136 

4137 elif key_is_persistent: 

4138 merged = self.get( 

4139 mapper.class_, 

4140 key[1], 

4141 identity_token=key[2], 

4142 options=options, 

4143 ) 

4144 

4145 if merged is None: 

4146 merged = mapper.class_manager.new_instance() 

4147 merged_state = attributes.instance_state(merged) 

4148 merged_dict = attributes.instance_dict(merged) 

4149 new_instance = True 

4150 self._save_or_update_state(merged_state) 

4151 else: 

4152 merged_state = attributes.instance_state(merged) 

4153 merged_dict = attributes.instance_dict(merged) 

4154 

4155 _recursive[state] = merged 

4156 _resolve_conflict_map[key] = merged 

4157 

4158 # check that we didn't just pull the exact same 

4159 # state out. 

4160 if state is not merged_state: 

4161 # version check if applicable 

4162 if mapper.version_id_col is not None: 

4163 existing_version = mapper._get_state_attr_by_column( 

4164 state, 

4165 state_dict, 

4166 mapper.version_id_col, 

4167 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4168 ) 

4169 

4170 merged_version = mapper._get_state_attr_by_column( 

4171 merged_state, 

4172 merged_dict, 

4173 mapper.version_id_col, 

4174 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4175 ) 

4176 

4177 if ( 

4178 existing_version 

4179 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4180 and merged_version 

4181 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4182 and existing_version != merged_version 

4183 ): 

4184 raise exc.StaleDataError( 

4185 "Version id '%s' on merged state %s " 

4186 "does not match existing version '%s'. " 

4187 "Leave the version attribute unset when " 

4188 "merging to update the most recent version." 

4189 % ( 

4190 existing_version, 

4191 state_str(merged_state), 

4192 merged_version, 

4193 ) 

4194 ) 

4195 

4196 merged_state.load_path = state.load_path 

4197 merged_state.load_options = state.load_options 

4198 

4199 # since we are copying load_options, we need to copy 

4200 # the callables_ that would have been generated by those 

4201 # load_options. 

4202 # assumes that the callables we put in state.callables_ 

4203 # are not instance-specific (which they should not be) 

4204 merged_state._copy_callables(state) 

4205 

4206 for prop in mapper.iterate_properties: 

4207 prop.merge( 

4208 self, 

4209 state, 

4210 state_dict, 

4211 merged_state, 

4212 merged_dict, 

4213 load, 

4214 _recursive, 

4215 _resolve_conflict_map, 

4216 ) 

4217 

4218 if not load: 

4219 # remove any history 

4220 merged_state._commit_all(merged_dict, self.identity_map) 

4221 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4222 merged_state, None 

4223 ) 

4224 

4225 if new_instance: 

4226 merged_state.manager.dispatch.load(merged_state, None) 

4227 

4228 return merged 

4229 

4230 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4231 if not self.identity_map.contains_state(state): 

4232 raise sa_exc.InvalidRequestError( 

4233 "Instance '%s' is not persistent within this Session" 

4234 % state_str(state) 

4235 ) 

4236 

4237 def _save_impl(self, state: InstanceState[Any]) -> None: 

4238 if state.key is not None: 

4239 raise sa_exc.InvalidRequestError( 

4240 "Object '%s' already has an identity - " 

4241 "it can't be registered as pending" % state_str(state) 

4242 ) 

4243 

4244 obj = state.obj() 

4245 to_attach = self._before_attach(state, obj) 

4246 if state not in self._new: 

4247 self._new[state] = obj 

4248 state.insert_order = len(self._new) 

4249 if to_attach: 

4250 self._after_attach(state, obj) 

4251 

4252 def _update_impl( 

4253 self, state: InstanceState[Any], revert_deletion: bool = False 

4254 ) -> None: 

4255 if state.key is None: 

4256 raise sa_exc.InvalidRequestError( 

4257 "Instance '%s' is not persisted" % state_str(state) 

4258 ) 

4259 

4260 if state._deleted: 

4261 if revert_deletion: 

4262 if not state._attached: 

4263 return 

4264 del state._deleted 

4265 else: 

4266 raise sa_exc.InvalidRequestError( 

4267 "Instance '%s' has been deleted. " 

4268 "Use the make_transient() " 

4269 "function to send this object back " 

4270 "to the transient state." % state_str(state) 

4271 ) 

4272 

4273 obj = state.obj() 

4274 

4275 # check for late gc 

4276 if obj is None: 

4277 return 

4278 

4279 to_attach = self._before_attach(state, obj) 

4280 

4281 self._deleted.pop(state, None) 

4282 if revert_deletion: 

4283 self.identity_map.replace(state) 

4284 else: 

4285 self.identity_map.add(state) 

4286 

4287 if to_attach: 

4288 self._after_attach(state, obj) 

4289 elif revert_deletion: 

4290 self.dispatch.deleted_to_persistent(self, state) 

4291 

4292 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4293 if state.key is None: 

4294 self._save_impl(state) 

4295 else: 

4296 self._update_impl(state) 

4297 

4298 def enable_relationship_loading(self, obj: object) -> None: 

4299 """Associate an object with this :class:`.Session` for related 

4300 object loading. 

4301 

4302 .. warning:: 

4303 

4304 :meth:`.enable_relationship_loading` exists to serve special 

4305 use cases and is not recommended for general use. 

4306 

4307 Accesses of attributes mapped with :func:`_orm.relationship` 

4308 will attempt to load a value from the database using this 

4309 :class:`.Session` as the source of connectivity. The values 

4310 will be loaded based on foreign key and primary key values 

4311 present on this object - if not present, then those relationships 

4312 will be unavailable. 

4313 

4314 The object will be attached to this session, but will 

4315 **not** participate in any persistence operations; its state 

4316 for almost all purposes will remain either "transient" or 

4317 "detached", except for the case of relationship loading. 

4318 

4319 Also note that backrefs will often not work as expected. 

4320 Altering a relationship-bound attribute on the target object 

4321 may not fire off a backref event, if the effective value 

4322 is what was already loaded from a foreign-key-holding value. 

4323 

4324 The :meth:`.Session.enable_relationship_loading` method is 

4325 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4326 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4327 an object to remain transient while still being able to load 

4328 related items. 

4329 

4330 To make a transient object associated with a :class:`.Session` 

4331 via :meth:`.Session.enable_relationship_loading` pending, add 

4332 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4333 If the object instead represents an existing identity in the database, 

4334 it should be merged using :meth:`.Session.merge`. 

4335 

4336 :meth:`.Session.enable_relationship_loading` does not improve 

4337 behavior when the ORM is used normally - object references should be 

4338 constructed at the object level, not at the foreign key level, so 

4339 that they are present in an ordinary way before flush() 

4340 proceeds. This method is not intended for general use. 

4341 

4342 .. seealso:: 

4343 

4344 :paramref:`_orm.relationship.load_on_pending` - this flag 

4345 allows per-relationship loading of many-to-ones on items that 

4346 are pending. 

4347 

4348 :func:`.make_transient_to_detached` - allows for an object to 

4349 be added to a :class:`.Session` without SQL emitted, which then 

4350 will unexpire attributes on access. 

4351 

4352 """ 

4353 try: 

4354 state = attributes.instance_state(obj) 

4355 except exc.NO_STATE as err: 

4356 raise exc.UnmappedInstanceError(obj) from err 

4357 

4358 to_attach = self._before_attach(state, obj) 

4359 state._load_pending = True 

4360 if to_attach: 

4361 self._after_attach(state, obj) 

4362 

4363 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4364 self._autobegin_t() 

4365 

4366 if state.session_id == self.hash_key: 

4367 return False 

4368 

4369 if state.session_id and state.session_id in _sessions: 

4370 raise sa_exc.InvalidRequestError( 

4371 "Object '%s' is already attached to session '%s' " 

4372 "(this is '%s')" 

4373 % (state_str(state), state.session_id, self.hash_key) 

4374 ) 

4375 

4376 self.dispatch.before_attach(self, state) 

4377 

4378 return True 

4379 

4380 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4381 state.session_id = self.hash_key 

4382 if state.modified and state._strong_obj is None: 

4383 state._strong_obj = obj 

4384 self.dispatch.after_attach(self, state) 

4385 

4386 if state.key: 

4387 self.dispatch.detached_to_persistent(self, state) 

4388 else: 

4389 self.dispatch.transient_to_pending(self, state) 

4390 

4391 def __contains__(self, instance: object) -> bool: 

4392 """Return True if the instance is associated with this session. 

4393 

4394 The instance may be pending or persistent within the Session for a 

4395 result of True. 

4396 

4397 """ 

4398 try: 

4399 state = attributes.instance_state(instance) 

4400 except exc.NO_STATE as err: 

4401 raise exc.UnmappedInstanceError(instance) from err 

4402 return self._contains_state(state) 

4403 

4404 def __iter__(self) -> Iterator[object]: 

4405 """Iterate over all pending or persistent instances within this 

4406 Session. 

4407 

4408 """ 

4409 return iter( 

4410 list(self._new.values()) + list(self.identity_map.values()) 

4411 ) 

4412 

4413 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4414 return state in self._new or self.identity_map.contains_state(state) 

4415 

4416 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4417 """Flush all the object changes to the database. 

4418 

4419 Writes out all pending object creations, deletions and modifications 

4420 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4421 automatically ordered by the Session's unit of work dependency 

4422 solver. 

4423 

4424 Database operations will be issued in the current transactional 

4425 context and do not affect the state of the transaction, unless an 

4426 error occurs, in which case the entire transaction is rolled back. 

4427 You may flush() as often as you like within a transaction to move 

4428 changes from Python to the database's transaction buffer. 

4429 

4430 :param objects: Optional; restricts the flush operation to operate 

4431 only on elements that are in the given collection. 

4432 

4433 This feature is for an extremely narrow set of use cases where 

4434 particular objects may need to be operated upon before the 

4435 full flush() occurs. It is not intended for general use. 

4436 

4437 .. deprecated:: 2.1 

4438 

4439 """ 

4440 

4441 if self._flushing: 

4442 raise sa_exc.InvalidRequestError("Session is already flushing") 

4443 

4444 if self._is_clean(): 

4445 return 

4446 try: 

4447 self._flushing = True 

4448 self._flush(objects) 

4449 finally: 

4450 self._flushing = False 

4451 

4452 def _flush_warning(self, method: Any) -> None: 

4453 util.warn( 

4454 "Usage of the '%s' operation is not currently supported " 

4455 "within the execution stage of the flush process. " 

4456 "Results may not be consistent. Consider using alternative " 

4457 "event listeners or connection-level operations instead." % method 

4458 ) 

4459 

4460 def _is_clean(self) -> bool: 

4461 return ( 

4462 not self.identity_map.check_modified() 

4463 and not self._deleted 

4464 and not self._new 

4465 ) 

4466 

4467 # have this here since it otherwise causes issues with the proxy 

4468 # method generation 

4469 @deprecated_params( 

4470 objects=( 

4471 "2.1", 

4472 "The `objects` parameter of `Session.flush` is deprecated", 

4473 ) 

4474 ) 

4475 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4476 dirty = self._dirty_states 

4477 if not dirty and not self._deleted and not self._new: 

4478 self.identity_map._modified.clear() 

4479 return 

4480 

4481 flush_context = UOWTransaction(self) 

4482 

4483 if self.dispatch.before_flush: 

4484 self.dispatch.before_flush(self, flush_context, objects) 

4485 # re-establish "dirty states" in case the listeners 

4486 # added 

4487 dirty = self._dirty_states 

4488 

4489 deleted = set(self._deleted) 

4490 new = set(self._new) 

4491 

4492 dirty = set(dirty).difference(deleted) 

4493 

4494 # create the set of all objects we want to operate upon 

4495 if objects: 

4496 # specific list passed in 

4497 objset = set() 

4498 for o in objects: 

4499 try: 

4500 state = attributes.instance_state(o) 

4501 

4502 except exc.NO_STATE as err: 

4503 raise exc.UnmappedInstanceError(o) from err 

4504 objset.add(state) 

4505 else: 

4506 objset = None 

4507 

4508 # store objects whose fate has been decided 

4509 processed = set() 

4510 

4511 # put all saves/updates into the flush context. detect top-level 

4512 # orphans and throw them into deleted. 

4513 if objset: 

4514 proc = new.union(dirty).intersection(objset).difference(deleted) 

4515 else: 

4516 proc = new.union(dirty).difference(deleted) 

4517 

4518 for state in proc: 

4519 is_orphan = _state_mapper(state)._is_orphan(state) 

4520 

4521 is_persistent_orphan = is_orphan and state.has_identity 

4522 

4523 if ( 

4524 is_orphan 

4525 and not is_persistent_orphan 

4526 and state._orphaned_outside_of_session 

4527 ): 

4528 self._expunge_states([state]) 

4529 else: 

4530 _reg = flush_context.register_object( 

4531 state, isdelete=is_persistent_orphan 

4532 ) 

4533 assert _reg, "Failed to add object to the flush context!" 

4534 processed.add(state) 

4535 

4536 # put all remaining deletes into the flush context. 

4537 if objset: 

4538 proc = deleted.intersection(objset).difference(processed) 

4539 else: 

4540 proc = deleted.difference(processed) 

4541 for state in proc: 

4542 _reg = flush_context.register_object(state, isdelete=True) 

4543 assert _reg, "Failed to add object to the flush context!" 

4544 

4545 if not flush_context.has_work: 

4546 return 

4547 

4548 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4549 try: 

4550 self._warn_on_events = True 

4551 try: 

4552 flush_context.execute() 

4553 finally: 

4554 self._warn_on_events = False 

4555 

4556 self.dispatch.after_flush(self, flush_context) 

4557 

4558 flush_context.finalize_flush_changes() 

4559 

4560 if not objects and self.identity_map._modified: 

4561 len_ = len(self.identity_map._modified) 

4562 

4563 statelib.InstanceState._commit_all_states( 

4564 [ 

4565 (state, state.dict) 

4566 for state in self.identity_map._modified 

4567 ], 

4568 instance_dict=self.identity_map, 

4569 ) 

4570 util.warn( 

4571 "Attribute history events accumulated on %d " 

4572 "previously clean instances " 

4573 "within inner-flush event handlers have been " 

4574 "reset, and will not result in database updates. " 

4575 "Consider using set_committed_value() within " 

4576 "inner-flush event handlers to avoid this warning." % len_ 

4577 ) 

4578 

4579 # useful assertions: 

4580 # if not objects: 

4581 # assert not self.identity_map._modified 

4582 # else: 

4583 # assert self.identity_map._modified == \ 

4584 # self.identity_map._modified.difference(objects) 

4585 

4586 self.dispatch.after_flush_postexec(self, flush_context) 

4587 

4588 transaction.commit() 

4589 

4590 except: 

4591 with util.safe_reraise(): 

4592 transaction.rollback(_capture_exception=True) 

4593 

4594 def bulk_save_objects( 

4595 self, 

4596 objects: Iterable[object], 

4597 return_defaults: bool = False, 

4598 update_changed_only: bool = True, 

4599 preserve_order: bool = True, 

4600 ) -> None: 

4601 """Perform a bulk save of the given list of objects. 

4602 

4603 .. legacy:: 

4604 

4605 This method is a legacy feature as of the 2.0 series of 

4606 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4607 the sections :ref:`orm_queryguide_bulk_insert` and 

4608 :ref:`orm_queryguide_bulk_update`. 

4609 

4610 For general INSERT and UPDATE of existing ORM mapped objects, 

4611 prefer standard :term:`unit of work` data management patterns, 

4612 introduced in the :ref:`unified_tutorial` at 

4613 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4614 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4615 which solves previous issues of bulk INSERT slowness. 

4616 

4617 :param objects: a sequence of mapped object instances. The mapped 

4618 objects are persisted as is, and are **not** associated with the 

4619 :class:`.Session` afterwards. 

4620 

4621 For each object, whether the object is sent as an INSERT or an 

4622 UPDATE is dependent on the same rules used by the :class:`.Session` 

4623 in traditional operation; if the object has the 

4624 :attr:`.InstanceState.key` 

4625 attribute set, then the object is assumed to be "detached" and 

4626 will result in an UPDATE. Otherwise, an INSERT is used. 

4627 

4628 In the case of an UPDATE, statements are grouped based on which 

4629 attributes have changed, and are thus to be the subject of each 

4630 SET clause. If ``update_changed_only`` is False, then all 

4631 attributes present within each object are applied to the UPDATE 

4632 statement, which may help in allowing the statements to be grouped 

4633 together into a larger executemany(), and will also reduce the 

4634 overhead of checking history on attributes. 

4635 

4636 :param return_defaults: when True, rows that are missing values which 

4637 generate defaults, namely integer primary key defaults and sequences, 

4638 will be inserted **one at a time**, so that the primary key value 

4639 is available. In particular this will allow joined-inheritance 

4640 and other multi-table mappings to insert correctly without the need 

4641 to provide primary key values ahead of time; however, 

4642 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4643 reduces the performance gains** of the method overall. It is strongly 

4644 advised to please use the standard :meth:`_orm.Session.add_all` 

4645 approach. 

4646 

4647 :param update_changed_only: when True, UPDATE statements are rendered 

4648 based on those attributes in each state that have logged changes. 

4649 When False, all attributes present are rendered into the SET clause 

4650 with the exception of primary key attributes. 

4651 

4652 :param preserve_order: when True, the order of inserts and updates 

4653 matches exactly the order in which the objects are given. When 

4654 False, common types of objects are grouped into inserts 

4655 and updates, to allow for more batching opportunities. 

4656 

4657 .. seealso:: 

4658 

4659 :doc:`queryguide/dml` 

4660 

4661 :meth:`.Session.bulk_insert_mappings` 

4662 

4663 :meth:`.Session.bulk_update_mappings` 

4664 

4665 """ 

4666 

4667 obj_states: Iterable[InstanceState[Any]] 

4668 

4669 obj_states = (attributes.instance_state(obj) for obj in objects) 

4670 

4671 if not preserve_order: 

4672 # the purpose of this sort is just so that common mappers 

4673 # and persistence states are grouped together, so that groupby 

4674 # will return a single group for a particular type of mapper. 

4675 # it's not trying to be deterministic beyond that. 

4676 obj_states = sorted( 

4677 obj_states, 

4678 key=lambda state: (id(state.mapper), state.key is not None), 

4679 ) 

4680 

4681 def grouping_key( 

4682 state: InstanceState[_O], 

4683 ) -> Tuple[Mapper[_O], bool]: 

4684 return (state.mapper, state.key is not None) 

4685 

4686 for (mapper, isupdate), states in itertools.groupby( 

4687 obj_states, grouping_key 

4688 ): 

4689 self._bulk_save_mappings( 

4690 mapper, 

4691 states, 

4692 isupdate=isupdate, 

4693 isstates=True, 

4694 return_defaults=return_defaults, 

4695 update_changed_only=update_changed_only, 

4696 render_nulls=False, 

4697 ) 

4698 

4699 def bulk_insert_mappings( 

4700 self, 

4701 mapper: Mapper[Any], 

4702 mappings: Iterable[Dict[str, Any]], 

4703 return_defaults: bool = False, 

4704 render_nulls: bool = False, 

4705 ) -> None: 

4706 """Perform a bulk insert of the given list of mapping dictionaries. 

4707 

4708 .. legacy:: 

4709 

4710 This method is a legacy feature as of the 2.0 series of 

4711 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4712 the sections :ref:`orm_queryguide_bulk_insert` and 

4713 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4714 implementation details with this method and adds new features 

4715 as well. 

4716 

4717 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4718 object, 

4719 representing the single kind of object represented within the mapping 

4720 list. 

4721 

4722 :param mappings: a sequence of dictionaries, each one containing the 

4723 state of the mapped row to be inserted, in terms of the attribute 

4724 names on the mapped class. If the mapping refers to multiple tables, 

4725 such as a joined-inheritance mapping, each dictionary must contain all 

4726 keys to be populated into all tables. 

4727 

4728 :param return_defaults: when True, the INSERT process will be altered 

4729 to ensure that newly generated primary key values will be fetched. 

4730 The rationale for this parameter is typically to enable 

4731 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4732 be bulk inserted. 

4733 

4734 .. note:: for backends that don't support RETURNING, the 

4735 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4736 parameter can significantly decrease performance as INSERT 

4737 statements can no longer be batched. See 

4738 :ref:`engine_insertmanyvalues` 

4739 for background on which backends are affected. 

4740 

4741 :param render_nulls: When True, a value of ``None`` will result 

4742 in a NULL value being included in the INSERT statement, rather 

4743 than the column being omitted from the INSERT. This allows all 

4744 the rows being INSERTed to have the identical set of columns which 

4745 allows the full set of rows to be batched to the DBAPI. Normally, 

4746 each column-set that contains a different combination of NULL values 

4747 than the previous row must omit a different series of columns from 

4748 the rendered INSERT statement, which means it must be emitted as a 

4749 separate statement. By passing this flag, the full set of rows 

4750 are guaranteed to be batchable into one batch; the cost however is 

4751 that server-side defaults which are invoked by an omitted column will 

4752 be skipped, so care must be taken to ensure that these are not 

4753 necessary. 

4754 

4755 .. warning:: 

4756 

4757 When this flag is set, **server side default SQL values will 

4758 not be invoked** for those columns that are inserted as NULL; 

4759 the NULL value will be sent explicitly. Care must be taken 

4760 to ensure that no server-side default functions need to be 

4761 invoked for the operation as a whole. 

4762 

4763 .. seealso:: 

4764 

4765 :doc:`queryguide/dml` 

4766 

4767 :meth:`.Session.bulk_save_objects` 

4768 

4769 :meth:`.Session.bulk_update_mappings` 

4770 

4771 """ 

4772 self._bulk_save_mappings( 

4773 mapper, 

4774 mappings, 

4775 isupdate=False, 

4776 isstates=False, 

4777 return_defaults=return_defaults, 

4778 update_changed_only=False, 

4779 render_nulls=render_nulls, 

4780 ) 

4781 

4782 def bulk_update_mappings( 

4783 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4784 ) -> None: 

4785 """Perform a bulk update of the given list of mapping dictionaries. 

4786 

4787 .. legacy:: 

4788 

4789 This method is a legacy feature as of the 2.0 series of 

4790 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4791 the sections :ref:`orm_queryguide_bulk_insert` and 

4792 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4793 implementation details with this method and adds new features 

4794 as well. 

4795 

4796 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4797 object, 

4798 representing the single kind of object represented within the mapping 

4799 list. 

4800 

4801 :param mappings: a sequence of dictionaries, each one containing the 

4802 state of the mapped row to be updated, in terms of the attribute names 

4803 on the mapped class. If the mapping refers to multiple tables, such 

4804 as a joined-inheritance mapping, each dictionary may contain keys 

4805 corresponding to all tables. All those keys which are present and 

4806 are not part of the primary key are applied to the SET clause of the 

4807 UPDATE statement; the primary key values, which are required, are 

4808 applied to the WHERE clause. 

4809 

4810 

4811 .. seealso:: 

4812 

4813 :doc:`queryguide/dml` 

4814 

4815 :meth:`.Session.bulk_insert_mappings` 

4816 

4817 :meth:`.Session.bulk_save_objects` 

4818 

4819 """ 

4820 self._bulk_save_mappings( 

4821 mapper, 

4822 mappings, 

4823 isupdate=True, 

4824 isstates=False, 

4825 return_defaults=False, 

4826 update_changed_only=False, 

4827 render_nulls=False, 

4828 ) 

4829 

4830 def _bulk_save_mappings( 

4831 self, 

4832 mapper: Mapper[_O], 

4833 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4834 *, 

4835 isupdate: bool, 

4836 isstates: bool, 

4837 return_defaults: bool, 

4838 update_changed_only: bool, 

4839 render_nulls: bool, 

4840 ) -> None: 

4841 mapper = _class_to_mapper(mapper) 

4842 self._flushing = True 

4843 

4844 transaction = self._autobegin_t()._begin() 

4845 try: 

4846 if isupdate: 

4847 bulk_persistence._bulk_update( 

4848 mapper, 

4849 mappings, 

4850 transaction, 

4851 isstates=isstates, 

4852 update_changed_only=update_changed_only, 

4853 ) 

4854 else: 

4855 bulk_persistence._bulk_insert( 

4856 mapper, 

4857 mappings, 

4858 transaction, 

4859 isstates=isstates, 

4860 return_defaults=return_defaults, 

4861 render_nulls=render_nulls, 

4862 ) 

4863 transaction.commit() 

4864 

4865 except: 

4866 with util.safe_reraise(): 

4867 transaction.rollback(_capture_exception=True) 

4868 finally: 

4869 self._flushing = False 

4870 

4871 def is_modified( 

4872 self, instance: object, include_collections: bool = True 

4873 ) -> bool: 

4874 r"""Return ``True`` if the given instance has locally 

4875 modified attributes. 

4876 

4877 This method retrieves the history for each instrumented 

4878 attribute on the instance and performs a comparison of the current 

4879 value to its previously flushed or committed value, if any. 

4880 

4881 It is in effect a more expensive and accurate 

4882 version of checking for the given instance in the 

4883 :attr:`.Session.dirty` collection; a full test for 

4884 each attribute's net "dirty" status is performed. 

4885 

4886 E.g.:: 

4887 

4888 return session.is_modified(someobject) 

4889 

4890 A few caveats to this method apply: 

4891 

4892 * Instances present in the :attr:`.Session.dirty` collection may 

4893 report ``False`` when tested with this method. This is because 

4894 the object may have received change events via attribute mutation, 

4895 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4896 is the same as that loaded from the database, resulting in no net 

4897 change here. 

4898 * Scalar attributes may not have recorded the previously set 

4899 value when a new value was applied, if the attribute was not loaded, 

4900 or was expired, at the time the new value was received - in these 

4901 cases, the attribute is assumed to have a change, even if there is 

4902 ultimately no net change against its database value. SQLAlchemy in 

4903 most cases does not need the "old" value when a set event occurs, so 

4904 it skips the expense of a SQL call if the old value isn't present, 

4905 based on the assumption that an UPDATE of the scalar value is 

4906 usually needed, and in those few cases where it isn't, is less 

4907 expensive on average than issuing a defensive SELECT. 

4908 

4909 The "old" value is fetched unconditionally upon set only if the 

4910 attribute container has the ``active_history`` flag set to ``True``. 

4911 This flag is set typically for primary key attributes and scalar 

4912 object references that are not a simple many-to-one. To set this 

4913 flag for any arbitrary mapped column, use the ``active_history`` 

4914 argument with :func:`.column_property`. 

4915 

4916 :param instance: mapped instance to be tested for pending changes. 

4917 :param include_collections: Indicates if multivalued collections 

4918 should be included in the operation. Setting this to ``False`` is a 

4919 way to detect only local-column based properties (i.e. scalar columns 

4920 or many-to-one foreign keys) that would result in an UPDATE for this 

4921 instance upon flush. 

4922 

4923 """ 

4924 state = object_state(instance) 

4925 

4926 if not state.modified: 

4927 return False 

4928 

4929 dict_ = state.dict 

4930 

4931 for attr in state.manager.attributes: 

4932 if ( 

4933 not include_collections 

4934 and hasattr(attr.impl, "get_collection") 

4935 ) or not hasattr(attr.impl, "get_history"): 

4936 continue 

4937 

4938 (added, unchanged, deleted) = attr.impl.get_history( 

4939 state, dict_, passive=PassiveFlag.NO_CHANGE 

4940 ) 

4941 

4942 if added or deleted: 

4943 return True 

4944 else: 

4945 return False 

4946 

4947 @property 

4948 def is_active(self) -> bool: 

4949 """True if this :class:`.Session` not in "partial rollback" state. 

4950 

4951 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4952 a new transaction immediately, so this attribute will be False 

4953 when the :class:`_orm.Session` is first instantiated. 

4954 

4955 "partial rollback" state typically indicates that the flush process 

4956 of the :class:`_orm.Session` has failed, and that the 

4957 :meth:`_orm.Session.rollback` method must be emitted in order to 

4958 fully roll back the transaction. 

4959 

4960 If this :class:`_orm.Session` is not in a transaction at all, the 

4961 :class:`_orm.Session` will autobegin when it is first used, so in this 

4962 case :attr:`_orm.Session.is_active` will return True. 

4963 

4964 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4965 and that transaction has not been rolled back internally, the 

4966 :attr:`_orm.Session.is_active` will also return True. 

4967 

4968 .. seealso:: 

4969 

4970 :ref:`faq_session_rollback` 

4971 

4972 :meth:`_orm.Session.in_transaction` 

4973 

4974 """ 

4975 return self._transaction is None or self._transaction.is_active 

4976 

4977 @property 

4978 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4979 """The set of all persistent states considered dirty. 

4980 

4981 This method returns all states that were modified including 

4982 those that were possibly deleted. 

4983 

4984 """ 

4985 return self.identity_map._dirty_states() 

4986 

4987 @property 

4988 def dirty(self) -> IdentitySet: 

4989 """The set of all persistent instances considered dirty. 

4990 

4991 E.g.:: 

4992 

4993 some_mapped_object in session.dirty 

4994 

4995 Instances are considered dirty when they were modified but not 

4996 deleted. 

4997 

4998 Note that this 'dirty' calculation is 'optimistic'; most 

4999 attribute-setting or collection modification operations will 

5000 mark an instance as 'dirty' and place it in this set, even if 

5001 there is no net change to the attribute's value. At flush 

5002 time, the value of each attribute is compared to its 

5003 previously saved value, and if there's no net change, no SQL 

5004 operation will occur (this is a more expensive operation so 

5005 it's only done at flush time). 

5006 

5007 To check if an instance has actionable net changes to its 

5008 attributes, use the :meth:`.Session.is_modified` method. 

5009 

5010 """ 

5011 return IdentitySet( 

5012 [ 

5013 state.obj() 

5014 for state in self._dirty_states 

5015 if state not in self._deleted 

5016 ] 

5017 ) 

5018 

5019 @property 

5020 def deleted(self) -> IdentitySet: 

5021 "The set of all instances marked as 'deleted' within this ``Session``" 

5022 

5023 return util.IdentitySet(list(self._deleted.values())) 

5024 

5025 @property 

5026 def new(self) -> IdentitySet: 

5027 "The set of all instances marked as 'new' within this ``Session``." 

5028 

5029 return util.IdentitySet(list(self._new.values())) 

5030 

5031 

5032_S = TypeVar("_S", bound="Session") 

5033 

5034 

5035class sessionmaker(_SessionClassMethods, Generic[_S]): 

5036 """A configurable :class:`.Session` factory. 

5037 

5038 The :class:`.sessionmaker` factory generates new 

5039 :class:`.Session` objects when called, creating them given 

5040 the configurational arguments established here. 

5041 

5042 e.g.:: 

5043 

5044 from sqlalchemy import create_engine 

5045 from sqlalchemy.orm import sessionmaker 

5046 

5047 # an Engine, which the Session will use for connection 

5048 # resources 

5049 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

5050 

5051 Session = sessionmaker(engine) 

5052 

5053 with Session() as session: 

5054 session.add(some_object) 

5055 session.add(some_other_object) 

5056 session.commit() 

5057 

5058 Context manager use is optional; otherwise, the returned 

5059 :class:`_orm.Session` object may be closed explicitly via the 

5060 :meth:`_orm.Session.close` method. Using a 

5061 ``try:/finally:`` block is optional, however will ensure that the close 

5062 takes place even if there are database errors:: 

5063 

5064 session = Session() 

5065 try: 

5066 session.add(some_object) 

5067 session.add(some_other_object) 

5068 session.commit() 

5069 finally: 

5070 session.close() 

5071 

5072 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5073 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5074 for :class:`_engine.Connection` objects. In this way it also includes 

5075 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5076 manager which both begins and commits a transaction, as well as closes 

5077 out the :class:`_orm.Session` when complete, rolling back the transaction 

5078 if any errors occur:: 

5079 

5080 Session = sessionmaker(engine) 

5081 

5082 with Session.begin() as session: 

5083 session.add(some_object) 

5084 session.add(some_other_object) 

5085 # commits transaction, closes session 

5086 

5087 .. versionadded:: 1.4 

5088 

5089 When calling upon :class:`_orm.sessionmaker` to construct a 

5090 :class:`_orm.Session`, keyword arguments may also be passed to the 

5091 method; these arguments will override that of the globally configured 

5092 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5093 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5094 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5095 

5096 Session = sessionmaker(engine) 

5097 

5098 # bind an individual session to a connection 

5099 

5100 with engine.connect() as connection: 

5101 with Session(bind=connection) as session: 

5102 ... # work with session 

5103 

5104 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5105 can be used to specify additional keyword arguments to the factory, which 

5106 will take effect for subsequent :class:`.Session` objects generated. This 

5107 is usually used to associate one or more :class:`_engine.Engine` objects 

5108 with an existing 

5109 :class:`.sessionmaker` factory before it is first used:: 

5110 

5111 # application starts, sessionmaker does not have 

5112 # an engine bound yet 

5113 Session = sessionmaker() 

5114 

5115 # ... later, when an engine URL is read from a configuration 

5116 # file or other events allow the engine to be created 

5117 engine = create_engine("sqlite:///foo.db") 

5118 Session.configure(bind=engine) 

5119 

5120 sess = Session() 

5121 # work with session 

5122 

5123 .. seealso:: 

5124 

5125 :ref:`session_getting` - introductory text on creating 

5126 sessions using :class:`.sessionmaker`. 

5127 

5128 """ 

5129 

5130 class_: Type[_S] 

5131 

5132 @overload 

5133 def __init__( 

5134 self, 

5135 bind: Optional[_SessionBind] = ..., 

5136 *, 

5137 class_: Type[_S], 

5138 autoflush: bool = ..., 

5139 expire_on_commit: bool = ..., 

5140 info: Optional[_InfoType] = ..., 

5141 **kw: Any, 

5142 ): ... 

5143 

5144 @overload 

5145 def __init__( 

5146 self: "sessionmaker[Session]", 

5147 bind: Optional[_SessionBind] = ..., 

5148 *, 

5149 autoflush: bool = ..., 

5150 expire_on_commit: bool = ..., 

5151 info: Optional[_InfoType] = ..., 

5152 **kw: Any, 

5153 ): ... 

5154 

5155 def __init__( 

5156 self, 

5157 bind: Optional[_SessionBind] = None, 

5158 *, 

5159 class_: Type[_S] = Session, # type: ignore 

5160 autoflush: bool = True, 

5161 expire_on_commit: bool = True, 

5162 info: Optional[_InfoType] = None, 

5163 **kw: Any, 

5164 ): 

5165 r"""Construct a new :class:`.sessionmaker`. 

5166 

5167 All arguments here except for ``class_`` correspond to arguments 

5168 accepted by :class:`.Session` directly. See the 

5169 :meth:`.Session.__init__` docstring for more details on parameters. 

5170 

5171 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5172 with 

5173 which newly created :class:`.Session` objects will be associated. 

5174 :param class\_: class to use in order to create new :class:`.Session` 

5175 objects. Defaults to :class:`.Session`. 

5176 :param autoflush: The autoflush setting to use with newly created 

5177 :class:`.Session` objects. 

5178 

5179 .. seealso:: 

5180 

5181 :ref:`session_flushing` - additional background on autoflush 

5182 

5183 :param expire_on_commit=True: the 

5184 :paramref:`_orm.Session.expire_on_commit` setting to use 

5185 with newly created :class:`.Session` objects. 

5186 

5187 :param info: optional dictionary of information that will be available 

5188 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5189 replaced, when the ``info`` parameter is specified to the specific 

5190 :class:`.Session` construction operation. 

5191 

5192 :param \**kw: all other keyword arguments are passed to the 

5193 constructor of newly created :class:`.Session` objects. 

5194 

5195 """ 

5196 kw["bind"] = bind 

5197 kw["autoflush"] = autoflush 

5198 kw["expire_on_commit"] = expire_on_commit 

5199 if info is not None: 

5200 kw["info"] = info 

5201 self.kw = kw 

5202 # make our own subclass of the given class, so that 

5203 # events can be associated with it specifically. 

5204 self.class_ = type(class_.__name__, (class_,), {}) 

5205 

5206 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5207 """Produce a context manager that both provides a new 

5208 :class:`_orm.Session` as well as a transaction that commits. 

5209 

5210 

5211 e.g.:: 

5212 

5213 Session = sessionmaker(some_engine) 

5214 

5215 with Session.begin() as session: 

5216 session.add(some_object) 

5217 

5218 # commits transaction, closes session 

5219 

5220 .. versionadded:: 1.4 

5221 

5222 

5223 """ 

5224 

5225 session = self() 

5226 return session._maker_context_manager() 

5227 

5228 def __call__(self, **local_kw: Any) -> _S: 

5229 """Produce a new :class:`.Session` object using the configuration 

5230 established in this :class:`.sessionmaker`. 

5231 

5232 In Python, the ``__call__`` method is invoked on an object when 

5233 it is "called" in the same way as a function:: 

5234 

5235 Session = sessionmaker(some_engine) 

5236 session = Session() # invokes sessionmaker.__call__() 

5237 

5238 """ 

5239 for k, v in self.kw.items(): 

5240 if k == "info" and "info" in local_kw: 

5241 d = v.copy() 

5242 d.update(local_kw["info"]) 

5243 local_kw["info"] = d 

5244 else: 

5245 local_kw.setdefault(k, v) 

5246 return self.class_(**local_kw) 

5247 

5248 def configure(self, **new_kw: Any) -> None: 

5249 """(Re)configure the arguments for this sessionmaker. 

5250 

5251 e.g.:: 

5252 

5253 Session = sessionmaker() 

5254 

5255 Session.configure(bind=create_engine("sqlite://")) 

5256 """ 

5257 self.kw.update(new_kw) 

5258 

5259 def __repr__(self) -> str: 

5260 return "%s(class_=%r, %s)" % ( 

5261 self.__class__.__name__, 

5262 self.class_.__name__, 

5263 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5264 ) 

5265 

5266 

5267def close_all_sessions() -> None: 

5268 """Close all sessions in memory. 

5269 

5270 This function consults a global registry of all :class:`.Session` objects 

5271 and calls :meth:`.Session.close` on them, which resets them to a clean 

5272 state. 

5273 

5274 This function is not for general use but may be useful for test suites 

5275 within the teardown scheme. 

5276 

5277 """ 

5278 

5279 for sess in _sessions.values(): 

5280 sess.close() 

5281 

5282 

5283def make_transient(instance: object) -> None: 

5284 """Alter the state of the given instance so that it is :term:`transient`. 

5285 

5286 .. note:: 

5287 

5288 :func:`.make_transient` is a special-case function for 

5289 advanced use cases only. 

5290 

5291 The given mapped instance is assumed to be in the :term:`persistent` or 

5292 :term:`detached` state. The function will remove its association with any 

5293 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5294 effect is that the object will behave as though it were newly constructed, 

5295 except retaining any attribute / collection values that were loaded at the 

5296 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5297 if this object had been deleted as a result of using 

5298 :meth:`.Session.delete`. 

5299 

5300 .. warning:: 

5301 

5302 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5303 load ORM-mapped attributes that are not currently loaded at the time 

5304 the function is called. This includes attributes which: 

5305 

5306 * were expired via :meth:`.Session.expire` 

5307 

5308 * were expired as the natural effect of committing a session 

5309 transaction, e.g. :meth:`.Session.commit` 

5310 

5311 * are normally :term:`lazy loaded` but are not currently loaded 

5312 

5313 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5314 not yet loaded 

5315 

5316 * were not present in the query which loaded this object, such as that 

5317 which is common in joined table inheritance and other scenarios. 

5318 

5319 After :func:`.make_transient` is called, unloaded attributes such 

5320 as those above will normally resolve to the value ``None`` when 

5321 accessed, or an empty collection for a collection-oriented attribute. 

5322 As the object is transient and un-associated with any database 

5323 identity, it will no longer retrieve these values. 

5324 

5325 .. seealso:: 

5326 

5327 :func:`.make_transient_to_detached` 

5328 

5329 """ 

5330 state = attributes.instance_state(instance) 

5331 s = _state_session(state) 

5332 if s: 

5333 s._expunge_states([state]) 

5334 

5335 # remove expired state 

5336 state.expired_attributes.clear() 

5337 

5338 # remove deferred callables 

5339 if state.callables: 

5340 del state.callables 

5341 

5342 if state.key: 

5343 del state.key 

5344 if state._deleted: 

5345 del state._deleted 

5346 

5347 

5348def make_transient_to_detached(instance: object) -> None: 

5349 """Make the given transient instance :term:`detached`. 

5350 

5351 .. note:: 

5352 

5353 :func:`.make_transient_to_detached` is a special-case function for 

5354 advanced use cases only. 

5355 

5356 All attribute history on the given instance 

5357 will be reset as though the instance were freshly loaded 

5358 from a query. Missing attributes will be marked as expired. 

5359 The primary key attributes of the object, which are required, will be made 

5360 into the "key" of the instance. 

5361 

5362 The object can then be added to a session, or merged 

5363 possibly with the load=False flag, at which point it will look 

5364 as if it were loaded that way, without emitting SQL. 

5365 

5366 This is a special use case function that differs from a normal 

5367 call to :meth:`.Session.merge` in that a given persistent state 

5368 can be manufactured without any SQL calls. 

5369 

5370 .. seealso:: 

5371 

5372 :func:`.make_transient` 

5373 

5374 :meth:`.Session.enable_relationship_loading` 

5375 

5376 """ 

5377 state = attributes.instance_state(instance) 

5378 if state.session_id or state.key: 

5379 raise sa_exc.InvalidRequestError("Given object must be transient") 

5380 state.key = state.mapper._identity_key_from_state(state) 

5381 if state._deleted: 

5382 del state._deleted 

5383 state._commit_all(state.dict) 

5384 state._expire_attributes(state.dict, state.unloaded) 

5385 

5386 

5387def object_session(instance: object) -> Optional[Session]: 

5388 """Return the :class:`.Session` to which the given instance belongs. 

5389 

5390 This is essentially the same as the :attr:`.InstanceState.session` 

5391 accessor. See that attribute for details. 

5392 

5393 """ 

5394 

5395 try: 

5396 state = attributes.instance_state(instance) 

5397 except exc.NO_STATE as err: 

5398 raise exc.UnmappedInstanceError(instance) from err 

5399 else: 

5400 return _state_session(state) 

5401 

5402 

5403_new_sessionid = util.counter()