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

1441 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 Result 

111 from ..engine import Row 

112 from ..engine import RowMapping 

113 from ..engine.base import Transaction 

114 from ..engine.base import TwoPhaseTransaction 

115 from ..engine.interfaces import _CoreAnyExecuteParams 

116 from ..engine.interfaces import _CoreSingleExecuteParams 

117 from ..engine.interfaces import _ExecuteOptions 

118 from ..engine.interfaces import CoreExecuteOptionsParameter 

119 from ..engine.result import ScalarResult 

120 from ..event import _InstanceLevelDispatch 

121 from ..sql._typing import _ColumnsClauseArgument 

122 from ..sql._typing import _InfoType 

123 from ..sql._typing import _T0 

124 from ..sql._typing import _T1 

125 from ..sql._typing import _T2 

126 from ..sql._typing import _T3 

127 from ..sql._typing import _T4 

128 from ..sql._typing import _T5 

129 from ..sql._typing import _T6 

130 from ..sql._typing import _T7 

131 from ..sql._typing import _TypedColumnClauseArgument as _TCCA 

132 from ..sql.base import Executable 

133 from ..sql.base import ExecutableOption 

134 from ..sql.elements import ClauseElement 

135 from ..sql.roles import TypedColumnsClauseRole 

136 from ..sql.selectable import ForUpdateParameter 

137 from ..sql.selectable import TypedReturnsRows 

138 

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

140_Ts = TypeVarTuple("_Ts") 

141 

142__all__ = [ 

143 "Session", 

144 "SessionTransaction", 

145 "sessionmaker", 

146 "ORMExecuteState", 

147 "close_all_sessions", 

148 "make_transient", 

149 "make_transient_to_detached", 

150 "object_session", 

151] 

152 

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

154 weakref.WeakValueDictionary() 

155) 

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

157""" 

158 

159statelib._sessions = _sessions 

160 

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

162 

163_BindArguments = Dict[str, Any] 

164 

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

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

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

168 

169JoinTransactionMode = Literal[ 

170 "conditional_savepoint", 

171 "rollback_only", 

172 "control_fully", 

173 "create_savepoint", 

174] 

175 

176 

177class _ConnectionCallableProto(Protocol): 

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

179 

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

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

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

183 as persistence time. 

184 

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

186 is established when using the horizontal sharding extension. 

187 

188 """ 

189 

190 def __call__( 

191 self, 

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

193 instance: Optional[object] = None, 

194 **kw: Any, 

195 ) -> Connection: ... 

196 

197 

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

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

200 associated, if any. 

201 """ 

202 return state.session 

203 

204 

205class _SessionClassMethods: 

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

207 

208 @classmethod 

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

210 def identity_key( 

211 cls, 

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

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

214 *, 

215 instance: Optional[Any] = None, 

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

217 identity_token: Optional[Any] = None, 

218 ) -> _IdentityKeyType[Any]: 

219 """Return an identity key. 

220 

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

222 

223 """ 

224 return util.preloaded.orm_util.identity_key( 

225 class_, 

226 ident, 

227 instance=instance, 

228 row=row, 

229 identity_token=identity_token, 

230 ) 

231 

232 @classmethod 

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

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

235 

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

237 

238 """ 

239 

240 return object_session(instance) 

241 

242 

243class SessionTransactionState(_StateChangeState): 

244 ACTIVE = 1 

245 PREPARED = 2 

246 COMMITTED = 3 

247 DEACTIVE = 4 

248 CLOSED = 5 

249 PROVISIONING_CONNECTION = 6 

250 

251 

252# backwards compatibility 

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

254 SessionTransactionState 

255) 

256 

257 

258class ORMExecuteState(util.MemoizedSlots): 

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

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

261 

262 .. versionadded:: 1.4 

263 

264 .. seealso:: 

265 

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

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

268 

269 """ 

270 

271 __slots__ = ( 

272 "session", 

273 "statement", 

274 "parameters", 

275 "execution_options", 

276 "local_execution_options", 

277 "bind_arguments", 

278 "identity_token", 

279 "_compile_state_cls", 

280 "_starting_event_idx", 

281 "_events_todo", 

282 "_update_execution_options", 

283 ) 

284 

285 session: Session 

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

287 

288 statement: Executable 

289 """The SQL statement being invoked. 

290 

291 For an ORM selection as would 

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

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

294 """ 

295 

296 parameters: Optional[_CoreAnyExecuteParams] 

297 """Dictionary of parameters that was passed to 

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

299 

300 execution_options: _ExecuteOptions 

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

302 

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

304 locally passed execution options. 

305 

306 .. seealso:: 

307 

308 :attr:`_orm.ORMExecuteState.local_execution_options` 

309 

310 :meth:`_sql.Executable.execution_options` 

311 

312 :ref:`orm_queryguide_execution_options` 

313 

314 """ 

315 

316 local_execution_options: _ExecuteOptions 

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

318 :meth:`.Session.execute` method. 

319 

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

321 being invoked. 

322 

323 .. seealso:: 

324 

325 :attr:`_orm.ORMExecuteState.execution_options` 

326 

327 """ 

328 

329 bind_arguments: _BindArguments 

330 """The dictionary passed as the 

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

332 

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

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

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

336 

337 """ 

338 

339 _compile_state_cls: Optional[Type[_ORMCompileState]] 

340 _starting_event_idx: int 

341 _events_todo: List[Any] 

342 _update_execution_options: Optional[_ExecuteOptions] 

343 

344 def __init__( 

345 self, 

346 session: Session, 

347 statement: Executable, 

348 parameters: Optional[_CoreAnyExecuteParams], 

349 execution_options: _ExecuteOptions, 

350 bind_arguments: _BindArguments, 

351 compile_state_cls: Optional[Type[_ORMCompileState]], 

352 events_todo: List[_InstanceLevelDispatch[Session]], 

353 ): 

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

355 

356 this object is constructed internally. 

357 

358 """ 

359 self.session = session 

360 self.statement = statement 

361 self.parameters = parameters 

362 self.local_execution_options = execution_options 

363 self.execution_options = statement._execution_options.union( 

364 execution_options 

365 ) 

366 self.bind_arguments = bind_arguments 

367 self._compile_state_cls = compile_state_cls 

368 self._events_todo = list(events_todo) 

369 

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

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

372 

373 def invoke_statement( 

374 self, 

375 statement: Optional[Executable] = None, 

376 params: Optional[_CoreAnyExecuteParams] = None, 

377 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

378 bind_arguments: Optional[_BindArguments] = None, 

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

380 """Execute the statement represented by this 

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

382 already proceeded. 

383 

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

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

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

387 that want to override how the ultimate 

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

389 retrieve results from an offline cache or which concatenate results 

390 from multiple executions. 

391 

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

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

394 is propagated to the calling 

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

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

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

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

399 

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

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

402 

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

404 which will be merged into the existing 

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

406 

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

408 for executemany executions. 

409 

410 :param execution_options: optional dictionary of execution options 

411 will be merged into the existing 

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

413 :class:`.ORMExecuteState`. 

414 

415 :param bind_arguments: optional dictionary of bind_arguments 

416 which will be merged amongst the current 

417 :attr:`.ORMExecuteState.bind_arguments` 

418 of this :class:`.ORMExecuteState`. 

419 

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

421 

422 .. seealso:: 

423 

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

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

426 

427 

428 """ 

429 

430 if statement is None: 

431 statement = self.statement 

432 

433 _bind_arguments = dict(self.bind_arguments) 

434 if bind_arguments: 

435 _bind_arguments.update(bind_arguments) 

436 _bind_arguments["_sa_skip_events"] = True 

437 

438 _params: Optional[_CoreAnyExecuteParams] 

439 if params: 

440 if self.is_executemany: 

441 _params = [] 

442 exec_many_parameters = cast( 

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

444 ) 

445 for _existing_params, _new_params in itertools.zip_longest( 

446 exec_many_parameters, 

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

448 ): 

449 if _existing_params is None or _new_params is None: 

450 raise sa_exc.InvalidRequestError( 

451 f"Can't apply executemany parameters to " 

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

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

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

455 f"to ORMExecuteState.invoke_statement() " 

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

457 ) 

458 _existing_params = dict(_existing_params) 

459 _existing_params.update(_new_params) 

460 _params.append(_existing_params) 

461 else: 

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

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

464 else: 

465 _params = self.parameters 

466 

467 _execution_options = self.local_execution_options 

468 if execution_options: 

469 _execution_options = _execution_options.union(execution_options) 

470 

471 return self.session._execute_internal( 

472 statement, 

473 _params, 

474 execution_options=_execution_options, 

475 bind_arguments=_bind_arguments, 

476 _parent_execute_state=self, 

477 ) 

478 

479 @property 

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

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

482 

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

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

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

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

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

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

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

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

491 would be selected. 

492 

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

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

495 way of getting this mapper. 

496 

497 .. versionadded:: 1.4.0b2 

498 

499 .. seealso:: 

500 

501 :attr:`_orm.ORMExecuteState.all_mappers` 

502 

503 

504 """ 

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

506 return mp 

507 

508 @property 

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

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

511 involved at the top level of this statement. 

512 

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

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

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

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

517 

518 .. versionadded:: 1.4.0b2 

519 

520 .. seealso:: 

521 

522 :attr:`_orm.ORMExecuteState.bind_mapper` 

523 

524 

525 

526 """ 

527 if not self.is_orm_statement: 

528 return [] 

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

530 result = [] 

531 seen = set() 

532 for d in self.statement.column_descriptions: 

533 ent = d["entity"] 

534 if ent: 

535 insp = inspect(ent, raiseerr=False) 

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

537 seen.add(insp.mapper) 

538 result.append(insp.mapper) 

539 return result 

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

541 return [self.bind_mapper] 

542 else: 

543 return [] 

544 

545 @property 

546 def is_orm_statement(self) -> bool: 

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

548 

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

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

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

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

553 and no ORM-level automation takes place. 

554 

555 """ 

556 return self._compile_state_cls is not None 

557 

558 @property 

559 def is_executemany(self) -> bool: 

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

561 dictionaries with more than one dictionary. 

562 

563 .. versionadded:: 2.0 

564 

565 """ 

566 return isinstance(self.parameters, list) 

567 

568 @property 

569 def is_select(self) -> bool: 

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

571 

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

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

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

575 ``select(Entity).from_statement(select(..))`` 

576 

577 """ 

578 return self.statement.is_select 

579 

580 @property 

581 def is_from_statement(self) -> bool: 

582 """return True if this operation is a 

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

584 

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

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

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

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

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

590 :class:`_sql.Select` construct. 

591 

592 .. versionadded:: 2.0.30 

593 

594 """ 

595 return self.statement.is_from_statement 

596 

597 @property 

598 def is_insert(self) -> bool: 

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

600 

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

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

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

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

605 

606 """ 

607 return self.statement.is_dml and self.statement.is_insert 

608 

609 @property 

610 def is_update(self) -> bool: 

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

612 

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

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

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

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

617 

618 """ 

619 return self.statement.is_dml and self.statement.is_update 

620 

621 @property 

622 def is_delete(self) -> bool: 

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

624 

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

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

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

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

629 

630 """ 

631 return self.statement.is_dml and self.statement.is_delete 

632 

633 @property 

634 def _is_crud(self) -> bool: 

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

636 

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

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

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

640 

641 def _orm_compile_options( 

642 self, 

643 ) -> Optional[ 

644 Union[ 

645 context._ORMCompileState.default_compile_options, 

646 Type[context._ORMCompileState.default_compile_options], 

647 ] 

648 ]: 

649 if not self.is_select: 

650 return None 

651 try: 

652 opts = self.statement._compile_options 

653 except AttributeError: 

654 return None 

655 

656 if opts is not None and opts.isinstance( 

657 context._ORMCompileState.default_compile_options 

658 ): 

659 return opts # type: ignore 

660 else: 

661 return None 

662 

663 @property 

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

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

666 for a lazy load operation. 

667 

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

669 sharding extension, where it is available within specific query 

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

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

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

673 compilation time. 

674 

675 """ 

676 return self.load_options._lazy_loaded_from 

677 

678 @property 

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

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

681 

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

683 when a particular object or collection is being loaded. 

684 

685 """ 

686 opts = self._orm_compile_options() 

687 if opts is not None: 

688 return opts._current_path 

689 else: 

690 return None 

691 

692 @property 

693 def is_column_load(self) -> bool: 

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

695 attributes on an existing ORM object. 

696 

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

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

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

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

701 loaded. 

702 

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

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

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

706 and loader options travelling with the instance 

707 will have already been added to the query. 

708 

709 .. versionadded:: 1.4.0b2 

710 

711 .. seealso:: 

712 

713 :attr:`_orm.ORMExecuteState.is_relationship_load` 

714 

715 """ 

716 opts = self._orm_compile_options() 

717 return opts is not None and opts._for_refresh_state 

718 

719 @property 

720 def is_relationship_load(self) -> bool: 

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

722 relationship. 

723 

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

725 SelectInLoader, SubqueryLoader, or similar, and the entire 

726 SELECT statement being emitted is on behalf of a relationship 

727 load. 

728 

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

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

731 capable of being propagated to relationship loaders and should 

732 be already present. 

733 

734 .. seealso:: 

735 

736 :attr:`_orm.ORMExecuteState.is_column_load` 

737 

738 """ 

739 opts = self._orm_compile_options() 

740 if opts is None: 

741 return False 

742 path = self.loader_strategy_path 

743 return path is not None and not path.is_root 

744 

745 @property 

746 def load_options( 

747 self, 

748 ) -> Union[ 

749 context.QueryContext.default_load_options, 

750 Type[context.QueryContext.default_load_options], 

751 ]: 

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

753 

754 if not self.is_select: 

755 raise sa_exc.InvalidRequestError( 

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

757 "so there are no load options." 

758 ) 

759 

760 lo: Union[ 

761 context.QueryContext.default_load_options, 

762 Type[context.QueryContext.default_load_options], 

763 ] = self.execution_options.get( 

764 "_sa_orm_load_options", context.QueryContext.default_load_options 

765 ) 

766 return lo 

767 

768 @property 

769 def update_delete_options( 

770 self, 

771 ) -> Union[ 

772 bulk_persistence._BulkUDCompileState.default_update_options, 

773 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

774 ]: 

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

776 execution.""" 

777 

778 if not self._is_crud: 

779 raise sa_exc.InvalidRequestError( 

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

781 "statement so there are no update options." 

782 ) 

783 uo: Union[ 

784 bulk_persistence._BulkUDCompileState.default_update_options, 

785 Type[bulk_persistence._BulkUDCompileState.default_update_options], 

786 ] = self.execution_options.get( 

787 "_sa_orm_update_options", 

788 bulk_persistence._BulkUDCompileState.default_update_options, 

789 ) 

790 return uo 

791 

792 @property 

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

794 return [ 

795 opt 

796 for opt in self.statement._with_options 

797 if is_orm_option(opt) and not opt._is_compile_state 

798 ] 

799 

800 @property 

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

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

803 associated with the statement being invoked. 

804 

805 """ 

806 return [ 

807 opt 

808 for opt in self.statement._with_options 

809 if is_user_defined_option(opt) 

810 ] 

811 

812 

813class SessionTransactionOrigin(Enum): 

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

815 

816 This enumeration is present on the 

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

818 :class:`.SessionTransaction` object. 

819 

820 .. versionadded:: 2.0 

821 

822 """ 

823 

824 AUTOBEGIN = 0 

825 """transaction were started by autobegin""" 

826 

827 BEGIN = 1 

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

829 

830 BEGIN_NESTED = 2 

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

832 

833 SUBTRANSACTION = 3 

834 """transaction is an internal "subtransaction" """ 

835 

836 

837class SessionTransaction(_StateChange, TransactionalContext): 

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

839 

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

841 :meth:`_orm.Session.begin` 

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

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

844 transactions. 

845 

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

847 at: :ref:`unitofwork_transaction`. 

848 

849 

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

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

852 

853 .. seealso:: 

854 

855 :ref:`unitofwork_transaction` 

856 

857 :meth:`.Session.begin` 

858 

859 :meth:`.Session.begin_nested` 

860 

861 :meth:`.Session.rollback` 

862 

863 :meth:`.Session.commit` 

864 

865 :meth:`.Session.in_transaction` 

866 

867 :meth:`.Session.in_nested_transaction` 

868 

869 :meth:`.Session.get_transaction` 

870 

871 :meth:`.Session.get_nested_transaction` 

872 

873 

874 """ 

875 

876 _rollback_exception: Optional[BaseException] = None 

877 

878 _connections: Dict[ 

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

880 ] 

881 session: Session 

882 _parent: Optional[SessionTransaction] 

883 

884 _state: SessionTransactionState 

885 

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

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

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

889 _key_switches: weakref.WeakKeyDictionary[ 

890 InstanceState[Any], Tuple[Any, Any] 

891 ] 

892 

893 origin: SessionTransactionOrigin 

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

895 

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

897 enumeration indicating the source event that led to constructing 

898 this :class:`_orm.SessionTransaction`. 

899 

900 .. versionadded:: 2.0 

901 

902 """ 

903 

904 nested: bool = False 

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

906 

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

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

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

910 

911 .. seealso:: 

912 

913 :attr:`.SessionTransaction.origin` 

914 

