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

1444 statements  

1# orm/session.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

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

9 

10from __future__ import annotations 

11 

12import contextlib 

13from enum import Enum 

14import itertools 

15import sys 

16import typing 

17from typing import Any 

18from typing import Callable 

19from typing import cast 

20from typing import Dict 

21from typing import Generic 

22from typing import Iterable 

23from typing import Iterator 

24from typing import List 

25from typing import NoReturn 

26from typing import Optional 

27from typing import overload 

28from typing import Protocol 

29from typing import Sequence 

30from typing import Set 

31from typing import Tuple 

32from typing import Type 

33from typing import TYPE_CHECKING 

34from typing import TypeVar 

35from typing import Union 

36import weakref 

37 

38from . import attributes 

39from . import bulk_persistence 

40from . import context 

41from . import descriptor_props 

42from . import exc 

43from . import identity 

44from . import loading 

45from . import query 

46from . import state as statelib 

47from ._typing import _O 

48from ._typing import insp_is_mapper 

49from ._typing import is_composite_class 

50from ._typing import is_orm_option 

51from ._typing import is_user_defined_option 

52from .base import _class_to_mapper 

53from .base import _none_set 

54from .base import _state_mapper 

55from .base import instance_str 

56from .base import LoaderCallableStatus 

57from .base import object_mapper 

58from .base import object_state 

59from .base import PassiveFlag 

60from .base import state_str 

61from .context import _ORMCompileState 

62from .context import FromStatement 

63from .identity import IdentityMap 

64from .query import Query 

65from .state import InstanceState 

66from .state_changes import _StateChange 

67from .state_changes import _StateChangeState 

68from .state_changes import _StateChangeStates 

69from .unitofwork import UOWTransaction 

70from .. import engine 

71from .. import exc as sa_exc 

72from .. import sql 

73from .. import util 

74from ..engine import Connection 

75from ..engine import Engine 

76from ..engine.util import TransactionalContext 

77from ..event import dispatcher 

78from ..event import EventTarget 

79from ..inspection import inspect 

80from ..inspection import Inspectable 

81from ..sql import coercions 

82from ..sql import dml 

83from ..sql import roles 

84from ..sql import Select 

85from ..sql import TableClause 

86from ..sql import visitors 

87from ..sql.base import _NoArg 

88from ..sql.base import CompileState 

89from ..sql.schema import Table 

90from ..sql.selectable import ForUpdateArg 

91from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

92from ..util import deprecated_params 

93from ..util import IdentitySet 

94from ..util.typing import Literal 

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 CursorResult 

111 from ..engine import Result 

112 from ..engine import Row 

113 from ..engine import RowMapping 

114 from ..engine.base import Transaction 

115 from ..engine.base import TwoPhaseTransaction 

116 from ..engine.interfaces import _CoreAnyExecuteParams 

117 from ..engine.interfaces import _CoreSingleExecuteParams 

118 from ..engine.interfaces import _ExecuteOptions 

119 from ..engine.interfaces import CoreExecuteOptionsParameter 

120 from ..engine.result import ScalarResult 

121 from ..event import _InstanceLevelDispatch 

122 from ..sql._typing import _ColumnsClauseArgument 

123 from ..sql._typing import _InfoType 

124 from ..sql._typing import _T0 

125 from ..sql._typing import _T1 

126 from ..sql._typing import _T2 

127 from ..sql._typing import _T3 

128 from ..sql._typing import _T4 

129 from ..sql._typing import _T5 

130 from ..sql._typing import _T6 

131 from ..sql._typing import _T7 

132 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

133 from ..sql.base import Executable 

134 from ..sql.base import ExecutableOption 

135 from ..sql.dml import UpdateBase 

136 from ..sql.elements import ClauseElement 

137 from ..sql.roles import TypedColumnsClauseRole 

138 from ..sql.selectable import ForUpdateParameter 

139 from ..sql.selectable import TypedReturnsRows 

140 

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

142_Ts = TypeVarTuple("_Ts") 

143 

144__all__ = [ 

145 "Session", 

146 "SessionTransaction", 

147 "sessionmaker", 

148 "ORMExecuteState", 

149 "close_all_sessions", 

150 "make_transient", 

151 "make_transient_to_detached", 

152 "object_session", 

153] 

154 

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

156 weakref.WeakValueDictionary() 

157) 

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

159""" 

160 

161statelib._sessions = _sessions 

162 

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

164 

165_BindArguments = Dict[str, Any] 

166 

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

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

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

170 

171JoinTransactionMode = Literal[ 

172 "conditional_savepoint", 

173 "rollback_only", 

174 "control_fully", 

175 "create_savepoint", 

176] 

177 

178 

179class _ConnectionCallableProto(Protocol): 

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

181 

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

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

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

185 as persistence time. 

186 

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

188 is established when using the horizontal sharding extension. 

189 

190 """ 

191 

192 def __call__( 

193 self, 

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

195 instance: Optional[object] = None, 

196 **kw: Any, 

197 ) -> Connection: ... 

198 

199 

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

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

202 associated, if any. 

203 """ 

204 return state.session 

205 

206 

207class _SessionClassMethods: 

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

209 

210 @classmethod 

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

212 def identity_key( 

213 cls, 

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

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

216 *, 

217 instance: Optional[Any] = None, 

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

219 identity_token: Optional[Any] = None, 

220 ) -> _IdentityKeyType[Any]: 

221 """Return an identity key. 

222 

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

224 

225 """ 

226 return util.preloaded.orm_util.identity_key( 

227 class_, 

228 ident, 

229 instance=instance, 

230 row=row, 

231 identity_token=identity_token, 

232 ) 

233 

234 @classmethod 

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

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

237 

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

239 

240 """ 

241 

242 return object_session(instance) 

243 

244 

245class SessionTransactionState(_StateChangeState): 

246 ACTIVE = 1 

247 PREPARED = 2 

248 COMMITTED = 3 

249 DEACTIVE = 4 

250 CLOSED = 5 

251 PROVISIONING_CONNECTION = 6 

252 

253 

254# backwards compatibility 

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

256 SessionTransactionState 

257) 

258 

259 

260class ORMExecuteState(util.MemoizedSlots): 

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

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

263 

264 .. versionadded:: 1.4 

265 

266 .. seealso:: 

267 

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

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

270 

271 """ 

272 

273 __slots__ = ( 

274 "session", 

275 "statement", 

276 "parameters", 

277 "execution_options", 

278 "local_execution_options", 

279 "bind_arguments", 

280 "identity_token", 

281 "_compile_state_cls", 

282 "_starting_event_idx", 

283 "_events_todo", 

284 "_update_execution_options", 

285 ) 

286 

287 session: Session 

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

289 

290 statement: Executable 

291 """The SQL statement being invoked. 

292 

293 For an ORM selection as would 

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

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

296 """ 

297 

298 parameters: Optional[_CoreAnyExecuteParams] 

299 """Dictionary of parameters that was passed to 

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

301 

302 execution_options: _ExecuteOptions 

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

304 

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

306 locally passed execution options. 

307 

308 .. seealso:: 

309 

310 :attr:`_orm.ORMExecuteState.local_execution_options` 

311 

312 :meth:`_sql.Executable.execution_options` 

313 

314 :ref:`orm_queryguide_execution_options` 

315 

316 """ 

317 

318 local_execution_options: _ExecuteOptions 

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

320 :meth:`.Session.execute` method. 

321 

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

323 being invoked. 

324 

325 .. seealso:: 

326 

327 :attr:`_orm.ORMExecuteState.execution_options` 

328 

329 """ 

330 

331 bind_arguments: _BindArguments 

332 """The dictionary passed as the 

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

334 

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

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

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

338 

339 """ 

340 

341 _compile_state_cls: Optional[Type[_ORMCompileState]] 

342 _starting_event_idx: int 

343 _events_todo: List[Any] 

344 _update_execution_options: Optional[_ExecuteOptions] 

345 

346 def __init__( 

347 self, 

348 session: Session, 

349 statement: Executable, 

350 parameters: Optional[_CoreAnyExecuteParams], 

351 execution_options: _ExecuteOptions, 

352 bind_arguments: _BindArguments, 

353 compile_state_cls: Optional[Type[_ORMCompileState]], 

354 events_todo: List[_InstanceLevelDispatch[Session]], 

355 ): 

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

357 

358 this object is constructed internally. 

359 

360 """ 

361 self.session = session 

362 self.statement = statement 

363 self.parameters = parameters 

364 self.local_execution_options = execution_options 

365 self.execution_options = statement._execution_options.union( 

366 execution_options 

367 ) 

368 self.bind_arguments = bind_arguments 

369 self._compile_state_cls = compile_state_cls 

370 self._events_todo = list(events_todo) 

371 

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

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

374 

375 def invoke_statement( 

376 self, 

377 statement: Optional[Executable] = None, 

378 params: Optional[_CoreAnyExecuteParams] = None, 

379 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

380 bind_arguments: Optional[_BindArguments] = None, 

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

382 """Execute the statement represented by this 

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

384 already proceeded. 

385 

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

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

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

389 that want to override how the ultimate 

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

391 retrieve results from an offline cache or which concatenate results 

392 from multiple executions. 

393 

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

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

396 is propagated to the calling 

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

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

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

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

401 

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

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

404 

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

406 which will be merged into the existing 

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

408 

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

410 for executemany executions. 

411 

412 :param execution_options: optional dictionary of execution options 

413 will be merged into the existing 

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

415 :class:`.ORMExecuteState`. 

416 

417 :param bind_arguments: optional dictionary of bind_arguments 

418 which will be merged amongst the current 

419 :attr:`.ORMExecuteState.bind_arguments` 

420 of this :class:`.ORMExecuteState`. 

421 

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

423 

424 .. seealso:: 

425 

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

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

428 

429 

430 """ 

431 

432 if statement is None: 

433 statement = self.statement 

434 

435 _bind_arguments = dict(self.bind_arguments) 

436 if bind_arguments: 

437 _bind_arguments.update(bind_arguments) 

438 _bind_arguments["_sa_skip_events"] = True 

439 

440 _params: Optional[_CoreAnyExecuteParams] 

441 if params: 

442 if self.is_executemany: 

443 _params = [] 

444 exec_many_parameters = cast( 

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

446 ) 

447 for _existing_params, _new_params in itertools.zip_longest( 

448 exec_many_parameters, 

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

450 ): 

451 if _existing_params is None or _new_params is None: 

452 raise sa_exc.InvalidRequestError( 

453 f"Can't apply executemany parameters to " 

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

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

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

457 f"to ORMExecuteState.invoke_statement() " 

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

459 ) 

460 _existing_params = dict(_existing_params) 

461 _existing_params.update(_new_params) 

462 _params.append(_existing_params) 

463 else: 

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

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

466 else: 

467 _params = self.parameters 

468 

469 _execution_options = self.local_execution_options 

470 if execution_options: 

471 _execution_options = _execution_options.union(execution_options) 

472 

473 return self.session._execute_internal( 

474 statement, 

475 _params, 

476 execution_options=_execution_options, 

477 bind_arguments=_bind_arguments, 

478 _parent_execute_state=self, 

479 ) 

480 

481 @property 

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

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

484 

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

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

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

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

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

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

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

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

493 would be selected. 

494 

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

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

497 way of getting this mapper. 

498 

499 .. versionadded:: 1.4.0b2 

500 

501 .. seealso:: 

502 

503 :attr:`_orm.ORMExecuteState.all_mappers` 

504 

505 

506 """ 

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

508 return mp 

509 

510 @property 

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

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

513 involved at the top level of this statement. 

514 

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

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

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

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

519 

520 .. versionadded:: 1.4.0b2 

521 

522 .. seealso:: 

523 

524 :attr:`_orm.ORMExecuteState.bind_mapper` 

525 

526 

527 

528 """ 

529 if not self.is_orm_statement: 

530 return [] 

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

532 result = [] 

533 seen = set() 

534 for d in self.statement.column_descriptions: 

535 ent = d["entity"] 

536 if ent: 

537 insp = inspect(ent, raiseerr=False) 

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

539 seen.add(insp.mapper) 

540 result.append(insp.mapper) 

541 return result 

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

543 return [self.bind_mapper] 

544 else: 

545 return [] 

546 

547 @property 

548 def is_orm_statement(self) -> bool: 

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

550 

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

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

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

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

555 and no ORM-level automation takes place. 

556 

557 """ 

558 return self._compile_state_cls is not None 

559 

560 @property 

561 def is_executemany(self) -> bool: 

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

563 dictionaries with more than one dictionary. 

564 

565 .. versionadded:: 2.0 

566 

567 """ 

568 return isinstance(self.parameters, list) 

569 

570 @property 

571 def is_select(self) -> bool: 

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

573 

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

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

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

577 ``select(Entity).from_statement(select(..))`` 

578 

579 """ 

580 return self.statement.is_select 

581 

582 @property 

583 def is_from_statement(self) -> bool: 

584 """return True if this operation is a 

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

586 

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

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

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

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

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

592 :class:`_sql.Select` construct. 

593 

594 .. versionadded:: 2.0.30 

595 

596 """ 

597 return self.statement.is_from_statement 

598 

599 @property 

600 def is_insert(self) -> bool: 

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

602 

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

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

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

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

607 

608 """ 

609 return self.statement.is_dml and self.statement.is_insert 

610 

611 @property 

612 def is_update(self) -> bool: 

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

614 

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

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

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

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

619 

620 """ 

621 return self.statement.is_dml and self.statement.is_update 

622 

623 @property 

624 def is_delete(self) -> bool: 

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

626 

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

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

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

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

631 

632 """ 

633 return self.statement.is_dml and self.statement.is_delete 

634 

635 @property 

636 def _is_crud(self) -> bool: 

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

638 

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

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

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

642 

643 def _orm_compile_options( 

644 self, 

645 ) -> Optional[ 

646 Union[ 

647 context._ORMCompileState.default_compile_options, 

648 Type[context._ORMCompileState.default_compile_options], 

649 ] 

650 ]: 

651 if not self.is_select: 

652 return None 

653 try: 

654 opts = self.statement._compile_options 

655 except AttributeError: 

656 return None 

657 

658 if opts is not None and opts.isinstance( 

659 context._ORMCompileState.default_compile_options 

660 ): 

661 return opts # type: ignore 

662 else: 

663 return None 

664 

665 @property 

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

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

668 for a lazy load operation. 

669 

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

671 sharding extension, where it is available within specific query 

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

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

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

675 compilation time. 

676 

677 """ 

678 return self.load_options._lazy_loaded_from 

679 

680 @property 

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

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

683 

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

685 when a particular object or collection is being loaded. 

686 

687 """ 

688 opts = self._orm_compile_options() 

689 if opts is not None: 

690 return opts._current_path 

691 else: 

692 return None 

693 

694 @property 

695 def is_column_load(self) -> bool: 

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

697 attributes on an existing ORM object. 

698 

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

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

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

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

703 loaded. 

704 

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

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

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

708 and loader options travelling with the instance 

709 will have already been added to the query. 

710 

711 .. versionadded:: 1.4.0b2 

712 

713 .. seealso:: 

714 

715 :attr:`_orm.ORMExecuteState.is_relationship_load` 

716 

717 """ 

718 opts = self._orm_compile_options() 

719 return opts is not None and opts._for_refresh_state 

720 

721 @property 

722 def is_relationship_load(self) -> bool: 

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

724 relationship. 

725 

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

727 SelectInLoader, SubqueryLoader, or similar, and the entire 

728 SELECT statement being emitted is on behalf of a relationship 

729 load. 

730 

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

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

733 capable of being propagated to relationship loaders and should 

734 be already present. 

735 

736 .. seealso:: 

737 

738 :attr:`_orm.ORMExecuteState.is_column_load` 

739 

740 """ 

741 opts = self._orm_compile_options() 

742 if opts is None: 

743 return False 

744 path = self.loader_strategy_path 

745 return path is not None and not path.is_root 

746 

747 @property 

748 def load_options( 

749 self, 

750 ) -> Union[ 

751 context.QueryContext.default_load_options, 

752 Type[context.QueryContext.default_load_options], 

753 ]: 

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

755 

756 if not self.is_select: 

757 raise sa_exc.InvalidRequestError( 

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

759 "so there are no load options." 

760 ) 

761 

762 lo: Union[ 

763 context.QueryContext.default_load_options, 

764 Type[context.QueryContext.default_load_options], 

765 ] = self.execution_options.get( 

766 "_sa_orm_load_options", context.QueryContext.default_load_options 

767 ) 

768 return lo 

769 

770 @property 

771 def update_delete_options( 

772 self, 

773 ) -> Union[ 

774 bulk_persistence._BulkUDCompileState.default_update_options, 

775 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

776 ]: 

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

778 execution.""" 

779 

780 if not self._is_crud: 

781 raise sa_exc.InvalidRequestError( 

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

783 "statement so there are no update options." 

784 ) 

785 uo: Union[ 

786 bulk_persistence._BulkUDCompileState.default_update_options, 

787 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

788 ] = self.execution_options.get( 

789 "_sa_orm_update_options", 

790 bulk_persistence._BulkUDCompileState.default_update_options, 

791 ) 

792 return uo 

793 

794 @property 

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

796 return [ 

797 opt 

798 for opt in self.statement._with_options 

799 if is_orm_option(opt) and not opt._is_compile_state 

800 ] 

801 

802 @property 

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

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

805 associated with the statement being invoked. 

806 

807 """ 

808 return [ 

809 opt 

810 for opt in self.statement._with_options 

811 if is_user_defined_option(opt) 

812 ] 

813 

814 

815class SessionTransactionOrigin(Enum): 

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

817 

818 This enumeration is present on the 

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

820 :class:`.SessionTransaction` object. 

821 

822 .. versionadded:: 2.0 

823 

824 """ 

825 

826 AUTOBEGIN = 0 

827 """transaction were started by autobegin""" 