915 """ 

916 

917 def __init__( 

918 self, 

919 session: Session, 

920 origin: SessionTransactionOrigin, 

921 parent: Optional[SessionTransaction] = None, 

922 ): 

923 TransactionalContext._trans_ctx_check(session) 

924 

925 self.session = session 

926 self._connections = {} 

927 self._parent = parent 

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

929 self.origin = origin 

930 

931 if session._close_state is _SessionCloseState.CLOSED: 

932 raise sa_exc.InvalidRequestError( 

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

934 "to handle any more transaction requests." 

935 ) 

936 

937 if nested: 

938 if not parent: 

939 raise sa_exc.InvalidRequestError( 

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

941 "transaction is in progress" 

942 ) 

943 

944 self._previous_nested_transaction = session._nested_transaction 

945 elif origin is SessionTransactionOrigin.SUBTRANSACTION: 

946 assert parent is not None 

947 else: 

948 assert parent is None 

949 

950 self._state = SessionTransactionState.ACTIVE 

951 

952 self._take_snapshot() 

953 

954 # make sure transaction is assigned before we call the 

955 # dispatch 

956 self.session._transaction = self 

957 

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

959 

960 def _raise_for_prerequisite_state( 

961 self, operation_name: str, state: _StateChangeState 

962 ) -> NoReturn: 

963 if state is SessionTransactionState.DEACTIVE: 

964 if self._rollback_exception: 

965 raise sa_exc.PendingRollbackError( 

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

967 "due to a previous exception during flush." 

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

969 "first issue Session.rollback()." 

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

971 code="7s2a", 

972 ) 

973 else: 

974 raise sa_exc.InvalidRequestError( 

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

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

977 "can be emitted within this transaction." 

978 ) 

979 elif state is SessionTransactionState.CLOSED: 

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

981 elif state is SessionTransactionState.PROVISIONING_CONNECTION: 

982 raise sa_exc.InvalidRequestError( 

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

984 "operations are not permitted", 

985 code="isce", 

986 ) 

987 else: 

988 raise sa_exc.InvalidRequestError( 

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

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

991 ) 

992 

993 @property 

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

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

996 :class:`.SessionTransaction`. 

997 

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

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

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

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

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

1003 "nested" / SAVEPOINT transaction. If the 

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

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

1006 

1007 """ 

1008 return self._parent 

1009 

1010 @property 

1011 def is_active(self) -> bool: 

1012 return ( 

1013 self.session is not None 

1014 and self._state is SessionTransactionState.ACTIVE 

1015 ) 

1016 

1017 @property 

1018 def _is_transaction_boundary(self) -> bool: 

1019 return self.nested or not self._parent 

1020 

1021 @_StateChange.declare_states( 

1022 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1023 ) 

1024 def connection( 

1025 self, 

1026 bindkey: Optional[Mapper[Any]], 

1027 execution_options: Optional[_ExecuteOptions] = None, 

1028 **kwargs: Any, 

1029 ) -> Connection: 

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

1031 return self._connection_for_bind(bind, execution_options) 

1032 

1033 @_StateChange.declare_states( 

1034 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1035 ) 

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

1037 return SessionTransaction( 

1038 self.session, 

1039 ( 

1040 SessionTransactionOrigin.BEGIN_NESTED 

1041 if nested 

1042 else SessionTransactionOrigin.SUBTRANSACTION 

1043 ), 

1044 self, 

1045 ) 

1046 

1047 def _iterate_self_and_parents( 

1048 self, upto: Optional[SessionTransaction] = None 

1049 ) -> Iterable[SessionTransaction]: 

1050 current = self 

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

1052 while current: 

1053 result += (current,) 

1054 if current._parent is upto: 

1055 break 

1056 elif current._parent is None: 

1057 raise sa_exc.InvalidRequestError( 

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

1059 % (upto) 

1060 ) 

1061 else: 

1062 current = current._parent 

1063 

1064 return result 

1065 

1066 def _take_snapshot(self) -> None: 

1067 if not self._is_transaction_boundary: 

1068 parent = self._parent 

1069 assert parent is not None 

1070 self._new = parent._new 

1071 self._deleted = parent._deleted 

1072 self._dirty = parent._dirty 

1073 self._key_switches = parent._key_switches 

1074 return 

1075 

1076 is_begin = self.origin in ( 

1077 SessionTransactionOrigin.BEGIN, 

1078 SessionTransactionOrigin.AUTOBEGIN, 

1079 ) 

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

1081 self.session.flush() 

1082 

1083 self._new = weakref.WeakKeyDictionary() 

1084 self._deleted = weakref.WeakKeyDictionary() 

1085 self._dirty = weakref.WeakKeyDictionary() 

1086 self._key_switches = weakref.WeakKeyDictionary() 

1087 

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

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

1090 

1091 Corresponds to a rollback. 

1092 

1093 """ 

1094 assert self._is_transaction_boundary 

1095 

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

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

1098 

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

1100 # we probably can do this conditionally based on 

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

1102 self.session.identity_map.safe_discard(s) 

1103 

1104 # restore the old key 

1105 s.key = oldkey 

1106 

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

1108 if s not in to_expunge: 

1109 self.session.identity_map.replace(s) 

1110 

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

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

1113 

1114 assert not self.session._deleted 

1115 

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

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

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

1119 

1120 def _remove_snapshot(self) -> None: 

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

1122 

1123 Corresponds to a commit. 

1124 

1125 """ 

1126 assert self._is_transaction_boundary 

1127 

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

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

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

1131 

1132 statelib.InstanceState._detach_states( 

1133 list(self._deleted), self.session 

1134 ) 

1135 self._deleted.clear() 

1136 elif self.nested: 

1137 parent = self._parent 

1138 assert parent is not None 

1139 parent._new.update(self._new) 

1140 parent._dirty.update(self._dirty) 

1141 parent._deleted.update(self._deleted) 

1142 parent._key_switches.update(self._key_switches) 

1143 

1144 @_StateChange.declare_states( 

1145 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE 

1146 ) 

1147 def _connection_for_bind( 

1148 self, 

1149 bind: _SessionBind, 

1150 execution_options: Optional[CoreExecuteOptionsParameter], 

1151 ) -> Connection: 

1152 if bind in self._connections: 

1153 if execution_options: 

1154 util.warn( 

1155 "Connection is already established for the " 

1156 "given bind; execution_options ignored" 

1157 ) 

1158 return self._connections[bind][0] 

1159 

1160 self._state = SessionTransactionState.PROVISIONING_CONNECTION 

1161 

1162 local_connect = False 

1163 should_commit = True 

1164 

1165 try: 

1166 if self._parent: 

1167 conn = self._parent._connection_for_bind( 

1168 bind, execution_options 

1169 ) 

1170 if not self.nested: 

1171 return conn 

1172 else: 

1173 if isinstance(bind, engine.Connection): 

1174 conn = bind 

1175 if conn.engine in self._connections: 

1176 raise sa_exc.InvalidRequestError( 

1177 "Session already has a Connection associated " 

1178 "for the given Connection's Engine" 

1179 ) 

1180 else: 

1181 conn = bind.connect() 

1182 local_connect = True 

1183 

1184 try: 

1185 if execution_options: 

1186 conn = conn.execution_options(**execution_options) 

1187 

1188 transaction: Transaction 

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

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

1191 # conn.in_transaction() ? 

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

1193 # that it is in fact twophase. 

1194 transaction = conn.begin_twophase() 

1195 elif self.nested: 

1196 transaction = conn.begin_nested() 

1197 elif conn.in_transaction(): 

1198 

1199 if local_connect: 

1200 _trans = conn.get_transaction() 

1201 assert _trans is not None 

1202 transaction = _trans 

1203 else: 

1204 join_transaction_mode = ( 

1205 self.session.join_transaction_mode 

1206 ) 

1207 

1208 if join_transaction_mode == "conditional_savepoint": 

1209 if conn.in_nested_transaction(): 

1210 join_transaction_mode = "create_savepoint" 

1211 else: 

1212 join_transaction_mode = "rollback_only" 

1213 

1214 if join_transaction_mode in ( 

1215 "control_fully", 

1216 "rollback_only", 

1217 ): 

1218 if conn.in_nested_transaction(): 

1219 transaction = ( 

1220 conn._get_required_nested_transaction() 

1221 ) 

1222 else: 

1223 transaction = conn._get_required_transaction() 

1224 if join_transaction_mode == "rollback_only": 

1225 should_commit = False 

1226 elif join_transaction_mode == "create_savepoint": 

1227 transaction = conn.begin_nested() 

1228 else: 

1229 assert False, join_transaction_mode 

1230 else: 

1231 transaction = conn.begin() 

1232 except: 

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

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

1235 if local_connect: 

1236 conn.close() 

1237 raise 

1238 else: 

1239 bind_is_connection = isinstance(bind, engine.Connection) 

1240 

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

1242 conn, 

1243 transaction, 

1244 should_commit, 

1245 not bind_is_connection, 

1246 ) 

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

1248 return conn 

1249 finally: 

1250 self._state = SessionTransactionState.ACTIVE 

1251 

1252 def prepare(self) -> None: 

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

1254 raise sa_exc.InvalidRequestError( 

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

1256 "can't prepare." 

1257 ) 

1258 self._prepare_impl() 

1259 

1260 @_StateChange.declare_states( 

1261 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED 

1262 ) 

1263 def _prepare_impl(self) -> None: 

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

1265 self.session.dispatch.before_commit(self.session) 

1266 

1267 stx = self.session._transaction 

1268 assert stx is not None 

1269 if stx is not self: 

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

1271 subtransaction.commit() 

1272 

1273 if not self.session._flushing: 

1274 for _flush_guard in range(100): 

1275 if self.session._is_clean(): 

1276 break 

1277 self.session.flush() 

1278 else: 

1279 raise exc.FlushError( 

1280 "Over 100 subsequent flushes have occurred within " 

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

1282 "creating new objects?" 

1283 ) 

1284 

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

1286 try: 

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

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

1289 except: 

1290 with util.safe_reraise(): 

1291 self.rollback() 

1292 

1293 self._state = SessionTransactionState.PREPARED 

1294 

1295 @_StateChange.declare_states( 

1296 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED), 

1297 SessionTransactionState.CLOSED, 

1298 ) 

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

1300 if self._state is not SessionTransactionState.PREPARED: 

1301 with self._expect_state(SessionTransactionState.PREPARED): 

1302 self._prepare_impl() 

1303 

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

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

1306 self._connections.values() 

1307 ): 

1308 if should_commit: 

1309 trans.commit() 

1310 

1311 self._state = SessionTransactionState.COMMITTED 

1312 self.session.dispatch.after_commit(self.session) 

1313 

1314 self._remove_snapshot() 

1315 

1316 with self._expect_state(SessionTransactionState.CLOSED): 

1317 self.close() 

1318 

1319 if _to_root and self._parent: 

1320 self._parent.commit(_to_root=True) 

1321 

1322 @_StateChange.declare_states( 

1323 ( 

1324 SessionTransactionState.ACTIVE, 

1325 SessionTransactionState.DEACTIVE, 

1326 SessionTransactionState.PREPARED, 

1327 ), 

1328 SessionTransactionState.CLOSED, 

1329 ) 

1330 def rollback( 

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

1332 ) -> None: 

1333 stx = self.session._transaction 

1334 assert stx is not None 

1335 if stx is not self: 

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

1337 subtransaction.close() 

1338 

1339 boundary = self 

1340 rollback_err = None 

1341 if self._state in ( 

1342 SessionTransactionState.ACTIVE, 

1343 SessionTransactionState.PREPARED, 

1344 ): 

1345 for transaction in self._iterate_self_and_parents(): 

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

1347 try: 

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

1349 t[1].rollback() 

1350 

1351 transaction._state = SessionTransactionState.DEACTIVE 

1352 self.session.dispatch.after_rollback(self.session) 

1353 except: 

1354 rollback_err = sys.exc_info() 

1355 finally: 

1356 transaction._state = SessionTransactionState.DEACTIVE 

1357 transaction._restore_snapshot( 

1358 dirty_only=transaction.nested 

1359 ) 

1360 boundary = transaction 

1361 break 

1362 else: 

1363 transaction._state = SessionTransactionState.DEACTIVE 

1364 

1365 sess = self.session 

1366 

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

1368 # if items were added, deleted, or mutated 

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

1370 util.warn( 

1371 "Session's state has been changed on " 

1372 "a non-active transaction - this state " 

1373 "will be discarded." 

1374 ) 

1375 boundary._restore_snapshot(dirty_only=boundary.nested) 

1376 

1377 with self._expect_state(SessionTransactionState.CLOSED): 

1378 self.close() 

1379 

1380 if self._parent and _capture_exception: 

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

1382 

1383 if rollback_err and rollback_err[1]: 

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

1385 

1386 sess.dispatch.after_soft_rollback(sess, self) 

1387 

1388 if _to_root and self._parent: 

1389 self._parent.rollback(_to_root=True) 

1390 

1391 @_StateChange.declare_states( 

1392 _StateChangeStates.ANY, SessionTransactionState.CLOSED 

1393 ) 

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

1395 if self.nested: 

1396 self.session._nested_transaction = ( 

1397 self._previous_nested_transaction 

1398 ) 

1399 

1400 self.session._transaction = self._parent 

1401 

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

1403 self._connections.values() 

1404 ): 

1405 if invalidate and self._parent is None: 

1406 connection.invalidate() 

1407 if should_commit and transaction.is_active: 

1408 transaction.close() 

1409 if autoclose and self._parent is None: 

1410 connection.close() 

1411 

1412 self._state = SessionTransactionState.CLOSED 

1413 sess = self.session 

1414 

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

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

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

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

1419 # passes with these commented out. 

1420 # self.session = None # type: ignore 

1421 # self._connections = None # type: ignore 

1422 

1423 sess.dispatch.after_transaction_end(sess, self) 

1424 

1425 def _get_subject(self) -> Session: 

1426 return self.session 

1427 

1428 def _transaction_is_active(self) -> bool: 

1429 return self._state is SessionTransactionState.ACTIVE 

1430 

1431 def _transaction_is_closed(self) -> bool: 

1432 return self._state is SessionTransactionState.CLOSED 

1433 

1434 def _rollback_can_be_called(self) -> bool: 

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

1436 

1437 

1438class _SessionCloseState(Enum): 

1439 ACTIVE = 1 

1440 CLOSED = 2 

1441 CLOSE_IS_RESET = 3 

1442 

1443 

1444class Session(_SessionClassMethods, EventTarget): 

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

1446 

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

1448 See :ref:`session_faq_threadsafe` for background. 

1449 

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

1451 

1452 

1453 """ 

1454 

1455 _is_asyncio = False 

1456 

1457 dispatch: dispatcher[Session] 

1458 

1459 identity_map: IdentityMap 

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

1461 

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

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

1464 that have row identity) currently in the session. 

1465 

1466 .. seealso:: 

1467 

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

1469 in this dictionary. 

1470 

1471 """ 

1472 

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

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

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

1476 __binds: Dict[_SessionBindKey, _SessionBind] 

1477 _flushing: bool 

1478 _warn_on_events: bool 

1479 _transaction: Optional[SessionTransaction] 

1480 _nested_transaction: Optional[SessionTransaction] 

1481 hash_key: int 

1482 autoflush: bool 

1483 expire_on_commit: bool 

1484 enable_baked_queries: bool 

1485 twophase: bool 

1486 join_transaction_mode: JoinTransactionMode 

1487 _query_cls: Type[Query[Any]] 

1488 _close_state: _SessionCloseState 

1489 

1490 def __init__( 

1491 self, 

1492 bind: Optional[_SessionBind] = None, 

1493 *, 

1494 autoflush: bool = True, 

1495 future: Literal[True] = True, 

1496 expire_on_commit: bool = True, 

1497 autobegin: bool = True, 

1498 twophase: bool = False, 

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

1500 enable_baked_queries: bool = True, 

1501 info: Optional[_InfoType] = None, 

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

1503 autocommit: Literal[False] = False, 

1504 join_transaction_mode: JoinTransactionMode = "conditional_savepoint", 

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

1506 ): 

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

1508 

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

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

1511 set of arguments. 

1512 

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

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

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

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

1517 results. 

1518 

1519 .. seealso:: 

1520 

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

1522 

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

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

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

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

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

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

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

1530 

1531 .. versionadded:: 2.0 

1532 

1533 .. seealso:: 

1534 

1535 :ref:`session_autobegin_disable` 

1536 

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

1538 :class:`_engine.Connection` to 

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

1540 operations performed by this session will execute via this 

1541 connectable. 

1542 

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

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

1545 objects as the source of 

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

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

1548 arbitrary Python classes that are bases for mapped classes, 

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

1550 The 

1551 values of the dictionary are then instances of 

1552 :class:`_engine.Engine` 

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

1554 Operations which 

1555 proceed relative to a particular mapped class will consult this 

1556 dictionary for the closest matching entity in order to determine 

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

1558 operation. The complete heuristics for resolution are 

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

1560 

1561 Session = sessionmaker( 

1562 binds={ 

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

1564 SomeDeclarativeBase: create_engine( 

1565 "postgresql+psycopg2://engine2" 

1566 ), 

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

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

1569 } 

1570 ) 

1571 

1572 .. seealso:: 

1573 

1574 :ref:`session_partitioning` 

1575 

1576 :meth:`.Session.bind_mapper` 

1577 

1578 :meth:`.Session.bind_table` 

1579 

1580 :meth:`.Session.get_bind` 

1581 

1582 

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

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

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

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

1587 constructor for ``Session``. 

1588 

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

1590 A parameter consumed 

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

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

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

1594 this particular extension is disabled. 

1595 

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

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

1598 flag therefore only affects applications that are making explicit 

1599 use of this extension within their own code. 

1600 

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

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

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

1604 transaction will load from the most recent database state. 

1605 

1606 .. seealso:: 

1607 

1608 :ref:`session_committing` 

1609 

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

1611 

1612 .. seealso:: 

1613 

1614 :ref:`migration_20_toplevel` 

1615 

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

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

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

1619 construction time so that modifications to the per- 

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

1621 :class:`.Session`. 

1622 

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

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

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

1626 

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

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

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

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

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

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

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

1634 transaction, before each transaction is committed. 

1635 

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

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

1638 

1639 :param join_transaction_mode: Describes the transactional behavior to 

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

1641 has already begun a transaction outside the scope of this 

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

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

1644 

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

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

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

1648 etc. are actually invoked: 

1649 

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

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

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

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

1654 a SAVEPOINT, in other words 

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

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

1657 

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

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

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

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

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

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

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

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

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

1667 

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

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

1670 its own transaction. This transaction by its nature rides 

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

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

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

1674 external transaction will remain unaffected throughout the 

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

1676 

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

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

1679 initiated transaction should remain unaffected; however, it relies 

1680 on proper SAVEPOINT support from the underlying driver and 

1681 database. 

1682 

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

1684 Python 3.11 does not handle SAVEPOINTs correctly in all cases 

1685 without workarounds. See the sections 

1686 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` 

1687 for details on current workarounds. 

1688 

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

1690 control of the given transaction as its own; 

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

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

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

1694 call ``.rollback`` on the transaction. 

1695 

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

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

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

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

1700 SAVEPOINT. 

1701 

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

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

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

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

1706 given transaction. 

1707 

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

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

1710 regular database transaction (i.e. 

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

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

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

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

1715 

1716 .. versionadded:: 2.0.0rc1 

1717 

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

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

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

1721 

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

1723 A future SQLAlchemy version may change the default value of 

1724 this flag to ``False``. 

1725 

1726 .. seealso:: 

1727 

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

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

1730 

1731 """ # noqa 

1732 

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

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

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

1736 # of cases including in our own test suite 

1737 if autocommit: 

1738 raise sa_exc.ArgumentError( 

1739 "autocommit=True is no longer supported" 

1740 ) 

1741 self.identity_map = identity._WeakInstanceDict() 

1742 

1743 if not future: 

1744 raise sa_exc.ArgumentError( 

1745 "The 'future' parameter passed to " 

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

1747 ) 

1748 

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

1750 self._deleted = {} # same 

1751 self.bind = bind 

1752 self.__binds = {} 

1753 self._flushing = False 

1754 self._warn_on_events = False 

1755 self._transaction = None 

1756 self._nested_transaction = None 

1757 self.hash_key = _new_sessionid() 

1758 self.autobegin = autobegin 

1759 self.autoflush = autoflush 

1760 self.expire_on_commit = expire_on_commit 

1761 self.enable_baked_queries = enable_baked_queries 

1762 

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

1764 # the default will switch to close_resets_only=False. 

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

1766 self._close_state = _SessionCloseState.CLOSE_IS_RESET 

1767 else: 

1768 self._close_state = _SessionCloseState.ACTIVE 

1769 if ( 

1770 join_transaction_mode 

1771 and join_transaction_mode 

1772 not in JoinTransactionMode.__args__ # type: ignore 

1773 ): 

1774 raise sa_exc.ArgumentError( 

1775 f"invalid selection for join_transaction_mode: " 

1776 f'"{join_transaction_mode}"' 

1777 ) 

1778 self.join_transaction_mode = join_transaction_mode 

1779 

1780 self.twophase = twophase 

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

1782 if info: 

1783 self.info.update(info) 

1784 

1785 if binds is not None: 

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

1787 self._add_bind(key, bind) 

1788 

1789 _sessions[self.hash_key] = self 

1790 

1791 # used by sqlalchemy.engine.util.TransactionalContext 

1792 _trans_context_manager: Optional[TransactionalContext] = None 

1793 

1794 connection_callable: Optional[_ConnectionCallableProto] = None 

1795 

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

1797 return self 

1798 

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

1800 self.close() 

1801 

1802 @contextlib.contextmanager 

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

1804 with self: 

1805 with self.begin(): 

1806 yield self 

1807 

1808 def in_transaction(self) -> bool: 

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

1810 

1811 .. versionadded:: 1.4 

1812 

1813 .. seealso:: 

1814 

1815 :attr:`_orm.Session.is_active` 

1816 

1817 

1818 """ 

1819 return self._transaction is not None 

1820 

1821 def in_nested_transaction(self) -> bool: 

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

1823 transaction, e.g. SAVEPOINT. 

1824 

1825 .. versionadded:: 1.4 

1826 

1827 """ 

1828 return self._nested_transaction is not None 

1829 

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

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

1832 

1833 .. versionadded:: 1.4 

1834 

1835 """ 

1836 trans = self._transaction 

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

1838 trans = trans._parent 

1839 return trans 

1840 

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

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

1843 

1844 .. versionadded:: 1.4 

1845 

1846 """ 

1847 

1848 return self._nested_transaction 

1849 

1850 @util.memoized_property 

1851 def info(self) -> _InfoType: 

1852 """A user-modifiable dictionary. 

1853 

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

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

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

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

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

1859 

1860 """ 

1861 return {} 

1862 

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

1864 if self._transaction is None: 

1865 if not begin and not self.autobegin: 

1866 raise sa_exc.InvalidRequestError( 

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

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

1869 ) 

1870 trans = SessionTransaction( 

1871 self, 

1872 ( 

1873 SessionTransactionOrigin.BEGIN 

1874 if begin 

1875 else SessionTransactionOrigin.AUTOBEGIN 

1876 ), 

1877 ) 

1878 assert self._transaction is trans 

1879 return trans 

1880 

1881 return self._transaction 

1882 

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

1884 """Begin a transaction, or nested transaction, 

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

1886 

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

1888 so that normally it is not necessary to call the 

1889 :meth:`_orm.Session.begin` 

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

1891 the scope of when the transactional state is begun. 

1892 

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

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

1895 

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

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

1898 documentation on SAVEPOINT transactions, please see 

1899 :ref:`session_begin_nested`. 

1900 

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

1902 :class:`.SessionTransaction` 

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

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

1905 an example. 

1906 

1907 .. seealso:: 

1908 

1909 :ref:`session_autobegin` 

1910 

1911 :ref:`unitofwork_transaction` 

1912 

1913 :meth:`.Session.begin_nested` 

1914 

1915 

1916 """ 

1917 

1918 trans = self._transaction 

1919 if trans is None: 

1920 trans = self._autobegin_t(begin=True) 

1921 

1922 if not nested: 

1923 return trans 

1924 

1925 assert trans is not None 

1926 

1927 if nested: 

1928 trans = trans._begin(nested=nested) 

1929 assert self._transaction is trans 

1930 self._nested_transaction = trans 

1931 else: 

1932 raise sa_exc.InvalidRequestError( 

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

1934 ) 

1935 

1936 return trans # needed for __enter__/__exit__ hook 

1937 

1938 def begin_nested(self) -> SessionTransaction: 

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

1940 

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

1942 SAVEPOINT for this method to function correctly. 

1943 

1944 For documentation on SAVEPOINT 

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

1946 

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

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

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

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

1951 

1952 .. seealso:: 

1953 

1954 :ref:`session_begin_nested` 

1955 

1956 :ref:`pysqlite_serializable` - special workarounds required 

1957 with the SQLite driver in order for SAVEPOINT to work 

1958 correctly. For asyncio use cases, see the section 

1959 :ref:`aiosqlite_serializable`. 

1960 

1961 """ 

1962 return self.begin(nested=True) 

1963 

1964 def rollback(self) -> None: 

1965 """Rollback the current transaction in progress. 

1966 

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

1968 

1969 The method always rolls back 

1970 the topmost database transaction, discarding any nested 

1971 transactions that may be in progress. 

1972 

1973 .. seealso:: 

1974 

1975 :ref:`session_rollback` 

1976 

1977 :ref:`unitofwork_transaction` 

1978 

1979 """ 

1980 if self._transaction is None: 

1981 pass 

1982 else: 

1983 self._transaction.rollback(_to_root=True) 

1984 

1985 def commit(self) -> None: 

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

1987 

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

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

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

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

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

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

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

1995 to disable this behavior. 

1996 

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

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

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

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

2001 normally affect the database unless pending flush changes were 

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

2003 rules. 

2004 

2005 The outermost database transaction is committed unconditionally, 

2006 automatically releasing any SAVEPOINTs in effect. 

2007 

2008 .. seealso:: 

2009 

2010 :ref:`session_committing` 

2011 

2012 :ref:`unitofwork_transaction` 

2013 

2014 :ref:`asyncio_orm_avoid_lazyloads` 

2015 

2016 """ 

2017 trans = self._transaction 

2018 if trans is None: 

2019 trans = self._autobegin_t() 

2020 

2021 trans.commit(_to_root=True) 

2022 

2023 def prepare(self) -> None: 

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

2025 

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

2027 :exc:`~sqlalchemy.exc.InvalidRequestError`. 

2028 

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

2030 current transaction is not such, an 

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

2032 

2033 """ 

2034 trans = self._transaction 

2035 if trans is None: 

2036 trans = self._autobegin_t() 

2037 

2038 trans.prepare() 

2039 

2040 def connection( 

2041 self, 

2042 bind_arguments: Optional[_BindArguments] = None, 

2043 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2044 ) -> Connection: 

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

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

2047 

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

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

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

2051 returned (note that no 

2052 transactional state is established with the DBAPI until the first 

2053 SQL statement is emitted). 

2054 

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

2056 resolved through any of the optional keyword arguments. This 

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

2058 

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

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

2061 to :meth:`.Session.get_bind`. 

2062 

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

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

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

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

2067 the arguments are ignored. 

2068 

2069 .. seealso:: 

2070 

2071 :ref:`session_transaction_isolation` 

2072 

2073 """ 

2074 

2075 if bind_arguments: 

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

2077 

2078 if bind is None: 

2079 bind = self.get_bind(**bind_arguments) 

2080 else: 

2081 bind = self.get_bind() 

2082 

2083 return self._connection_for_bind( 

2084 bind, 

2085 execution_options=execution_options, 

2086 ) 

2087 

2088 def _connection_for_bind( 

2089 self, 

2090 engine: _SessionBind, 

2091 execution_options: Optional[CoreExecuteOptionsParameter] = None, 

2092 **kw: Any, 

2093 ) -> Connection: 

2094 TransactionalContext._trans_ctx_check(self) 

2095 

2096 trans = self._transaction 

2097 if trans is None: 

2098 trans = self._autobegin_t() 

2099 return trans._connection_for_bind(engine, execution_options) 

2100 

2101 @overload 

2102 def _execute_internal( 

2103 self, 

2104 statement: Executable, 

2105 params: Optional[_CoreSingleExecuteParams] = None, 

2106 *, 

2107 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2108 bind_arguments: Optional[_BindArguments] = None, 

2109 _parent_execute_state: Optional[Any] = None, 

2110 _add_event: Optional[Any] = None, 

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

2112 ) -> Any: ... 

2113 

2114 @overload 

2115 def _execute_internal( 

2116 self, 

2117 statement: Executable, 

2118 params: Optional[_CoreAnyExecuteParams] = None, 

2119 *, 

2120 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2121 bind_arguments: Optional[_BindArguments] = None, 

2122 _parent_execute_state: Optional[Any] = None, 

2123 _add_event: Optional[Any] = None, 

2124 _scalar_result: bool = ..., 

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

2126 

2127 def _execute_internal( 

2128 self, 

2129 statement: Executable, 

2130 params: Optional[_CoreAnyExecuteParams] = None, 

2131 *, 

2132 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2133 bind_arguments: Optional[_BindArguments] = None, 

2134 _parent_execute_state: Optional[Any] = None, 

2135 _add_event: Optional[Any] = None, 

2136 _scalar_result: bool = False, 

2137 ) -> Any: 

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

2139 

2140 if not bind_arguments: 

2141 bind_arguments = {} 

2142 else: 

2143 bind_arguments = dict(bind_arguments) 

2144 

2145 if ( 

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

2147 == "orm" 

2148 ): 

2149 compile_state_cls = CompileState._get_plugin_class_for_plugin( 

2150 statement, "orm" 

2151 ) 

2152 if TYPE_CHECKING: 

2153 assert isinstance( 

2154 compile_state_cls, context._AbstractORMCompileState 

2155 ) 

2156 else: 

2157 compile_state_cls = None 

2158 bind_arguments.setdefault("clause", statement) 

2159 

2160 execution_options = util.coerce_to_immutabledict(execution_options) 

2161 

2162 if _parent_execute_state: 

2163 events_todo = _parent_execute_state._remaining_events() 

2164 else: 

2165 events_todo = self.dispatch.do_orm_execute 

2166 if _add_event: 

2167 events_todo = list(events_todo) + [_add_event] 

2168 

2169 if events_todo: 

2170 if compile_state_cls is not None: 

2171 # for event handlers, do the orm_pre_session_exec 

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

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

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

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

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

2177 ( 

2178 statement, 

2179 execution_options, 

2180 ) = compile_state_cls.orm_pre_session_exec( 

2181 self, 

2182 statement, 

2183 params, 

2184 execution_options, 

2185 bind_arguments, 

2186 True, 

2187 ) 

2188 

2189 orm_exec_state = ORMExecuteState( 

2190 self, 

2191 statement, 

2192 params, 

2193 execution_options, 

2194 bind_arguments, 

2195 compile_state_cls, 

2196 events_todo, 

2197 ) 

2198 for idx, fn in enumerate(events_todo): 

2199 orm_exec_state._starting_event_idx = idx 

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

2201 orm_exec_state 

2202 ) 

2203 if fn_result: 

2204 if _scalar_result: 

2205 return fn_result.scalar() 

2206 else: 

2207 return fn_result 

2208 

2209 statement = orm_exec_state.statement 

2210 execution_options = orm_exec_state.local_execution_options 

2211 

2212 if compile_state_cls is not None: 

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

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

2215 # new execution_options into load_options / update_delete_options, 

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

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

2218 ( 

2219 statement, 

2220 execution_options, 

2221 ) = compile_state_cls.orm_pre_session_exec( 

2222 self, 

2223 statement, 

2224 params, 

2225 execution_options, 

2226 bind_arguments, 

2227 False, 

2228 ) 

2229 else: 

2230 # Issue #9809: unconditionally autoflush for Core statements 

2231 self._autoflush() 

2232 

2233 bind = self.get_bind(**bind_arguments) 

2234 

2235 conn = self._connection_for_bind(bind) 

2236 

2237 if _scalar_result and not compile_state_cls: 

2238 if TYPE_CHECKING: 

2239 params = cast(_CoreSingleExecuteParams, params) 

2240 return conn.scalar( 

2241 statement, params or {}, execution_options=execution_options 

2242 ) 

2243 

2244 if compile_state_cls: 

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

2246 compile_state_cls.orm_execute_statement( 

2247 self, 

2248 statement, 

2249 params or {}, 

2250 execution_options, 

2251 bind_arguments, 

2252 conn, 

2253 ) 

2254 ) 

2255 else: 

2256 result = conn.execute( 

2257 statement, params, execution_options=execution_options 

2258 ) 

2259 

2260 if _scalar_result: 

2261 return result.scalar() 

2262 else: 

2263 return result 

2264 

2265 @overload 

2266 def execute( 

2267 self, 

2268 statement: TypedReturnsRows[Unpack[_Ts]], 

2269 params: Optional[_CoreAnyExecuteParams] = None, 

2270 *, 

2271 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2272 bind_arguments: Optional[_BindArguments] = None, 

2273 _parent_execute_state: Optional[Any] = None, 

2274 _add_event: Optional[Any] = None, 

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

2276 

2277 @overload 

2278 def execute( 

2279 self, 

2280 statement: Executable, 

2281 params: Optional[_CoreAnyExecuteParams] = None, 

2282 *, 

2283 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2284 bind_arguments: Optional[_BindArguments] = None, 

2285 _parent_execute_state: Optional[Any] = None, 

2286 _add_event: Optional[Any] = None, 

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

2288 

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 r"""Execute a SQL expression construct. 

2300 

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

2302 results of the statement execution. 

2303 

2304 E.g.:: 

2305 

2306 from sqlalchemy import select 

2307 

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

2309 

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

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

2312 of :class:`_engine.Connection`. 

2313 

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

2315 now the primary point of ORM statement execution when using 

2316 :term:`2.0 style` ORM usage. 

2317 

2318 :param statement: 

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

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

2321 

2322 :param params: 

2323 Optional dictionary, or list of dictionaries, containing 

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

2325 execution occurs; if a list of dictionaries, an 

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

2327 must correspond to parameter names present in the statement. 

2328 

2329 :param execution_options: optional dictionary of execution options, 

2330 which will be associated with the statement execution. This 

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

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

2333 provide additional options understood only in an ORM context. 

2334 

2335 .. seealso:: 

2336 

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

2338 options 

2339 

2340 :param bind_arguments: dictionary of additional arguments to determine 

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

2342 Contents of this dictionary are passed to the 

2343 :meth:`.Session.get_bind` method. 

2344 

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

2346 

2347 

2348 """ 

2349 return self._execute_internal( 

2350 statement, 

2351 params, 

2352 execution_options=execution_options, 

2353 bind_arguments=bind_arguments, 

2354 _parent_execute_state=_parent_execute_state, 

2355 _add_event=_add_event, 

2356 ) 

2357 

2358 @overload 

2359 def scalar( 

2360 self, 

2361 statement: TypedReturnsRows[_T], 

2362 params: Optional[_CoreSingleExecuteParams] = None, 

2363 *, 

2364 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2365 bind_arguments: Optional[_BindArguments] = None, 

2366 **kw: Any, 

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

2368 

2369 @overload 

2370 def scalar( 

2371 self, 

2372 statement: Executable, 

2373 params: Optional[_CoreSingleExecuteParams] = None, 

2374 *, 

2375 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2376 bind_arguments: Optional[_BindArguments] = None, 

2377 **kw: Any, 

2378 ) -> Any: ... 

2379 

2380 def scalar( 

2381 self, 

2382 statement: Executable, 

2383 params: Optional[_CoreSingleExecuteParams] = None, 

2384 *, 

2385 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2386 bind_arguments: Optional[_BindArguments] = None, 

2387 **kw: Any, 

2388 ) -> Any: 

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

2390 

2391 Usage and parameters are the same as that of 

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

2393 value. 

2394 

2395 """ 

2396 

2397 return self._execute_internal( 

2398 statement, 

2399 params, 

2400 execution_options=execution_options, 

2401 bind_arguments=bind_arguments, 

2402 _scalar_result=True, 

2403 **kw, 

2404 ) 

2405 

2406 @overload 

2407 def scalars( 

2408 self, 

2409 statement: TypedReturnsRows[_T], 

2410 params: Optional[_CoreAnyExecuteParams] = None, 

2411 *, 

2412 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2413 bind_arguments: Optional[_BindArguments] = None, 

2414 **kw: Any, 

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

2416 

2417 @overload 

2418 def scalars( 

2419 self, 

2420 statement: Executable, 

2421 params: Optional[_CoreAnyExecuteParams] = None, 

2422 *, 

2423 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2424 bind_arguments: Optional[_BindArguments] = None, 

2425 **kw: Any, 

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

2427 

2428 def scalars( 

2429 self, 

2430 statement: Executable, 

2431 params: Optional[_CoreAnyExecuteParams] = None, 

2432 *, 

2433 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2434 bind_arguments: Optional[_BindArguments] = None, 

2435 **kw: Any, 

2436 ) -> ScalarResult[Any]: 

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

2438 

2439 Usage and parameters are the same as that of 

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

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

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

2443 

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

2445 

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

2447 

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

2449 

2450 .. seealso:: 

2451 

2452 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior 

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

2454 

2455 """ 

2456 

2457 return self._execute_internal( 

2458 statement, 

2459 params=params, 

2460 execution_options=execution_options, 

2461 bind_arguments=bind_arguments, 

2462 _scalar_result=False, # mypy appreciates this 

2463 **kw, 

2464 ).scalars() 

2465 

2466 def close(self) -> None: 

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

2468 :class:`_orm.Session`. 

2469 

2470 This expunges all ORM objects associated with this 

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

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

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

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

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

2476 

2477 .. tip:: 

2478 

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

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

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

2482 distinct "closed" state; it merely means 

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

2484 and ORM objects. 

2485 

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

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

2488 any further action on the session will be forbidden. 

2489 

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

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

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

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

2494 

2495 .. seealso:: 

2496 

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

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

2499 

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

2501 ``close()`` with the parameter 

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

2503 

2504 """ 

2505 self._close_impl(invalidate=False) 

2506 

2507 def reset(self) -> None: 

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

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

2510 

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

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

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

2514 brand new, and ready to be used again. 

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

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

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

2518 

2519 .. versionadded:: 2.0.22 

2520 

2521 .. seealso:: 

2522 

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

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

2525 

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

2527 prevent re-use of the Session when the parameter 

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

2529 """ 

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

2531 

2532 def invalidate(self) -> None: 

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

2534 

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

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

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

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

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

2540 multiple engines). 

2541 

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

2543 the connections are no longer safe to be used. 

2544 

2545 Below illustrates a scenario when using `gevent 

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

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