828 

829 BEGIN = 1 

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

831 

832 BEGIN_NESTED = 2 

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

834 

835 SUBTRANSACTION = 3 

836 """transaction is an internal "subtransaction" """ 

837 

838 

839class SessionTransaction(_StateChange, TransactionalContext): 

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

841 

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

843 :meth:`_orm.Session.begin` 

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

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

846 transactions. 

847 

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

849 at: :ref:`unitofwork_transaction`. 

850 

851 

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

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

854 

855 .. seealso:: 

856 

857 :ref:`unitofwork_transaction` 

858 

859 :meth:`.Session.begin` 

860 

861 :meth:`.Session.begin_nested` 

862 

863 :meth:`.Session.rollback` 

864 

865 :meth:`.Session.commit` 

866 

867 :meth:`.Session.in_transaction` 

868 

869 :meth:`.Session.in_nested_transaction` 

870 

871 :meth:`.Session.get_transaction` 

872 

873 :meth:`.Session.get_nested_transaction` 

874 

875 

876 """ 

877 

878 _rollback_exception: Optional[BaseException] = None 

879 

880 _connections: Dict[ 

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

882 ] 

883 session: Session 

884 _parent: Optional[SessionTransaction] 

885 

886 _state: SessionTransactionState 

887 

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

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

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

891 _key_switches: weakref.WeakKeyDictionary[ 

892 InstanceState[Any], Tuple[Any, Any] 

893 ] 

894 

895 origin: SessionTransactionOrigin 

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

897 

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

899 enumeration indicating the source event that led to constructing 

900 this :class:`_orm.SessionTransaction`. 

901 

902 .. versionadded:: 2.0 

903 

904 """ 

905 

906 nested: bool = False 

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

908 

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

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

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

912 

913 .. seealso:: 

914 

915 :attr:`.SessionTransaction.origin` 

916 

917 """ 

918 

919 def __init__( 

920 self, 

921 session: Session, 

922 origin: SessionTransactionOrigin, 

923 parent: Optional[SessionTransaction] = None, 

924 ): 

925 TransactionalContext._trans_ctx_check(session) 

926 

927 self.session = session 

928 self._connections = {} 

929 self._parent = parent 

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

931 self.origin = origin 

932 

933 if session._close_state is _SessionCloseState.CLOSED: 

934 raise sa_exc.InvalidRequestError( 

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

936 "to handle any more transaction requests." 

937 ) 

938 

939 if nested: 

940 if not parent: 

941 raise sa_exc.InvalidRequestError( 

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

943 "transaction is in progress" 

944 ) 

945 

946 self._previous_nested_transaction = session._nested_transaction 

947 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

948 assert parent is not None 

949 else: 

950 assert parent is None 

951 

952 self._state = SessionTransactionState.ACTIVE 

953 

954 self._take_snapshot() 

955 

956 # make sure transaction is assigned before we call the 

957 # dispatch 

958 self.session._transaction = self 

959 

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

961 

962 def _raise_for_prerequisite_state( 

963 self, operation_name: str, state: _StateChangeState 

964 ) -> NoReturn: 

965 if state is SessionTransactionState.DEACTIVE: 

966 if self._rollback_exception: 

967 raise sa_exc.PendingRollbackError( 

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

969 "due to a previous exception during flush." 

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

971 "first issue Session.rollback()." 

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

973 code="7s2a", 

974 ) 

975 else: 

976 raise sa_exc.InvalidRequestError( 

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

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

979 "can be emitted within this transaction." 

980 ) 

981 elif state is SessionTransactionState.CLOSED: 

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

983 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

984 raise sa_exc.InvalidRequestError( 

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

986 "operations are not permitted", 

987 code="isce", 

988 ) 

989 else: 

990 raise sa_exc.InvalidRequestError( 

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

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

993 ) 

994 

995 @property 

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

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

998 :class:`.SessionTransaction`. 

999 

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

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

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

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

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

1005 "nested" / SAVEPOINT transaction. If the 

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

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

1008 

1009 """ 

1010 return self._parent 

1011 

1012 @property 

1013 def is_active(self) -> bool: 

1014 return ( 

1015 self.session is not None 

1016 and self._state is SessionTransactionState.ACTIVE 

1017 ) 

1018 

1019 @property 

1020 def _is_transaction_boundary(self) -> bool: 

1021 return self.nested or not self._parent 

1022 

1023 @_StateChange.declare_states( 

1024 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1025 ) 

1026 def connection( 

1027 self, 

1028 bindkey: Optional[Mapper[Any]], 

1029 execution_options: Optional[_ExecuteOptions] = None, 

1030 **kwargs: Any, 

1031 ) -> Connection: 

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

1033 return self._connection_for_bind(bind, execution_options) 

1034 

1035 @_StateChange.declare_states( 

1036 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1037 ) 

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

1039 return SessionTransaction( 

1040 self.session, 

1041 ( 

1042 SessionTransactionOrigin.BEGIN_NESTED 

1043 if nested 

1044 else SessionTransactionOrigin.SUBTRANSACTION 

1045 ), 

1046 self, 

1047 ) 

1048 

1049 def _iterate_self_and_parents( 

1050 self, upto: Optional[SessionTransaction] = None 

1051 ) -> Iterable[SessionTransaction]: 

1052 current = self 

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

1054 while current: 

1055 result += (current,) 

1056 if current._parent is upto: 

1057 break 

1058 elif current._parent is None: 

1059 raise sa_exc.InvalidRequestError( 

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

1061 % (upto) 

1062 ) 

1063 else: 

1064 current = current._parent 

1065 

1066 return result 

1067 

1068 def _take_snapshot(self) -> None: 

1069 if not self._is_transaction_boundary: 

1070 parent = self._parent 

1071 assert parent is not None 

1072 self._new = parent._new 

1073 self._deleted = parent._deleted 

1074 self._dirty = parent._dirty 

1075 self._key_switches = parent._key_switches 

1076 return 

1077 

1078 is_begin = self.origin in ( 

1079 SessionTransactionOrigin.BEGIN, 

1080 SessionTransactionOrigin.AUTOBEGIN, 

1081 ) 

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

1083 self.session.flush() 

1084 

1085 self._new = weakref.WeakKeyDictionary() 

1086 self._deleted = weakref.WeakKeyDictionary() 

1087 self._dirty = weakref.WeakKeyDictionary() 

1088 self._key_switches = weakref.WeakKeyDictionary() 

1089 

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

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

1092 

1093 Corresponds to a rollback. 

1094 

1095 """ 

1096 assert self._is_transaction_boundary 

1097 

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

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

1100 

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

1102 # we probably can do this conditionally based on 

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

1104 self.session.identity_map.safe_discard(s) 

1105 

1106 # restore the old key 

1107 s.key = oldkey 

1108 

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

1110 if s not in to_expunge: 

1111 self.session.identity_map.replace(s) 

1112 

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

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

1115 

1116 assert not self.session._deleted 

1117 

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

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

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

1121 

1122 def _remove_snapshot(self) -> None: 

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

1124 

1125 Corresponds to a commit. 

1126 

1127 """ 

1128 assert self._is_transaction_boundary 

1129 

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

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

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

1133 

1134 statelib.InstanceState._detach_states( 

1135 list(self._deleted), self.session 

1136 ) 

1137 self._deleted.clear() 

1138 elif self.nested: 

1139 parent = self._parent 

1140 assert parent is not None 

1141 parent._new.update(self._new) 

1142 parent._dirty.update(self._dirty) 

1143 parent._deleted.update(self._deleted) 

1144 parent._key_switches.update(self._key_switches) 

1145 

1146 @_StateChange.declare_states( 

1147 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1148 ) 

1149 def _connection_for_bind( 

1150 self, 

1151 bind: _SessionBind, 

1152 execution_options: Optional[CoreExecuteOptionsParameter], 

1153 ) -> Connection: 

1154 if bind in self._connections: 

1155 if execution_options: 

1156 util.warn( 

1157 "Connection is already established for the " 

1158 "given bind; execution_options ignored" 

1159 ) 

1160 return self._connections[bind][0] 

1161 

1162 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1163 

1164 local_connect = False 

1165 should_commit = True 

1166 

1167 try: 

1168 if self._parent: 

1169 conn = self._parent._connection_for_bind( 

1170 bind, execution_options 

1171 ) 

1172 if not self.nested: 

1173 return conn 

1174 else: 

1175 if isinstance(bind, engine.Connection): 

1176 conn = bind 

1177 if conn.engine in self._connections: 

1178 raise sa_exc.InvalidRequestError( 

1179 "Session already has a Connection associated " 

1180 "for the given Connection's Engine" 

1181 ) 

1182 else: 

1183 conn = bind.connect() 

1184 local_connect = True 

1185 

1186 try: 

1187 if execution_options: 

1188 conn = conn.execution_options(**execution_options) 

1189 

1190 transaction: Transaction 

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

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

1193 # conn.in_transaction() ? 

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

1195 # that it is in fact twophase. 

1196 transaction = conn.begin_twophase() 

1197 elif self.nested: 

1198 transaction = conn.begin_nested() 

1199 elif conn.in_transaction(): 

1200 

1201 if local_connect: 

1202 _trans = conn.get_transaction() 

1203 assert _trans is not None 

1204 transaction = _trans 

1205 else: 

1206 join_transaction_mode = ( 

1207 self.session.join_transaction_mode 

1208 ) 

1209 

1210 if join_transaction_mode == "conditional_savepoint": 

1211 if conn.in_nested_transaction(): 

1212 join_transaction_mode = "create_savepoint" 

1213 else: 

1214 join_transaction_mode = "rollback_only" 

1215 

1216 if join_transaction_mode in ( 

1217 "control_fully", 

1218 "rollback_only", 

1219 ): 

1220 if conn.in_nested_transaction(): 

1221 transaction = ( 

1222 conn._get_required_nested_transaction() 

1223 ) 

1224 else: 

1225 transaction = conn._get_required_transaction() 

1226 if join_transaction_mode == "rollback_only": 

1227 should_commit = False 

1228 elif join_transaction_mode == "create_savepoint": 

1229 transaction = conn.begin_nested() 

1230 else: 

1231 assert False, join_transaction_mode 

1232 else: 

1233 transaction = conn.begin() 

1234 except: 

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

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

1237 if local_connect: 

1238 conn.close() 

1239 raise 

1240 else: 

1241 bind_is_connection = isinstance(bind, engine.Connection) 

1242 

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

1244 conn, 

1245 transaction, 

1246 should_commit, 

1247 not bind_is_connection, 

1248 ) 

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

1250 return conn 

1251 finally: 

1252 self._state = SessionTransactionState.ACTIVE 

1253 

1254 def prepare(self) -> None: 

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

1256 raise sa_exc.InvalidRequestError( 

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

1258 "can't prepare." 

1259 ) 

1260 self._prepare_impl() 

1261 

1262 @_StateChange.declare_states( 

1263 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1264 ) 

1265 def _prepare_impl(self) -> None: 

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

1267 self.session.dispatch.before_commit(self.session) 

1268 

1269 stx = self.session._transaction 

1270 assert stx is not None 

1271 if stx is not self: 

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

1273 subtransaction.commit() 

1274 

1275 if not self.session._flushing: 

1276 for _flush_guard in range(100): 

1277 if self.session._is_clean(): 

1278 break 

1279 self.session.flush() 

1280 else: 

1281 raise exc.FlushError( 

1282 "Over 100 subsequent flushes have occurred within " 

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

1284 "creating new objects?" 

1285 ) 

1286 

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

1288 try: 

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

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

1291 except: 

1292 with util.safe_reraise(): 

1293 self.rollback() 

1294 

1295 self._state = SessionTransactionState.PREPARED 

1296 

1297 @_StateChange.declare_states( 

1298 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1299 SessionTransactionState.CLOSED, 

1300 ) 

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

1302 if self._state is not SessionTransactionState.PREPARED: 

1303 with self._expect_state(SessionTransactionState.PREPARED): 

1304 self._prepare_impl() 

1305 

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

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

1308 self._connections.values() 

1309 ): 

1310 if should_commit: 

1311 trans.commit() 

1312 

1313 self._state = SessionTransactionState.COMMITTED 

1314 self.session.dispatch.after_commit(self.session) 

1315 

1316 self._remove_snapshot() 

1317 

1318 with self._expect_state(SessionTransactionState.CLOSED): 

1319 self.close() 

1320 

1321 if _to_root and self._parent: 

1322 self._parent.commit(_to_root=True) 

1323 

1324 @_StateChange.declare_states( 

1325 ( 

1326 SessionTransactionState.ACTIVE, 

1327 SessionTransactionState.DEACTIVE, 

1328 SessionTransactionState.PREPARED, 

1329 ), 

1330 SessionTransactionState.CLOSED, 

1331 ) 

1332 def rollback( 

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

1334 ) -> None: 

1335 stx = self.session._transaction 

1336 assert stx is not None 

1337 if stx is not self: 

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

1339 subtransaction.close() 

1340 

1341 boundary = self 

1342 rollback_err = None 

1343 if self._state in ( 

1344 SessionTransactionState.ACTIVE, 

1345 SessionTransactionState.PREPARED, 

1346 ): 

1347 for transaction in self._iterate_self_and_parents(): 

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

1349 try: 

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

1351 t[1].rollback() 

1352 

1353 transaction._state = SessionTransactionState.DEACTIVE 

1354 self.session.dispatch.after_rollback(self.session) 

1355 except: 

1356 rollback_err = sys.exc_info() 

1357 finally: 

1358 transaction._state = SessionTransactionState.DEACTIVE 

1359 transaction._restore_snapshot( 

1360 dirty_only=transaction.nested 

1361 ) 

1362 boundary = transaction 

1363 break 

1364 else: 

1365 transaction._state = SessionTransactionState.DEACTIVE 

1366 

1367 sess = self.session 

1368 

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

1370 # if items were added, deleted, or mutated 

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

1372 util.warn( 

1373 "Session's state has been changed on " 

1374 "a non-active transaction - this state " 

1375 "will be discarded." 

1376 ) 

1377 boundary._restore_snapshot(dirty_only=boundary.nested) 

1378 

1379 with self._expect_state(SessionTransactionState.CLOSED): 

1380 self.close() 

1381 

1382 if self._parent and _capture_exception: 

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

1384 

1385 if rollback_err and rollback_err[1]: 

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

1387 

1388 sess.dispatch.after_soft_rollback(sess, self) 

1389 

1390 if _to_root and self._parent: 

1391 self._parent.rollback(_to_root=True) 

1392 

1393 @_StateChange.declare_states( 

1394 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1395 ) 

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

1397 if self.nested: 

1398 self.session._nested_transaction = ( 

1399 self._previous_nested_transaction 

1400 ) 

1401 

1402 self.session._transaction = self._parent 

1403 

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

1405 self._connections.values() 

1406 ): 

1407 if invalidate and self._parent is None: 

1408 connection.invalidate() 

1409 if should_commit and transaction.is_active: 

1410 transaction.close() 

1411 if autoclose and self._parent is None: 

1412 connection.close() 

1413 

1414 self._state = SessionTransactionState.CLOSED 

1415 sess = self.session 

1416 

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

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

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

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

1421 # passes with these commented out. 

1422 # self.session = None # type: ignore 

1423 # self._connections = None # type: ignore 

1424 

1425 sess.dispatch.after_transaction_end(sess, self) 

1426 

1427 def _get_subject(self) -> Session: 

1428 return self.session 

1429 

1430 def _transaction_is_active(self) -> bool: 

1431 return self._state is SessionTransactionState.ACTIVE 

1432 

1433 def _transaction_is_closed(self) -> bool: 

1434 return self._state is SessionTransactionState.CLOSED 

1435 

1436 def _rollback_can_be_called(self) -> bool: 

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

1438 

1439 

1440class _SessionCloseState(Enum): 

1441 ACTIVE = 1 

1442 CLOSED = 2 

1443 CLOSE_IS_RESET = 3 

1444 

1445 

1446class Session(_SessionClassMethods, EventTarget): 

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

1448 

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

1450 See :ref:`session_faq_threadsafe` for background. 

1451 

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

1453 

1454 

1455 """ 

1456 

1457 _is_asyncio = False 

1458 

1459 dispatch: dispatcher[Session] 

1460 

1461 identity_map: IdentityMap 

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

1463 

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

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

1466 that have row identity) currently in the session. 

1467 

1468 .. seealso:: 

1469 

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

1471 in this dictionary. 

1472 

1473 """ 

1474 

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

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

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

1478 __binds: Dict[_SessionBindKey, _SessionBind] 

1479 _flushing: bool 

1480 _warn_on_events: bool 

1481 _transaction: Optional[SessionTransaction] 

1482 _nested_transaction: Optional[SessionTransaction] 

1483 hash_key: int 

1484 autoflush: bool 

1485 expire_on_commit: bool 

1486 enable_baked_queries: bool 

1487 twophase: bool 

1488 join_transaction_mode: JoinTransactionMode 

1489 _query_cls: Type[Query[Any]] 

1490 _close_state: _SessionCloseState 

1491 

1492 def __init__( 

1493 self, 

1494 bind: Optional[_SessionBind] = None, 

1495 *, 

1496 autoflush: bool = True, 

1497 future: Literal[True] = True, 

1498 expire_on_commit: bool = True, 

1499 autobegin: bool = True, 

1500 twophase: bool = False, 

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

1502 enable_baked_queries: bool = True, 

1503 info: Optional[_InfoType] = None, 

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

1505 autocommit: Literal[False] = False, 

1506 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1508 ): 

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

1510 

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

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

1513 set of arguments. 

1514 

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

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

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

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

1519 results. 

1520 

1521 .. seealso:: 

1522 

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

1524 

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

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

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

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

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

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

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

1532 

1533 .. versionadded:: 2.0 

1534 

1535 .. seealso:: 

1536 