2548 

2549 import gevent 

2550 

2551 try: 

2552 sess = Session() 

2553 sess.add(User()) 

2554 sess.commit() 

2555 except gevent.Timeout: 

2556 sess.invalidate() 

2557 raise 

2558 except: 

2559 sess.rollback() 

2560 raise 

2561 

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

2563 does, including that all ORM objects are expunged. 

2564 

2565 """ 

2566 self._close_impl(invalidate=True) 

2567 

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

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

2570 self._close_state = _SessionCloseState.CLOSED 

2571 self.expunge_all() 

2572 if self._transaction is not None: 

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

2574 transaction.close(invalidate) 

2575 

2576 def expunge_all(self) -> None: 

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

2578 

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

2580 ``Session``. 

2581 

2582 """ 

2583 

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

2585 self.identity_map._kill() 

2586 self.identity_map = identity._WeakInstanceDict() 

2587 self._new = {} 

2588 self._deleted = {} 

2589 

2590 statelib.InstanceState._detach_states(all_states, self) 

2591 

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

2593 try: 

2594 insp = inspect(key) 

2595 except sa_exc.NoInspectionAvailable as err: 

2596 if not isinstance(key, type): 

2597 raise sa_exc.ArgumentError( 

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

2599 ) from err 

2600 else: 

2601 self.__binds[key] = bind 

2602 else: 

2603 if TYPE_CHECKING: 

2604 assert isinstance(insp, Inspectable) 

2605 

2606 if isinstance(insp, TableClause): 

2607 self.__binds[insp] = bind 

2608 elif insp_is_mapper(insp): 

2609 self.__binds[insp.class_] = bind 

2610 for _selectable in insp._all_tables: 

2611 self.__binds[_selectable] = bind 

2612 else: 

2613 raise sa_exc.ArgumentError( 

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

2615 ) 

2616 

2617 def bind_mapper( 

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

2619 ) -> None: 

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

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

2622 :class:`_engine.Connection`. 

2623 

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

2625 :meth:`.Session.get_bind` method. 

2626 

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

2628 or an instance of a mapped 

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

2630 classes. 

2631 

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

2633 object. 

2634 

2635 .. seealso:: 

2636 

2637 :ref:`session_partitioning` 

2638 

2639 :paramref:`.Session.binds` 

2640 

2641 :meth:`.Session.bind_table` 

2642 

2643 

2644 """ 

2645 self._add_bind(mapper, bind) 

2646 

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

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

2649 :class:`_engine.Engine` 

2650 or :class:`_engine.Connection`. 

2651 

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

2653 :meth:`.Session.get_bind` method. 

2654 

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

2656 which is typically the target 

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

2658 mapped. 

2659 

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

2661 object. 

2662 

2663 .. seealso:: 

2664 

2665 :ref:`session_partitioning` 

2666 

2667 :paramref:`.Session.binds` 

2668 

2669 :meth:`.Session.bind_mapper` 

2670 

2671 

2672 """ 

2673 self._add_bind(table, bind) 

2674 

2675 def get_bind( 

2676 self, 

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

2678 *, 

2679 clause: Optional[ClauseElement] = None, 

2680 bind: Optional[_SessionBind] = None, 

2681 _sa_skip_events: Optional[bool] = None, 

2682 _sa_skip_for_implicit_returning: bool = False, 

2683 **kw: Any, 

2684 ) -> Union[Engine, Connection]: 

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

2686 

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

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

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

2690 

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

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

2693 appropriate bind to return. 

2694 

2695 Note that the "mapper" argument is usually present 

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

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

2698 individual INSERT/UPDATE/DELETE operation within a 

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

2700 

2701 The order of resolution is: 

2702 

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

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

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

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

2707 superclasses to more general. 

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

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

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

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

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

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

2714 associated with the clause. 

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

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

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

2718 selectable to which the mapper is mapped. 

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

2720 is raised. 

2721 

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

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

2724 of bind resolution scheme. See the example at 

2725 :ref:`session_custom_partitioning`. 

2726 

2727 :param mapper: 

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

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

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

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

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

2733 mapped for a bind. 

2734 

2735 :param clause: 

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

2737 :func:`_expression.select`, 

2738 :func:`_expression.text`, 

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

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

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

2742 associated with 

2743 bound :class:`_schema.MetaData`. 

2744 

2745 .. seealso:: 

2746 

2747 :ref:`session_partitioning` 

2748 

2749 :paramref:`.Session.binds` 

2750 

2751 :meth:`.Session.bind_mapper` 

2752 

2753 :meth:`.Session.bind_table` 

2754 

2755 """ 

2756 

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

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

2759 if bind: 

2760 return bind 

2761 elif not self.__binds and self.bind: 

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

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

2764 return self.bind 

2765 

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

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

2768 # mapper and the clause 

2769 if mapper is None and clause is None: 

2770 if self.bind: 

2771 return self.bind 

2772 else: 

2773 raise sa_exc.UnboundExecutionError( 

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

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

2776 "a binding." 

2777 ) 

2778 

2779 # look more closely at the mapper. 

2780 if mapper is not None: 

2781 try: 

2782 inspected_mapper = inspect(mapper) 

2783 except sa_exc.NoInspectionAvailable as err: 

2784 if isinstance(mapper, type): 

2785 raise exc.UnmappedClassError(mapper) from err 

2786 else: 

2787 raise 

2788 else: 

2789 inspected_mapper = None 

2790 

2791 # match up the mapper or clause in the __binds 

2792 if self.__binds: 

2793 # matching mappers and selectables to entries in the 

2794 # binds dictionary; supported use case. 

2795 if inspected_mapper: 

2796 for cls in inspected_mapper.class_.__mro__: 

2797 if cls in self.__binds: 

2798 return self.__binds[cls] 

2799 if clause is None: 

2800 clause = inspected_mapper.persist_selectable 

2801 

2802 if clause is not None: 

2803 plugin_subject = clause._propagate_attrs.get( 

2804 "plugin_subject", None 

2805 ) 

2806 

2807 if plugin_subject is not None: 

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

2809 if cls in self.__binds: 

2810 return self.__binds[cls] 

2811 

2812 for obj in visitors.iterate(clause): 

2813 if obj in self.__binds: 

2814 if TYPE_CHECKING: 

2815 assert isinstance(obj, Table) 

2816 return self.__binds[obj] 

2817 

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

2819 # return that 

2820 if self.bind: 

2821 return self.bind 

2822 

2823 context = [] 

2824 if inspected_mapper is not None: 

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

2826 if clause is not None: 

2827 context.append("SQL expression") 

2828 

2829 raise sa_exc.UnboundExecutionError( 

2830 f"Could not locate a bind configured on " 

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

2832 ) 

2833 

2834 @overload 

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

2836 

2837 @overload 

2838 def query( 

2839 self, _colexpr: TypedColumnsClauseRole[_T] 

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

2841 

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

2843 

2844 # code within this block is **programmatically, 

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

2846 

2847 @overload 

2848 def query( 

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

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

2851 

2852 @overload 

2853 def query( 

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

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

2856 

2857 @overload 

2858 def query( 

2859 self, 

2860 __ent0: _TCCA[_T0], 

2861 __ent1: _TCCA[_T1], 

2862 __ent2: _TCCA[_T2], 

2863 __ent3: _TCCA[_T3], 

2864 /, 

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

2866 

2867 @overload 

2868 def query( 

2869 self, 

2870 __ent0: _TCCA[_T0], 

2871 __ent1: _TCCA[_T1], 

2872 __ent2: _TCCA[_T2], 

2873 __ent3: _TCCA[_T3], 

2874 __ent4: _TCCA[_T4], 

2875 /, 

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

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 __ent5: _TCCA[_T5], 

2887 /, 

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

2889 

2890 @overload 

2891 def query( 

2892 self, 

2893 __ent0: _TCCA[_T0], 

2894 __ent1: _TCCA[_T1], 

2895 __ent2: _TCCA[_T2], 

2896 __ent3: _TCCA[_T3], 

2897 __ent4: _TCCA[_T4], 

2898 __ent5: _TCCA[_T5], 

2899 __ent6: _TCCA[_T6], 

2900 /, 

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

2902 

2903 @overload 

2904 def query( 

2905 self, 

2906 __ent0: _TCCA[_T0], 

2907 __ent1: _TCCA[_T1], 

2908 __ent2: _TCCA[_T2], 

2909 __ent3: _TCCA[_T3], 

2910 __ent4: _TCCA[_T4], 

2911 __ent5: _TCCA[_T5], 

2912 __ent6: _TCCA[_T6], 

2913 __ent7: _TCCA[_T7], 

2914 /, 

2915 *entities: _ColumnsClauseArgument[Any], 

2916 ) -> RowReturningQuery[ 

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

2918 ]: ... 

2919 

2920 # END OVERLOADED FUNCTIONS self.query 

2921 

2922 @overload 

2923 def query( 

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

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

2926 

2927 def query( 

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

2929 ) -> Query[Any]: 

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

2931 :class:`_orm.Session`. 

2932 

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

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

2935 to construct ORM queries. 

2936 

2937 .. seealso:: 

2938 

2939 :ref:`unified_tutorial` 

2940 

2941 :ref:`queryguide_toplevel` 

2942 

2943 :ref:`query_api_toplevel` - legacy API doc 

2944 

2945 """ 

2946 

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

2948 

2949 def _identity_lookup( 

2950 self, 

2951 mapper: Mapper[_O], 

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

2953 identity_token: Any = None, 

2954 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

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

2956 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

2957 bind_arguments: Optional[_BindArguments] = None, 

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

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

2960 

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

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

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

2964 check if was deleted). 

2965 

2966 e.g.:: 

2967 

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

2969 

2970 :param mapper: mapper in use 

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

2972 a tuple. 

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

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

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

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

2977 :param passive: passive load flag passed to 

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

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

2980 if the flag allows for SQL to be emitted. 

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

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

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

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

2985 relationship-loaded). 

2986 

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

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

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

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

2991 

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

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

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

2995 :class:`_query.Query` object. 

2996 

2997 

2998 """ 

2999 

3000 key = mapper.identity_key_from_primary_key( 

3001 primary_key_identity, identity_token=identity_token 

3002 ) 

3003 

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

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

3006 return return_value 

3007 

3008 @util.non_memoized_property 

3009 @contextlib.contextmanager 

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

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

3012 

3013 e.g.:: 

3014 

3015 with session.no_autoflush: 

3016 

3017 some_object = SomeClass() 

3018 session.add(some_object) 

3019 # won't autoflush 

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

3021 

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

3023 will not be subject to flushes occurring upon query 

3024 access. This is useful when initializing a series 

3025 of objects which involve existing database queries, 

3026 where the uncompleted object should not yet be flushed. 

3027 

3028 """ 

3029 autoflush = self.autoflush 

3030 self.autoflush = False 

3031 try: 

3032 yield self 

3033 finally: 

3034 self.autoflush = autoflush 

3035 

3036 @util.langhelpers.tag_method_for_warnings( 

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

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

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

3040 "warning happened while initializing objects.", 

3041 sa_exc.SAWarning, 

3042 ) 

3043 def _autoflush(self) -> None: 

3044 if self.autoflush and not self._flushing: 

3045 try: 

3046 self.flush() 

3047 except sa_exc.StatementError as e: 

3048 # note we are reraising StatementError as opposed to 

3049 # raising FlushError with "chaining" to remain compatible 

3050 # with code that catches StatementError, IntegrityError, 

3051 # etc. 

3052 e.add_detail( 

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

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

3055 "flush is occurring prematurely" 

3056 ) 

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

3058 

3059 def refresh( 

3060 self, 

3061 instance: object, 

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

3063 with_for_update: ForUpdateParameter = None, 

3064 ) -> None: 

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

3066 

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

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

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

3070 value available in the current transaction. 

3071 

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

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

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

3075 

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

3077 can also refresh eagerly loaded attributes. 

3078 

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

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

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

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

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

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

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

3086 refreshed. 

3087 

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

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

3090 attributes for those which are named explicitly in the 

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

3092 

3093 .. tip:: 

3094 

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

3096 refreshing both column and relationship oriented attributes, its 

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

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

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

3100 once while having explicit control over relationship loader 

3101 strategies, use the 

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

3103 instead. 

3104 

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

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

3107 in database state outside of that transaction. Refreshing 

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

3109 where database rows have not yet been accessed. 

3110 

3111 :param attribute_names: optional. An iterable collection of 

3112 string attribute names indicating a subset of attributes to 

3113 be refreshed. 

3114 

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

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

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

3118 flags should match the parameters of 

3119 :meth:`_query.Query.with_for_update`. 

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

3121 

3122 .. seealso:: 

3123 

3124 :ref:`session_expire` - introductory material 

3125 

3126 :meth:`.Session.expire` 

3127 

3128 :meth:`.Session.expire_all` 

3129 

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

3131 to refresh objects as they would be loaded normally. 

3132 

3133 """ 

3134 try: 

3135 state = attributes.instance_state(instance) 

3136 except exc.NO_STATE as err: 

3137 raise exc.UnmappedInstanceError(instance) from err 

3138 

3139 self._expire_state(state, attribute_names) 

3140 

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

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

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

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

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

3146 # load_on_ident. 

3147 self._autoflush() 

3148 

3149 if with_for_update == {}: 

3150 raise sa_exc.ArgumentError( 

3151 "with_for_update should be the boolean value " 

3152 "True, or a dictionary with options. " 

3153 "A blank dictionary is ambiguous." 

3154 ) 

3155 

3156 with_for_update = ForUpdateArg._from_argument(with_for_update) 

3157 

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

3159 if ( 

3160 loading._load_on_ident( 

3161 self, 

3162 stmt, 

3163 state.key, 

3164 refresh_state=state, 

3165 with_for_update=with_for_update, 

3166 only_load_props=attribute_names, 

3167 require_pk_cols=True, 

3168 # technically unnecessary as we just did autoflush 

3169 # above, however removes the additional unnecessary 

3170 # call to _autoflush() 

3171 no_autoflush=True, 

3172 is_user_refresh=True, 

3173 ) 

3174 is None 

3175 ): 

3176 raise sa_exc.InvalidRequestError( 

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

3178 ) 

3179 

3180 def expire_all(self) -> None: 

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

3182 

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

3184 a query will be issued using the 

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

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

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

3188 previously read in that same transaction, regardless of changes 

3189 in database state outside of that transaction. 

3190 

3191 To expire individual objects and individual attributes 

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

3193 

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

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

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

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

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

3199 assuming the transaction is isolated. 

3200 

3201 .. seealso:: 

3202 

3203 :ref:`session_expire` - introductory material 

3204 

3205 :meth:`.Session.expire` 

3206 

3207 :meth:`.Session.refresh` 

3208 

3209 :meth:`_orm.Query.populate_existing` 

3210 

3211 """ 

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

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

3214 

3215 def expire( 

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

3217 ) -> None: 

3218 """Expire the attributes on an instance. 

3219 

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

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

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

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

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

3225 previously read in that same transaction, regardless of changes 

3226 in database state outside of that transaction. 

3227 

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

3229 use :meth:`Session.expire_all`. 

3230 

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

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

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

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

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

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

3237 transaction. 

3238 

3239 :param instance: The instance to be refreshed. 

3240 :param attribute_names: optional list of string attribute names 

3241 indicating a subset of attributes to be expired. 

3242 

3243 .. seealso:: 

3244 

3245 :ref:`session_expire` - introductory material 

3246 

3247 :meth:`.Session.expire` 

3248 

3249 :meth:`.Session.refresh` 

3250 

3251 :meth:`_orm.Query.populate_existing` 

3252 

3253 """ 

3254 try: 

3255 state = attributes.instance_state(instance) 

3256 except exc.NO_STATE as err: 

3257 raise exc.UnmappedInstanceError(instance) from err 

3258 self._expire_state(state, attribute_names) 

3259 

3260 def _expire_state( 

3261 self, 

3262 state: InstanceState[Any], 

3263 attribute_names: Optional[Iterable[str]], 

3264 ) -> None: 

3265 self._validate_persistent(state) 

3266 if attribute_names: 

3267 state._expire_attributes(state.dict, attribute_names) 

3268 else: 

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

3270 # remove associations 

3271 cascaded = list( 

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

3273 ) 

3274 self._conditional_expire(state) 

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

3276 self._conditional_expire(st_) 

3277 

3278 def _conditional_expire( 

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

3280 ) -> None: 

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

3282 

3283 if state.key: 

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

3285 elif state in self._new: 

3286 self._new.pop(state) 

3287 state._detach(self) 

3288 

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

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

3291 

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

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

3294 

3295 """ 

3296 try: 

3297 state = attributes.instance_state(instance) 

3298 except exc.NO_STATE as err: 

3299 raise exc.UnmappedInstanceError(instance) from err 

3300 if state.session_id is not self.hash_key: 

3301 raise sa_exc.InvalidRequestError( 

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

3303 ) 

3304 

3305 cascaded = list( 

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

3307 ) 

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

3309 

3310 def _expunge_states( 

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

3312 ) -> None: 

3313 for state in states: 

3314 if state in self._new: 

3315 self._new.pop(state) 

3316 elif self.identity_map.contains_state(state): 

3317 self.identity_map.safe_discard(state) 

3318 self._deleted.pop(state, None) 

3319 elif self._transaction: 

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

3321 # in the transaction snapshot 

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

3323 statelib.InstanceState._detach_states( 

3324 states, self, to_transient=to_transient 

3325 ) 

3326 

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

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

3329 

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

3331 state as well as already persistent objects. 

3332 

3333 """ 

3334 

3335 pending_to_persistent = self.dispatch.pending_to_persistent or None 

3336 for state in states: 

3337 mapper = _state_mapper(state) 

3338 

3339 # prevent against last minute dereferences of the object 

3340 obj = state.obj() 

3341 if obj is not None: 

3342 instance_key = mapper._identity_key_from_state(state) 

3343 

3344 if ( 

3345 _none_set.intersection(instance_key[1]) 

3346 and not mapper.allow_partial_pks 

3347 or _none_set.issuperset(instance_key[1]) 

3348 ): 

3349 raise exc.FlushError( 

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

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

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

3353 "that the mapped Column object is configured to " 

3354 "expect these generated values. Ensure also that " 

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

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

3357 % state_str(state) 

3358 ) 

3359 

3360 if state.key is None: 

3361 state.key = instance_key 

3362 elif state.key != instance_key: 

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

3364 # state has already replaced this one in the identity 

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

3366 self.identity_map.safe_discard(state) 

3367 trans = self._transaction 

3368 assert trans is not None 

3369 if state in trans._key_switches: 

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

3371 else: 

3372 orig_key = state.key 

3373 trans._key_switches[state] = ( 

3374 orig_key, 

3375 instance_key, 

3376 ) 

3377 state.key = instance_key 

3378 

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

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

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

3382 old = self.identity_map.replace(state) 

3383 if ( 

3384 old is not None 

3385 and mapper._identity_key_from_state(old) == instance_key 

3386 and old.obj() is not None 

3387 ): 

3388 util.warn( 

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

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

3391 "load operations occurring inside of an event handler " 

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

3393 ) 

3394 state._orphaned_outside_of_session = False 

3395 

3396 statelib.InstanceState._commit_all_states( 

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

3398 ) 

3399 

3400 self._register_altered(states) 

3401 

3402 if pending_to_persistent is not None: 

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

3404 pending_to_persistent(self, state) 

3405 

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

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

3408 self._new.pop(state) 

3409 

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

3411 if self._transaction: 

3412 for state in states: 

3413 if state in self._new: 

3414 self._transaction._new[state] = True 

3415 else: 

3416 self._transaction._dirty[state] = True 

3417 

3418 def _remove_newly_deleted( 

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

3420 ) -> None: 

3421 persistent_to_deleted = self.dispatch.persistent_to_deleted or None 

3422 for state in states: 

3423 if self._transaction: 

3424 self._transaction._deleted[state] = True 

3425 

3426 if persistent_to_deleted is not None: 

3427 # get a strong reference before we pop out of 

3428 # self._deleted 

3429 obj = state.obj() # noqa 

3430 

3431 self.identity_map.safe_discard(state) 

3432 self._deleted.pop(state, None) 

3433 state._deleted = True 

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

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

3436 # tracked as part of that 

3437 if persistent_to_deleted is not None: 

3438 persistent_to_deleted(self, state) 

3439 

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

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

3442 

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

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

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

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

3447 

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

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

3450 state directly. 

3451 

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

3453 objects which were transient when they were passed to 

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

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

3456 :class:`_orm.Session`. 

3457 

3458 .. seealso:: 

3459 

3460 :meth:`_orm.Session.add_all` 

3461 

3462 :ref:`session_adding` - at :ref:`session_basics` 

3463 

3464 """ 

3465 if _warn and self._warn_on_events: 

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

3467 

3468 try: 

3469 state = attributes.instance_state(instance) 

3470 except exc.NO_STATE as err: 

3471 raise exc.UnmappedInstanceError(instance) from err 

3472 

3473 self._save_or_update_state(state) 

3474 

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

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

3477 

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

3479 behavioral description. 

3480 

3481 .. seealso:: 

3482 

3483 :meth:`_orm.Session.add` 

3484 

3485 :ref:`session_adding` - at :ref:`session_basics` 

3486 

3487 """ 

3488 

3489 if self._warn_on_events: 

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

3491 

3492 for instance in instances: 

3493 self.add(instance, _warn=False) 

3494 

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

3496 state._orphaned_outside_of_session = False 

3497 self._save_or_update_impl(state) 

3498 

3499 mapper = _state_mapper(state) 

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

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

3502 ): 

3503 self._save_or_update_impl(st_) 

3504 

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

3506 """Mark an instance as deleted. 

3507 

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

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

3510 object will remain in the :term:`persistent` state until the next 

3511 flush proceeds. During this time, the object will also be a member 

3512 of the :attr:`_orm.Session.deleted` collection. 

3513 

3514 When the next flush proceeds, the object will move to the 

3515 :term:`deleted` state, indicating a ``DELETE`` statement was emitted 

3516 for its row within the current transaction. When the transaction 

3517 is successfully committed, 

3518 the deleted object is moved to the :term:`detached` state and is 

3519 no longer present within this :class:`_orm.Session`. 

3520 

3521 .. seealso:: 

3522 

3523 :ref:`session_deleting` - at :ref:`session_basics` 

3524 

3525 :meth:`.Session.delete_all` - multiple instance version 

3526 

3527 """ 

3528 if self._warn_on_events: 

3529 self._flush_warning("Session.delete()") 

3530 

3531 self._delete_impl(object_state(instance), instance, head=True) 

3532 

3533 def delete_all(self, instances: Iterable[object]) -> None: 

3534 """Calls :meth:`.Session.delete` on multiple instances. 

3535 

3536 .. seealso:: 

3537 

3538 :meth:`.Session.delete` - main documentation on delete 

3539 

3540 .. versionadded:: 2.1 

3541 

3542 """ 

3543 

3544 if self._warn_on_events: 

3545 self._flush_warning("Session.delete_all()") 

3546 

3547 for instance in instances: 

3548 self._delete_impl(object_state(instance), instance, head=True) 

3549 

3550 def _delete_impl( 

3551 self, state: InstanceState[Any], obj: object, head: bool 

3552 ) -> None: 

3553 if state.key is None: 

3554 if head: 

3555 raise sa_exc.InvalidRequestError( 

3556 "Instance '%s' is not persisted" % state_str(state) 

3557 ) 

3558 else: 

3559 return 

3560 

3561 to_attach = self._before_attach(state, obj) 

3562 

3563 if state in self._deleted: 

3564 return 

3565 

3566 self.identity_map.add(state) 

3567 

3568 if to_attach: 

3569 self._after_attach(state, obj) 

3570 

3571 if head: 

3572 # grab the cascades before adding the item to the deleted list 

3573 # so that autoflush does not delete the item 

3574 # the strong reference to the instance itself is significant here 

3575 cascade_states = list( 

3576 state.manager.mapper.cascade_iterator("delete", state) 

3577 ) 

3578 else: 

3579 cascade_states = None 

3580 

3581 self._deleted[state] = obj 

3582 

3583 if head: 

3584 if TYPE_CHECKING: 

3585 assert cascade_states is not None 

3586 for o, m, st_, dct_ in cascade_states: 

3587 self._delete_impl(st_, o, False) 

3588 

3589 def get( 

3590 self, 

3591 entity: _EntityBindKey[_O], 

3592 ident: _PKIdentityArgument, 

3593 *, 

3594 options: Optional[Sequence[ORMOption]] = None, 

3595 populate_existing: bool = False, 

3596 with_for_update: ForUpdateParameter = None, 

3597 identity_token: Optional[Any] = None, 

3598 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3599 bind_arguments: Optional[_BindArguments] = None, 

3600 ) -> Optional[_O]: 

3601 """Return an instance based on the given primary key identifier, 

3602 or ``None`` if not found. 

3603 

3604 E.g.:: 

3605 

3606 my_user = session.get(User, 5) 

3607 

3608 some_object = session.get(VersionedFoo, (5, 10)) 

3609 

3610 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10}) 

3611 

3612 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved 

3613 from the now legacy :meth:`_orm.Query.get` method. 

3614 

3615 :meth:`_orm.Session.get` is special in that it provides direct 

3616 access to the identity map of the :class:`.Session`. 

3617 If the given primary key identifier is present 

3618 in the local identity map, the object is returned 

3619 directly from this collection and no SQL is emitted, 

3620 unless the object has been marked fully expired. 

3621 If not present, 

3622 a SELECT is performed in order to locate the object. 

3623 

3624 :meth:`_orm.Session.get` also will perform a check if 

3625 the object is present in the identity map and 

3626 marked as expired - a SELECT 

3627 is emitted to refresh the object as well as to 

3628 ensure that the row is still present. 

3629 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised. 

3630 

3631 :param entity: a mapped class or :class:`.Mapper` indicating the 

3632 type of entity to be loaded. 

3633 

3634 :param ident: A scalar, tuple, or dictionary representing the 

3635 primary key. For a composite (e.g. multiple column) primary key, 

3636 a tuple or dictionary should be passed. 

3637 

3638 For a single-column primary key, the scalar calling form is typically 

3639 the most expedient. If the primary key of a row is the value "5", 

3640 the call looks like:: 

3641 

3642 my_object = session.get(SomeClass, 5) 

3643 

3644 The tuple form contains primary key values typically in 

3645 the order in which they correspond to the mapped 

3646 :class:`_schema.Table` 

3647 object's primary key columns, or if the 

3648 :paramref:`_orm.Mapper.primary_key` configuration parameter were 

3649 used, in 

3650 the order used for that parameter. For example, if the primary key 

3651 of a row is represented by the integer 

3652 digits "5, 10" the call would look like:: 

3653 

3654 my_object = session.get(SomeClass, (5, 10)) 

3655 

3656 The dictionary form should include as keys the mapped attribute names 

3657 corresponding to each element of the primary key. If the mapped class 

3658 has the attributes ``id``, ``version_id`` as the attributes which 

3659 store the object's primary key value, the call would look like:: 

3660 

3661 my_object = session.get(SomeClass, {"id": 5, "version_id": 10}) 

3662 

3663 :param options: optional sequence of loader options which will be 

3664 applied to the query, if one is emitted. 

3665 

3666 :param populate_existing: causes the method to unconditionally emit 

3667 a SQL query and refresh the object with the newly loaded data, 

3668 regardless of whether or not the object is already present. 

3669 

3670 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE 

3671 should be used, or may be a dictionary containing flags to 

3672 indicate a more specific set of FOR UPDATE flags for the SELECT; 

3673 flags should match the parameters of 

3674 :meth:`_query.Query.with_for_update`. 

3675 Supersedes the :paramref:`.Session.refresh.lockmode` parameter. 

3676 

3677 :param execution_options: optional dictionary of execution options, 

3678 which will be associated with the query execution if one is emitted. 

3679 This dictionary can provide a subset of the options that are 

3680 accepted by :meth:`_engine.Connection.execution_options`, and may 

3681 also provide additional options understood only in an ORM context. 

3682 

3683 .. versionadded:: 1.4.29 

3684 

3685 .. seealso:: 

3686 

3687 :ref:`orm_queryguide_execution_options` - ORM-specific execution 

3688 options 

3689 

3690 :param bind_arguments: dictionary of additional arguments to determine 

3691 the bind. May include "mapper", "bind", or other custom arguments. 

3692 Contents of this dictionary are passed to the 

3693 :meth:`.Session.get_bind` method. 

3694 

3695 .. versionadded:: 2.0.0rc1 

3696 

3697 :return: The object instance, or ``None``. 

3698 

3699 """ # noqa: E501 

3700 return self._get_impl( 

3701 entity, 

3702 ident, 

3703 loading._load_on_pk_identity, 

3704 options=options, 

3705 populate_existing=populate_existing, 

3706 with_for_update=with_for_update, 

3707 identity_token=identity_token, 

3708 execution_options=execution_options, 

3709 bind_arguments=bind_arguments, 

3710 ) 

3711 

3712 def get_one( 

3713 self, 

3714 entity: _EntityBindKey[_O], 

3715 ident: _PKIdentityArgument, 

3716 *, 

3717 options: Optional[Sequence[ORMOption]] = None, 

3718 populate_existing: bool = False, 

3719 with_for_update: ForUpdateParameter = None, 

3720 identity_token: Optional[Any] = None, 

3721 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3722 bind_arguments: Optional[_BindArguments] = None, 

3723 ) -> _O: 

3724 """Return exactly one instance based on the given primary key 

3725 identifier, or raise an exception if not found. 

3726 

3727 Raises :class:`_exc.NoResultFound` if the query selects no rows. 

3728 

3729 For a detailed documentation of the arguments see the 

3730 method :meth:`.Session.get`. 

3731 

3732 .. versionadded:: 2.0.22 

3733 

3734 :return: The object instance. 

3735 

3736 .. seealso:: 

3737 

3738 :meth:`.Session.get` - equivalent method that instead 

3739 returns ``None`` if no row was found with the provided primary 

3740 key 

3741 

3742 """ 

3743 

3744 instance = self.get( 

3745 entity, 

3746 ident, 

3747 options=options, 

3748 populate_existing=populate_existing, 

3749 with_for_update=with_for_update, 

3750 identity_token=identity_token, 

3751 execution_options=execution_options, 

3752 bind_arguments=bind_arguments, 

3753 ) 

3754 

3755 if instance is None: 

3756 raise sa_exc.NoResultFound( 

3757 "No row was found when one was required" 

3758 ) 

3759 

3760 return instance 

3761 

3762 def _get_impl( 

3763 self, 

3764 entity: _EntityBindKey[_O], 

3765 primary_key_identity: _PKIdentityArgument, 

3766 db_load_fn: Callable[..., _O], 

3767 *, 

3768 options: Optional[Sequence[ExecutableOption]] = None, 

3769 populate_existing: bool = False, 

3770 with_for_update: ForUpdateParameter = None, 

3771 identity_token: Optional[Any] = None, 

3772 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT, 

3773 bind_arguments: Optional[_BindArguments] = None, 

3774 ) -> Optional[_O]: 

3775 # convert composite types to individual args 

3776 if ( 

3777 is_composite_class(primary_key_identity) 

3778 and type(primary_key_identity) 

3779 in descriptor_props._composite_getters 

3780 ): 

3781 getter = descriptor_props._composite_getters[ 

3782 type(primary_key_identity) 

3783 ] 

3784 primary_key_identity = getter(primary_key_identity) 

3785 

3786 mapper: Optional[Mapper[_O]] = inspect(entity) 

3787 

3788 if mapper is None or not mapper.is_mapper: 

3789 raise sa_exc.ArgumentError( 

3790 "Expected mapped class or mapper, got: %r" % entity 

3791 ) 

3792 

3793 is_dict = isinstance(primary_key_identity, dict) 

3794 if not is_dict: 

3795 primary_key_identity = util.to_list( 

3796 primary_key_identity, default=[None] 

3797 ) 

3798 

3799 if len(primary_key_identity) != len(mapper.primary_key): 

3800 raise sa_exc.InvalidRequestError( 

3801 "Incorrect number of values in identifier to formulate " 

3802 "primary key for session.get(); primary key columns " 

3803 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key) 

3804 ) 

3805 

3806 if is_dict: 

3807 pk_synonyms = mapper._pk_synonyms 

3808 

3809 if pk_synonyms: 

3810 correct_keys = set(pk_synonyms).intersection( 

3811 primary_key_identity 

3812 ) 

3813 