1537 :ref:`session_autobegin_disable` 

1538 

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

1540 :class:`_engine.Connection` to 

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

1542 operations performed by this session will execute via this 

1543 connectable. 

1544 

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

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

1547 objects as the source of 

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

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

1550 arbitrary Python classes that are bases for mapped classes, 

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

1552 The 

1553 values of the dictionary are then instances of 

1554 :class:`_engine.Engine` 

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

1556 Operations which 

1557 proceed relative to a particular mapped class will consult this 

1558 dictionary for the closest matching entity in order to determine 

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

1560 operation. The complete heuristics for resolution are 

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

1562 

1563 Session = sessionmaker( 

1564 binds={ 

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

1566 SomeDeclarativeBase: create_engine( 

1567 "postgresql+psycopg2://engine2" 

1568 ), 

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

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

1571 } 

1572 ) 

1573 

1574 .. seealso:: 

1575 

1576 :ref:`session_partitioning` 

1577 

1578 :meth:`.Session.bind_mapper` 

1579 

1580 :meth:`.Session.bind_table` 

1581 

1582 :meth:`.Session.get_bind` 

1583 

1584 

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

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

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

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

1589 constructor for ``Session``. 

1590 

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

1592 A parameter consumed 

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

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

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

1596 this particular extension is disabled. 

1597 

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

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

1600 flag therefore only affects applications that are making explicit 

1601 use of this extension within their own code. 

1602 

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

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

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

1606 transaction will load from the most recent database state. 

1607 

1608 .. seealso:: 

1609 

1610 :ref:`session_committing` 

1611 

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

1613 

1614 .. seealso:: 

1615 

1616 :ref:`migration_20_toplevel` 

1617 

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

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

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

1621 construction time so that modifications to the per- 

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

1623 :class:`.Session`. 

1624 

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

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

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

1628 

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

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

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

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

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

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

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

1636 transaction, before each transaction is committed. 

1637 

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

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

1640 

1641 :param join_transaction_mode: Describes the transactional behavior to 

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

1643 has already begun a transaction outside the scope of this 

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

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

1646 

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

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

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

1650 etc. are actually invoked: 

1651 

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

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

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

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

1656 a SAVEPOINT, in other words 

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

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

1659 

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

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

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

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

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

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

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

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

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

1669 

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

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

1672 its own transaction. This transaction by its nature rides 

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

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

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

1676 external transaction will remain unaffected throughout the 

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

1678 

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

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

1681 initiated transaction should remain unaffected; however, it relies 

1682 on proper SAVEPOINT support from the underlying driver and 

1683 database. 

1684 

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

1686 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1687 without workarounds. See the sections 

1688 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1689 for details on current workarounds. 

1690 

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

1692 control of the given transaction as its own; 

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

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

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

1696 call ``.rollback`` on the transaction. 

1697 

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

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

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

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

1702 SAVEPOINT. 

1703 

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

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

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

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

1708 given transaction. 

1709 

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

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