3814 if correct_keys: 

3815 primary_key_identity = dict(primary_key_identity) 

3816 for k in correct_keys: 

3817 primary_key_identity[pk_synonyms[k]] = ( 

3818 primary_key_identity[k] 

3819 ) 

3820 

3821 try: 

3822 primary_key_identity = list( 

3823 primary_key_identity[prop.key] 

3824 for prop in mapper._identity_key_props 

3825 ) 

3826 

3827 except KeyError as err: 

3828 raise sa_exc.InvalidRequestError( 

3829 "Incorrect names of values in identifier to formulate " 

3830 "primary key for session.get(); primary key attribute " 

3831 "names are %s (synonym names are also accepted)" 

3832 % ",".join( 

3833 "'%s'" % prop.key 

3834 for prop in mapper._identity_key_props 

3835 ) 

3836 ) from err 

3837 

3838 if ( 

3839 not populate_existing 

3840 and not mapper.always_refresh 

3841 and with_for_update is None 

3842 ): 

3843 instance = self._identity_lookup( 

3844 mapper, 

3845 primary_key_identity, 

3846 identity_token=identity_token, 

3847 execution_options=execution_options, 

3848 bind_arguments=bind_arguments, 

3849 ) 

3850 

3851 if instance is not None: 

3852 # reject calls for id in identity map but class 

3853 # mismatch. 

3854 if not isinstance(instance, mapper.class_): 

3855 return None 

3856 return instance 

3857 

3858 # TODO: this was being tested before, but this is not possible 

3859 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH 

3860 

3861 # set_label_style() not strictly necessary, however this will ensure 

3862 # that tablename_colname style is used which at the moment is 

3863 # asserted in a lot of unit tests :) 

3864 

3865 load_options = context.QueryContext.default_load_options 

3866 

3867 if populate_existing: 

3868 load_options += {"_populate_existing": populate_existing} 

3869 statement = sql.select(mapper).set_label_style( 

3870 LABEL_STYLE_TABLENAME_PLUS_COL 

3871 ) 

3872 if with_for_update is not None: 

3873 statement._for_update_arg = ForUpdateArg._from_argument( 

3874 with_for_update 

3875 ) 

3876 

3877 if options: 

3878 statement = statement.options(*options) 

3879 return db_load_fn( 

3880 self, 

3881 statement, 

3882 primary_key_identity, 

3883 load_options=load_options, 

3884 identity_token=identity_token, 

3885 execution_options=execution_options, 

3886 bind_arguments=bind_arguments, 

3887 ) 

3888 

3889 def merge( 

3890 self, 

3891 instance: _O, 

3892 *, 

3893 load: bool = True, 

3894 options: Optional[Sequence[ORMOption]] = None, 

3895 ) -> _O: 

3896 """Copy the state of a given instance into a corresponding instance 

3897 within this :class:`.Session`. 

3898 

3899 :meth:`.Session.merge` examines the primary key attributes of the 

3900 source instance, and attempts to reconcile it with an instance of the 

3901 same primary key in the session. If not found locally, it attempts 

3902 to load the object from the database based on primary key, and if 

3903 none can be located, creates a new instance. The state of each 

3904 attribute on the source instance is then copied to the target 

3905 instance. The resulting target instance is then returned by the 

3906 method; the original source instance is left unmodified, and 

3907 un-associated with the :class:`.Session` if not already. 

3908 

3909 This operation cascades to associated instances if the association is 

3910 mapped with ``cascade="merge"``. 

3911 

3912 See :ref:`unitofwork_merging` for a detailed discussion of merging. 

3913 

3914 :param instance: Instance to be merged. 

3915 :param load: Boolean, when False, :meth:`.merge` switches into 

3916 a "high performance" mode which causes it to forego emitting history 

3917 events as well as all database access. This flag is used for 

3918 cases such as transferring graphs of objects into a :class:`.Session` 

3919 from a second level cache, or to transfer just-loaded objects 

3920 into the :class:`.Session` owned by a worker thread or process 

3921 without re-querying the database. 

3922 

3923 The ``load=False`` use case adds the caveat that the given 

3924 object has to be in a "clean" state, that is, has no pending changes 

3925 to be flushed - even if the incoming object is detached from any 

3926 :class:`.Session`. This is so that when 

3927 the merge operation populates local attributes and 

3928 cascades to related objects and 

3929 collections, the values can be "stamped" onto the 

3930 target object as is, without generating any history or attribute 

3931 events, and without the need to reconcile the incoming data with 

3932 any existing related objects or collections that might not 

3933 be loaded. The resulting objects from ``load=False`` are always 

3934 produced as "clean", so it is only appropriate that the given objects 

3935 should be "clean" as well, else this suggests a mis-use of the 

3936 method. 

3937 :param options: optional sequence of loader options which will be 

3938 applied to the :meth:`_orm.Session.get` method when the merge 

3939 operation loads the existing version of the object from the database. 

3940 

3941 .. versionadded:: 1.4.24 

3942 

3943 

3944 .. seealso:: 

3945 

3946 :func:`.make_transient_to_detached` - provides for an alternative 

3947 means of "merging" a single object into the :class:`.Session` 

3948 

3949 :meth:`.Session.merge_all` - multiple instance version 

3950 

3951 """ 

3952 

3953 if self._warn_on_events: 

3954 self._flush_warning("Session.merge()") 

3955 

3956 if load: 

3957 # flush current contents if we expect to load data 

3958 self._autoflush() 

3959 

3960 with self.no_autoflush: 

3961 return self._merge( 

3962 object_state(instance), 

3963 attributes.instance_dict(instance), 

3964 load=load, 

3965 options=options, 

3966 _recursive={}, 

3967 _resolve_conflict_map={}, 

3968 ) 

3969 

3970 def merge_all( 

3971 self, 

3972 instances: Iterable[_O], 

3973 *, 

3974 load: bool = True, 

3975 options: Optional[Sequence[ORMOption]] = None, 

3976 ) -> Sequence[_O]: 

3977 """Calls :meth:`.Session.merge` on multiple instances. 

3978 

3979 .. seealso:: 

3980 

3981 :meth:`.Session.merge` - main documentation on merge 

3982 

3983 .. versionadded:: 2.1 

3984 

3985 """ 

3986 

3987 if self._warn_on_events: 

3988 self._flush_warning("Session.merge_all()") 

3989 

3990 if load: 

3991 # flush current contents if we expect to load data 

3992 self._autoflush() 

3993 

3994 return [ 

3995 self._merge( 

3996 object_state(instance), 

3997 attributes.instance_dict(instance), 

3998 load=load, 

3999 options=options, 

4000 _recursive={}, 

4001 _resolve_conflict_map={}, 

4002 ) 

4003 for instance in instances 

4004 ] 

4005 

4006 def _merge( 

4007 self, 

4008 state: InstanceState[_O], 

4009 state_dict: _InstanceDict, 

4010 *, 

4011 options: Optional[Sequence[ORMOption]] = None, 

4012 load: bool, 

4013 _recursive: Dict[Any, object], 

4014 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

4015 ) -> _O: 

4016 mapper: Mapper[_O] = _state_mapper(state) 

4017 if state in _recursive: 

4018 return cast(_O, _recursive[state]) 

4019 

4020 new_instance = False 

4021 key = state.key 

4022 

4023 merged: Optional[_O] 

4024 

4025 if key is None: 

4026 if state in self._new: 

4027 util.warn( 

4028 "Instance %s is already pending in this Session yet is " 

4029 "being merged again; this is probably not what you want " 

4030 "to do" % state_str(state) 

4031 ) 

4032 

4033 if not load: 

4034 raise sa_exc.InvalidRequestError( 

4035 "merge() with load=False option does not support " 

4036 "objects transient (i.e. unpersisted) objects. flush() " 

4037 "all changes on mapped instances before merging with " 

4038 "load=False." 

4039 ) 

4040 key = mapper._identity_key_from_state(state) 

4041 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[ 

4042 1 

4043 ] and ( 

4044 not _none_set.intersection(key[1]) 

4045 or ( 

4046 mapper.allow_partial_pks 

4047 and not _none_set.issuperset(key[1]) 

4048 ) 

4049 ) 

4050 else: 

4051 key_is_persistent = True 

4052 

4053 merged = self.identity_map.get(key) 

4054 

4055 if merged is None: 

4056 if key_is_persistent and key in _resolve_conflict_map: 

4057 merged = cast(_O, _resolve_conflict_map[key]) 

4058 

4059 elif not load: 

4060 if state.modified: 

4061 raise sa_exc.InvalidRequestError( 

4062 "merge() with load=False option does not support " 

4063 "objects marked as 'dirty'. flush() all changes on " 

4064 "mapped instances before merging with load=False." 

4065 ) 

4066 merged = mapper.class_manager.new_instance() 

4067 merged_state = attributes.instance_state(merged) 

4068 merged_state.key = key 

4069 self._update_impl(merged_state) 

4070 new_instance = True 

4071 

4072 elif key_is_persistent: 

4073 merged = self.get( 

4074 mapper.class_, 

4075 key[1], 

4076 identity_token=key[2], 

4077 options=options, 

4078 ) 

4079 

4080 if merged is None: 

4081 merged = mapper.class_manager.new_instance() 

4082 merged_state = attributes.instance_state(merged) 

4083 merged_dict = attributes.instance_dict(merged) 

4084 new_instance = True 

4085 self._save_or_update_state(merged_state) 

4086 else: 

4087 merged_state = attributes.instance_state(merged) 

4088 merged_dict = attributes.instance_dict(merged) 

4089 

4090 _recursive[state] = merged 

4091 _resolve_conflict_map[key] = merged 

4092 

4093 # check that we didn't just pull the exact same 

4094 # state out. 

4095 if state is not merged_state: 

4096 # version check if applicable 

4097 if mapper.version_id_col is not None: 

4098 existing_version = mapper._get_state_attr_by_column( 

4099 state, 

4100 state_dict, 

4101 mapper.version_id_col, 

4102 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4103 ) 

4104 

4105 merged_version = mapper._get_state_attr_by_column( 

4106 merged_state, 

4107 merged_dict, 

4108 mapper.version_id_col, 

4109 passive=PassiveFlag.PASSIVE_NO_INITIALIZE, 

4110 ) 

4111 

4112 if ( 

4113 existing_version 

4114 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4115 and merged_version 

4116 is not LoaderCallableStatus.PASSIVE_NO_RESULT 

4117 and existing_version != merged_version 

4118 ): 

4119 raise exc.StaleDataError( 

4120 "Version id '%s' on merged state %s " 

4121 "does not match existing version '%s'. " 

4122 "Leave the version attribute unset when " 

4123 "merging to update the most recent version." 

4124 % ( 

4125 existing_version, 

4126 state_str(merged_state), 

4127 merged_version, 

4128 ) 

4129 ) 

4130 

4131 merged_state.load_path = state.load_path 

4132 merged_state.load_options = state.load_options 

4133 

4134 # since we are copying load_options, we need to copy 

4135 # the callables_ that would have been generated by those 

4136 # load_options. 

4137 # assumes that the callables we put in state.callables_ 

4138 # are not instance-specific (which they should not be) 

4139 merged_state._copy_callables(state) 

4140 

4141 for prop in mapper.iterate_properties: 

4142 prop.merge( 

4143 self, 

4144 state, 

4145 state_dict, 

4146 merged_state, 

4147 merged_dict, 

4148 load, 

4149 _recursive, 

4150 _resolve_conflict_map, 

4151 ) 

4152 

4153 if not load: 

4154 # remove any history 

4155 merged_state._commit_all(merged_dict, self.identity_map) 

4156 merged_state.manager.dispatch._sa_event_merge_wo_load( 

4157 merged_state, None 

4158 ) 

4159 

4160 if new_instance: 

4161 merged_state.manager.dispatch.load(merged_state, None) 

4162 

4163 return merged 

4164 

4165 def _validate_persistent(self, state: InstanceState[Any]) -> None: 

4166 if not self.identity_map.contains_state(state): 

4167 raise sa_exc.InvalidRequestError( 

4168 "Instance '%s' is not persistent within this Session" 

4169 % state_str(state) 

4170 ) 

4171 

4172 def _save_impl(self, state: InstanceState[Any]) -> None: 

4173 if state.key is not None: 

4174 raise sa_exc.InvalidRequestError( 

4175 "Object '%s' already has an identity - " 

4176 "it can't be registered as pending" % state_str(state) 

4177 ) 

4178 

4179 obj = state.obj() 

4180 to_attach = self._before_attach(state, obj) 

4181 if state not in self._new: 

4182 self._new[state] = obj 

4183 state.insert_order = len(self._new) 

4184 if to_attach: 

4185 self._after_attach(state, obj) 

4186 

4187 def _update_impl( 

4188 self, state: InstanceState[Any], revert_deletion: bool = False 

4189 ) -> None: 

4190 if state.key is None: 

4191 raise sa_exc.InvalidRequestError( 

4192 "Instance '%s' is not persisted" % state_str(state) 

4193 ) 

4194 

4195 if state._deleted: 

4196 if revert_deletion: 

4197 if not state._attached: 

4198 return 

4199 del state._deleted 

4200 else: 

4201 raise sa_exc.InvalidRequestError( 

4202 "Instance '%s' has been deleted. " 

4203 "Use the make_transient() " 

4204 "function to send this object back " 

4205 "to the transient state." % state_str(state) 

4206 ) 

4207 

4208 obj = state.obj() 

4209 

4210 # check for late gc 

4211 if obj is None: 

4212 return 

4213 

4214 to_attach = self._before_attach(state, obj) 

4215 

4216 self._deleted.pop(state, None) 

4217 if revert_deletion: 

4218 self.identity_map.replace(state) 

4219 else: 

4220 self.identity_map.add(state) 

4221 

4222 if to_attach: 

4223 self._after_attach(state, obj) 

4224 elif revert_deletion: 

4225 self.dispatch.deleted_to_persistent(self, state) 

4226 

4227 def _save_or_update_impl(self, state: InstanceState[Any]) -> None: 

4228 if state.key is None: 

4229 self._save_impl(state) 

4230 else: 

4231 self._update_impl(state) 

4232 

4233 def enable_relationship_loading(self, obj: object) -> None: 

4234 """Associate an object with this :class:`.Session` for related 

4235 object loading. 

4236 

4237 .. warning:: 

4238 

4239 :meth:`.enable_relationship_loading` exists to serve special 

4240 use cases and is not recommended for general use. 

4241 

4242 Accesses of attributes mapped with :func:`_orm.relationship` 

4243 will attempt to load a value from the database using this 

4244 :class:`.Session` as the source of connectivity. The values 

4245 will be loaded based on foreign key and primary key values 

4246 present on this object - if not present, then those relationships 

4247 will be unavailable. 

4248 

4249 The object will be attached to this session, but will 

4250 **not** participate in any persistence operations; its state 

4251 for almost all purposes will remain either "transient" or 

4252 "detached", except for the case of relationship loading. 

4253 

4254 Also note that backrefs will often not work as expected. 

4255 Altering a relationship-bound attribute on the target object 

4256 may not fire off a backref event, if the effective value 

4257 is what was already loaded from a foreign-key-holding value. 

4258 

4259 The :meth:`.Session.enable_relationship_loading` method is 

4260 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`. 

4261 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows 

4262 an object to remain transient while still being able to load 

4263 related items. 

4264 

4265 To make a transient object associated with a :class:`.Session` 

4266 via :meth:`.Session.enable_relationship_loading` pending, add 

4267 it to the :class:`.Session` using :meth:`.Session.add` normally. 

4268 If the object instead represents an existing identity in the database, 

4269 it should be merged using :meth:`.Session.merge`. 

4270 

4271 :meth:`.Session.enable_relationship_loading` does not improve 

4272 behavior when the ORM is used normally - object references should be 

4273 constructed at the object level, not at the foreign key level, so 

4274 that they are present in an ordinary way before flush() 

4275 proceeds. This method is not intended for general use. 

4276 

4277 .. seealso:: 

4278 

4279 :paramref:`_orm.relationship.load_on_pending` - this flag 

4280 allows per-relationship loading of many-to-ones on items that 

4281 are pending. 

4282 

4283 :func:`.make_transient_to_detached` - allows for an object to 

4284 be added to a :class:`.Session` without SQL emitted, which then 

4285 will unexpire attributes on access. 

4286 

4287 """ 

4288 try: 

4289 state = attributes.instance_state(obj) 

4290 except exc.NO_STATE as err: 

4291 raise exc.UnmappedInstanceError(obj) from err 

4292 

4293 to_attach = self._before_attach(state, obj) 

4294 state._load_pending = True 

4295 if to_attach: 

4296 self._after_attach(state, obj) 

4297 

4298 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool: 

4299 self._autobegin_t() 

4300 

4301 if state.session_id == self.hash_key: 

4302 return False 

4303 

4304 if state.session_id and state.session_id in _sessions: 

4305 raise sa_exc.InvalidRequestError( 

4306 "Object '%s' is already attached to session '%s' " 

4307 "(this is '%s')" 

4308 % (state_str(state), state.session_id, self.hash_key) 

4309 ) 

4310 

4311 self.dispatch.before_attach(self, state) 

4312 

4313 return True 

4314 

4315 def _after_attach(self, state: InstanceState[Any], obj: object) -> None: 

4316 state.session_id = self.hash_key 

4317 if state.modified and state._strong_obj is None: 

4318 state._strong_obj = obj 

4319 self.dispatch.after_attach(self, state) 

4320 

4321 if state.key: 

4322 self.dispatch.detached_to_persistent(self, state) 

4323 else: 

4324 self.dispatch.transient_to_pending(self, state) 

4325 

4326 def __contains__(self, instance: object) -> bool: 

4327 """Return True if the instance is associated with this session. 

4328 

4329 The instance may be pending or persistent within the Session for a 

4330 result of True. 

4331 

4332 """ 

4333 try: 

4334 state = attributes.instance_state(instance) 

4335 except exc.NO_STATE as err: 

4336 raise exc.UnmappedInstanceError(instance) from err 

4337 return self._contains_state(state) 

4338 

4339 def __iter__(self) -> Iterator[object]: 

4340 """Iterate over all pending or persistent instances within this 

4341 Session. 

4342 

4343 """ 

4344 return iter( 

4345 list(self._new.values()) + list(self.identity_map.values()) 

4346 ) 

4347 

4348 def _contains_state(self, state: InstanceState[Any]) -> bool: 

4349 return state in self._new or self.identity_map.contains_state(state) 

4350 

4351 def flush(self, objects: Optional[Sequence[Any]] = None) -> None: 

4352 """Flush all the object changes to the database. 

4353 

4354 Writes out all pending object creations, deletions and modifications 

4355 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are 

4356 automatically ordered by the Session's unit of work dependency 

4357 solver. 

4358 

4359 Database operations will be issued in the current transactional 

4360 context and do not affect the state of the transaction, unless an 

4361 error occurs, in which case the entire transaction is rolled back. 

4362 You may flush() as often as you like within a transaction to move 

4363 changes from Python to the database's transaction buffer. 

4364 

4365 :param objects: Optional; restricts the flush operation to operate 

4366 only on elements that are in the given collection. 

4367 

4368 This feature is for an extremely narrow set of use cases where 

4369 particular objects may need to be operated upon before the 

4370 full flush() occurs. It is not intended for general use. 

4371 

4372 .. deprecated:: 2.1 

4373 

4374 """ 

4375 

4376 if self._flushing: 

4377 raise sa_exc.InvalidRequestError("Session is already flushing") 

4378 

4379 if self._is_clean(): 

4380 return 

4381 try: 

4382 self._flushing = True 

4383 self._flush(objects) 

4384 finally: 

4385 self._flushing = False 

4386 

4387 def _flush_warning(self, method: Any) -> None: 

4388 util.warn( 

4389 "Usage of the '%s' operation is not currently supported " 

4390 "within the execution stage of the flush process. " 

4391 "Results may not be consistent. Consider using alternative " 

4392 "event listeners or connection-level operations instead." % method 

4393 ) 

4394 

4395 def _is_clean(self) -> bool: 

4396 return ( 

4397 not self.identity_map.check_modified() 

4398 and not self._deleted 

4399 and not self._new 

4400 ) 

4401 

4402 # have this here since it otherwise causes issues with the proxy 

4403 # method generation 

4404 @deprecated_params( 

4405 objects=( 

4406 "2.1", 

4407 "The `objects` parameter of `Session.flush` is deprecated", 

4408 ) 

4409 ) 

4410 def _flush(self, objects: Optional[Sequence[object]] = None) -> None: 

4411 dirty = self._dirty_states 

4412 if not dirty and not self._deleted and not self._new: 

4413 self.identity_map._modified.clear() 

4414 return 

4415 

4416 flush_context = UOWTransaction(self) 

4417 

4418 if self.dispatch.before_flush: 

4419 self.dispatch.before_flush(self, flush_context, objects) 

4420 # re-establish "dirty states" in case the listeners 

4421 # added 

4422 dirty = self._dirty_states 

4423 

4424 deleted = set(self._deleted) 

4425 new = set(self._new) 

4426 

4427 dirty = set(dirty).difference(deleted) 

4428 

4429 # create the set of all objects we want to operate upon 

4430 if objects: 

4431 # specific list passed in 

4432 objset = set() 

4433 for o in objects: 

4434 try: 

4435 state = attributes.instance_state(o) 

4436 

4437 except exc.NO_STATE as err: 

4438 raise exc.UnmappedInstanceError(o) from err 

4439 objset.add(state) 

4440 else: 

4441 objset = None 

4442 

4443 # store objects whose fate has been decided 

4444 processed = set() 

4445 

4446 # put all saves/updates into the flush context. detect top-level 

4447 # orphans and throw them into deleted. 

4448 if objset: 

4449 proc = new.union(dirty).intersection(objset).difference(deleted) 

4450 else: 

4451 proc = new.union(dirty).difference(deleted) 

4452 

4453 for state in proc: 

4454 is_orphan = _state_mapper(state)._is_orphan(state) 

4455 

4456 is_persistent_orphan = is_orphan and state.has_identity 

4457 

4458 if ( 

4459 is_orphan 

4460 and not is_persistent_orphan 

4461 and state._orphaned_outside_of_session 

4462 ): 

4463 self._expunge_states([state]) 

4464 else: 

4465 _reg = flush_context.register_object( 

4466 state, isdelete=is_persistent_orphan 

4467 ) 

4468 assert _reg, "Failed to add object to the flush context!" 

4469 processed.add(state) 

4470 

4471 # put all remaining deletes into the flush context. 

4472 if objset: 

4473 proc = deleted.intersection(objset).difference(processed) 

4474 else: 

4475 proc = deleted.difference(processed) 

4476 for state in proc: 

4477 _reg = flush_context.register_object(state, isdelete=True) 

4478 assert _reg, "Failed to add object to the flush context!" 

4479 

4480 if not flush_context.has_work: 

4481 return 

4482 

4483 flush_context.transaction = transaction = self._autobegin_t()._begin() 

4484 try: 

4485 self._warn_on_events = True 

4486 try: 

4487 flush_context.execute() 

4488 finally: 

4489 self._warn_on_events = False 

4490 

4491 self.dispatch.after_flush(self, flush_context) 

4492 

4493 flush_context.finalize_flush_changes() 

4494 

4495 if not objects and self.identity_map._modified: 

4496 len_ = len(self.identity_map._modified) 

4497 

4498 statelib.InstanceState._commit_all_states( 

4499 [ 

4500 (state, state.dict) 

4501 for state in self.identity_map._modified 

4502 ], 

4503 instance_dict=self.identity_map, 

4504 ) 

4505 util.warn( 

4506 "Attribute history events accumulated on %d " 

4507 "previously clean instances " 

4508 "within inner-flush event handlers have been " 

4509 "reset, and will not result in database updates. " 

4510 "Consider using set_committed_value() within " 

4511 "inner-flush event handlers to avoid this warning." % len_ 

4512 ) 

4513 

4514 # useful assertions: 

4515 # if not objects: 

4516 # assert not self.identity_map._modified 

4517 # else: 

4518 # assert self.identity_map._modified == \ 

4519 # self.identity_map._modified.difference(objects) 

4520 

4521 self.dispatch.after_flush_postexec(self, flush_context) 

4522 

4523 transaction.commit() 

4524 

4525 except: 

4526 with util.safe_reraise(): 

4527 transaction.rollback(_capture_exception=True) 

4528 

4529 def bulk_save_objects( 

4530 self, 

4531 objects: Iterable[object], 

4532 return_defaults: bool = False, 

4533 update_changed_only: bool = True, 

4534 preserve_order: bool = True, 

4535 ) -> None: 

4536 """Perform a bulk save of the given list of objects. 

4537 

4538 .. legacy:: 

4539 

4540 This method is a legacy feature as of the 2.0 series of 

4541 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4542 the sections :ref:`orm_queryguide_bulk_insert` and 

4543 :ref:`orm_queryguide_bulk_update`. 

4544 

4545 For general INSERT and UPDATE of existing ORM mapped objects, 

4546 prefer standard :term:`unit of work` data management patterns, 

4547 introduced in the :ref:`unified_tutorial` at 

4548 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0 

4549 now uses :ref:`engine_insertmanyvalues` with modern dialects 

4550 which solves previous issues of bulk INSERT slowness. 

4551 

4552 :param objects: a sequence of mapped object instances. The mapped 

4553 objects are persisted as is, and are **not** associated with the 

4554 :class:`.Session` afterwards. 

4555 

4556 For each object, whether the object is sent as an INSERT or an 

4557 UPDATE is dependent on the same rules used by the :class:`.Session` 

4558 in traditional operation; if the object has the 

4559 :attr:`.InstanceState.key` 

4560 attribute set, then the object is assumed to be "detached" and 

4561 will result in an UPDATE. Otherwise, an INSERT is used. 

4562 

4563 In the case of an UPDATE, statements are grouped based on which 

4564 attributes have changed, and are thus to be the subject of each 

4565 SET clause. If ``update_changed_only`` is False, then all 

4566 attributes present within each object are applied to the UPDATE 

4567 statement, which may help in allowing the statements to be grouped 

4568 together into a larger executemany(), and will also reduce the 

4569 overhead of checking history on attributes. 

4570 

4571 :param return_defaults: when True, rows that are missing values which 

4572 generate defaults, namely integer primary key defaults and sequences, 

4573 will be inserted **one at a time**, so that the primary key value 

4574 is available. In particular this will allow joined-inheritance 

4575 and other multi-table mappings to insert correctly without the need 

4576 to provide primary key values ahead of time; however, 

4577 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly 

4578 reduces the performance gains** of the method overall. It is strongly 

4579 advised to please use the standard :meth:`_orm.Session.add_all` 

4580 approach. 

4581 

4582 :param update_changed_only: when True, UPDATE statements are rendered 

4583 based on those attributes in each state that have logged changes. 

4584 When False, all attributes present are rendered into the SET clause 

4585 with the exception of primary key attributes. 

4586 

4587 :param preserve_order: when True, the order of inserts and updates 

4588 matches exactly the order in which the objects are given. When 

4589 False, common types of objects are grouped into inserts 

4590 and updates, to allow for more batching opportunities. 

4591 

4592 .. seealso:: 

4593 

4594 :doc:`queryguide/dml` 

4595 

4596 :meth:`.Session.bulk_insert_mappings` 

4597 

4598 :meth:`.Session.bulk_update_mappings` 

4599 

4600 """ 

4601 

4602 obj_states: Iterable[InstanceState[Any]] 

4603 

4604 obj_states = (attributes.instance_state(obj) for obj in objects) 

4605 

4606 if not preserve_order: 

4607 # the purpose of this sort is just so that common mappers 

4608 # and persistence states are grouped together, so that groupby 

4609 # will return a single group for a particular type of mapper. 

4610 # it's not trying to be deterministic beyond that. 

4611 obj_states = sorted( 

4612 obj_states, 

4613 key=lambda state: (id(state.mapper), state.key is not None), 

4614 ) 

4615 

4616 def grouping_key( 

4617 state: InstanceState[_O], 

4618 ) -> Tuple[Mapper[_O], bool]: 

4619 return (state.mapper, state.key is not None) 

4620 

4621 for (mapper, isupdate), states in itertools.groupby( 

4622 obj_states, grouping_key 

4623 ): 

4624 self._bulk_save_mappings( 

4625 mapper, 

4626 states, 

4627 isupdate=isupdate, 

4628 isstates=True, 

4629 return_defaults=return_defaults, 

4630 update_changed_only=update_changed_only, 

4631 render_nulls=False, 

4632 ) 

4633 

4634 def bulk_insert_mappings( 

4635 self, 

4636 mapper: Mapper[Any], 

4637 mappings: Iterable[Dict[str, Any]], 

4638 return_defaults: bool = False, 

4639 render_nulls: bool = False, 

4640 ) -> None: 

4641 """Perform a bulk insert of the given list of mapping dictionaries. 

4642 

4643 .. legacy:: 

4644 

4645 This method is a legacy feature as of the 2.0 series of 

4646 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4647 the sections :ref:`orm_queryguide_bulk_insert` and 

4648 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4649 implementation details with this method and adds new features 

4650 as well. 

4651 

4652 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4653 object, 

4654 representing the single kind of object represented within the mapping 

4655 list. 

4656 

4657 :param mappings: a sequence of dictionaries, each one containing the 

4658 state of the mapped row to be inserted, in terms of the attribute 

4659 names on the mapped class. If the mapping refers to multiple tables, 

4660 such as a joined-inheritance mapping, each dictionary must contain all 

4661 keys to be populated into all tables. 

4662 

4663 :param return_defaults: when True, the INSERT process will be altered 

4664 to ensure that newly generated primary key values will be fetched. 

4665 The rationale for this parameter is typically to enable 

4666 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to 

4667 be bulk inserted. 

4668 

4669 .. note:: for backends that don't support RETURNING, the 

4670 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults` 

4671 parameter can significantly decrease performance as INSERT 

4672 statements can no longer be batched. See 

4673 :ref:`engine_insertmanyvalues` 

4674 for background on which backends are affected. 

4675 

4676 :param render_nulls: When True, a value of ``None`` will result 

4677 in a NULL value being included in the INSERT statement, rather 

4678 than the column being omitted from the INSERT. This allows all 

4679 the rows being INSERTed to have the identical set of columns which 

4680 allows the full set of rows to be batched to the DBAPI. Normally, 

4681 each column-set that contains a different combination of NULL values 

4682 than the previous row must omit a different series of columns from 

4683 the rendered INSERT statement, which means it must be emitted as a 

4684 separate statement. By passing this flag, the full set of rows 

4685 are guaranteed to be batchable into one batch; the cost however is 

4686 that server-side defaults which are invoked by an omitted column will 

4687 be skipped, so care must be taken to ensure that these are not 

4688 necessary. 

4689 

4690 .. warning:: 

4691 

4692 When this flag is set, **server side default SQL values will 

4693 not be invoked** for those columns that are inserted as NULL; 

4694 the NULL value will be sent explicitly. Care must be taken 

4695 to ensure that no server-side default functions need to be 

4696 invoked for the operation as a whole. 

4697 

4698 .. seealso:: 

4699 

4700 :doc:`queryguide/dml` 

4701 

4702 :meth:`.Session.bulk_save_objects` 

4703 

4704 :meth:`.Session.bulk_update_mappings` 

4705 

4706 """ 

4707 self._bulk_save_mappings( 

4708 mapper, 

4709 mappings, 

4710 isupdate=False, 

4711 isstates=False, 

4712 return_defaults=return_defaults, 

4713 update_changed_only=False, 

4714 render_nulls=render_nulls, 

4715 ) 

4716 

4717 def bulk_update_mappings( 

4718 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] 

4719 ) -> None: 

4720 """Perform a bulk update of the given list of mapping dictionaries. 

4721 

4722 .. legacy:: 

4723 

4724 This method is a legacy feature as of the 2.0 series of 

4725 SQLAlchemy. For modern bulk INSERT and UPDATE, see 

4726 the sections :ref:`orm_queryguide_bulk_insert` and 

4727 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares 

4728 implementation details with this method and adds new features 

4729 as well. 

4730 

4731 :param mapper: a mapped class, or the actual :class:`_orm.Mapper` 

4732 object, 

4733 representing the single kind of object represented within the mapping 

4734 list. 

4735 

4736 :param mappings: a sequence of dictionaries, each one containing the 

4737 state of the mapped row to be updated, in terms of the attribute names 

4738 on the mapped class. If the mapping refers to multiple tables, such 

4739 as a joined-inheritance mapping, each dictionary may contain keys 

4740 corresponding to all tables. All those keys which are present and 

4741 are not part of the primary key are applied to the SET clause of the 

4742 UPDATE statement; the primary key values, which are required, are 

4743 applied to the WHERE clause. 

4744 

4745 

4746 .. seealso:: 

4747 

4748 :doc:`queryguide/dml` 

4749 

4750 :meth:`.Session.bulk_insert_mappings` 

4751 

4752 :meth:`.Session.bulk_save_objects` 

4753 

4754 """ 

4755 self._bulk_save_mappings( 

4756 mapper, 

4757 mappings, 

4758 isupdate=True, 

4759 isstates=False, 

4760 return_defaults=False, 

4761 update_changed_only=False, 

4762 render_nulls=False, 

4763 ) 

4764 

4765 def _bulk_save_mappings( 

4766 self, 

4767 mapper: Mapper[_O], 

4768 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], 

4769 *, 

4770 isupdate: bool, 

4771 isstates: bool, 

4772 return_defaults: bool, 

4773 update_changed_only: bool, 

4774 render_nulls: bool, 

4775 ) -> None: 

4776 mapper = _class_to_mapper(mapper) 

4777 self._flushing = True 

4778 

4779 transaction = self._autobegin_t()._begin() 

4780 try: 

4781 if isupdate: 

4782 bulk_persistence._bulk_update( 

4783 mapper, 

4784 mappings, 

4785 transaction, 

4786 isstates=isstates, 

4787 update_changed_only=update_changed_only, 

4788 ) 

4789 else: 

4790 bulk_persistence._bulk_insert( 

4791 mapper, 

4792 mappings, 

4793 transaction, 

4794 isstates=isstates, 

4795 return_defaults=return_defaults, 

4796 render_nulls=render_nulls, 

4797 ) 

4798 transaction.commit() 

4799 

4800 except: 

4801 with util.safe_reraise(): 

4802 transaction.rollback(_capture_exception=True) 

4803 finally: 

4804 self._flushing = False 

4805 

4806 def is_modified( 

4807 self, instance: object, include_collections: bool = True 

4808 ) -> bool: 