1712 regular database transaction (i.e. 

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

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

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

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

1717 

1718 .. versionadded:: 2.0.0rc1 

1719 

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

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

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

1723 

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

1725 A future SQLAlchemy version may change the default value of 

1726 this flag to ``False``. 

1727 

1728 .. seealso:: 

1729 

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

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

1732 

1733 """ # noqa 

1734 

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

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

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

1738 # of cases including in our own test suite 

1739 if autocommit: 

1740 raise sa_exc.ArgumentError( 

1741 "autocommit=True is no longer supported" 

1742 ) 

1743 self.identity_map = identity._WeakInstanceDict() 

1744 

1745 if not future: 

1746 raise sa_exc.ArgumentError( 

1747 "The 'future' parameter passed to " 

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

1749 ) 

1750 

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

1752 self._deleted = {} # same 

1753 self.bind = bind 

1754 self.__binds = {} 

1755 self._flushing = False 

1756 self._warn_on_events = False 

1757 self._transaction = None 

1758 self._nested_transaction = None 

1759 self.hash_key = _new_sessionid() 

1760 self.autobegin = autobegin 

1761 self.autoflush = autoflush 

1762 self.expire_on_commit = expire_on_commit 

1763 self.enable_baked_queries = enable_baked_queries 

1764 

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

1766 # the default will switch to close_resets_only=False. 

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

1768 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1769 else: 

1770 self._close_state = _SessionCloseState.ACTIVE 

1771 if ( 

1772 join_transaction_mode 

1773 and join_transaction_mode 

1774 not in JoinTransactionMode.__args__ # type: ignore 

1775 ): 

1776 raise sa_exc.ArgumentError( 

1777 f"invalid selection for join_transaction_mode: " 

1778 f'"{join_transaction_mode}"' 

1779 ) 

1780 self.join_transaction_mode = join_transaction_mode 

1781 

1782 self.twophase = twophase 

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

1784 if info: 

1785 self.info.update(info) 

1786 

1787 if binds is not None: 

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

1789 self._add_bind(key, bind) 

1790 

1791 _sessions[self.hash_key] = self 

1792 

1793 # used by sqlalchemy.engine.util.TransactionalContext 

1794 _trans_context_manager: Optional[TransactionalContext] = None 

1795 

1796 connection_callable: Optional[_ConnectionCallableProto] = None 

1797 

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

1799 return self 

1800 

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

1802 self.close() 

1803 

1804 @contextlib.contextmanager 

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

1806 with self: 

1807 with self.begin(): 

1808 yield self 

1809 

1810 def in_transaction(self) -> bool: 

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

1812 

1813 .. versionadded:: 1.4 

1814 

1815 .. seealso:: 

1816 

1817 :attr:`_orm.Session.is_active` 

1818 

1819 

1820 """ 

1821 return self._transaction is not None 

1822 

1823 def in_nested_transaction(self) -> bool: 

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

1825 transaction, e.g. SAVEPOINT. 

1826 

1827 .. versionadded:: 1.4 

1828 

1829 """ 

1830 return self._nested_transaction is not None 

1831 

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

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

1834 

1835 .. versionadded:: 1.4 

1836 

1837 """ 

1838 trans = self._transaction 

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

1840 trans = trans._parent 

1841 return trans 

1842 

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

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

1845 

1846 .. versionadded:: 1.4 

1847 

1848 """ 

1849 

1850 return self._nested_transaction 

1851 

1852 @util.memoized_property 

1853 def info(self) -> _InfoType: 

1854 """A user-modifiable dictionary. 

1855 

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

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

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

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

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

1861 

1862 """ 

1863 return {} 

1864 

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

1866 if self._transaction is None: 

1867 if not begin and not self.autobegin: 

1868 raise sa_exc.InvalidRequestError( 

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

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

1871 ) 

1872 trans = SessionTransaction( 

1873 self, 

1874 ( 

1875 SessionTransactionOrigin.BEGIN 

1876 if begin 

1877 else SessionTransactionOrigin.AUTOBEGIN 

1878 ), 

1879 ) 

1880 assert self._transaction is trans 

1881 return trans 

1882 

1883 return self._transaction 

1884 

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

1886 """Begin a transaction, or nested transaction, 

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

1888 

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

1890 so that normally it is not necessary to call the 

1891 :meth:`_orm.Session.begin` 

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

1893 the scope of when the transactional state is begun. 

1894 

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

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

1897 

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

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

1900 documentation on SAVEPOINT transactions, please see 

1901 :ref:`session_begin_nested`. 

1902 

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

1904 :class:`.SessionTransaction` 

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

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

1907 an example. 

1908 

1909 .. seealso:: 

1910 

1911 :ref:`session_autobegin` 

1912 

1913 :ref:`unitofwork_transaction` 

1914 

1915 :meth:`.Session.begin_nested` 

1916 

1917 

1918 """ 

1919 

1920 trans = self._transaction 

1921 if trans is None: 

1922 trans = self._autobegin_t(begin=True) 

1923 

1924 if not nested: 

1925 return trans 

1926 

1927 assert trans is not None 

1928 

1929 if nested: 

1930 trans = trans._begin(nested=nested) 

1931 assert self._transaction is trans 

1932 self._nested_transaction = trans 

1933 else: 

1934 raise sa_exc.InvalidRequestError( 

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

1936 ) 

1937 

1938 return trans # needed for __enter__/__exit__ hook 

1939 

1940 def begin_nested(self) -> SessionTransaction: 

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

1942 

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

1944 SAVEPOINT for this method to function correctly. 

1945 

1946 For documentation on SAVEPOINT 

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

1948 

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

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

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

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

1953 

1954 .. seealso:: 

1955 

1956 :ref:`session_begin_nested` 

1957 

1958 :ref:`pysqlite_serializable` - special workarounds required 

1959 with the SQLite driver in order for SAVEPOINT to work 

1960 correctly. For asyncio use cases, see the section 

1961 :ref:`aiosqlite_serializable`. 

1962 

1963 """ 

1964 return self.begin(nested=True) 

1965 

1966 def rollback(self) -> None: 

1967 """Rollback the current transaction in progress. 

1968 

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

1970 

1971 The method always rolls back 

1972 the topmost database transaction, discarding any nested 

1973 transactions that may be in progress. 

1974 

1975 .. seealso:: 

1976 

1977 :ref:`session_rollback` 

1978 

1979 :ref:`unitofwork_transaction` 

1980 

1981 """ 

1982 if self._transaction is None: 

1983 pass 

1984 else: 

1985 self._transaction.rollback(_to_root=True) 

1986 

1987 def commit(self) -> None: 

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

1989 

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

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

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

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

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

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

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

1997 to disable this behavior. 

1998 

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

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

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

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

2003 normally affect the database unless pending flush changes were 

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

2005 rules. 

2006 

2007 The outermost database transaction is committed unconditionally, 

2008 automatically releasing any SAVEPOINTs in effect. 

2009 

2010 .. seealso:: 

2011 

2012 :ref:`session_committing` 

2013 

2014 :ref:`unitofwork_transaction` 

2015 

2016 :ref:`asyncio_orm_avoid_lazyloads` 

2017 

2018 """ 

2019 trans = self._transaction 

2020 if trans is None: 

2021 trans = self._autobegin_t() 

2022 

2023 trans.commit(_to_root=True) 

2024 

2025 def prepare(self) -> None: 

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

2027 

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

2029 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2030 

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

2032 current transaction is not such, an 

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

2034 

2035 """ 

2036 trans = self._transaction 

2037 if trans is None: 

2038 trans = self._autobegin_t() 

2039 

2040 trans.prepare() 

2041 

2042 def connection( 

2043 self, 

2044 bind_arguments: Optional[_BindArguments] = None, 

2045 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2046 ) -> Connection: 

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

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

2049 

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

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

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

2053 returned (note that no 

2054 transactional state is established with the DBAPI until the first 

2055 SQL statement is emitted). 

2056 

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

2058 resolved through any of the optional keyword arguments. This 

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

2060 

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

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

2063 to :meth:`.Session.get_bind`. 

2064 

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

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

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

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

2069 the arguments are ignored. 

2070 

2071 .. seealso:: 

2072 

2073 :ref:`session_transaction_isolation` 

2074 

2075 """ 

2076 

2077 if bind_arguments: 

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

2079 

2080 if bind is None: 

2081 bind = self.get_bind(**bind_arguments) 

2082 else: 

2083 bind = self.get_bind() 

2084 

2085 return self._connection_for_bind( 

2086 bind, 

2087 execution_options=execution_options, 

2088 ) 

2089 

2090 def _connection_for_bind( 

2091 self, 

2092 engine: _SessionBind, 

2093 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2094 **kw: Any, 

2095 ) -> Connection: 

2096 TransactionalContext._trans_ctx_check(self) 

2097 

2098 trans = self._transaction 

2099 if trans is None: 

2100 trans = self._autobegin_t() 

2101 return trans._connection_for_bind(engine, execution_options) 

2102 

2103 @overload 

2104 def _execute_internal( 

2105 self, 

2106 statement: Executable, 

2107 params: Optional[_CoreSingleExecuteParams] = None, 

2108 *, 

2109 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2110 bind_arguments: Optional[_BindArguments] = None, 

2111 _parent_execute_state: Optional[Any] = None, 

2112 _add_event: Optional[Any] = None, 

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

2114 ) -> Any: ... 

2115 

2116 @overload 

2117 def _execute_internal( 

2118 self, 

2119 statement: Executable, 

2120 params: Optional[_CoreAnyExecuteParams] = None, 

2121 *, 

2122 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2123 bind_arguments: Optional[_BindArguments] = None, 

2124 _parent_execute_state: Optional[Any] = None, 

2125 _add_event: Optional[Any] = None, 

2126 _scalar_result: bool = ..., 

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

2128 

2129 def _execute_internal( 

2130 self, 

2131 statement: Executable, 

2132 params: Optional[_CoreAnyExecuteParams] = None, 

2133 *, 

2134 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2135 bind_arguments: Optional[_BindArguments] = None, 

2136 _parent_execute_state: Optional[Any] = None, 

2137 _add_event: Optional[Any] = None, 

2138 _scalar_result: bool = False, 

2139 ) -> Any: 

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

2141 

2142 if not bind_arguments: 

2143 bind_arguments = {} 

2144 else: 

2145 bind_arguments = dict(bind_arguments) 

2146 

2147 if ( 

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

2149 == "orm" 

2150 ): 

2151 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2152 statement, "orm" 

2153 ) 

2154 if TYPE_CHECKING: 

2155 assert isinstance( 

2156 compile_state_cls, context._AbstractORMCompileState 

2157 ) 

2158 else: 

2159 compile_state_cls = None 

2160 bind_arguments.setdefault("clause", statement) 

2161 

2162 execution_options = util.coerce_to_immutabledict(execution_options) 

2163 

2164 if _parent_execute_state: 

2165 events_todo = _parent_execute_state._remaining_events() 

2166 else: 

2167 events_todo = self.dispatch.do_orm_execute 

2168 if _add_event: 

2169 events_todo = list(events_todo) + [_add_event] 

2170 

2171 if events_todo: 

2172 if compile_state_cls is not None: 

2173 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2179 ( 

2180 statement, 

2181 execution_options, 

2182 ) = compile_state_cls.orm_pre_session_exec( 

2183 self, 

2184 statement, 

2185 params, 

2186 execution_options, 

2187 bind_arguments, 

2188 True, 

2189 ) 

2190 

2191 orm_exec_state = ORMExecuteState( 

2192 self, 

2193 statement, 

2194 params, 

2195 execution_options, 

2196 bind_arguments, 

2197 compile_state_cls, 

2198 events_todo, 

2199 ) 

2200 for idx, fn in enumerate(events_todo): 

2201 orm_exec_state._starting_event_idx = idx 

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

2203 orm_exec_state 

2204 ) 

2205 if fn_result: 

2206 if _scalar_result: 

2207 return fn_result.scalar() 

2208 else: 

2209 return fn_result 

2210 

2211 statement = orm_exec_state.statement 

2212 execution_options = orm_exec_state.local_execution_options 

2213 

2214 if compile_state_cls is not None: 

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

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

2217 # new execution_options into load_options / update_delete_options, 

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

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

2220 ( 

2221 statement, 

2222 execution_options, 

2223 ) = compile_state_cls.orm_pre_session_exec( 

2224 self, 

2225 statement, 

2226 params, 

2227 execution_options, 

2228 bind_arguments, 

2229 False, 

2230 ) 

2231 

2232 bind = self.get_bind(**bind_arguments) 

2233 

2234 conn = self._connection_for_bind(bind) 

2235 

2236 if _scalar_result and not compile_state_cls: 

2237 if TYPE_CHECKING: 

2238 params = cast(_CoreSingleExecuteParams, params) 

2239 return conn.scalar( 

2240 statement, params or {}, execution_options=execution_options 

2241 ) 

2242 

2243 if compile_state_cls: 

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

2245 compile_state_cls.orm_execute_statement( 

2246 self, 

2247 statement, 

2248 params or {}, 

2249 execution_options, 

2250 bind_arguments, 

2251 conn, 

2252 ) 

2253 ) 

2254 else: 

2255 result = conn.execute( 

2256 statement, params, execution_options=execution_options 

2257 ) 

2258 

2259 if _scalar_result: 

2260 return result.scalar() 

2261 else: 

2262 return result 

2263 

2264 @overload 

2265 def execute( 

2266 self, 

2267 statement: TypedReturnsRows[Unpack[_Ts]], 

2268 params: Optional[_CoreAnyExecuteParams] = None, 

2269 *, 

2270 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2271 bind_arguments: Optional[_BindArguments] = None, 

2272 _parent_execute_state: Optional[Any] = None, 

2273 _add_event: Optional[Any] = None, 

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

2275 

2276 @overload 

2277 def execute( 

2278 self, 

2279 statement: UpdateBase, 

2280 params: Optional[_CoreAnyExecuteParams] = None, 

2281 *, 

2282 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2283 bind_arguments: Optional[_BindArguments] = None, 

2284 _parent_execute_state: Optional[Any] = None, 

2285 _add_event: Optional[Any] = None, 

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

2287 

2288 @overload 

2289 def execute( 

2290 self, 

2291 statement: Executable, 

2292 params: Optional[_CoreAnyExecuteParams] = None, 

2293 *, 

2294 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2295 bind_arguments: Optional[_BindArguments] = None, 

2296 _parent_execute_state: Optional[Any] = None, 

2297 _add_event: Optional[Any] = None, 

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

2299 

2300 def execute( 

2301 self, 

2302 statement: Executable, 

2303 params: Optional[_CoreAnyExecuteParams] = None, 

2304 *, 

2305 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2306 bind_arguments: Optional[_BindArguments] = None, 

2307 _parent_execute_state: Optional[Any] = None, 

2308 _add_event: Optional[Any] = None, 

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

2310 r"""Execute a SQL expression construct. 

2311 

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

2313 results of the statement execution. 

2314 

2315 E.g.:: 

2316 

2317 from sqlalchemy import select 

2318 

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

2320 

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

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

2323 of :class:`_engine.Connection`. 

2324 

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

2326 now the primary point of ORM statement execution when using 

2327 :term:`2.0 style` ORM usage. 

2328 

2329 :param statement: 

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

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

2332 

2333 :param params: 

2334 Optional dictionary, or list of dictionaries, containing 

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

2336 execution occurs; if a list of dictionaries, an 

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

2338 must correspond to parameter names present in the statement. 

2339 

2340 :param execution_options: optional dictionary of execution options, 

2341 which will be associated with the statement execution. This 

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

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

2344 provide additional options understood only in an ORM context. 

2345 

2346 .. seealso:: 

2347 

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

2349 options 

2350 

2351 :param bind_arguments: dictionary of additional arguments to determine 

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

2353 Contents of this dictionary are passed to the 

2354 :meth:`.Session.get_bind` method. 

2355 

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

2357 

2358 

2359 """ 

2360 return self._execute_internal( 

2361 statement, 

2362 params, 

2363 execution_options=execution_options, 

2364 bind_arguments=bind_arguments, 

2365 _parent_execute_state=_parent_execute_state, 

2366 _add_event=_add_event, 

2367 ) 

2368 

2369 @overload 

2370 def scalar( 

2371 self, 

2372 statement: TypedReturnsRows[_T], 

2373 params: Optional[_CoreSingleExecuteParams] = None, 

2374 *, 

2375 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2376 bind_arguments: Optional[_BindArguments] = None, 

2377 **kw: Any, 

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

2379 

2380 @overload 

2381 def scalar( 

2382 self, 

2383 statement: Executable, 

2384 params: Optional[_CoreSingleExecuteParams] = None, 

2385 *, 

2386 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2387 bind_arguments: Optional[_BindArguments] = None, 

2388 **kw: Any, 

2389 ) -> Any: ... 

2390 

2391 def scalar( 

2392 self, 

2393 statement: Executable, 

2394 params: Optional[_CoreSingleExecuteParams] = None, 

2395 *, 

2396 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2397 bind_arguments: Optional[_BindArguments] = None, 

2398 **kw: Any, 

2399 ) -> Any: 

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

2401 

2402 Usage and parameters are the same as that of 

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

2404 value. 

2405 

2406 """ 

2407 

2408 return self._execute_internal( 

2409 statement, 

2410 params, 

2411 execution_options=execution_options, 

2412 bind_arguments=bind_arguments, 

2413 _scalar_result=True, 

2414 **kw, 

2415 ) 

2416 

2417 @overload 

2418 def scalars( 

2419 self, 

2420 statement: TypedReturnsRows[_T], 

2421 params: Optional[_CoreAnyExecuteParams] = None, 

2422 *, 

2423 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2424 bind_arguments: Optional[_BindArguments] = None, 

2425 **kw: Any, 

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

2427 

2428 @overload 

2429 def scalars( 

2430 self, 

2431 statement: Executable, 

2432 params: Optional[_CoreAnyExecuteParams] = None, 

2433 *, 

2434 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2435 bind_arguments: Optional[_BindArguments] = None, 

2436 **kw: Any, 

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

2438 

2439 def scalars( 

2440 self, 

2441 statement: Executable, 

2442 params: Optional[_CoreAnyExecuteParams] = None, 

2443 *, 

2444 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2445 bind_arguments: Optional[_BindArguments] = None, 

2446 **kw: Any, 

2447 ) -> ScalarResult[Any]: 

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

2449 

2450 Usage and parameters are the same as that of 

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

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

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

2454 

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

2456 

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

2458 

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

2460 

2461 .. seealso:: 

2462 

2463 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2465 

2466 """ 

2467 

2468 return self._execute_internal( 

2469 statement, 

2470 params=params, 

2471 execution_options=execution_options, 

2472 bind_arguments=bind_arguments, 

2473 _scalar_result=False, # mypy appreciates this 

2474 **kw, 

2475 ).scalars() 

2476 

2477 def close(self) -> None: 

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

2479 :class:`_orm.Session`. 

2480 

2481 This expunges all ORM objects associated with this 

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

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

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

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

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

2487 

2488 .. tip:: 

2489 

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

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

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

2493 distinct "closed" state; it merely means 

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

2495 and ORM objects. 

2496 

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

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

2499 any further action on the session will be forbidden. 

2500 

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

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

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

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

2505 

2506 .. seealso:: 

2507 

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

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

2510 

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

2512 ``close()`` with the parameter 

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

2514 

2515 """ 

2516 self._close_impl(invalidate=False) 

2517 

2518 def reset(self) -> None: 

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

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

2521 

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

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

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

2525 brand new, and ready to be used again. 

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

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

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

2529 

2530 .. versionadded:: 2.0.22 

2531 

2532 .. seealso:: 

2533 

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

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

2536 

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

2538 prevent re-use of the Session when the parameter 

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

2540 """ 

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

2542 

2543 def invalidate(self) -> None: 

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

2545 

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

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

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

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

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

2551 multiple engines). 

2552 

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

2554 the connections are no longer safe to be used. 

2555 

2556 Below illustrates a scenario when using `gevent 

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

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

2559 

2560 import gevent 

2561 

2562 try: 

2563 sess = Session() 

2564 sess.add(User()) 

2565 sess.commit() 

2566 except gevent.Timeout: 

2567 sess.invalidate() 

2568 raise 

2569 except: 

2570 sess.rollback() 

2571 raise 

2572 

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

2574 does, including that all ORM objects are expunged. 

2575 

2576 """ 

2577 self._close_impl(invalidate=True) 

2578 

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

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

2581 self._close_state = _SessionCloseState.CLOSED 

2582 self.expunge_all() 

2583 if self._transaction is not None: 

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

2585 transaction.close(invalidate) 

2586 

2587 def expunge_all(self) -> None: 

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

2589 

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

2591 ``Session``. 

2592 

2593 """ 

2594 

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

2596 self.identity_map._kill() 

2597 self.identity_map = identity._WeakInstanceDict() 

2598 self._new = {} 

2599 self._deleted = {} 

2600 

2601 statelib.InstanceState._detach_states(all_states, self) 

2602 

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

2604 try: 

2605 insp = inspect(key) 

2606 except sa_exc.NoInspectionAvailable as err: 

2607 if not isinstance(key, type): 

2608 raise sa_exc.ArgumentError( 

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

2610 ) from err 

2611 else: 

2612 self.__binds[key] = bind 

2613 else: 

2614 if TYPE_CHECKING: 

2615 assert isinstance(insp, Inspectable) 

2616 

2617 if isinstance(insp, TableClause): 

2618 self.__binds[insp] = bind 

2619 elif insp_is_mapper(insp): 

2620 self.__binds[insp.class_] = bind 

2621 for _selectable in insp._all_tables: 

2622 self.__binds[_selectable] = bind 

2623 else: 

2624 raise sa_exc.ArgumentError( 

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

2626 ) 

2627 

2628 def bind_mapper( 

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

2630 ) -> None: 

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

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

2633 :class:`_engine.Connection`. 

2634 

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

2636 :meth:`.Session.get_bind` method. 

2637 

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

2639 or an instance of a mapped 

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

2641 classes. 

2642 

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

2644 object. 

2645 

2646 .. seealso:: 

2647 

2648 :ref:`session_partitioning` 

2649 

2650 :paramref:`.Session.binds` 

2651 

2652 :meth:`.Session.bind_table` 

2653 

2654 

2655 """ 

2656 self._add_bind(mapper, bind) 

2657 

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

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

2660 :class:`_engine.Engine` 

2661 or :class:`_engine.Connection`. 

2662 

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

2664 :meth:`.Session.get_bind` method. 

2665 

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

2667 which is typically the target 

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

2669 mapped. 

2670 

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

2672 object. 

2673 

2674 .. seealso:: 

2675 

2676 :ref:`session_partitioning` 

2677 

2678 :paramref:`.Session.binds` 

2679 

2680 :meth:`.Session.bind_mapper` 

2681 

2682 

2683 """ 

2684 self._add_bind(table, bind) 

2685 

2686 def get_bind( 

2687 self, 

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

2689 *, 

2690 clause: Optional[ClauseElement] = None, 

2691 bind: Optional[_SessionBind] = None, 

2692 _sa_skip_events: Optional[bool] = None, 

2693 _sa_skip_for_implicit_returning: bool = False, 

2694 **kw: Any, 

2695 ) -> Union[Engine, Connection]: 

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

2697 

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

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

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

2701 

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

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

2704 appropriate bind to return. 

2705 

2706 Note that the "mapper" argument is usually present 

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

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

2709 individual INSERT/UPDATE/DELETE operation within a 

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

2711 

2712 The order of resolution is: 

2713 

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

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

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

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

2718 superclasses to more general. 

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

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

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

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

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

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

2725 associated with the clause. 

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

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

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

2729 selectable to which the mapper is mapped. 

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

2731 is raised. 

2732 

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

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

2735 of bind resolution scheme. See the example at 

2736 :ref:`session_custom_partitioning`. 

2737 

2738 :param mapper: 

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

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

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

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

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

2744 mapped for a bind. 

2745 

2746 :param clause: 

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

2748 :func:`_expression.select`, 

2749 :func:`_expression.text`, 

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

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

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

2753 associated with 

2754 bound :class:`_schema.MetaData`. 

2755 

2756 .. seealso:: 

2757 

2758 :ref:`session_partitioning` 

2759 

2760 :paramref:`.Session.binds` 

2761 

2762 :meth:`.Session.bind_mapper` 

2763 

2764 :meth:`.Session.bind_table` 

2765 

2766 """ 

2767 

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

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

2770 if bind: 

2771 return bind 

2772 elif not self.__binds and self.bind: 

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

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

2775 return self.bind 

2776 

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

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

2779 # mapper and the clause 

2780 if mapper is None and clause is None: 

2781 if self.bind: 

2782 return self.bind 

2783 else: 

2784 raise sa_exc.UnboundExecutionError( 

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

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

2787 "a binding." 

2788 ) 

2789 

2790 # look more closely at the mapper. 

2791 if mapper is not None: 

2792 try: 

2793 inspected_mapper = inspect(mapper) 

2794 except sa_exc.NoInspectionAvailable as err: 

2795 if isinstance(mapper, type): 

2796 raise exc.UnmappedClassError(mapper) from err 

2797 else: 

2798 raise 

2799 else: 

2800 inspected_mapper = None 

2801 

2802 # match up the mapper or clause in the __binds 

2803 if self.__binds: 

2804 # matching mappers and selectables to entries in the 

2805 # binds dictionary; supported use case. 

2806 if inspected_mapper: 

2807 for cls in inspected_mapper.class_.__mro__: 

2808 if cls in self.__binds: 

2809 return self.__binds[cls] 

2810 if clause is None: 

2811 clause = inspected_mapper.persist_selectable 

2812 

2813 if clause is not None: 

2814 plugin_subject = clause._propagate_attrs.get( 

2815 "plugin_subject", None 

2816 ) 

2817 

2818 if plugin_subject is not None: 

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

2820 if cls in self.__binds: 

2821 return self.__binds[cls] 

2822 

2823 for obj in visitors.iterate(clause): 

2824 if obj in self.__binds: 

2825 if TYPE_CHECKING: 

2826 assert isinstance(obj, Table) 

2827 return self.__binds[obj] 

2828 

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

2830 # return that 

2831 if self.bind: 

2832 return self.bind 

2833 

2834 context = [] 

2835 if inspected_mapper is not None: 

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

2837 if clause is not None: 

2838 context.append("SQL expression") 

2839 

2840 raise sa_exc.UnboundExecutionError( 

2841 f"Could not locate a bind configured on " 

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

2843 ) 

2844 

2845 @overload 

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

2847 

2848 @overload 

2849 def query( 

2850 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2852 

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

2854 

2855 # code within this block is **programmatically, 

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

2857 

2858 @overload 

2859 def query( 

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

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

2862 

2863 @overload 

2864 def query( 

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

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

2867 

2868 @overload 

2869 def query( 

2870 self, 

2871 __ent0: _TCCA[_T0], 

2872 __ent1: _TCCA[_T1], 

2873 __ent2: _TCCA[_T2], 

2874 __ent3: _TCCA[_T3], 

2875 /, 

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

2877 

2878 @overload 

2879 def query( 

2880 self, 

2881 __ent0: _TCCA[_T0], 

2882 __ent1: _TCCA[_T1], 

2883 __ent2: _TCCA[_T2], 

2884 __ent3: _TCCA[_T3], 

2885 __ent4: _TCCA[_T4], 

2886 /, 

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

2888 

2889 @overload 

2890 def query( 

2891 self, 

2892 __ent0: _TCCA[_T0], 

2893 __ent1: _TCCA[_T1], 

2894 __ent2: _TCCA[_T2], 

2895 __ent3: _TCCA[_T3], 

2896 __ent4: _TCCA[_T4], 

2897 __ent5: _TCCA[_T5], 

2898 /, 

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

2900 

2901 @overload 

2902 def query( 

2903 self, 

2904 __ent0: _TCCA[_T0], 

2905 __ent1: _TCCA[_T1], 

2906 __ent2: _TCCA[_T2], 

2907 __ent3: _TCCA[_T3], 

2908 __ent4: _TCCA[_T4], 

2909 __ent5: _TCCA[_T5], 

2910 __ent6: _TCCA[_T6], 

2911 /, 

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

2913 

2914 @overload 

2915 def query( 

2916 self, 

2917 __ent0: _TCCA[_T0], 

2918 __ent1: _TCCA[_T1], 

2919 __ent2: _TCCA[_T2], 

2920 __ent3: _TCCA[_T3], 

2921 __ent4: _TCCA[_T4], 

2922 __ent5: _TCCA[_T5], 

2923 __ent6: _TCCA[_T6], 

2924 __ent7: _TCCA[_T7], 

2925 /, 

2926 *entities: _ColumnsClauseArgument[Any], 

2927 ) -> RowReturningQuery[ 

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

2929 ]: ... 

2930 

2931 # END OVERLOADED FUNCTIONS self.query 

2932 

2933 @overload 

2934 def query( 

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

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

2937 

2938 def query( 

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

2940 ) -> Query[Any]: 

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

2942 :class:`_orm.Session`. 

2943 

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

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

2946 to construct ORM queries. 

2947 

2948 .. seealso:: 

2949 

2950 :ref:`unified_tutorial` 

2951 

2952 :ref:`queryguide_toplevel` 

2953 

2954 :ref:`query_api_toplevel` - legacy API doc 

2955 

2956 """ 

2957 

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

2959 

2960 def _identity_lookup( 

2961 self, 

2962 mapper: Mapper[_O], 

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

2964 identity_token: Any = None, 

2965 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

2967 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2968 bind_arguments: Optional[_BindArguments] = None, 

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

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

2971 

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

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

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

2975 check if was deleted). 

2976 

2977 e.g.:: 

2978 

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

2980 

2981 :param mapper: mapper in use 

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

2983 a tuple. 

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

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

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

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

2988 :param passive: passive load flag passed to 

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

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

2991 if the flag allows for SQL to be emitted. 

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

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

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

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

2996 relationship-loaded). 

2997 

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

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

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

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

3002 

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

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

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

3006 :class:`_query.Query` object. 

3007 

3008 

3009 """ 

3010 

3011 key = mapper.identity_key_from_primary_key( 

3012 primary_key_identity, identity_token=identity_token 

3013 ) 

3014 

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

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

3017 return return_value 

3018 

3019 @util.non_memoized_property 

3020 @contextlib.contextmanager 

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

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

3023 

3024 e.g.:: 

3025 

3026 with session.no_autoflush: 

3027 

3028 some_object = SomeClass() 

3029 session.add(some_object) 

3030 # won't autoflush 

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

3032 

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

3034 will not be subject to flushes occurring upon query 

3035 access. This is useful when initializing a series 

3036 of objects which involve existing database queries, 

3037 where the uncompleted object should not yet be flushed. 

3038 

3039 """ 

3040 autoflush = self.autoflush 

3041 self.autoflush = False 

3042 try: 

3043 yield self 

3044 finally: 

3045 self.autoflush = autoflush 

3046 

3047 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3051 "warning happened while initializing objects.", 

3052 sa_exc.SAWarning, 

3053 ) 

3054 def _autoflush(self) -> None: 

3055 if self.autoflush and not self._flushing: 

3056 try: 

3057 self.flush() 

3058 except sa_exc.StatementError as e: 

3059 # note we are reraising StatementError as opposed to 

3060 # raising FlushError with "chaining" to remain compatible 

3061 # with code that catches StatementError, IntegrityError, 

3062 # etc. 

3063 e.add_detail( 

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

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

3066 "flush is occurring prematurely" 

3067 ) 

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

3069 

3070 def refresh( 

3071 self, 

3072 instance: object, 

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

3074 with_for_update: ForUpdateParameter = None, 

3075 ) -> None: 

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

3077 

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

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

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

3081 value available in the current transaction. 

3082 

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

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

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

3086 

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

3088 can also refresh eagerly loaded attributes. 

3089 

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

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

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

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

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

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

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

3097 refreshed. 

3098 

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

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

3101 attributes for those which are named explicitly in the 

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

3103 

3104 .. tip:: 

3105 

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

3107 refreshing both column and relationship oriented attributes, its 

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

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

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

3111 once while having explicit control over relationship loader 

3112 strategies, use the 

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

3114 instead. 

3115 

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

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

3118 in database state outside of that transaction. Refreshing 

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

3120 where database rows have not yet been accessed. 

3121 

3122 :param attribute_names: optional. An iterable collection of 

3123 string attribute names indicating a subset of attributes to 

3124 be refreshed. 

3125 

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

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

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

3129 flags should match the parameters of 

3130 :meth:`_query.Query.with_for_update`. 

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

3132 

3133 .. seealso:: 

3134 

3135 :ref:`session_expire` - introductory material 

3136 

3137 :meth:`.Session.expire` 

3138 

3139 :meth:`.Session.expire_all` 

3140 

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

3142 to refresh objects as they would be loaded normally. 

3143 

3144 """ 

3145 try: 

3146 state = attributes.instance_state(instance) 

3147 except exc.NO_STATE as err: 

3148 raise exc.UnmappedInstanceError(instance) from err 

3149 

3150 self._expire_state(state, attribute_names) 

3151 

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

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

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

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

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

3157 # load_on_ident. 

3158 self._autoflush() 

3159 

3160 if with_for_update == {}: 

3161 raise sa_exc.ArgumentError( 

3162 "with_for_update should be the boolean value " 

3163 "True, or a dictionary with options. " 

3164 "A blank dictionary is ambiguous." 

3165 ) 

3166 

3167 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3168 

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

3170 if ( 

3171 loading._load_on_ident( 

3172 self, 

3173 stmt, 

3174 state.key, 

3175 refresh_state=state, 

3176 with_for_update=with_for_update, 

3177 only_load_props=attribute_names, 

3178 require_pk_cols=True, 

3179 # technically unnecessary as we just did autoflush 

3180 # above, however removes the additional unnecessary 

3181 # call to _autoflush() 

3182 no_autoflush=True, 

3183 is_user_refresh=True, 

3184 ) 

3185 is None 

3186 ): 

3187 raise sa_exc.InvalidRequestError( 

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

3189 ) 

3190 

3191 def expire_all(self) -> None: 

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

3193 

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

3195 a query will be issued using the 

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

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

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

3199 previously read in that same transaction, regardless of changes 

3200 in database state outside of that transaction. 

3201 

3202 To expire individual objects and individual attributes 

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

3204 

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

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

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

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

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

3210 assuming the transaction is isolated. 

3211 

3212 .. seealso:: 

3213 

3214 :ref:`session_expire` - introductory material 

3215 

3216 :meth:`.Session.expire` 

3217 

3218 :meth:`.Session.refresh` 

3219 

3220 :meth:`_orm.Query.populate_existing` 

3221 

3222 """ 

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

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

3225 

3226 def expire( 

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

3228 ) -> None: 

3229 """Expire the attributes on an instance. 

3230 

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

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

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

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

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

3236 previously read in that same transaction, regardless of changes 

3237 in database state outside of that transaction. 

3238 

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

3240 use :meth:`Session.expire_all`. 

3241 

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

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

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

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

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

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

3248 transaction. 

3249 

3250 :param instance: The instance to be refreshed. 

3251 :param attribute_names: optional list of string attribute names 

3252 indicating a subset of attributes to be expired. 

3253 

3254 .. seealso:: 

3255 

3256 :ref:`session_expire` - introductory material 

3257 

3258 :meth:`.Session.expire` 

3259 

3260 :meth:`.Session.refresh` 

3261 

3262 :meth:`_orm.Query.populate_existing` 

3263 

3264 """ 

3265 try: 

3266 state = attributes.instance_state(instance) 

3267 except exc.NO_STATE as err: 

3268 raise exc.UnmappedInstanceError(instance) from err 

3269 self._expire_state(state, attribute_names) 

3270 

3271 def _expire_state( 

3272 self, 

3273 state: InstanceState[Any], 

3274 attribute_names: Optional[Iterable[str]], 

3275 ) -> None: 

3276 self._validate_persistent(state) 

3277 if attribute_names: 

3278 state._expire_attributes(state.dict, attribute_names) 

3279 else: 

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

3281 # remove associations 

3282 cascaded = list( 

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

3284 ) 

3285 self._conditional_expire(state) 

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

3287 self._conditional_expire(st_) 

3288 

3289 def _conditional_expire( 

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

3291 ) -> None: 

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

3293 

3294 if state.key: 

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

3296 elif state in self._new: 

3297 self._new.pop(state) 

3298 state._detach(self) 

3299 

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

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

3302 

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

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

3305 

3306 """ 

3307 try: 

3308 state = attributes.instance_state(instance) 

3309 except exc.NO_STATE as err: 

3310 raise exc.UnmappedInstanceError(instance) from err 

3311 if state.session_id is not self.hash_key: 

3312 raise sa_exc.InvalidRequestError( 

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

3314 ) 

3315 

3316 cascaded = list( 

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

3318 ) 

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

3320 

3321 def _expunge_states( 

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

3323 ) -> None: 

3324 for state in states: 

3325 if state in self._new: 

3326 self._new.pop(state) 

3327 elif self.identity_map.contains_state(state): 

3328 self.identity_map.safe_discard(state) 

3329 self._deleted.pop(state, None) 

3330 elif self._transaction: 

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

3332 # in the transaction snapshot 

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

3334 statelib.InstanceState._detach_states( 

3335 states, self, to_transient=to_transient 

3336 ) 

3337 

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

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

3340 

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

3342 state as well as already persistent objects. 

3343 

3344 """ 

3345 

3346 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3347 for state in states: 

3348 mapper = _state_mapper(state) 

3349 

3350 # prevent against last minute dereferences of the object 

3351 obj = state.obj() 

3352 if obj is not None: 

3353 instance_key = mapper._identity_key_from_state(state) 

3354 

3355 if ( 

3356 _none_set.intersection(instance_key[1]) 

3357 and not mapper.allow_partial_pks 

3358 or _none_set.issuperset(instance_key[1]) 

3359 ): 

3360 raise exc.FlushError( 

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

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

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

3364 "that the mapped Column object is configured to " 

3365 "expect these generated values. Ensure also that " 

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

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

3368 % state_str(state) 

3369 ) 

3370 

3371 if state.key is None: 

3372 state.key = instance_key 

3373 elif state.key != instance_key: 

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

3375 # state has already replaced this one in the identity 

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

3377 self.identity_map.safe_discard(state) 

3378 trans = self._transaction 

3379 assert trans is not None 

3380 if state in trans._key_switches: 

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

3382 else: 

3383 orig_key = state.key 

3384 trans._key_switches[state] = ( 

3385 orig_key, 

3386 instance_key, 

3387 ) 

3388 state.key = instance_key 

3389 

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

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

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

3393 old = self.identity_map.replace(state) 

3394 if ( 

3395 old is not None 

3396 and mapper._identity_key_from_state(old) == instance_key 

3397 and old.obj() is not None 

3398 ): 

3399 util.warn( 

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

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

3402 "load operations occurring inside of an event handler " 

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

3404 ) 

3405 state._orphaned_outside_of_session = False 

3406 

3407 statelib.InstanceState._commit_all_states( 

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

3409 ) 

3410 

3411 self._register_altered(states) 

3412 

3413 if pending_to_persistent is not None: 

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

3415 pending_to_persistent(self, state) 

3416 

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

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

3419 self._new.pop(state) 

3420 

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

3422 if self._transaction: 

3423 for state in states: 

3424 if state in self._new: 

3425 self._transaction._new[state] = True 

3426 else: 

3427 self._transaction._dirty[state] = True 

3428 

3429 def _remove_newly_deleted( 

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

3431 ) -> None: 

3432 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3433 for state in states: 

3434 if self._transaction: 

3435 self._transaction._deleted[state] = True 

3436 

3437 if persistent_to_deleted is not None: 

3438 # get a strong reference before we pop out of 

3439 # self._deleted 

3440 obj = state.obj() # noqa 

3441 

3442 self.identity_map.safe_discard(state) 

3443 self._deleted.pop(state, None) 

3444 state._deleted = True 

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

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

3447 # tracked as part of that 

3448 if persistent_to_deleted is not None: 

3449 persistent_to_deleted(self, state) 

3450 

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

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

3453 

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

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

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

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

3458 

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

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

3461 state directly. 

3462 

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

3464 objects which were transient when they were passed to 

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

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

3467 :class:`_orm.Session`. 

3468 

3469 .. seealso:: 

3470 

3471 :meth:`_orm.Session.add_all` 

3472 

3473 :ref:`session_adding` - at :ref:`session_basics` 

3474 

3475 """ 

3476 if _warn and self._warn_on_events: 

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

3478 

3479 try: 

3480 state = attributes.instance_state(instance) 

3481 except exc.NO_STATE as err: 

3482 raise exc.UnmappedInstanceError(instance) from err 

3483 

3484 self._save_or_update_state(state) 

3485 

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

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

3488 

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

3490 behavioral description. 

3491 

3492 .. seealso:: 

3493 

3494 :meth:`_orm.Session.add` 

3495 

3496 :ref:`session_adding` - at :ref:`session_basics` 

3497 

3498 """ 

3499 

3500 if self._warn_on_events: 

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

3502 

3503 for instance in instances: 

3504 self.add(instance, _warn=False) 

3505 

3506 def _save_or_update_state(self, state: InstanceState[Any]) -> None: 

3507 state._orphaned_outside_of_session = False 

3508 self._save_or_update_impl(state) 

3509 

3510 mapper = _state_mapper(state) 

3511 for o, m, st_, dct_ in mapper.cascade_iterator( 

3512 "save-update", state, halt_on=self._contains_state 

3513 ): 

3514 self._save_or_update_impl(st_) 

3515 

3516 def delete(self, instance: object) -> None: 

3517 """Mark an instance as deleted. 

3518 

3519 The object is assumed to be either :term:`persistent` or 

3520 :term:`detached` when passed; after the method is called, the 

3521 object will remain in the :term:`persistent` state until the next 

3522 flush proceeds. During this time, the object will also be a member 

3523 of the :attr:`_orm.Session.deleted` collection. 

3524 

3525 When the next flush proceeds, the object will move to the 

3526 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3527 for its row within the current transaction. When the transaction 

3528 is successfully committed, 

3529 the deleted object is moved to the :term:`detached` state and is 

3530 no longer present within this :class:`_orm.Session`. 

3531 

3532 .. seealso:: 

3533 

3534 :ref:`session_deleting` - at :ref:`session_basics` 

3535 

3536 :meth:`.Session.delete_all` - multiple instance version 

3537 

3538 """ 

3539 if self._warn_on_events: 

3540 self._flush_warning("Session.delete()") 

3541 

3542 self._delete_impl(object_state(instance), instance, head=True) 

3543 

3544 def delete_all(self, instances: Iterable[object]) -> None: 

3545 """Calls :meth:`.Session.delete` on multiple instances. 

3546 

3547 .. seealso:: 

3548 

3549 :meth:`.Session.delete` - main documentation on delete 

3550 

3551 .. versionadded:: 2.1 

3552 

3553 """ 

3554 

3555 if self._warn_on_events: 

3556 self._flush_warning("Session.delete_all()") 

3557 

3558 for instance in instances: 

3559 self._delete_impl(object_state(instance), instance, head=True) 

3560 

3561 def _delete_impl( 

3562 self, state: InstanceState[Any], obj: object, head: bool 

3563 ) -> None: 

3564 if state.key is None: 

3565 if head: 

3566 raise sa_exc.InvalidRequestError( 

3567 "Instance '%s' is not persisted" % state_str(state) 

3568 ) 

3569 else: 

3570 return 

3571 

3572 to_attach = self._before_attach(state, obj) 

3573 

3574 if state in self._deleted: 

3575 return 

3576 

3577 self.identity_map.add(state) 

3578 

3579 if to_attach: 

3580 self._after_attach(state, obj) 

3581 

3582 if head: 

3583 # grab the cascades before adding the item to the deleted list 

3584 # so that autoflush does not delete the item 

3585 # the strong reference to the instance itself is significant here 

3586 cascade_states = list( 

3587 state.manager.mapper.cascade_iterator("delete", state) 

3588 ) 

3589 else: 

3590 cascade_states = None 

3591 

3592 self._deleted[state] = obj 

3593 

3594 if head: 

3595 if TYPE_CHECKING: 

3596 assert cascade_states is not None 

3597 for o, m, st_, dct_ in cascade_states: 

3598 self._delete_impl(st_, o, False) 

3599 

3600 def get( 

3601 self, 

3602 entity: _EntityBindKey[_O], 

3603 ident: _PKIdentityArgument, 

3604 *, 

3605 options: Optional[Sequence[ORMOption]] = None, 

3606 populate_existing: bool = False, 

3607 with_for_update: ForUpdateParameter = None, 

3608 identity_token: Optional[Any] = None, 

3609 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3610 bind_arguments: Optional[_BindArguments] = None, 

3611 ) -> Optional[_O]: 

3612 """Return an instance based on the given primary key identifier, 

3613 or ``None`` if not found. 

3614 

3615 E.g.:: 

3616 

3617 my_user = session.get(User, 5) 

3618 

3619 some_object = session.get(VersionedFoo, (5, 10)) 

3620 

3621 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3622 

3623 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3624 from the now legacy :meth:`_orm.Query.get` method. 

3625 

3626 :meth:`_orm.Session.get` is special in that it provides direct 

3627 access to the identity map of the :class:`.Session`. 

3628 If the given primary key identifier is present 

3629 in the local identity map, the object is returned 

3630 directly from this collection and no SQL is emitted, 

3631 unless the object has been marked fully expired. 

3632 If not present, 

3633 a SELECT is performed in order to locate the object. 

3634 

3635 :meth:`_orm.Session.get` also will perform a check if 

3636 the object is present in the identity map and 

3637 marked as expired - a SELECT 

3638 is emitted to refresh the object as well as to 

3639 ensure that the row is still present. 

3640 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3641 

3642 :param entity: a mapped class or :class:`.Mapper` indicating the 

3643 type of entity to be loaded. 

3644 

3645 :param ident: A scalar, tuple, or dictionary representing the 

3646 primary key. For a composite (e.g. multiple column) primary key, 

3647 a tuple or dictionary should be passed. 

3648 

3649 For a single-column primary key, the scalar calling form is typically 

3650 the most expedient. If the primary key of a row is the value "5", 

3651 the call looks like:: 

3652 

3653 my_object = session.get(SomeClass, 5) 

3654 

3655 The tuple form contains primary key values typically in 

3656 the order in which they correspond to the mapped 

3657 :class:`_schema.Table` 

3658 object's primary key columns, or if the 

3659 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3660 used, in 

3661 the order used for that parameter. For example, if the primary key 

3662 of a row is represented by the integer 

3663 digits "5, 10" the call would look like:: 

3664 

3665 my_object = session.get(SomeClass, (5, 10)) 

3666 

3667 The dictionary form should include as keys the mapped attribute names 

3668 corresponding to each element of the primary key. If the mapped class 

3669 has the attributes ``id``, ``version_id`` as the attributes which 

3670 store the object's primary key value, the call would look like:: 

3671 

3672 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3673 

3674 :param options: optional sequence of loader options which will be 

3675 applied to the query, if one is emitted. 

3676 

3677 :param populate_existing: causes the method to unconditionally emit 

3678 a SQL query and refresh the object with the newly loaded data, 

3679 regardless of whether or not the object is already present. 

3680 

3681 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE 

3682 should be used, or may be a dictionary containing flags to 

3683 indicate a more specific set of FOR UPDATE flags for the SELECT; 

3684 flags should match the parameters of 

3685 :meth:`_query.Query.with_for_update`. 

3686 Supersedes the :paramref:`.Session.refresh.lockmode` parameter. 

3687 

3688 :param execution_options: optional dictionary of execution options, 

3689 which will be associated with the query execution if one is emitted. 

3690 This dictionary can provide a subset of the options that are 

3691 accepted by :meth:`_engine.Connection.execution_options`, and may 

3692 also provide additional options understood only in an ORM context. 

3693 

3694 .. versionadded:: 1.4.29 

3695 

3696 .. seealso:: 

3697 

3698 :ref:`orm_queryguide_execution_options` - ORM-specific execution 

3699 options 

3700 

3701 :param bind_arguments: dictionary of additional arguments to determine 

3702 the bind. May include "mapper", "bind", or other custom arguments. 

3703 Contents of this dictionary are passed to the 

3704 :meth:`.Session.get_bind` method. 

3705 

3706 .. versionadded:: 2.0.0rc1 

3707 

3708 :return: The object instance, or ``None``. 

3709 

3710 """ # noqa: E501 

3711 return self._get_impl( 

3712 entity, 

3713 ident, 

3714 loading._load_on_pk_identity, 

3715 options=options, 

3716 populate_existing=populate_existing, 

3717 with_for_update=with_for_update, 

3718 identity_token=identity_token, 

3719 execution_options=execution_options, 

3720 bind_arguments=bind_arguments, 

3721 ) 

3722 

3723 def get_one( 

3724 self, 

3725 entity: _EntityBindKey[_O], 

3726 ident: _PKIdentityArgument, 

3727 *, 

3728 options: Optional[Sequence[ORMOption]] = None, 

3729 populate_existing: bool = False, 

3730 with_for_update: ForUpdateParameter = None, 

3731 identity_token: Optional[Any] = None, 

3732 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3733 bind_arguments: Optional[_BindArguments] = None, 

3734 ) -> _O: 

3735 """Return exactly one instance based on the given primary key 

3736 identifier, or raise an exception if not found. 

3737 

3738 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3739 

3740 For a detailed documentation of the arguments see the 

3741 method :meth:`.Session.get`. 

3742 

3743 .. versionadded:: 2.0.22 

3744 

3745 :return: The object instance. 

3746 

3747 .. seealso:: 

3748 

3749 :meth:`.Session.get` - equivalent method that instead 

3750 returns ``None`` if no row was found with the provided primary 

3751 key 

3752 

3753 """ 

3754 

3755 instance = self.get( 

3756 entity, 

3757 ident, 

3758 options=options, 

3759 populate_existing=populate_existing, 

3760 with_for_update=with_for_update, 

3761 identity_token=identity_token, 

3762 execution_options=execution_options, 

3763 bind_arguments=bind_arguments, 

3764 ) 

3765 

3766 if instance is None: 

3767 raise sa_exc.NoResultFound( 

3768 "No row was found when one was required" 

3769 ) 

3770 

3771 return instance 

3772 

3773 def _get_impl( 

3774 self, 

3775 entity: _EntityBindKey[_O], 

3776 primary_key_identity: _PKIdentityArgument, 

3777 db_load_fn: Callable[..., _O], 

3778 *, 

3779 options: Optional[Sequence[ExecutableOption]] = None, 

3780 populate_existing: bool = False, 

3781 with_for_update: ForUpdateParameter = None, 

3782 identity_token: Optional[Any] = None, 

3783 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3784 bind_arguments: Optional[_BindArguments] = None, 

3785 ) -> Optional[_O]: 

3786 # convert composite types to individual args 

3787 if ( 

3788 is_composite_class(primary_key_identity) 

3789 and type(primary_key_identity) 

3790 in descriptor_props._composite_getters 

3791 ): 

3792 getter = descriptor_props._composite_getters[ 

3793 type(primary_key_identity) 

3794 ] 

3795 primary_key_identity = getter(primary_key_identity) 

3796 

3797 mapper: Optional[Mapper[_O]] = inspect(entity) 

3798 

3799 if mapper is None or not mapper.is_mapper: 

3800 raise sa_exc.ArgumentError( 

3801 "Expected mapped class or mapper, got: %r" % entity 

3802 ) 

3803 

3804 is_dict = isinstance(primary_key_identity, dict) 

3805 if not is_dict: 

3806 primary_key_identity = util.to_list( 

3807 primary_key_identity, default=[None] 

3808 ) 

3809 

3810 if len(primary_key_identity) != len(mapper.primary_key): 

3811 raise sa_exc.InvalidRequestError( 

3812 "Incorrect number of values in identifier to formulate " 

3813 "primary key for session.get(); primary key columns " 

3814 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3815 ) 

3816 

3817 if is_dict: 

3818 pk_synonyms = mapper._pk_synonyms 

3819 

3820 if pk_synonyms: 

3821 correct_keys = set(pk_synonyms).intersection( 

3822 primary_key_identity 

3823 ) 

3824 

3825 if correct_keys: 

3826 primary_key_identity = dict(primary_key_identity) 

3827 for k in correct_keys: 

3828 primary_key_identity[pk_synonyms[k]] = ( 

3829 primary_key_identity[k] 

3830 ) 

3831 

3832 try: 

3833 primary_key_identity = list( 

3834 primary_key_identity[prop.key] 

3835 for prop in mapper._identity_key_props 

3836 ) 

3837 

3838 except KeyError as err: 

3839 raise sa_exc.InvalidRequestError( 

3840 "Incorrect names of values in identifier to formulate " 

3841 "primary key for session.get(); primary key attribute " 

3842 "names are %s (synonym names are also accepted)" 

3843 % ",".join( 

3844 "'%s'" % prop.key 

3845 for prop in mapper._identity_key_props 

3846 ) 

3847 ) from err 

3848 

3849 if ( 

3850 not populate_existing 

3851 and not mapper.always_refresh 

3852 and with_for_update is None 

3853 ): 

3854 instance = self._identity_lookup( 

3855 mapper, 

3856 primary_key_identity, 

3857 identity_token=identity_token, 

3858 execution_options=execution_options, 

3859 bind_arguments=bind_arguments, 

3860 ) 

3861 

3862 if instance is not None: 

3863 # reject calls for id in identity map but class 

3864 # mismatch. 

3865 if not isinstance(instance, mapper.class_): 

3866 return None 

3867 return instance 

3868 

3869 # TODO: this was being tested before, but this is not possible 

3870 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3871 

3872 # set_label_style() not strictly necessary, however this will ensure 

3873 # that tablename_colname style is used which at the moment is 

3874 # asserted in a lot of unit tests :) 

3875 

3876 load_options = context.QueryContext.default_load_options 

3877 

3878 if populate_existing: 

3879 load_options += {"_populate_existing": populate_existing} 

3880 statement = sql.select(mapper).set_label_style( 

3881 LABEL_STYLE_TABLENAME_PLUS_COL 

3882 ) 

3883 if with_for_update is not None: 

3884 statement._for_update_arg = ForUpdateArg._from_argument( 

3885 with_for_update 

3886 ) 

3887 

3888 if options: 

3889 statement = statement.options(*options) 

3890 return db_load_fn( 

3891 self, 

3892 statement, 

3893 primary_key_identity, 

3894 load_options=load_options, 

3895 identity_token=identity_token, 

3896 execution_options=execution_options, 

3897 bind_arguments=bind_arguments, 

3898 ) 

3899 

3900 def merge( 

3901 self, 

3902 instance: _O, 

3903 *, 

3904 load: bool = True, 

3905 options: Optional[Sequence[ORMOption]] = None, 

3906 ) -> _O: 

3907 """Copy the state of a given instance into a corresponding instance 

3908 within this :class:`.Session`. 

3909 

3910 :meth:`.Session.merge` examines the primary key attributes of the 

3911 source instance, and attempts to reconcile it with an instance of the 

3912 same primary key in the session. If not found locally, it attempts 

3913 to load the object from the database based on primary key, and if 

3914 none can be located, creates a new instance. The state of each 

3915 attribute on the source instance is then copied to the target 

3916 instance. The resulting target instance is then returned by the 

3917 method; the original source instance is left unmodified, and 

3918 un-associated with the :class:`.Session` if not already. 

3919 

3920 This operation cascades to associated instances if the association is 

3921 mapped with ``cascade="merge"``. 

3922 

3923 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3924 

3925 :param instance: Instance to be merged. 

3926 :param load: Boolean, when False, :meth:`.merge` switches into 

3927 a "high performance" mode which causes it to forego emitting history 

3928 events as well as all database access. This flag is used for 

3929 cases such as transferring graphs of objects into a :class:`.Session` 

3930 from a second level cache, or to transfer just-loaded objects 

3931 into the :class:`.Session` owned by a worker thread or process 

3932 without re-querying the database. 

3933 

3934 The ``load=False`` use case adds the caveat that the given 

3935 object has to be in a "clean" state, that is, has no pending changes 

3936 to be flushed - even if the incoming object is detached from any 

3937 :class:`.Session`. This is so that when 

3938 the merge operation populates local attributes and 

3939 cascades to related objects and 

3940 collections, the values can be "stamped" onto the 

3941 target object as is, without generating any history or attribute 

3942 events, and without the need to reconcile the incoming data with 

3943 any existing related objects or collections that might not 

3944 be loaded. The resulting objects from ``load=False`` are always 

3945 produced as "clean", so it is only appropriate that the given objects 

3946 should be "clean" as well, else this suggests a mis-use of the 

3947 method. 

3948 :param options: optional sequence of loader options which will be 

3949 applied to the :meth:`_orm.Session.get` method when the merge 

3950 operation loads the existing version of the object from the database. 

3951 

3952 .. versionadded:: 1.4.24 

3953 

3954 

3955 .. seealso:: 

3956 

3957 :func:`.make_transient_to_detached` - provides for an alternative 

3958 means of "merging" a single object into the :class:`.Session` 

3959 

3960 :meth:`.Session.merge_all` - multiple instance version 

3961 

3962 """ 

3963 

3964 if self._warn_on_events: 

3965 self._flush_warning("Session.merge()") 

3966 

3967 if load: 

3968 # flush current contents if we expect to load data 

3969 self._autoflush() 

3970 

3971 with self.no_autoflush: 

3972 return self._merge( 

3973 object_state(instance), 

3974 attributes.instance_dict(instance), 

3975 load=load, 

3976 options=options, 

3977 _recursive={}, 

3978 _resolve_conflict_map={}, 

3979 ) 

3980 

3981 def merge_all( 

3982 self, 

3983 instances: Iterable[_O], 

3984 *, 

3985 load: bool = True, 

3986 options: Optional[Sequence[ORMOption]] = None, 

3987 ) -> Sequence[_O]: 

3988 """Calls :meth:`.Session.merge` on multiple instances. 

3989 

3990 .. seealso:: 

3991 

3992 :meth:`.Session.merge` - main documentation on merge 

3993 

3994 .. versionadded:: 2.1 

3995 

3996 """ 

3997 

3998 if self._warn_on_events: 

3999 self._flush_warning("Session.merge_all()") 

4000 

4001 if load: 

4002 # flush current contents if we expect to load data 

4003 self._autoflush() 

4004 

4005 return [ 

4006 self._merge( 

4007 object_state(instance), 

4008 attributes.instance_dict(instance), 

4009 load=load, 

4010 options=options, 

4011 _recursive={}, 

4012 _resolve_conflict_map={}, 

4013 ) 

4014 for instance in instances 

4015 ] 

4016 

4017 def _merge( 

4018 self, 

4019 state: InstanceState[_O], 

4020 state_dict: _InstanceDict, 

4021 *, 

4022 options: Optional[Sequence[ORMOption]] = None, 

4023 load: bool, 

4024 _recursive: Dict[Any, object], 

4025 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4026 ) -> _O: 

4027 mapper: Mapper[_O] = _state_mapper(state) 

4028 if state in _recursive: 

4029 return cast(_O, _recursive[state]) 

4030 

4031 new_instance = False 

4032 key = state.key 

4033 

4034 merged: Optional[_O] 

4035 

4036 if key is None: 

4037 if state in self._new: 

4038 util.warn( 

4039 "Instance %s is already pending in this Session yet is " 

4040 "being merged again; this is probably not what you want " 

4041 "to do" % state_str(state) 

4042 ) 

4043 

4044 if not load: 

4045 raise sa_exc.InvalidRequestError( 

4046 "merge() with load=False option does not support " 

4047 "objects transient (i.e. unpersisted) objects. flush() " 

4048 "all changes on mapped instances before merging with " 

4049 "load=False." 

4050 ) 

4051 key = mapper._identity_key_from_state(state) 

4052 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4053 1 

4054 ] and ( 

4055 not _none_set.intersection(key[1]) 

4056 or ( 

4057 mapper.allow_partial_pks 

4058 and not _none_set.issuperset(key[1]) 

4059 ) 

4060 ) 

4061 else: 

4062 key_is_persistent = True 

4063 

4064 merged = self.identity_map.get(key) 

4065 

4066 if merged is None: 

4067 if key_is_persistent and key in _resolve_conflict_map: 

4068 merged = cast(_O, _resolve_conflict_map[key]) 

4069 

4070 elif not load: 

4071 if state.modified: 

4072 raise sa_exc.InvalidRequestError( 

4073 "merge() with load=False option does not support " 

4074 "objects marked as 'dirty'. flush() all changes on " 

4075 "mapped instances before merging with load=False." 

4076 ) 

4077 merged = mapper.class_manager.new_instance() 

4078 merged_state = attributes.instance_state(merged) 

4079 merged_state.key = key 

4080 self._update_impl(merged_state) 

4081 new_instance = True 

4082 

4083 elif key_is_persistent: 

4084 merged = self.get( 

4085 mapper.class_, 

4086 key[1], 

4087 identity_token=key[2], 

4088 options=options, 

4089 ) 

4090 

4091 if merged is None: 

4092 merged = mapper.class_manager.new_instance() 

4093 merged_state = attributes.instance_state(merged) 

4094 merged_dict = attributes.instance_dict(merged) 

4095 new_instance = True 

4096 self._save_or_update_state(merged_state) 

4097 else: 

4098 merged_state = attributes.instance_state(merged) 

4099 merged_dict = attributes.instance_dict(merged) 

4100 

4101 _recursive[state] = merged 

4102 _resolve_conflict_map[key] = merged 

4103 

4104 # check that we didn't just pull the exact same 

4105 # state out. 

4106 if state is not merged_state: 

4107 # version check if applicable 

4108 if mapper.version_id_col is not None: 

4109 existing_version = mapper._get_state_attr_by_column( 

4110 state, 

4111 state_dict, 

4112 mapper.version_id_col, 

4113 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4114 ) 

4115 

4116 merged_version = mapper._get_state_attr_by_column( 

4117 merged_state, 

4118 merged_dict, 

4119 mapper.version_id_col, 

4120 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4121 ) 

4122 

4123 if ( 

4124 existing_version 

4125 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4126 and merged_version 

4127 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4128 and existing_version != merged_version 

4129 ): 

4130 raise exc.StaleDataError( 

4131 "Version id '%s' on merged state %s " 

4132 "does not match existing version '%s'. " 

4133 "Leave the version attribute unset when " 

4134 "merging to update the most recent version." 

4135 % ( 

4136 existing_version, 

4137 state_str(merged_state), 

4138 merged_version, 

4139 ) 

4140 ) 

4141 

4142 merged_state.load_path = state.load_path 

4143 merged_state.load_options = state.load_options 

4144 

4145 # since we are copying load_options, we need to copy 

4146 # the callables_ that would have been generated by those 

4147 # load_options. 

4148 # assumes that the callables we put in state.callables_ 

4149 # are not instance-specific (which they should not be) 

4150 merged_state._copy_callables(state) 

4151 

4152 for prop in mapper.iterate_properties: 

4153 prop.merge( 

4154 self, 

4155 state, 

4156 state_dict, 

4157 merged_state, 

4158 merged_dict, 

4159 load, 

4160 _recursive, 

4161 _resolve_conflict_map, 

4162 ) 

4163 

4164 if not load: 

4165 # remove any history 

4166 merged_state._commit_all(merged_dict, self.identity_map) 

4167 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4168 merged_state, None 

4169 ) 

4170 

4171 if new_instance: 

4172 merged_state.manager.dispatch.load(merged_state, None) 

4173 

4174 return merged 

4175 

4176 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4177 if not self.identity_map.contains_state(state): 

4178 raise sa_exc.InvalidRequestError( 

4179 "Instance '%s' is not persistent within this Session" 

4180 % state_str(state) 

4181 ) 

4182 

4183 def _save_impl(self, state: InstanceState[Any]) -> None: 

4184 if state.key is not None: 

4185 raise sa_exc.InvalidRequestError( 

4186 "Object '%s' already has an identity - " 

4187 "it can't be registered as pending" % state_str(state) 

4188 ) 

4189 

4190 obj = state.obj() 

4191 to_attach = self._before_attach(state, obj) 

4192 if state not in self._new: 

4193 self._new[state] = obj 

4194 state.insert_order = len(self._new) 

4195 if to_attach: 

4196 self._after_attach(state, obj) 

4197 

4198 def _update_impl( 

4199 self, state: InstanceState[Any], revert_deletion: bool = False 

4200 ) -> None: 

4201 if state.key is None: 

4202 raise sa_exc.InvalidRequestError( 

4203 "Instance '%s' is not persisted" % state_str(state) 

4204 ) 

4205 

4206 if state._deleted: 

4207 if revert_deletion: 

4208 if not state._attached: 

4209 return 

4210 del state._deleted 

4211 else: 

4212 raise sa_exc.InvalidRequestError( 

4213 "Instance '%s' has been deleted. " 

4214 "Use the make_transient() " 

4215 "function to send this object back " 

4216 "to the transient state." % state_str(state) 

4217 ) 

4218 

4219 obj = state.obj() 

4220 

4221 # check for late gc 

4222 if obj is None: 

4223 return 

4224 

4225 to_attach = self._before_attach(state, obj) 

4226 

4227 self._deleted.pop(state, None) 

4228 if revert_deletion: 

4229 self.identity_map.replace(state) 

4230 else: 

4231 self.identity_map.add(state) 

4232 

4233 if to_attach: 

4234 self._after_attach(state, obj) 

4235 elif revert_deletion: 

4236 self.dispatch.deleted_to_persistent(self, state) 

4237 

4238 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4239 if state.key is None: 

4240 self._save_impl(state) 

4241 else: 

4242 self._update_impl(state) 

4243 

4244 def enable_relationship_loading(self, obj: object) -> None: 

4245 """Associate an object with this :class:`.Session` for related 

4246 object loading. 

4247 

4248 .. warning:: 

4249 

4250 :meth:`.enable_relationship_loading` exists to serve special 

4251 use cases and is not recommended for general use. 

4252 

4253 Accesses of attributes mapped with :func:`_orm.relationship` 

4254 will attempt to load a value from the database using this 

4255 :class:`.Session` as the source of connectivity. The values 

4256 will be loaded based on foreign key and primary key values 

4257 present on this object - if not present, then those relationships 

4258 will be unavailable. 

4259 

4260 The object will be attached to this session, but will 

4261 **not** participate in any persistence operations; its state 

4262 for almost all purposes will remain either "transient" or 

4263 "detached", except for the case of relationship loading. 

4264 

4265 Also note that backrefs will often not work as expected. 

4266 Altering a relationship-bound attribute on the target object 

4267 may not fire off a backref event, if the effective value 

4268 is what was already loaded from a foreign-key-holding value. 

4269 

4270 The :meth:`.Session.enable_relationship_loading` method is 

4271 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4272 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4273 an object to remain transient while still being able to load 

4274 related items. 

4275 

4276 To make a transient object associated with a :class:`.Session` 

4277 via :meth:`.Session.enable_relationship_loading` pending, add 

4278 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4279 If the object instead represents an existing identity in the database, 

4280 it should be merged using :meth:`.Session.merge`. 

4281 

4282 :meth:`.Session.enable_relationship_loading` does not improve 

4283 behavior when the ORM is used normally - object references should be 

4284 constructed at the object level, not at the foreign key level, so 

4285 that they are present in an ordinary way before flush() 

4286 proceeds. This method is not intended for general use. 

4287 

4288 .. seealso:: 

4289 

4290 :paramref:`_orm.relationship.load_on_pending` - this flag 

4291 allows per-relationship loading of many-to-ones on items that 

4292 are pending. 

4293 

4294 :func:`.make_transient_to_detached` - allows for an object to 

4295 be added to a :class:`.Session` without SQL emitted, which then 

4296 will unexpire attributes on access. 

4297 

4298 """ 

4299 try: 

4300 state = attributes.instance_state(obj) 

4301 except exc.NO_STATE as err: 

4302 raise exc.UnmappedInstanceError(obj) from err 

4303 

4304 to_attach = self._before_attach(state, obj) 

4305 state._load_pending = True 

4306 if to_attach: 

4307 self._after_attach(state, obj) 

4308 

4309 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4310 self._autobegin_t() 

4311 

4312 if state.session_id == self.hash_key: 

4313 return False 

4314 

4315 if state.session_id and state.session_id in _sessions: 

4316 raise sa_exc.InvalidRequestError( 

4317 "Object '%s' is already attached to session '%s' " 

4318 "(this is '%s')" 

4319 % (state_str(state), state.session_id, self.hash_key) 

4320 ) 

4321 

4322 self.dispatch.before_attach(self, state) 

4323 

4324 return True 

4325 

4326 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4327 state.session_id = self.hash_key 

4328 if state.modified and state._strong_obj is None: 

4329 state._strong_obj = obj 

4330 self.dispatch.after_attach(self, state) 

4331 

4332 if state.key: 

4333 self.dispatch.detached_to_persistent(self, state) 

4334 else: 

4335 self.dispatch.transient_to_pending(self, state) 

4336 

4337 def __contains__(self, instance: object) -> bool: 

4338 """Return True if the instance is associated with this session. 

4339 

4340 The instance may be pending or persistent within the Session for a 

4341 result of True. 

4342 

4343 """ 

4344 try: 

4345 state = attributes.instance_state(instance) 

4346 except exc.NO_STATE as err: 

4347 raise exc.UnmappedInstanceError(instance) from err 

4348 return self._contains_state(state) 

4349 

4350 def __iter__(self) -> Iterator[object]: 

4351 """Iterate over all pending or persistent instances within this 

4352 Session. 

4353 

4354 """ 

4355 return iter( 

4356 list(self._new.values()) + list(self.identity_map.values()) 

4357 ) 

4358 

4359 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4360 return state in self._new or self.identity_map.contains_state(state) 

4361 

4362 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4363 """Flush all the object changes to the database. 

4364 

4365 Writes out all pending object creations, deletions and modifications 

4366 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4367 automatically ordered by the Session's unit of work dependency 

4368 solver. 

4369 

4370 Database operations will be issued in the current transactional 

4371 context and do not affect the state of the transaction, unless an 

4372 error occurs, in which case the entire transaction is rolled back. 

4373 You may flush() as often as you like within a transaction to move 

4374 changes from Python to the database's transaction buffer. 

4375 

4376 :param objects: Optional; restricts the flush operation to operate 

4377 only on elements that are in the given collection. 

4378 

4379 This feature is for an extremely narrow set of use cases where 

4380 particular objects may need to be operated upon before the 

4381 full flush() occurs. It is not intended for general use. 

4382 

4383 .. deprecated:: 2.1 

4384 

4385 """ 

4386 

4387 if self._flushing: 

4388 raise sa_exc.InvalidRequestError("Session is already flushing") 

4389 

4390 if self._is_clean(): 

4391 return 

4392 try: 

4393 self._flushing = True 

4394 self._flush(objects) 

4395 finally: 

4396 self._flushing = False 

4397 

4398 def _flush_warning(self, method: Any) -> None: 

4399 util.warn( 

4400 "Usage of the '%s' operation is not currently supported " 

4401 "within the execution stage of the flush process. " 

4402 "Results may not be consistent. Consider using alternative " 

4403 "event listeners or connection-level operations instead." % method 

4404 ) 

4405 

4406 def _is_clean(self) -> bool: 

4407 return ( 

4408 not self.identity_map.check_modified() 

4409 and not self._deleted 

4410 and not self._new 

4411 ) 

4412 

4413 # have this here since it otherwise causes issues with the proxy 

4414 # method generation 

4415 @deprecated_params( 

4416 objects=( 

4417 "2.1", 

4418 "The `objects` parameter of `Session.flush` is deprecated", 

4419 ) 

4420 ) 

4421 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4422 dirty = self._dirty_states 

4423 if not dirty and not self._deleted and not self._new: 

4424 self.identity_map._modified.clear() 

4425 return 

4426 

4427 flush_context = UOWTransaction(self) 

4428 

4429 if self.dispatch.before_flush: 

4430 self.dispatch.before_flush(self, flush_context, objects) 

4431 # re-establish "dirty states" in case the listeners 

4432 # added 

4433 dirty = self._dirty_states 

4434 

4435 deleted = set(self._deleted) 

4436 new = set(self._new) 

4437 

4438 dirty = set(dirty).difference(deleted) 

4439 

4440 # create the set of all objects we want to operate upon 

4441 if objects: 

4442 # specific list passed in 

4443 objset = set() 

4444 for o in objects: 

4445 try: 

4446 state = attributes.instance_state(o) 

4447 

4448 except exc.NO_STATE as err: 

4449 raise exc.UnmappedInstanceError(o) from err 

4450 objset.add(state) 

4451 else: 

4452 objset = None 

4453 

4454 # store objects whose fate has been decided 

4455 processed = set() 

4456 

4457 # put all saves/updates into the flush context. detect top-level 

4458 # orphans and throw them into deleted. 

4459 if objset: 

4460 proc = new.union(dirty).intersection(objset).difference(deleted) 

4461 else: 

4462 proc = new.union(dirty).difference(deleted) 

4463 

4464 for state in proc: 

4465 is_orphan = _state_mapper(state)._is_orphan(state) 

4466 

4467 is_persistent_orphan = is_orphan and state.has_identity 

4468 

4469 if ( 

4470 is_orphan 

4471 and not is_persistent_orphan 

4472 and state._orphaned_outside_of_session 

4473 ): 

4474 self._expunge_states([state]) 

4475 else: 

4476 _reg = flush_context.register_object( 

4477 state, isdelete=is_persistent_orphan 

4478 ) 

4479 assert _reg, "Failed to add object to the flush context!" 

4480 processed.add(state) 

4481 

4482 # put all remaining deletes into the flush context. 

4483 if objset: 

4484 proc = deleted.intersection(objset).difference(processed) 

4485 else: 

4486 proc = deleted.difference(processed) 

4487 for state in proc: 

4488 _reg = flush_context.register_object(state, isdelete=True) 

4489 assert _reg, "Failed to add object to the flush context!" 

4490 

4491 if not flush_context.has_work: 

4492 return 

4493 

4494 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4495 try: 

4496 self._warn_on_events = True 

4497 try: 

4498 flush_context.execute() 

4499 finally: 

4500 self._warn_on_events = False 

4501 

4502 self.dispatch.after_flush(self, flush_context) 

4503 

4504 flush_context.finalize_flush_changes() 

4505 

4506 if not objects and self.identity_map._modified: 

4507 len_ = len(self.identity_map._modified) 

4508 

4509 statelib.InstanceState._commit_all_states( 

4510 [ 

4511 (state, state.dict) 

4512 for state in self.identity_map._modified 

4513 ], 

4514 instance_dict=self.identity_map, 

4515 ) 

4516 util.warn( 

4517 "Attribute history events accumulated on %d " 

4518 "previously clean instances " 

4519 "within inner-flush event handlers have been " 

4520 "reset, and will not result in database updates. " 

4521 "Consider using set_committed_value() within " 

4522 "inner-flush event handlers to avoid this warning." % len_ 

4523 ) 

4524 

4525 # useful assertions: 

4526 # if not objects: 

4527 # assert not self.identity_map._modified 

4528 # else: 

4529 # assert self.identity_map._modified == \ 

4530 # self.identity_map._modified.difference(objects) 

4531 

4532 self.dispatch.after_flush_postexec(self, flush_context) 

4533 

4534 transaction.commit() 

4535 

4536 except: 

4537 with util.safe_reraise(): 

4538 transaction.rollback(_capture_exception=True) 

4539 

4540 def bulk_save_objects( 

4541 self, 

4542 objects: Iterable[object], 

4543 return_defaults: bool = False, 

4544 update_changed_only: bool = True, 

4545 preserve_order: bool = True, 

4546 ) -> None: 

4547 """Perform a bulk save of the given list of objects. 

4548 

4549 .. legacy:: 

4550 

4551 This method is a legacy feature as of the 2.0 series of 

4552 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4553 the sections :ref:`orm_queryguide_bulk_insert` and 

4554 :ref:`orm_queryguide_bulk_update`. 

4555 

4556 For general INSERT and UPDATE of existing ORM mapped objects, 

4557 prefer standard :term:`unit of work` data management patterns, 

4558 introduced in the :ref:`unified_tutorial` at 

4559 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4560 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4561 which solves previous issues of bulk INSERT slowness. 

4562 

4563 :param objects: a sequence of mapped object instances. The mapped 

4564 objects are persisted as is, and are **not** associated with the 

4565 :class:`.Session` afterwards. 

4566 

4567 For each object, whether the object is sent as an INSERT or an 

4568 UPDATE is dependent on the same rules used by the :class:`.Session` 

4569 in traditional operation; if the object has the 

4570 :attr:`.InstanceState.key` 

4571 attribute set, then the object is assumed to be "detached" and 

4572 will result in an UPDATE. Otherwise, an INSERT is used. 

4573 

4574 In the case of an UPDATE, statements are grouped based on which 

4575 attributes have changed, and are thus to be the subject of each 

4576 SET clause. If ``update_changed_only`` is False, then all 

4577 attributes present within each object are applied to the UPDATE 

4578 statement, which may help in allowing the statements to be grouped 

4579 together into a larger executemany(), and will also reduce the 

4580 overhead of checking history on attributes. 

4581 

4582 :param return_defaults: when True, rows that are missing values which 

4583 generate defaults, namely integer primary key defaults and sequences, 

4584 will be inserted **one at a time**, so that the primary key value 

4585 is available. In particular this will allow joined-inheritance 

4586 and other multi-table mappings to insert correctly without the need 

4587 to provide primary key values ahead of time; however, 

4588 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4589 reduces the performance gains** of the method overall. It is strongly 

4590 advised to please use the standard :meth:`_orm.Session.add_all` 

4591 approach. 

4592 

4593 :param update_changed_only: when True, UPDATE statements are rendered 

4594 based on those attributes in each state that have logged changes. 

4595 When False, all attributes present are rendered into the SET clause 

4596 with the exception of primary key attributes. 

4597 

4598 :param preserve_order: when True, the order of inserts and updates 

4599 matches exactly the order in which the objects are given. When 

4600 False, common types of objects are grouped into inserts 

4601 and updates, to allow for more batching opportunities. 

4602 

4603 .. seealso:: 

4604 

4605 :doc:`queryguide/dml` 

4606 

4607 :meth:`.Session.bulk_insert_mappings` 

4608 

4609 :meth:`.Session.bulk_update_mappings` 

4610 

4611 """ 

4612 

4613 obj_states: Iterable[InstanceState[Any]] 

4614 

4615 obj_states = (attributes.instance_state(obj) for obj in objects) 

4616 

4617 if not preserve_order: 

4618 # the purpose of this sort is just so that common mappers 

4619 # and persistence states are grouped together, so that groupby 

4620 # will return a single group for a particular type of mapper. 

4621 # it's not trying to be deterministic beyond that. 

4622 obj_states = sorted( 

4623 obj_states, 

4624 key=lambda state: (id(state.mapper), state.key is not None), 

4625 ) 

4626 

4627 def grouping_key( 

4628 state: InstanceState[_O], 

4629 ) -> Tuple[Mapper[_O], bool]: 

4630 return (state.mapper, state.key is not None) 

4631 

4632 for (mapper, isupdate), states in itertools.groupby( 

4633 obj_states, grouping_key 

4634 ): 

4635 self._bulk_save_mappings( 

4636 mapper, 

4637 states, 

4638 isupdate=isupdate, 

4639 isstates=True, 

4640 return_defaults=return_defaults, 

4641 update_changed_only=update_changed_only, 

4642 render_nulls=False, 

4643 ) 

4644 

4645 def bulk_insert_mappings( 

4646 self, 

4647 mapper: Mapper[Any], 

4648 mappings: Iterable[Dict[str, Any]], 

4649 return_defaults: bool = False, 

4650 render_nulls: bool = False, 

4651 ) -> None: 

4652 """Perform a bulk insert of the given list of mapping dictionaries. 

4653 

4654 .. legacy:: 

4655 

4656 This method is a legacy feature as of the 2.0 series of 

4657 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4658 the sections :ref:`orm_queryguide_bulk_insert` and 

4659 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4660 implementation details with this method and adds new features 

4661 as well. 

4662 

4663 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4664 object, 

4665 representing the single kind of object represented within the mapping 

4666 list. 

4667 

4668 :param mappings: a sequence of dictionaries, each one containing the 

4669 state of the mapped row to be inserted, in terms of the attribute 

4670 names on the mapped class. If the mapping refers to multiple tables, 

4671 such as a joined-inheritance mapping, each dictionary must contain all 

4672 keys to be populated into all tables. 

4673 

4674 :param return_defaults: when True, the INSERT process will be altered 

4675 to ensure that newly generated primary key values will be fetched. 

4676 The rationale for this parameter is typically to enable 

4677 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4678 be bulk inserted. 

4679 

4680 .. note:: for backends that don't support RETURNING, the 

4681 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4682 parameter can significantly decrease performance as INSERT 

4683 statements can no longer be batched. See 

4684 :ref:`engine_insertmanyvalues` 

4685 for background on which backends are affected. 

4686 

4687 :param render_nulls: When True, a value of ``None`` will result 

4688 in a NULL value being included in the INSERT statement, rather 

4689 than the column being omitted from the INSERT. This allows all 

4690 the rows being INSERTed to have the identical set of columns which 

4691 allows the full set of rows to be batched to the DBAPI. Normally, 

4692 each column-set that contains a different combination of NULL values 

4693 than the previous row must omit a different series of columns from 

4694 the rendered INSERT statement, which means it must be emitted as a 

4695 separate statement. By passing this flag, the full set of rows 

4696 are guaranteed to be batchable into one batch; the cost however is 

4697 that server-side defaults which are invoked by an omitted column will 

4698 be skipped, so care must be taken to ensure that these are not 

4699 necessary. 

4700 

4701 .. warning:: 

4702 

4703 When this flag is set, **server side default SQL values will 

4704 not be invoked** for those columns that are inserted as NULL; 

4705 the NULL value will be sent explicitly. Care must be taken 

4706 to ensure that no server-side default functions need to be 

4707 invoked for the operation as a whole. 

4708 

4709 .. seealso:: 

4710 

4711 :doc:`queryguide/dml` 

4712 

4713 :meth:`.Session.bulk_save_objects` 

4714 

4715 :meth:`.Session.bulk_update_mappings` 

4716 

4717 """ 

4718 self._bulk_save_mappings( 

4719 mapper, 

4720 mappings, 

4721 isupdate=False, 

4722 isstates=False, 

4723 return_defaults=return_defaults, 

4724 update_changed_only=False, 

4725 render_nulls=render_nulls, 

4726 ) 

4727 

4728 def bulk_update_mappings( 

4729 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4730 ) -> None: 

4731 """Perform a bulk update of the given list of mapping dictionaries. 

4732 

4733 .. legacy:: 

4734 

4735 This method is a legacy feature as of the 2.0 series of 

4736 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4737 the sections :ref:`orm_queryguide_bulk_insert` and 

4738 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4739 implementation details with this method and adds new features 

4740 as well. 

4741 

4742 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4743 object, 

4744 representing the single kind of object represented within the mapping 

4745 list. 

4746 

4747 :param mappings: a sequence of dictionaries, each one containing the 

4748 state of the mapped row to be updated, in terms of the attribute names 

4749 on the mapped class. If the mapping refers to multiple tables, such 

4750 as a joined-inheritance mapping, each dictionary may contain keys 

4751 corresponding to all tables. All those keys which are present and 

4752 are not part of the primary key are applied to the SET clause of the 

4753 UPDATE statement; the primary key values, which are required, are 

4754 applied to the WHERE clause. 

4755 

4756 

4757 .. seealso:: 

4758 

4759 :doc:`queryguide/dml` 

4760 

4761 :meth:`.Session.bulk_insert_mappings` 

4762 

4763 :meth:`.Session.bulk_save_objects` 

4764 

4765 """ 

4766 self._bulk_save_mappings( 

4767 mapper, 

4768 mappings, 

4769 isupdate=True, 

4770 isstates=False, 

4771 return_defaults=False, 

4772 update_changed_only=False, 

4773 render_nulls=False, 

4774 ) 

4775 

4776 def _bulk_save_mappings( 

4777 self, 

4778 mapper: Mapper[_O], 

4779 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4780 *, 

4781 isupdate: bool, 

4782 isstates: bool, 

4783 return_defaults: bool, 

4784 update_changed_only: bool, 

4785 render_nulls: bool, 

4786 ) -> None: 

4787 mapper = _class_to_mapper(mapper) 

4788 self._flushing = True 

4789 

4790 transaction = self._autobegin_t()._begin() 

4791 try: 

4792 if isupdate: 

4793 bulk_persistence._bulk_update( 

4794 mapper, 

4795 mappings, 

4796 transaction, 

4797 isstates=isstates, 

4798 update_changed_only=update_changed_only, 

4799 ) 

4800 else: 

4801 bulk_persistence._bulk_insert( 

4802 mapper, 

4803 mappings, 

4804 transaction, 

4805 isstates=isstates, 

4806 return_defaults=return_defaults, 

4807 render_nulls=render_nulls, 

4808 ) 

4809 transaction.commit() 

4810 

4811 except: 

4812 with util.safe_reraise(): 

4813 transaction.rollback(_capture_exception=True) 

4814 finally: 

4815 self._flushing = False 

4816 

4817 def is_modified( 

4818 self, instance: object, include_collections: bool = True 

4819 ) -> bool: 

4820 r"""Return ``True`` if the given instance has locally 

4821 modified attributes. 

4822 

4823 This method retrieves the history for each instrumented 

4824 attribute on the instance and performs a comparison of the current 

4825 value to its previously flushed or committed value, if any. 

4826 

4827 It is in effect a more expensive and accurate 

4828 version of checking for the given instance in the 

4829 :attr:`.Session.dirty` collection; a full test for 

4830 each attribute's net "dirty" status is performed. 

4831 

4832 E.g.:: 

4833 

4834 return session.is_modified(someobject) 

4835 

4836 A few caveats to this method apply: 

4837 

4838 * Instances present in the :attr:`.Session.dirty` collection may 

4839 report ``False`` when tested with this method. This is because 

4840 the object may have received change events via attribute mutation, 

4841 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4842 is the same as that loaded from the database, resulting in no net 

4843 change here. 

4844 * Scalar attributes may not have recorded the previously set 

4845 value when a new value was applied, if the attribute was not loaded, 

4846 or was expired, at the time the new value was received - in these 

4847 cases, the attribute is assumed to have a change, even if there is 

4848 ultimately no net change against its database value. SQLAlchemy in 

4849 most cases does not need the "old" value when a set event occurs, so 

4850 it skips the expense of a SQL call if the old value isn't present, 

4851 based on the assumption that an UPDATE of the scalar value is 

4852 usually needed, and in those few cases where it isn't, is less 

4853 expensive on average than issuing a defensive SELECT. 

4854 

4855 The "old" value is fetched unconditionally upon set only if the 

4856 attribute container has the ``active_history`` flag set to ``True``. 

4857 This flag is set typically for primary key attributes and scalar 

4858 object references that are not a simple many-to-one. To set this 

4859 flag for any arbitrary mapped column, use the ``active_history`` 

4860 argument with :func:`.column_property`. 

4861 

4862 :param instance: mapped instance to be tested for pending changes. 

4863 :param include_collections: Indicates if multivalued collections 

4864 should be included in the operation. Setting this to ``False`` is a 

4865 way to detect only local-column based properties (i.e. scalar columns 

4866 or many-to-one foreign keys) that would result in an UPDATE for this 

4867 instance upon flush. 

4868 

4869 """ 

4870 state = object_state(instance) 

4871 

4872 if not state.modified: 

4873 return False 

4874 

4875 dict_ = state.dict 

4876 

4877 for attr in state.manager.attributes: 

4878 if ( 

4879 not include_collections 

4880 and hasattr(attr.impl, "get_collection") 

4881 ) or not hasattr(attr.impl, "get_history"): 

4882 continue 

4883 

4884 (added, unchanged, deleted) = attr.impl.get_history( 

4885 state, dict_, passive=PassiveFlag.NO_CHANGE 

4886 ) 

4887 

4888 if added or deleted: 

4889 return True 

4890 else: 

4891 return False 

4892 

4893 @property 

4894 def is_active(self) -> bool: 

4895 """True if this :class:`.Session` not in "partial rollback" state. 

4896 

4897 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4898 a new transaction immediately, so this attribute will be False 

4899 when the :class:`_orm.Session` is first instantiated. 

4900 

4901 "partial rollback" state typically indicates that the flush process 

4902 of the :class:`_orm.Session` has failed, and that the 

4903 :meth:`_orm.Session.rollback` method must be emitted in order to 

4904 fully roll back the transaction. 

4905 

4906 If this :class:`_orm.Session` is not in a transaction at all, the 

4907 :class:`_orm.Session` will autobegin when it is first used, so in this 

4908 case :attr:`_orm.Session.is_active` will return True. 

4909 

4910 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4911 and that transaction has not been rolled back internally, the 

4912 :attr:`_orm.Session.is_active` will also return True. 

4913 

4914 .. seealso:: 

4915 

4916 :ref:`faq_session_rollback` 

4917 

4918 :meth:`_orm.Session.in_transaction` 

4919 

4920 """ 

4921 return self._transaction is None or self._transaction.is_active 

4922 

4923 @property 

4924 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4925 """The set of all persistent states considered dirty. 

4926 

4927 This method returns all states that were modified including 

4928 those that were possibly deleted. 

4929 

4930 """ 

4931 return self.identity_map._dirty_states() 

4932 

4933 @property 

4934 def dirty(self) -> IdentitySet: 

4935 """The set of all persistent instances considered dirty. 

4936 

4937 E.g.:: 

4938 

4939 some_mapped_object in session.dirty 

4940 

4941 Instances are considered dirty when they were modified but not 

4942 deleted. 

4943 

4944 Note that this 'dirty' calculation is 'optimistic'; most 

4945 attribute-setting or collection modification operations will 

4946 mark an instance as 'dirty' and place it in this set, even if 

4947 there is no net change to the attribute's value. At flush 

4948 time, the value of each attribute is compared to its 

4949 previously saved value, and if there's no net change, no SQL 

4950 operation will occur (this is a more expensive operation so 

4951 it's only done at flush time). 

4952 

4953 To check if an instance has actionable net changes to its 

4954 attributes, use the :meth:`.Session.is_modified` method. 

4955 

4956 """ 

4957 return IdentitySet( 

4958 [ 

4959 state.obj() 

4960 for state in self._dirty_states 

4961 if state not in self._deleted 

4962 ] 

4963 ) 

4964 

4965 @property 

4966 def deleted(self) -> IdentitySet: 

4967 "The set of all instances marked as 'deleted' within this ``Session``" 

4968 

4969 return util.IdentitySet(list(self._deleted.values())) 

4970 

4971 @property 

4972 def new(self) -> IdentitySet: 

4973 "The set of all instances marked as 'new' within this ``Session``." 

4974 

4975 return util.IdentitySet(list(self._new.values())) 

4976 

4977 

4978_S = TypeVar("_S", bound="Session") 

4979 

4980 

4981class sessionmaker(_SessionClassMethods, Generic[_S]): 

4982 """A configurable :class:`.Session` factory. 

4983 

4984 The :class:`.sessionmaker` factory generates new 

4985 :class:`.Session` objects when called, creating them given 

4986 the configurational arguments established here. 

4987 

4988 e.g.:: 

4989 

4990 from sqlalchemy import create_engine 

4991 from sqlalchemy.orm import sessionmaker 

4992 

4993 # an Engine, which the Session will use for connection 

4994 # resources 

4995 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

4996 

4997 Session = sessionmaker(engine) 

4998 

4999 with Session() as session: 

5000 session.add(some_object) 

5001 session.add(some_other_object) 

5002 session.commit() 

5003 

5004 Context manager use is optional; otherwise, the returned 

5005 :class:`_orm.Session` object may be closed explicitly via the 

5006 :meth:`_orm.Session.close` method. Using a 

5007 ``try:/finally:`` block is optional, however will ensure that the close 

5008 takes place even if there are database errors:: 

5009 

5010 session = Session() 

5011 try: 

5012 session.add(some_object) 

5013 session.add(some_other_object) 

5014 session.commit() 

5015 finally: 

5016 session.close() 

5017 

5018 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5019 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5020 for :class:`_engine.Connection` objects. In this way it also includes 

5021 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5022 manager which both begins and commits a transaction, as well as closes 

5023 out the :class:`_orm.Session` when complete, rolling back the transaction 

5024 if any errors occur:: 

5025 

5026 Session = sessionmaker(engine) 

5027 

5028 with Session.begin() as session: 

5029 session.add(some_object) 

5030 session.add(some_other_object) 

5031 # commits transaction, closes session 

5032 

5033 .. versionadded:: 1.4 

5034 

5035 When calling upon :class:`_orm.sessionmaker` to construct a 

5036 :class:`_orm.Session`, keyword arguments may also be passed to the 

5037 method; these arguments will override that of the globally configured 

5038 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5039 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5040 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5041 

5042 Session = sessionmaker(engine) 

5043 

5044 # bind an individual session to a connection 

5045 

5046 with engine.connect() as connection: 

5047 with Session(bind=connection) as session: 

5048 ... # work with session 

5049 

5050 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5051 can be used to specify additional keyword arguments to the factory, which 

5052 will take effect for subsequent :class:`.Session` objects generated. This 

5053 is usually used to associate one or more :class:`_engine.Engine` objects 

5054 with an existing 

5055 :class:`.sessionmaker` factory before it is first used:: 

5056 

5057 # application starts, sessionmaker does not have 

5058 # an engine bound yet 

5059 Session = sessionmaker() 

5060 

5061 # ... later, when an engine URL is read from a configuration 

5062 # file or other events allow the engine to be created 

5063 engine = create_engine("sqlite:///foo.db") 

5064 Session.configure(bind=engine) 

5065 

5066 sess = Session() 

5067 # work with session 

5068 

5069 .. seealso:: 

5070 

5071 :ref:`session_getting` - introductory text on creating 

5072 sessions using :class:`.sessionmaker`. 

5073 

5074 """ 

5075 

5076 class_: Type[_S] 

5077 

5078 @overload 

5079 def __init__( 

5080 self, 

5081 bind: Optional[_SessionBind] = ..., 

5082 *, 

5083 class_: Type[_S], 

5084 autoflush: bool = ..., 

5085 expire_on_commit: bool = ..., 

5086 info: Optional[_InfoType] = ..., 

5087 **kw: Any, 

5088 ): ... 

5089 

5090 @overload 

5091 def __init__( 

5092 self: "sessionmaker[Session]", 

5093 bind: Optional[_SessionBind] = ..., 

5094 *, 

5095 autoflush: bool = ..., 

5096 expire_on_commit: bool = ..., 

5097 info: Optional[_InfoType] = ..., 

5098 **kw: Any, 

5099 ): ... 

5100 

5101 def __init__( 

5102 self, 

5103 bind: Optional[_SessionBind] = None, 

5104 *, 

5105 class_: Type[_S] = Session, # type: ignore 

5106 autoflush: bool = True, 

5107 expire_on_commit: bool = True, 

5108 info: Optional[_InfoType] = None, 

5109 **kw: Any, 

5110 ): 

5111 r"""Construct a new :class:`.sessionmaker`. 

5112 

5113 All arguments here except for ``class_`` correspond to arguments 

5114 accepted by :class:`.Session` directly. See the 

5115 :meth:`.Session.__init__` docstring for more details on parameters. 

5116 

5117 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5118 with 

5119 which newly created :class:`.Session` objects will be associated. 

5120 :param class\_: class to use in order to create new :class:`.Session` 

5121 objects. Defaults to :class:`.Session`. 

5122 :param autoflush: The autoflush setting to use with newly created 

5123 :class:`.Session` objects. 

5124 

5125 .. seealso:: 

5126 

5127 :ref:`session_flushing` - additional background on autoflush 

5128 

5129 :param expire_on_commit=True: the 

5130 :paramref:`_orm.Session.expire_on_commit` setting to use 

5131 with newly created :class:`.Session` objects. 

5132 

5133 :param info: optional dictionary of information that will be available 

5134 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5135 replaced, when the ``info`` parameter is specified to the specific 

5136 :class:`.Session` construction operation. 

5137 

5138 :param \**kw: all other keyword arguments are passed to the 

5139 constructor of newly created :class:`.Session` objects. 

5140 

5141 """ 

5142 kw["bind"] = bind 

5143 kw["autoflush"] = autoflush 

5144 kw["expire_on_commit"] = expire_on_commit 

5145 if info is not None: 

5146 kw["info"] = info 

5147 self.kw = kw 

5148 # make our own subclass of the given class, so that 

5149 # events can be associated with it specifically. 

5150 self.class_ = type(class_.__name__, (class_,), {}) 

5151 

5152 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5153 """Produce a context manager that both provides a new 

5154 :class:`_orm.Session` as well as a transaction that commits. 

5155 

5156 

5157 e.g.:: 

5158 

5159 Session = sessionmaker(some_engine) 

5160 

5161 with Session.begin() as session: 

5162 session.add(some_object) 

5163 

5164 # commits transaction, closes session 

5165 

5166 .. versionadded:: 1.4 

5167 

5168 

5169 """ 

5170 

5171 session = self() 

5172 return session._maker_context_manager() 

5173 

5174 def __call__(self, **local_kw: Any) -> _S: 

5175 """Produce a new :class:`.Session` object using the configuration 

5176 established in this :class:`.sessionmaker`. 

5177 

5178 In Python, the ``__call__`` method is invoked on an object when 

5179 it is "called" in the same way as a function:: 

5180 

5181 Session = sessionmaker(some_engine) 

5182 session = Session() # invokes sessionmaker.__call__() 

5183 

5184 """ 

5185 for k, v in self.kw.items(): 

5186 if k == "info" and "info" in local_kw: 

5187 d = v.copy() 

5188 d.update(local_kw["info"]) 

5189 local_kw["info"] = d 

5190 else: 

5191 local_kw.setdefault(k, v) 

5192 return self.class_(**local_kw) 

5193 

5194 def configure(self, **new_kw: Any) -> None: 

5195 """(Re)configure the arguments for this sessionmaker. 

5196 

5197 e.g.:: 

5198 

5199 Session = sessionmaker() 

5200 

5201 Session.configure(bind=create_engine("sqlite://")) 

5202 """ 

5203 self.kw.update(new_kw) 

5204 

5205 def __repr__(self) -> str: 

5206 return "%s(class_=%r, %s)" % ( 

5207 self.__class__.__name__, 

5208 self.class_.__name__, 

5209 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5210 ) 

5211 

5212 

5213def close_all_sessions() -> None: 

5214 """Close all sessions in memory. 

5215 

5216 This function consults a global registry of all :class:`.Session` objects 

5217 and calls :meth:`.Session.close` on them, which resets them to a clean 

5218 state. 

5219 

5220 This function is not for general use but may be useful for test suites 

5221 within the teardown scheme. 

5222 

5223 """ 

5224 

5225 for sess in _sessions.values(): 

5226 sess.close() 

5227 

5228 

5229def make_transient(instance: object) -> None: 

5230 """Alter the state of the given instance so that it is :term:`transient`. 

5231 

5232 .. note:: 

5233 

5234 :func:`.make_transient` is a special-case function for 

5235 advanced use cases only. 

5236 

5237 The given mapped instance is assumed to be in the :term:`persistent` or 

5238 :term:`detached` state. The function will remove its association with any 

5239 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5240 effect is that the object will behave as though it were newly constructed, 

5241 except retaining any attribute / collection values that were loaded at the 

5242 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5243 if this object had been deleted as a result of using 

5244 :meth:`.Session.delete`. 

5245 

5246 .. warning:: 

5247 

5248 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5249 load ORM-mapped attributes that are not currently loaded at the time 

5250 the function is called. This includes attributes which: 

5251 

5252 * were expired via :meth:`.Session.expire` 

5253 

5254 * were expired as the natural effect of committing a session 

5255 transaction, e.g. :meth:`.Session.commit` 

5256 

5257 * are normally :term:`lazy loaded` but are not currently loaded 

5258 

5259 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5260 not yet loaded 

5261 

5262 * were not present in the query which loaded this object, such as that 

5263 which is common in joined table inheritance and other scenarios. 

5264 

5265 After :func:`.make_transient` is called, unloaded attributes such 

5266 as those above will normally resolve to the value ``None`` when 

5267 accessed, or an empty collection for a collection-oriented attribute. 

5268 As the object is transient and un-associated with any database 

5269 identity, it will no longer retrieve these values. 

5270 

5271 .. seealso:: 

5272 

5273 :func:`.make_transient_to_detached` 

5274 

5275 """ 

5276 state = attributes.instance_state(instance) 

5277 s = _state_session(state) 

5278 if s: 

5279 s._expunge_states([state]) 

5280 

5281 # remove expired state 

5282 state.expired_attributes.clear() 

5283 

5284 # remove deferred callables 

5285 if state.callables: 

5286 del state.callables 

5287 

5288 if state.key: 

5289 del state.key 

5290 if state._deleted: 

5291 del state._deleted 

5292 

5293 

5294def make_transient_to_detached(instance: object) -> None: 

5295 """Make the given transient instance :term:`detached`. 

5296 

5297 .. note:: 

5298 

5299 :func:`.make_transient_to_detached` is a special-case function for 

5300 advanced use cases only. 

5301 

5302 All attribute history on the given instance 

5303 will be reset as though the instance were freshly loaded 

5304 from a query. Missing attributes will be marked as expired. 

5305 The primary key attributes of the object, which are required, will be made 

5306 into the "key" of the instance. 

5307 

5308 The object can then be added to a session, or merged 

5309 possibly with the load=False flag, at which point it will look 

5310 as if it were loaded that way, without emitting SQL. 

5311 

5312 This is a special use case function that differs from a normal 

5313 call to :meth:`.Session.merge` in that a given persistent state 

5314 can be manufactured without any SQL calls. 

5315 

5316 .. seealso:: 

5317 

5318 :func:`.make_transient` 

5319 

5320 :meth:`.Session.enable_relationship_loading` 

5321 

5322 """ 

5323 state = attributes.instance_state(instance) 

5324 if state.session_id or state.key: 

5325 raise sa_exc.InvalidRequestError("Given object must be transient") 

5326 state.key = state.mapper._identity_key_from_state(state) 

5327 if state._deleted: 

5328 del state._deleted 

5329 state._commit_all(state.dict) 

5330 state._expire_attributes(state.dict, state.unloaded) 

5331 

5332 

5333def object_session(instance: object) -> Optional[Session]: 

5334 """Return the :class:`.Session` to which the given instance belongs. 

5335 

5336 This is essentially the same as the :attr:`.InstanceState.session` 

5337 accessor. See that attribute for details. 

5338 

5339 """ 

5340 

5341 try: 

5342 state = attributes.instance_state(instance) 

5343 except exc.NO_STATE as err: 

5344 raise exc.UnmappedInstanceError(instance) from err 

5345 else: 

5346 return _state_session(state) 

5347 

5348 

5349_new_sessionid = util.counter()