4809 r"""Return ``True`` if the given instance has locally 

4810 modified attributes. 

4811 

4812 This method retrieves the history for each instrumented 

4813 attribute on the instance and performs a comparison of the current 

4814 value to its previously flushed or committed value, if any. 

4815 

4816 It is in effect a more expensive and accurate 

4817 version of checking for the given instance in the 

4818 :attr:`.Session.dirty` collection; a full test for 

4819 each attribute's net "dirty" status is performed. 

4820 

4821 E.g.:: 

4822 

4823 return session.is_modified(someobject) 

4824 

4825 A few caveats to this method apply: 

4826 

4827 * Instances present in the :attr:`.Session.dirty` collection may 

4828 report ``False`` when tested with this method. This is because 

4829 the object may have received change events via attribute mutation, 

4830 thus placing it in :attr:`.Session.dirty`, but ultimately the state 

4831 is the same as that loaded from the database, resulting in no net 

4832 change here. 

4833 * Scalar attributes may not have recorded the previously set 

4834 value when a new value was applied, if the attribute was not loaded, 

4835 or was expired, at the time the new value was received - in these 

4836 cases, the attribute is assumed to have a change, even if there is 

4837 ultimately no net change against its database value. SQLAlchemy in 

4838 most cases does not need the "old" value when a set event occurs, so 

4839 it skips the expense of a SQL call if the old value isn't present, 

4840 based on the assumption that an UPDATE of the scalar value is 

4841 usually needed, and in those few cases where it isn't, is less 

4842 expensive on average than issuing a defensive SELECT. 

4843 

4844 The "old" value is fetched unconditionally upon set only if the 

4845 attribute container has the ``active_history`` flag set to ``True``. 

4846 This flag is set typically for primary key attributes and scalar 

4847 object references that are not a simple many-to-one. To set this 

4848 flag for any arbitrary mapped column, use the ``active_history`` 

4849 argument with :func:`.column_property`. 

4850 

4851 :param instance: mapped instance to be tested for pending changes. 

4852 :param include_collections: Indicates if multivalued collections 

4853 should be included in the operation. Setting this to ``False`` is a 

4854 way to detect only local-column based properties (i.e. scalar columns 

4855 or many-to-one foreign keys) that would result in an UPDATE for this 

4856 instance upon flush. 

4857 

4858 """ 

4859 state = object_state(instance) 

4860 

4861 if not state.modified: 

4862 return False 

4863 

4864 dict_ = state.dict 

4865 

4866 for attr in state.manager.attributes: 

4867 if ( 

4868 not include_collections 

4869 and hasattr(attr.impl, "get_collection") 

4870 ) or not hasattr(attr.impl, "get_history"): 

4871 continue 

4872 

4873 (added, unchanged, deleted) = attr.impl.get_history( 

4874 state, dict_, passive=PassiveFlag.NO_CHANGE 

4875 ) 

4876 

4877 if added or deleted: 

4878 return True 

4879 else: 

4880 return False 

4881 

4882 @property 

4883 def is_active(self) -> bool: 

4884 """True if this :class:`.Session` not in "partial rollback" state. 

4885 

4886 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins 

4887 a new transaction immediately, so this attribute will be False 

4888 when the :class:`_orm.Session` is first instantiated. 

4889 

4890 "partial rollback" state typically indicates that the flush process 

4891 of the :class:`_orm.Session` has failed, and that the 

4892 :meth:`_orm.Session.rollback` method must be emitted in order to 

4893 fully roll back the transaction. 

4894 

4895 If this :class:`_orm.Session` is not in a transaction at all, the 

4896 :class:`_orm.Session` will autobegin when it is first used, so in this 

4897 case :attr:`_orm.Session.is_active` will return True. 

4898 

4899 Otherwise, if this :class:`_orm.Session` is within a transaction, 

4900 and that transaction has not been rolled back internally, the 

4901 :attr:`_orm.Session.is_active` will also return True. 

4902 

4903 .. seealso:: 

4904 

4905 :ref:`faq_session_rollback` 

4906 

4907 :meth:`_orm.Session.in_transaction` 

4908 

4909 """ 

4910 return self._transaction is None or self._transaction.is_active 

4911 

4912 @property 

4913 def _dirty_states(self) -> Iterable[InstanceState[Any]]: 

4914 """The set of all persistent states considered dirty. 

4915 

4916 This method returns all states that were modified including 

4917 those that were possibly deleted. 

4918 

4919 """ 

4920 return self.identity_map._dirty_states() 

4921 

4922 @property 

4923 def dirty(self) -> IdentitySet: 

4924 """The set of all persistent instances considered dirty. 

4925 

4926 E.g.:: 

4927 

4928 some_mapped_object in session.dirty 

4929 

4930 Instances are considered dirty when they were modified but not 

4931 deleted. 

4932 

4933 Note that this 'dirty' calculation is 'optimistic'; most 

4934 attribute-setting or collection modification operations will 

4935 mark an instance as 'dirty' and place it in this set, even if 

4936 there is no net change to the attribute's value. At flush 

4937 time, the value of each attribute is compared to its 

4938 previously saved value, and if there's no net change, no SQL 

4939 operation will occur (this is a more expensive operation so 

4940 it's only done at flush time). 

4941 

4942 To check if an instance has actionable net changes to its 

4943 attributes, use the :meth:`.Session.is_modified` method. 

4944 

4945 """ 

4946 return IdentitySet( 

4947 [ 

4948 state.obj() 

4949 for state in self._dirty_states 

4950 if state not in self._deleted 

4951 ] 

4952 ) 

4953 

4954 @property 

4955 def deleted(self) -> IdentitySet: 

4956 "The set of all instances marked as 'deleted' within this ``Session``" 

4957 

4958 return util.IdentitySet(list(self._deleted.values())) 

4959 

4960 @property 

4961 def new(self) -> IdentitySet: 

4962 "The set of all instances marked as 'new' within this ``Session``." 

4963 

4964 return util.IdentitySet(list(self._new.values())) 

4965 

4966 

4967_S = TypeVar("_S", bound="Session") 

4968 

4969 

4970class sessionmaker(_SessionClassMethods, Generic[_S]): 

4971 """A configurable :class:`.Session` factory. 

4972 

4973 The :class:`.sessionmaker` factory generates new 

4974 :class:`.Session` objects when called, creating them given 

4975 the configurational arguments established here. 

4976 

4977 e.g.:: 

4978 

4979 from sqlalchemy import create_engine 

4980 from sqlalchemy.orm import sessionmaker 

4981 

4982 # an Engine, which the Session will use for connection 

4983 # resources 

4984 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/") 

4985 

4986 Session = sessionmaker(engine) 

4987 

4988 with Session() as session: 

4989 session.add(some_object) 

4990 session.add(some_other_object) 

4991 session.commit() 

4992 

4993 Context manager use is optional; otherwise, the returned 

4994 :class:`_orm.Session` object may be closed explicitly via the 

4995 :meth:`_orm.Session.close` method. Using a 

4996 ``try:/finally:`` block is optional, however will ensure that the close 

4997 takes place even if there are database errors:: 

4998 

4999 session = Session() 

5000 try: 

5001 session.add(some_object) 

5002 session.add(some_other_object) 

5003 session.commit() 

5004 finally: 

5005 session.close() 

5006 

5007 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session` 

5008 objects in the same way as an :class:`_engine.Engine` acts as a factory 

5009 for :class:`_engine.Connection` objects. In this way it also includes 

5010 a :meth:`_orm.sessionmaker.begin` method, that provides a context 

5011 manager which both begins and commits a transaction, as well as closes 

5012 out the :class:`_orm.Session` when complete, rolling back the transaction 

5013 if any errors occur:: 

5014 

5015 Session = sessionmaker(engine) 

5016 

5017 with Session.begin() as session: 

5018 session.add(some_object) 

5019 session.add(some_other_object) 

5020 # commits transaction, closes session 

5021 

5022 .. versionadded:: 1.4 

5023 

5024 When calling upon :class:`_orm.sessionmaker` to construct a 

5025 :class:`_orm.Session`, keyword arguments may also be passed to the 

5026 method; these arguments will override that of the globally configured 

5027 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain 

5028 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead 

5029 bound to a specific :class:`_engine.Connection` procured from that engine:: 

5030 

5031 Session = sessionmaker(engine) 

5032 

5033 # bind an individual session to a connection 

5034 

5035 with engine.connect() as connection: 

5036 with Session(bind=connection) as session: 

5037 ... # work with session 

5038 

5039 The class also includes a method :meth:`_orm.sessionmaker.configure`, which 

5040 can be used to specify additional keyword arguments to the factory, which 

5041 will take effect for subsequent :class:`.Session` objects generated. This 

5042 is usually used to associate one or more :class:`_engine.Engine` objects 

5043 with an existing 

5044 :class:`.sessionmaker` factory before it is first used:: 

5045 

5046 # application starts, sessionmaker does not have 

5047 # an engine bound yet 

5048 Session = sessionmaker() 

5049 

5050 # ... later, when an engine URL is read from a configuration 

5051 # file or other events allow the engine to be created 

5052 engine = create_engine("sqlite:///foo.db") 

5053 Session.configure(bind=engine) 

5054 

5055 sess = Session() 

5056 # work with session 

5057 

5058 .. seealso:: 

5059 

5060 :ref:`session_getting` - introductory text on creating 

5061 sessions using :class:`.sessionmaker`. 

5062 

5063 """ 

5064 

5065 class_: Type[_S] 

5066 

5067 @overload 

5068 def __init__( 

5069 self, 

5070 bind: Optional[_SessionBind] = ..., 

5071 *, 

5072 class_: Type[_S], 

5073 autoflush: bool = ..., 

5074 expire_on_commit: bool = ..., 

5075 info: Optional[_InfoType] = ..., 

5076 **kw: Any, 

5077 ): ... 

5078 

5079 @overload 

5080 def __init__( 

5081 self: "sessionmaker[Session]", 

5082 bind: Optional[_SessionBind] = ..., 

5083 *, 

5084 autoflush: bool = ..., 

5085 expire_on_commit: bool = ..., 

5086 info: Optional[_InfoType] = ..., 

5087 **kw: Any, 

5088 ): ... 

5089 

5090 def __init__( 

5091 self, 

5092 bind: Optional[_SessionBind] = None, 

5093 *, 

5094 class_: Type[_S] = Session, # type: ignore 

5095 autoflush: bool = True, 

5096 expire_on_commit: bool = True, 

5097 info: Optional[_InfoType] = None, 

5098 **kw: Any, 

5099 ): 

5100 r"""Construct a new :class:`.sessionmaker`. 

5101 

5102 All arguments here except for ``class_`` correspond to arguments 

5103 accepted by :class:`.Session` directly. See the 

5104 :meth:`.Session.__init__` docstring for more details on parameters. 

5105 

5106 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable` 

5107 with 

5108 which newly created :class:`.Session` objects will be associated. 

5109 :param class\_: class to use in order to create new :class:`.Session` 

5110 objects. Defaults to :class:`.Session`. 

5111 :param autoflush: The autoflush setting to use with newly created 

5112 :class:`.Session` objects. 

5113 

5114 .. seealso:: 

5115 

5116 :ref:`session_flushing` - additional background on autoflush 

5117 

5118 :param expire_on_commit=True: the 

5119 :paramref:`_orm.Session.expire_on_commit` setting to use 

5120 with newly created :class:`.Session` objects. 

5121 

5122 :param info: optional dictionary of information that will be available 

5123 via :attr:`.Session.info`. Note this dictionary is *updated*, not 

5124 replaced, when the ``info`` parameter is specified to the specific 

5125 :class:`.Session` construction operation. 

5126 

5127 :param \**kw: all other keyword arguments are passed to the 

5128 constructor of newly created :class:`.Session` objects. 

5129 

5130 """ 

5131 kw["bind"] = bind 

5132 kw["autoflush"] = autoflush 

5133 kw["expire_on_commit"] = expire_on_commit 

5134 if info is not None: 

5135 kw["info"] = info 

5136 self.kw = kw 

5137 # make our own subclass of the given class, so that 

5138 # events can be associated with it specifically. 

5139 self.class_ = type(class_.__name__, (class_,), {}) 

5140 

5141 def begin(self) -> contextlib.AbstractContextManager[_S]: 

5142 """Produce a context manager that both provides a new 

5143 :class:`_orm.Session` as well as a transaction that commits. 

5144 

5145 

5146 e.g.:: 

5147 

5148 Session = sessionmaker(some_engine) 

5149 

5150 with Session.begin() as session: 

5151 session.add(some_object) 

5152 

5153 # commits transaction, closes session 

5154 

5155 .. versionadded:: 1.4 

5156 

5157 

5158 """ 

5159 

5160 session = self() 

5161 return session._maker_context_manager() 

5162 

5163 def __call__(self, **local_kw: Any) -> _S: 

5164 """Produce a new :class:`.Session` object using the configuration 

5165 established in this :class:`.sessionmaker`. 

5166 

5167 In Python, the ``__call__`` method is invoked on an object when 

5168 it is "called" in the same way as a function:: 

5169 

5170 Session = sessionmaker(some_engine) 

5171 session = Session() # invokes sessionmaker.__call__() 

5172 

5173 """ 

5174 for k, v in self.kw.items(): 

5175 if k == "info" and "info" in local_kw: 

5176 d = v.copy() 

5177 d.update(local_kw["info"]) 

5178 local_kw["info"] = d 

5179 else: 

5180 local_kw.setdefault(k, v) 

5181 return self.class_(**local_kw) 

5182 

5183 def configure(self, **new_kw: Any) -> None: 

5184 """(Re)configure the arguments for this sessionmaker. 

5185 

5186 e.g.:: 

5187 

5188 Session = sessionmaker() 

5189 

5190 Session.configure(bind=create_engine("sqlite://")) 

5191 """ 

5192 self.kw.update(new_kw) 

5193 

5194 def __repr__(self) -> str: 

5195 return "%s(class_=%r, %s)" % ( 

5196 self.__class__.__name__, 

5197 self.class_.__name__, 

5198 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()), 

5199 ) 

5200 

5201 

5202def close_all_sessions() -> None: 

5203 """Close all sessions in memory. 

5204 

5205 This function consults a global registry of all :class:`.Session` objects 

5206 and calls :meth:`.Session.close` on them, which resets them to a clean 

5207 state. 

5208 

5209 This function is not for general use but may be useful for test suites 

5210 within the teardown scheme. 

5211 

5212 """ 

5213 

5214 for sess in _sessions.values(): 

5215 sess.close() 

5216 

5217 

5218def make_transient(instance: object) -> None: 

5219 """Alter the state of the given instance so that it is :term:`transient`. 

5220 

5221 .. note:: 

5222 

5223 :func:`.make_transient` is a special-case function for 

5224 advanced use cases only. 

5225 

5226 The given mapped instance is assumed to be in the :term:`persistent` or 

5227 :term:`detached` state. The function will remove its association with any 

5228 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The 

5229 effect is that the object will behave as though it were newly constructed, 

5230 except retaining any attribute / collection values that were loaded at the 

5231 time of the call. The :attr:`.InstanceState.deleted` flag is also reset 

5232 if this object had been deleted as a result of using 

5233 :meth:`.Session.delete`. 

5234 

5235 .. warning:: 

5236 

5237 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly 

5238 load ORM-mapped attributes that are not currently loaded at the time 

5239 the function is called. This includes attributes which: 

5240 

5241 * were expired via :meth:`.Session.expire` 

5242 

5243 * were expired as the natural effect of committing a session 

5244 transaction, e.g. :meth:`.Session.commit` 

5245 

5246 * are normally :term:`lazy loaded` but are not currently loaded 

5247 

5248 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are 

5249 not yet loaded 

5250 

5251 * were not present in the query which loaded this object, such as that 

5252 which is common in joined table inheritance and other scenarios. 

5253 

5254 After :func:`.make_transient` is called, unloaded attributes such 

5255 as those above will normally resolve to the value ``None`` when 

5256 accessed, or an empty collection for a collection-oriented attribute. 

5257 As the object is transient and un-associated with any database 

5258 identity, it will no longer retrieve these values. 

5259 

5260 .. seealso:: 

5261 

5262 :func:`.make_transient_to_detached` 

5263 

5264 """ 

5265 state = attributes.instance_state(instance) 

5266 s = _state_session(state) 

5267 if s: 

5268 s._expunge_states([state]) 

5269 

5270 # remove expired state 

5271 state.expired_attributes.clear() 

5272 

5273 # remove deferred callables 

5274 if state.callables: 

5275 del state.callables 

5276 

5277 if state.key: 

5278 del state.key 

5279 if state._deleted: 

5280 del state._deleted 

5281 

5282 

5283def make_transient_to_detached(instance: object) -> None: 

5284 """Make the given transient instance :term:`detached`. 

5285 

5286 .. note:: 

5287 

5288 :func:`.make_transient_to_detached` is a special-case function for 

5289 advanced use cases only. 

5290 

5291 All attribute history on the given instance 

5292 will be reset as though the instance were freshly loaded 

5293 from a query. Missing attributes will be marked as expired. 

5294 The primary key attributes of the object, which are required, will be made 

5295 into the "key" of the instance. 

5296 

5297 The object can then be added to a session, or merged 

5298 possibly with the load=False flag, at which point it will look 

5299 as if it were loaded that way, without emitting SQL. 

5300 

5301 This is a special use case function that differs from a normal 

5302 call to :meth:`.Session.merge` in that a given persistent state 

5303 can be manufactured without any SQL calls. 

5304 

5305 .. seealso:: 

5306 

5307 :func:`.make_transient` 

5308 

5309 :meth:`.Session.enable_relationship_loading` 

5310 

5311 """ 

5312 state = attributes.instance_state(instance) 

5313 if state.session_id or state.key: 

5314 raise sa_exc.InvalidRequestError("Given object must be transient") 

5315 state.key = state.mapper._identity_key_from_state(state) 

5316 if state._deleted: 

5317 del state._deleted 

5318 state._commit_all(state.dict) 

5319 state._expire_attributes(state.dict, state.unloaded) 

5320 

5321 

5322def object_session(instance: object) -> Optional[Session]: 

5323 """Return the :class:`.Session` to which the given instance belongs. 

5324 

5325 This is essentially the same as the :attr:`.InstanceState.session` 

5326 accessor. See that attribute for details. 

5327 

5328 """ 

5329 

5330 try: 

5331 state = attributes.instance_state(instance) 

5332 except exc.NO_STATE as err: 

5333 raise exc.UnmappedInstanceError(instance) from err 

5334 else: 

5335 return _state_session(state) 

5336 

5337 

5338_new_sessionid = util.counter()