From 12cf076118570eebbff08c6b3090e0d4798447a1 Mon Sep 17 00:00:00 2001 From: cyfraeviolae Date: Wed, 3 Apr 2024 03:17:55 -0400 Subject: no venv --- .../site-packages/sqlalchemy/pool/__init__.py | 44 - .../pool/__pycache__/__init__.cpython-311.pyc | Bin 1878 -> 0 bytes .../pool/__pycache__/base.cpython-311.pyc | Bin 59317 -> 0 bytes .../pool/__pycache__/events.cpython-311.pyc | Bin 14483 -> 0 bytes .../pool/__pycache__/impl.cpython-311.pyc | Bin 27550 -> 0 bytes .../site-packages/sqlalchemy/pool/base.py | 1515 -------------------- .../site-packages/sqlalchemy/pool/events.py | 370 ----- .../site-packages/sqlalchemy/pool/impl.py | 581 -------- 8 files changed, 2510 deletions(-) delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/pool/__init__.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/base.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/events.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/pool/base.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/pool/events.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/pool/impl.py (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/pool') diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/pool/__init__.py deleted file mode 100644 index 29fd652..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# pool/__init__.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php - - -"""Connection pooling for DB-API connections. - -Provides a number of connection pool implementations for a variety of -usage scenarios and thread behavior requirements imposed by the -application, DB-API or database itself. - -Also provides a DB-API 2.0 connection proxying mechanism allowing -regular DB-API connect() methods to be transparently managed by a -SQLAlchemy connection pool. -""" - -from . import events -from .base import _AdhocProxiedConnection as _AdhocProxiedConnection -from .base import _ConnectionFairy as _ConnectionFairy -from .base import _ConnectionRecord -from .base import _CreatorFnType as _CreatorFnType -from .base import _CreatorWRecFnType as _CreatorWRecFnType -from .base import _finalize_fairy -from .base import _ResetStyleArgType as _ResetStyleArgType -from .base import ConnectionPoolEntry as ConnectionPoolEntry -from .base import ManagesConnection as ManagesConnection -from .base import Pool as Pool -from .base import PoolProxiedConnection as PoolProxiedConnection -from .base import PoolResetState as PoolResetState -from .base import reset_commit as reset_commit -from .base import reset_none as reset_none -from .base import reset_rollback as reset_rollback -from .impl import AssertionPool as AssertionPool -from .impl import AsyncAdaptedQueuePool as AsyncAdaptedQueuePool -from .impl import ( - FallbackAsyncAdaptedQueuePool as FallbackAsyncAdaptedQueuePool, -) -from .impl import NullPool as NullPool -from .impl import QueuePool as QueuePool -from .impl import SingletonThreadPool as SingletonThreadPool -from .impl import StaticPool as StaticPool diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 6bb2901..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/base.cpython-311.pyc deleted file mode 100644 index 14fc526..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/events.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/events.cpython-311.pyc deleted file mode 100644 index 0fb8362..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/events.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-311.pyc deleted file mode 100644 index 7939b43..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/pool/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/pool/base.py deleted file mode 100644 index 98d2027..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/pool/base.py +++ /dev/null @@ -1,1515 +0,0 @@ -# pool/base.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php - - -"""Base constructs for connection pools. - -""" - -from __future__ import annotations - -from collections import deque -import dataclasses -from enum import Enum -import threading -import time -import typing -from typing import Any -from typing import Callable -from typing import cast -from typing import Deque -from typing import Dict -from typing import List -from typing import Optional -from typing import Tuple -from typing import TYPE_CHECKING -from typing import Union -import weakref - -from .. import event -from .. import exc -from .. import log -from .. import util -from ..util.typing import Literal -from ..util.typing import Protocol - -if TYPE_CHECKING: - from ..engine.interfaces import DBAPIConnection - from ..engine.interfaces import DBAPICursor - from ..engine.interfaces import Dialect - from ..event import _DispatchCommon - from ..event import _ListenerFnType - from ..event import dispatcher - from ..sql._typing import _InfoType - - -@dataclasses.dataclass(frozen=True) -class PoolResetState: - """describes the state of a DBAPI connection as it is being passed to - the :meth:`.PoolEvents.reset` connection pool event. - - .. versionadded:: 2.0.0b3 - - """ - - __slots__ = ("transaction_was_reset", "terminate_only", "asyncio_safe") - - transaction_was_reset: bool - """Indicates if the transaction on the DBAPI connection was already - essentially "reset" back by the :class:`.Connection` object. - - This boolean is True if the :class:`.Connection` had transactional - state present upon it, which was then not closed using the - :meth:`.Connection.rollback` or :meth:`.Connection.commit` method; - instead, the transaction was closed inline within the - :meth:`.Connection.close` method so is guaranteed to remain non-present - when this event is reached. - - """ - - terminate_only: bool - """indicates if the connection is to be immediately terminated and - not checked in to the pool. - - This occurs for connections that were invalidated, as well as asyncio - connections that were not cleanly handled by the calling code that - are instead being garbage collected. In the latter case, - operations can't be safely run on asyncio connections within garbage - collection as there is not necessarily an event loop present. - - """ - - asyncio_safe: bool - """Indicates if the reset operation is occurring within a scope where - an enclosing event loop is expected to be present for asyncio applications. - - Will be False in the case that the connection is being garbage collected. - - """ - - -class ResetStyle(Enum): - """Describe options for "reset on return" behaviors.""" - - reset_rollback = 0 - reset_commit = 1 - reset_none = 2 - - -_ResetStyleArgType = Union[ - ResetStyle, - Literal[True, None, False, "commit", "rollback"], -] -reset_rollback, reset_commit, reset_none = list(ResetStyle) - - -class _ConnDialect: - """partial implementation of :class:`.Dialect` - which provides DBAPI connection methods. - - When a :class:`_pool.Pool` is combined with an :class:`_engine.Engine`, - the :class:`_engine.Engine` replaces this with its own - :class:`.Dialect`. - - """ - - is_async = False - has_terminate = False - - def do_rollback(self, dbapi_connection: PoolProxiedConnection) -> None: - dbapi_connection.rollback() - - def do_commit(self, dbapi_connection: PoolProxiedConnection) -> None: - dbapi_connection.commit() - - def do_terminate(self, dbapi_connection: DBAPIConnection) -> None: - dbapi_connection.close() - - def do_close(self, dbapi_connection: DBAPIConnection) -> None: - dbapi_connection.close() - - def _do_ping_w_event(self, dbapi_connection: DBAPIConnection) -> bool: - raise NotImplementedError( - "The ping feature requires that a dialect is " - "passed to the connection pool." - ) - - def get_driver_connection(self, connection: DBAPIConnection) -> Any: - return connection - - -class _AsyncConnDialect(_ConnDialect): - is_async = True - - -class _CreatorFnType(Protocol): - def __call__(self) -> DBAPIConnection: ... - - -class _CreatorWRecFnType(Protocol): - def __call__(self, rec: ConnectionPoolEntry) -> DBAPIConnection: ... - - -class Pool(log.Identified, event.EventTarget): - """Abstract base class for connection pools.""" - - dispatch: dispatcher[Pool] - echo: log._EchoFlagType - - _orig_logging_name: Optional[str] - _dialect: Union[_ConnDialect, Dialect] = _ConnDialect() - _creator_arg: Union[_CreatorFnType, _CreatorWRecFnType] - _invoke_creator: _CreatorWRecFnType - _invalidate_time: float - - def __init__( - self, - creator: Union[_CreatorFnType, _CreatorWRecFnType], - recycle: int = -1, - echo: log._EchoFlagType = None, - logging_name: Optional[str] = None, - reset_on_return: _ResetStyleArgType = True, - events: Optional[List[Tuple[_ListenerFnType, str]]] = None, - dialect: Optional[Union[_ConnDialect, Dialect]] = None, - pre_ping: bool = False, - _dispatch: Optional[_DispatchCommon[Pool]] = None, - ): - """ - Construct a Pool. - - :param creator: a callable function that returns a DB-API - connection object. The function will be called with - parameters. - - :param recycle: If set to a value other than -1, number of - seconds between connection recycling, which means upon - checkout, if this timeout is surpassed the connection will be - closed and replaced with a newly opened connection. Defaults to -1. - - :param logging_name: String identifier which will be used within - the "name" field of logging records generated within the - "sqlalchemy.pool" logger. Defaults to a hexstring of the object's - id. - - :param echo: if True, the connection pool will log - informational output such as when connections are invalidated - as well as when connections are recycled to the default log handler, - which defaults to ``sys.stdout`` for output.. If set to the string - ``"debug"``, the logging will include pool checkouts and checkins. - - The :paramref:`_pool.Pool.echo` parameter can also be set from the - :func:`_sa.create_engine` call by using the - :paramref:`_sa.create_engine.echo_pool` parameter. - - .. seealso:: - - :ref:`dbengine_logging` - further detail on how to configure - logging. - - :param reset_on_return: Determine steps to take on - connections as they are returned to the pool, which were - not otherwise handled by a :class:`_engine.Connection`. - Available from :func:`_sa.create_engine` via the - :paramref:`_sa.create_engine.pool_reset_on_return` parameter. - - :paramref:`_pool.Pool.reset_on_return` can have any of these values: - - * ``"rollback"`` - call rollback() on the connection, - to release locks and transaction resources. - This is the default value. The vast majority - of use cases should leave this value set. - * ``"commit"`` - call commit() on the connection, - to release locks and transaction resources. - A commit here may be desirable for databases that - cache query plans if a commit is emitted, - such as Microsoft SQL Server. However, this - value is more dangerous than 'rollback' because - any data changes present on the transaction - are committed unconditionally. - * ``None`` - don't do anything on the connection. - This setting may be appropriate if the database / DBAPI - works in pure "autocommit" mode at all times, or if - a custom reset handler is established using the - :meth:`.PoolEvents.reset` event handler. - - * ``True`` - same as 'rollback', this is here for - backwards compatibility. - * ``False`` - same as None, this is here for - backwards compatibility. - - For further customization of reset on return, the - :meth:`.PoolEvents.reset` event hook may be used which can perform - any connection activity desired on reset. - - .. seealso:: - - :ref:`pool_reset_on_return` - - :meth:`.PoolEvents.reset` - - :param events: a list of 2-tuples, each of the form - ``(callable, target)`` which will be passed to :func:`.event.listen` - upon construction. Provided here so that event listeners - can be assigned via :func:`_sa.create_engine` before dialect-level - listeners are applied. - - :param dialect: a :class:`.Dialect` that will handle the job - of calling rollback(), close(), or commit() on DBAPI connections. - If omitted, a built-in "stub" dialect is used. Applications that - make use of :func:`_sa.create_engine` should not use this parameter - as it is handled by the engine creation strategy. - - :param pre_ping: if True, the pool will emit a "ping" (typically - "SELECT 1", but is dialect-specific) on the connection - upon checkout, to test if the connection is alive or not. If not, - the connection is transparently re-connected and upon success, all - other pooled connections established prior to that timestamp are - invalidated. Requires that a dialect is passed as well to - interpret the disconnection error. - - .. versionadded:: 1.2 - - """ - if logging_name: - self.logging_name = self._orig_logging_name = logging_name - else: - self._orig_logging_name = None - - log.instance_logger(self, echoflag=echo) - self._creator = creator - self._recycle = recycle - self._invalidate_time = 0 - self._pre_ping = pre_ping - self._reset_on_return = util.parse_user_argument_for_enum( - reset_on_return, - { - ResetStyle.reset_rollback: ["rollback", True], - ResetStyle.reset_none: ["none", None, False], - ResetStyle.reset_commit: ["commit"], - }, - "reset_on_return", - ) - - self.echo = echo - - if _dispatch: - self.dispatch._update(_dispatch, only_propagate=False) - if dialect: - self._dialect = dialect - if events: - for fn, target in events: - event.listen(self, target, fn) - - @util.hybridproperty - def _is_asyncio(self) -> bool: - return self._dialect.is_async - - @property - def _creator(self) -> Union[_CreatorFnType, _CreatorWRecFnType]: - return self._creator_arg - - @_creator.setter - def _creator( - self, creator: Union[_CreatorFnType, _CreatorWRecFnType] - ) -> None: - self._creator_arg = creator - - # mypy seems to get super confused assigning functions to - # attributes - self._invoke_creator = self._should_wrap_creator(creator) - - @_creator.deleter - def _creator(self) -> None: - # needed for mock testing - del self._creator_arg - del self._invoke_creator - - def _should_wrap_creator( - self, creator: Union[_CreatorFnType, _CreatorWRecFnType] - ) -> _CreatorWRecFnType: - """Detect if creator accepts a single argument, or is sent - as a legacy style no-arg function. - - """ - - try: - argspec = util.get_callable_argspec(self._creator, no_self=True) - except TypeError: - creator_fn = cast(_CreatorFnType, creator) - return lambda rec: creator_fn() - - if argspec.defaults is not None: - defaulted = len(argspec.defaults) - else: - defaulted = 0 - positionals = len(argspec[0]) - defaulted - - # look for the exact arg signature that DefaultStrategy - # sends us - if (argspec[0], argspec[3]) == (["connection_record"], (None,)): - return cast(_CreatorWRecFnType, creator) - # or just a single positional - elif positionals == 1: - return cast(_CreatorWRecFnType, creator) - # all other cases, just wrap and assume legacy "creator" callable - # thing - else: - creator_fn = cast(_CreatorFnType, creator) - return lambda rec: creator_fn() - - def _close_connection( - self, connection: DBAPIConnection, *, terminate: bool = False - ) -> None: - self.logger.debug( - "%s connection %r", - "Hard-closing" if terminate else "Closing", - connection, - ) - try: - if terminate: - self._dialect.do_terminate(connection) - else: - self._dialect.do_close(connection) - except BaseException as e: - self.logger.error( - f"Exception {'terminating' if terminate else 'closing'} " - f"connection %r", - connection, - exc_info=True, - ) - if not isinstance(e, Exception): - raise - - def _create_connection(self) -> ConnectionPoolEntry: - """Called by subclasses to create a new ConnectionRecord.""" - - return _ConnectionRecord(self) - - def _invalidate( - self, - connection: PoolProxiedConnection, - exception: Optional[BaseException] = None, - _checkin: bool = True, - ) -> None: - """Mark all connections established within the generation - of the given connection as invalidated. - - If this pool's last invalidate time is before when the given - connection was created, update the timestamp til now. Otherwise, - no action is performed. - - Connections with a start time prior to this pool's invalidation - time will be recycled upon next checkout. - """ - rec = getattr(connection, "_connection_record", None) - if not rec or self._invalidate_time < rec.starttime: - self._invalidate_time = time.time() - if _checkin and getattr(connection, "is_valid", False): - connection.invalidate(exception) - - def recreate(self) -> Pool: - """Return a new :class:`_pool.Pool`, of the same class as this one - and configured with identical creation arguments. - - This method is used in conjunction with :meth:`dispose` - to close out an entire :class:`_pool.Pool` and create a new one in - its place. - - """ - - raise NotImplementedError() - - def dispose(self) -> None: - """Dispose of this pool. - - This method leaves the possibility of checked-out connections - remaining open, as it only affects connections that are - idle in the pool. - - .. seealso:: - - :meth:`Pool.recreate` - - """ - - raise NotImplementedError() - - def connect(self) -> PoolProxiedConnection: - """Return a DBAPI connection from the pool. - - The connection is instrumented such that when its - ``close()`` method is called, the connection will be returned to - the pool. - - """ - return _ConnectionFairy._checkout(self) - - def _return_conn(self, record: ConnectionPoolEntry) -> None: - """Given a _ConnectionRecord, return it to the :class:`_pool.Pool`. - - This method is called when an instrumented DBAPI connection - has its ``close()`` method called. - - """ - self._do_return_conn(record) - - def _do_get(self) -> ConnectionPoolEntry: - """Implementation for :meth:`get`, supplied by subclasses.""" - - raise NotImplementedError() - - def _do_return_conn(self, record: ConnectionPoolEntry) -> None: - """Implementation for :meth:`return_conn`, supplied by subclasses.""" - - raise NotImplementedError() - - def status(self) -> str: - raise NotImplementedError() - - -class ManagesConnection: - """Common base for the two connection-management interfaces - :class:`.PoolProxiedConnection` and :class:`.ConnectionPoolEntry`. - - These two objects are typically exposed in the public facing API - via the connection pool event hooks, documented at :class:`.PoolEvents`. - - .. versionadded:: 2.0 - - """ - - __slots__ = () - - dbapi_connection: Optional[DBAPIConnection] - """A reference to the actual DBAPI connection being tracked. - - This is a :pep:`249`-compliant object that for traditional sync-style - dialects is provided by the third-party - DBAPI implementation in use. For asyncio dialects, the implementation - is typically an adapter object provided by the SQLAlchemy dialect - itself; the underlying asyncio object is available via the - :attr:`.ManagesConnection.driver_connection` attribute. - - SQLAlchemy's interface for the DBAPI connection is based on the - :class:`.DBAPIConnection` protocol object - - .. seealso:: - - :attr:`.ManagesConnection.driver_connection` - - :ref:`faq_dbapi_connection` - - """ - - driver_connection: Optional[Any] - """The "driver level" connection object as used by the Python - DBAPI or database driver. - - For traditional :pep:`249` DBAPI implementations, this object will - be the same object as that of - :attr:`.ManagesConnection.dbapi_connection`. For an asyncio database - driver, this will be the ultimate "connection" object used by that - driver, such as the ``asyncpg.Connection`` object which will not have - standard pep-249 methods. - - .. versionadded:: 1.4.24 - - .. seealso:: - - :attr:`.ManagesConnection.dbapi_connection` - - :ref:`faq_dbapi_connection` - - """ - - @util.ro_memoized_property - def info(self) -> _InfoType: - """Info dictionary associated with the underlying DBAPI connection - referred to by this :class:`.ManagesConnection` instance, allowing - user-defined data to be associated with the connection. - - The data in this dictionary is persistent for the lifespan - of the DBAPI connection itself, including across pool checkins - and checkouts. When the connection is invalidated - and replaced with a new one, this dictionary is cleared. - - For a :class:`.PoolProxiedConnection` instance that's not associated - with a :class:`.ConnectionPoolEntry`, such as if it were detached, the - attribute returns a dictionary that is local to that - :class:`.ConnectionPoolEntry`. Therefore the - :attr:`.ManagesConnection.info` attribute will always provide a Python - dictionary. - - .. seealso:: - - :attr:`.ManagesConnection.record_info` - - - """ - raise NotImplementedError() - - @util.ro_memoized_property - def record_info(self) -> Optional[_InfoType]: - """Persistent info dictionary associated with this - :class:`.ManagesConnection`. - - Unlike the :attr:`.ManagesConnection.info` dictionary, the lifespan - of this dictionary is that of the :class:`.ConnectionPoolEntry` - which owns it; therefore this dictionary will persist across - reconnects and connection invalidation for a particular entry - in the connection pool. - - For a :class:`.PoolProxiedConnection` instance that's not associated - with a :class:`.ConnectionPoolEntry`, such as if it were detached, the - attribute returns None. Contrast to the :attr:`.ManagesConnection.info` - dictionary which is never None. - - - .. seealso:: - - :attr:`.ManagesConnection.info` - - """ - raise NotImplementedError() - - def invalidate( - self, e: Optional[BaseException] = None, soft: bool = False - ) -> None: - """Mark the managed connection as invalidated. - - :param e: an exception object indicating a reason for the invalidation. - - :param soft: if True, the connection isn't closed; instead, this - connection will be recycled on next checkout. - - .. seealso:: - - :ref:`pool_connection_invalidation` - - - """ - raise NotImplementedError() - - -class ConnectionPoolEntry(ManagesConnection): - """Interface for the object that maintains an individual database - connection on behalf of a :class:`_pool.Pool` instance. - - The :class:`.ConnectionPoolEntry` object represents the long term - maintainance of a particular connection for a pool, including expiring or - invalidating that connection to have it replaced with a new one, which will - continue to be maintained by that same :class:`.ConnectionPoolEntry` - instance. Compared to :class:`.PoolProxiedConnection`, which is the - short-term, per-checkout connection manager, this object lasts for the - lifespan of a particular "slot" within a connection pool. - - The :class:`.ConnectionPoolEntry` object is mostly visible to public-facing - API code when it is delivered to connection pool event hooks, such as - :meth:`_events.PoolEvents.connect` and :meth:`_events.PoolEvents.checkout`. - - .. versionadded:: 2.0 :class:`.ConnectionPoolEntry` provides the public - facing interface for the :class:`._ConnectionRecord` internal class. - - """ - - __slots__ = () - - @property - def in_use(self) -> bool: - """Return True the connection is currently checked out""" - - raise NotImplementedError() - - def close(self) -> None: - """Close the DBAPI connection managed by this connection pool entry.""" - raise NotImplementedError() - - -class _ConnectionRecord(ConnectionPoolEntry): - """Maintains a position in a connection pool which references a pooled - connection. - - This is an internal object used by the :class:`_pool.Pool` implementation - to provide context management to a DBAPI connection maintained by - that :class:`_pool.Pool`. The public facing interface for this class - is described by the :class:`.ConnectionPoolEntry` class. See that - class for public API details. - - .. seealso:: - - :class:`.ConnectionPoolEntry` - - :class:`.PoolProxiedConnection` - - """ - - __slots__ = ( - "__pool", - "fairy_ref", - "finalize_callback", - "fresh", - "starttime", - "dbapi_connection", - "__weakref__", - "__dict__", - ) - - finalize_callback: Deque[Callable[[DBAPIConnection], None]] - fresh: bool - fairy_ref: Optional[weakref.ref[_ConnectionFairy]] - starttime: float - - def __init__(self, pool: Pool, connect: bool = True): - self.fresh = False - self.fairy_ref = None - self.starttime = 0 - self.dbapi_connection = None - - self.__pool = pool - if connect: - self.__connect() - self.finalize_callback = deque() - - dbapi_connection: Optional[DBAPIConnection] - - @property - def driver_connection(self) -> Optional[Any]: # type: ignore[override] # mypy#4125 # noqa: E501 - if self.dbapi_connection is None: - return None - else: - return self.__pool._dialect.get_driver_connection( - self.dbapi_connection - ) - - @property - @util.deprecated( - "2.0", - "The _ConnectionRecord.connection attribute is deprecated; " - "please use 'driver_connection'", - ) - def connection(self) -> Optional[DBAPIConnection]: - return self.dbapi_connection - - _soft_invalidate_time: float = 0 - - @util.ro_memoized_property - def info(self) -> _InfoType: - return {} - - @util.ro_memoized_property - def record_info(self) -> Optional[_InfoType]: - return {} - - @classmethod - def checkout(cls, pool: Pool) -> _ConnectionFairy: - if TYPE_CHECKING: - rec = cast(_ConnectionRecord, pool._do_get()) - else: - rec = pool._do_get() - - try: - dbapi_connection = rec.get_connection() - except BaseException as err: - with util.safe_reraise(): - rec._checkin_failed(err, _fairy_was_created=False) - - # not reached, for code linters only - raise - - echo = pool._should_log_debug() - fairy = _ConnectionFairy(pool, dbapi_connection, rec, echo) - - rec.fairy_ref = ref = weakref.ref( - fairy, - lambda ref: ( - _finalize_fairy( - None, rec, pool, ref, echo, transaction_was_reset=False - ) - if _finalize_fairy is not None - else None - ), - ) - _strong_ref_connection_records[ref] = rec - if echo: - pool.logger.debug( - "Connection %r checked out from pool", dbapi_connection - ) - return fairy - - def _checkin_failed( - self, err: BaseException, _fairy_was_created: bool = True - ) -> None: - self.invalidate(e=err) - self.checkin( - _fairy_was_created=_fairy_was_created, - ) - - def checkin(self, _fairy_was_created: bool = True) -> None: - if self.fairy_ref is None and _fairy_was_created: - # _fairy_was_created is False for the initial get connection phase; - # meaning there was no _ConnectionFairy and we must unconditionally - # do a checkin. - # - # otherwise, if fairy_was_created==True, if fairy_ref is None here - # that means we were checked in already, so this looks like - # a double checkin. - util.warn("Double checkin attempted on %s" % self) - return - self.fairy_ref = None - connection = self.dbapi_connection - pool = self.__pool - while self.finalize_callback: - finalizer = self.finalize_callback.pop() - if connection is not None: - finalizer(connection) - if pool.dispatch.checkin: - pool.dispatch.checkin(connection, self) - - pool._return_conn(self) - - @property - def in_use(self) -> bool: - return self.fairy_ref is not None - - @property - def last_connect_time(self) -> float: - return self.starttime - - def close(self) -> None: - if self.dbapi_connection is not None: - self.__close() - - def invalidate( - self, e: Optional[BaseException] = None, soft: bool = False - ) -> None: - # already invalidated - if self.dbapi_connection is None: - return - if soft: - self.__pool.dispatch.soft_invalidate( - self.dbapi_connection, self, e - ) - else: - self.__pool.dispatch.invalidate(self.dbapi_connection, self, e) - if e is not None: - self.__pool.logger.info( - "%sInvalidate connection %r (reason: %s:%s)", - "Soft " if soft else "", - self.dbapi_connection, - e.__class__.__name__, - e, - ) - else: - self.__pool.logger.info( - "%sInvalidate connection %r", - "Soft " if soft else "", - self.dbapi_connection, - ) - - if soft: - self._soft_invalidate_time = time.time() - else: - self.__close(terminate=True) - self.dbapi_connection = None - - def get_connection(self) -> DBAPIConnection: - recycle = False - - # NOTE: the various comparisons here are assuming that measurable time - # passes between these state changes. however, time.time() is not - # guaranteed to have sub-second precision. comparisons of - # "invalidation time" to "starttime" should perhaps use >= so that the - # state change can take place assuming no measurable time has passed, - # however this does not guarantee correct behavior here as if time - # continues to not pass, it will try to reconnect repeatedly until - # these timestamps diverge, so in that sense using > is safer. Per - # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be - # within 16 milliseconds accuracy, so unit tests for connection - # invalidation need a sleep of at least this long between initial start - # time and invalidation for the logic below to work reliably. - - if self.dbapi_connection is None: - self.info.clear() - self.__connect() - elif ( - self.__pool._recycle > -1 - and time.time() - self.starttime > self.__pool._recycle - ): - self.__pool.logger.info( - "Connection %r exceeded timeout; recycling", - self.dbapi_connection, - ) - recycle = True - elif self.__pool._invalidate_time > self.starttime: - self.__pool.logger.info( - "Connection %r invalidated due to pool invalidation; " - + "recycling", - self.dbapi_connection, - ) - recycle = True - elif self._soft_invalidate_time > self.starttime: - self.__pool.logger.info( - "Connection %r invalidated due to local soft invalidation; " - + "recycling", - self.dbapi_connection, - ) - recycle = True - - if recycle: - self.__close(terminate=True) - self.info.clear() - - self.__connect() - - assert self.dbapi_connection is not None - return self.dbapi_connection - - def _is_hard_or_soft_invalidated(self) -> bool: - return ( - self.dbapi_connection is None - or self.__pool._invalidate_time > self.starttime - or (self._soft_invalidate_time > self.starttime) - ) - - def __close(self, *, terminate: bool = False) -> None: - self.finalize_callback.clear() - if self.__pool.dispatch.close: - self.__pool.dispatch.close(self.dbapi_connection, self) - assert self.dbapi_connection is not None - self.__pool._close_connection( - self.dbapi_connection, terminate=terminate - ) - self.dbapi_connection = None - - def __connect(self) -> None: - pool = self.__pool - - # ensure any existing connection is removed, so that if - # creator fails, this attribute stays None - self.dbapi_connection = None - try: - self.starttime = time.time() - self.dbapi_connection = connection = pool._invoke_creator(self) - pool.logger.debug("Created new connection %r", connection) - self.fresh = True - except BaseException as e: - with util.safe_reraise(): - pool.logger.debug("Error on connect(): %s", e) - else: - # in SQLAlchemy 1.4 the first_connect event is not used by - # the engine, so this will usually not be set - if pool.dispatch.first_connect: - pool.dispatch.first_connect.for_modify( - pool.dispatch - ).exec_once_unless_exception(self.dbapi_connection, self) - - # init of the dialect now takes place within the connect - # event, so ensure a mutex is used on the first run - pool.dispatch.connect.for_modify( - pool.dispatch - )._exec_w_sync_on_first_run(self.dbapi_connection, self) - - -def _finalize_fairy( - dbapi_connection: Optional[DBAPIConnection], - connection_record: Optional[_ConnectionRecord], - pool: Pool, - ref: Optional[ - weakref.ref[_ConnectionFairy] - ], # this is None when called directly, not by the gc - echo: Optional[log._EchoFlagType], - transaction_was_reset: bool = False, - fairy: Optional[_ConnectionFairy] = None, -) -> None: - """Cleanup for a :class:`._ConnectionFairy` whether or not it's already - been garbage collected. - - When using an async dialect no IO can happen here (without using - a dedicated thread), since this is called outside the greenlet - context and with an already running loop. In this case function - will only log a message and raise a warning. - """ - - is_gc_cleanup = ref is not None - - if is_gc_cleanup: - assert ref is not None - _strong_ref_connection_records.pop(ref, None) - assert connection_record is not None - if connection_record.fairy_ref is not ref: - return - assert dbapi_connection is None - dbapi_connection = connection_record.dbapi_connection - - elif fairy: - _strong_ref_connection_records.pop(weakref.ref(fairy), None) - - # null pool is not _is_asyncio but can be used also with async dialects - dont_restore_gced = pool._dialect.is_async - - if dont_restore_gced: - detach = connection_record is None or is_gc_cleanup - can_manipulate_connection = not is_gc_cleanup - can_close_or_terminate_connection = ( - not pool._dialect.is_async or pool._dialect.has_terminate - ) - requires_terminate_for_close = ( - pool._dialect.is_async and pool._dialect.has_terminate - ) - - else: - detach = connection_record is None - can_manipulate_connection = can_close_or_terminate_connection = True - requires_terminate_for_close = False - - if dbapi_connection is not None: - if connection_record and echo: - pool.logger.debug( - "Connection %r being returned to pool", dbapi_connection - ) - - try: - if not fairy: - assert connection_record is not None - fairy = _ConnectionFairy( - pool, - dbapi_connection, - connection_record, - echo, - ) - assert fairy.dbapi_connection is dbapi_connection - - fairy._reset( - pool, - transaction_was_reset=transaction_was_reset, - terminate_only=detach, - asyncio_safe=can_manipulate_connection, - ) - - if detach: - if connection_record: - fairy._pool = pool - fairy.detach() - - if can_close_or_terminate_connection: - if pool.dispatch.close_detached: - pool.dispatch.close_detached(dbapi_connection) - - pool._close_connection( - dbapi_connection, - terminate=requires_terminate_for_close, - ) - - except BaseException as e: - pool.logger.error( - "Exception during reset or similar", exc_info=True - ) - if connection_record: - connection_record.invalidate(e=e) - if not isinstance(e, Exception): - raise - finally: - if detach and is_gc_cleanup and dont_restore_gced: - message = ( - "The garbage collector is trying to clean up " - f"non-checked-in connection {dbapi_connection!r}, " - f"""which will be { - 'dropped, as it cannot be safely terminated' - if not can_close_or_terminate_connection - else 'terminated' - }. """ - "Please ensure that SQLAlchemy pooled connections are " - "returned to " - "the pool explicitly, either by calling ``close()`` " - "or by using appropriate context managers to manage " - "their lifecycle." - ) - pool.logger.error(message) - util.warn(message) - - if connection_record and connection_record.fairy_ref is not None: - connection_record.checkin() - - # give gc some help. See - # test/engine/test_pool.py::PoolEventsTest::test_checkin_event_gc[True] - # which actually started failing when pytest warnings plugin was - # turned on, due to util.warn() above - if fairy is not None: - fairy.dbapi_connection = None # type: ignore - fairy._connection_record = None - del dbapi_connection - del connection_record - del fairy - - -# a dictionary of the _ConnectionFairy weakrefs to _ConnectionRecord, so that -# GC under pypy will call ConnectionFairy finalizers. linked directly to the -# weakref that will empty itself when collected so that it should not create -# any unmanaged memory references. -_strong_ref_connection_records: Dict[ - weakref.ref[_ConnectionFairy], _ConnectionRecord -] = {} - - -class PoolProxiedConnection(ManagesConnection): - """A connection-like adapter for a :pep:`249` DBAPI connection, which - includes additional methods specific to the :class:`.Pool` implementation. - - :class:`.PoolProxiedConnection` is the public-facing interface for the - internal :class:`._ConnectionFairy` implementation object; users familiar - with :class:`._ConnectionFairy` can consider this object to be equivalent. - - .. versionadded:: 2.0 :class:`.PoolProxiedConnection` provides the public- - facing interface for the :class:`._ConnectionFairy` internal class. - - """ - - __slots__ = () - - if typing.TYPE_CHECKING: - - def commit(self) -> None: ... - - def cursor(self) -> DBAPICursor: ... - - def rollback(self) -> None: ... - - @property - def is_valid(self) -> bool: - """Return True if this :class:`.PoolProxiedConnection` still refers - to an active DBAPI connection.""" - - raise NotImplementedError() - - @property - def is_detached(self) -> bool: - """Return True if this :class:`.PoolProxiedConnection` is detached - from its pool.""" - - raise NotImplementedError() - - def detach(self) -> None: - """Separate this connection from its Pool. - - This means that the connection will no longer be returned to the - pool when closed, and will instead be literally closed. The - associated :class:`.ConnectionPoolEntry` is de-associated from this - DBAPI connection. - - Note that any overall connection limiting constraints imposed by a - Pool implementation may be violated after a detach, as the detached - connection is removed from the pool's knowledge and control. - - """ - - raise NotImplementedError() - - def close(self) -> None: - """Release this connection back to the pool. - - The :meth:`.PoolProxiedConnection.close` method shadows the - :pep:`249` ``.close()`` method, altering its behavior to instead - :term:`release` the proxied connection back to the connection pool. - - Upon release to the pool, whether the connection stays "opened" and - pooled in the Python process, versus actually closed out and removed - from the Python process, is based on the pool implementation in use and - its configuration and current state. - - """ - raise NotImplementedError() - - -class _AdhocProxiedConnection(PoolProxiedConnection): - """provides the :class:`.PoolProxiedConnection` interface for cases where - the DBAPI connection is not actually proxied. - - This is used by the engine internals to pass a consistent - :class:`.PoolProxiedConnection` object to consuming dialects in response to - pool events that may not always have the :class:`._ConnectionFairy` - available. - - """ - - __slots__ = ("dbapi_connection", "_connection_record", "_is_valid") - - dbapi_connection: DBAPIConnection - _connection_record: ConnectionPoolEntry - - def __init__( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - ): - self.dbapi_connection = dbapi_connection - self._connection_record = connection_record - self._is_valid = True - - @property - def driver_connection(self) -> Any: # type: ignore[override] # mypy#4125 - return self._connection_record.driver_connection - - @property - def connection(self) -> DBAPIConnection: - return self.dbapi_connection - - @property - def is_valid(self) -> bool: - """Implement is_valid state attribute. - - for the adhoc proxied connection it's assumed the connection is valid - as there is no "invalidate" routine. - - """ - return self._is_valid - - def invalidate( - self, e: Optional[BaseException] = None, soft: bool = False - ) -> None: - self._is_valid = False - - @util.ro_non_memoized_property - def record_info(self) -> Optional[_InfoType]: - return self._connection_record.record_info - - def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor: - return self.dbapi_connection.cursor(*args, **kwargs) - - def __getattr__(self, key: Any) -> Any: - return getattr(self.dbapi_connection, key) - - -class _ConnectionFairy(PoolProxiedConnection): - """Proxies a DBAPI connection and provides return-on-dereference - support. - - This is an internal object used by the :class:`_pool.Pool` implementation - to provide context management to a DBAPI connection delivered by - that :class:`_pool.Pool`. The public facing interface for this class - is described by the :class:`.PoolProxiedConnection` class. See that - class for public API details. - - The name "fairy" is inspired by the fact that the - :class:`._ConnectionFairy` object's lifespan is transitory, as it lasts - only for the length of a specific DBAPI connection being checked out from - the pool, and additionally that as a transparent proxy, it is mostly - invisible. - - .. seealso:: - - :class:`.PoolProxiedConnection` - - :class:`.ConnectionPoolEntry` - - - """ - - __slots__ = ( - "dbapi_connection", - "_connection_record", - "_echo", - "_pool", - "_counter", - "__weakref__", - "__dict__", - ) - - pool: Pool - dbapi_connection: DBAPIConnection - _echo: log._EchoFlagType - - def __init__( - self, - pool: Pool, - dbapi_connection: DBAPIConnection, - connection_record: _ConnectionRecord, - echo: log._EchoFlagType, - ): - self._pool = pool - self._counter = 0 - self.dbapi_connection = dbapi_connection - self._connection_record = connection_record - self._echo = echo - - _connection_record: Optional[_ConnectionRecord] - - @property - def driver_connection(self) -> Optional[Any]: # type: ignore[override] # mypy#4125 # noqa: E501 - if self._connection_record is None: - return None - return self._connection_record.driver_connection - - @property - @util.deprecated( - "2.0", - "The _ConnectionFairy.connection attribute is deprecated; " - "please use 'driver_connection'", - ) - def connection(self) -> DBAPIConnection: - return self.dbapi_connection - - @classmethod - def _checkout( - cls, - pool: Pool, - threadconns: Optional[threading.local] = None, - fairy: Optional[_ConnectionFairy] = None, - ) -> _ConnectionFairy: - if not fairy: - fairy = _ConnectionRecord.checkout(pool) - - if threadconns is not None: - threadconns.current = weakref.ref(fairy) - - assert ( - fairy._connection_record is not None - ), "can't 'checkout' a detached connection fairy" - assert ( - fairy.dbapi_connection is not None - ), "can't 'checkout' an invalidated connection fairy" - - fairy._counter += 1 - if ( - not pool.dispatch.checkout and not pool._pre_ping - ) or fairy._counter != 1: - return fairy - - # Pool listeners can trigger a reconnection on checkout, as well - # as the pre-pinger. - # there are three attempts made here, but note that if the database - # is not accessible from a connection standpoint, those won't proceed - # here. - - attempts = 2 - - while attempts > 0: - connection_is_fresh = fairy._connection_record.fresh - fairy._connection_record.fresh = False - try: - if pool._pre_ping: - if not connection_is_fresh: - if fairy._echo: - pool.logger.debug( - "Pool pre-ping on connection %s", - fairy.dbapi_connection, - ) - result = pool._dialect._do_ping_w_event( - fairy.dbapi_connection - ) - if not result: - if fairy._echo: - pool.logger.debug( - "Pool pre-ping on connection %s failed, " - "will invalidate pool", - fairy.dbapi_connection, - ) - raise exc.InvalidatePoolError() - elif fairy._echo: - pool.logger.debug( - "Connection %s is fresh, skipping pre-ping", - fairy.dbapi_connection, - ) - - pool.dispatch.checkout( - fairy.dbapi_connection, fairy._connection_record, fairy - ) - return fairy - except exc.DisconnectionError as e: - if e.invalidate_pool: - pool.logger.info( - "Disconnection detected on checkout, " - "invalidating all pooled connections prior to " - "current timestamp (reason: %r)", - e, - ) - fairy._connection_record.invalidate(e) - pool._invalidate(fairy, e, _checkin=False) - else: - pool.logger.info( - "Disconnection detected on checkout, " - "invalidating individual connection %s (reason: %r)", - fairy.dbapi_connection, - e, - ) - fairy._connection_record.invalidate(e) - try: - fairy.dbapi_connection = ( - fairy._connection_record.get_connection() - ) - except BaseException as err: - with util.safe_reraise(): - fairy._connection_record._checkin_failed( - err, - _fairy_was_created=True, - ) - - # prevent _ConnectionFairy from being carried - # in the stack trace. Do this after the - # connection record has been checked in, so that - # if the del triggers a finalize fairy, it won't - # try to checkin a second time. - del fairy - - # never called, this is for code linters - raise - - attempts -= 1 - except BaseException as be_outer: - with util.safe_reraise(): - rec = fairy._connection_record - if rec is not None: - rec._checkin_failed( - be_outer, - _fairy_was_created=True, - ) - - # prevent _ConnectionFairy from being carried - # in the stack trace, see above - del fairy - - # never called, this is for code linters - raise - - pool.logger.info("Reconnection attempts exhausted on checkout") - fairy.invalidate() - raise exc.InvalidRequestError("This connection is closed") - - def _checkout_existing(self) -> _ConnectionFairy: - return _ConnectionFairy._checkout(self._pool, fairy=self) - - def _checkin(self, transaction_was_reset: bool = False) -> None: - _finalize_fairy( - self.dbapi_connection, - self._connection_record, - self._pool, - None, - self._echo, - transaction_was_reset=transaction_was_reset, - fairy=self, - ) - - def _close(self) -> None: - self._checkin() - - def _reset( - self, - pool: Pool, - transaction_was_reset: bool, - terminate_only: bool, - asyncio_safe: bool, - ) -> None: - if pool.dispatch.reset: - pool.dispatch.reset( - self.dbapi_connection, - self._connection_record, - PoolResetState( - transaction_was_reset=transaction_was_reset, - terminate_only=terminate_only, - asyncio_safe=asyncio_safe, - ), - ) - - if not asyncio_safe: - return - - if pool._reset_on_return is reset_rollback: - if transaction_was_reset: - if self._echo: - pool.logger.debug( - "Connection %s reset, transaction already reset", - self.dbapi_connection, - ) - else: - if self._echo: - pool.logger.debug( - "Connection %s rollback-on-return", - self.dbapi_connection, - ) - pool._dialect.do_rollback(self) - elif pool._reset_on_return is reset_commit: - if self._echo: - pool.logger.debug( - "Connection %s commit-on-return", - self.dbapi_connection, - ) - pool._dialect.do_commit(self) - - @property - def _logger(self) -> log._IdentifiedLoggerType: - return self._pool.logger - - @property - def is_valid(self) -> bool: - return self.dbapi_connection is not None - - @property - def is_detached(self) -> bool: - return self._connection_record is None - - @util.ro_memoized_property - def info(self) -> _InfoType: - if self._connection_record is None: - return {} - else: - return self._connection_record.info - - @util.ro_non_memoized_property - def record_info(self) -> Optional[_InfoType]: - if self._connection_record is None: - return None - else: - return self._connection_record.record_info - - def invalidate( - self, e: Optional[BaseException] = None, soft: bool = False - ) -> None: - if self.dbapi_connection is None: - util.warn("Can't invalidate an already-closed connection.") - return - if self._connection_record: - self._connection_record.invalidate(e=e, soft=soft) - if not soft: - # prevent any rollback / reset actions etc. on - # the connection - self.dbapi_connection = None # type: ignore - - # finalize - self._checkin() - - def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor: - assert self.dbapi_connection is not None - return self.dbapi_connection.cursor(*args, **kwargs) - - def __getattr__(self, key: str) -> Any: - return getattr(self.dbapi_connection, key) - - def detach(self) -> None: - if self._connection_record is not None: - rec = self._connection_record - rec.fairy_ref = None - rec.dbapi_connection = None - # TODO: should this be _return_conn? - self._pool._do_return_conn(self._connection_record) - - # can't get the descriptor assignment to work here - # in pylance. mypy is OK w/ it - self.info = self.info.copy() # type: ignore - - self._connection_record = None - - if self._pool.dispatch.detach: - self._pool.dispatch.detach(self.dbapi_connection, rec) - - def close(self) -> None: - self._counter -= 1 - if self._counter == 0: - self._checkin() - - def _close_special(self, transaction_reset: bool = False) -> None: - self._counter -= 1 - if self._counter == 0: - self._checkin(transaction_was_reset=transaction_reset) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/pool/events.py b/venv/lib/python3.11/site-packages/sqlalchemy/pool/events.py deleted file mode 100644 index 4b4f4e4..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/pool/events.py +++ /dev/null @@ -1,370 +0,0 @@ -# pool/events.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -from __future__ import annotations - -import typing -from typing import Any -from typing import Optional -from typing import Type -from typing import Union - -from .base import ConnectionPoolEntry -from .base import Pool -from .base import PoolProxiedConnection -from .base import PoolResetState -from .. import event -from .. import util - -if typing.TYPE_CHECKING: - from ..engine import Engine - from ..engine.interfaces import DBAPIConnection - - -class PoolEvents(event.Events[Pool]): - """Available events for :class:`_pool.Pool`. - - The methods here define the name of an event as well - as the names of members that are passed to listener - functions. - - e.g.:: - - from sqlalchemy import event - - def my_on_checkout(dbapi_conn, connection_rec, connection_proxy): - "handle an on checkout event" - - event.listen(Pool, 'checkout', my_on_checkout) - - In addition to accepting the :class:`_pool.Pool` class and - :class:`_pool.Pool` instances, :class:`_events.PoolEvents` also accepts - :class:`_engine.Engine` objects and the :class:`_engine.Engine` class as - targets, which will be resolved to the ``.pool`` attribute of the - given engine or the :class:`_pool.Pool` class:: - - engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test") - - # will associate with engine.pool - event.listen(engine, 'checkout', my_on_checkout) - - """ # noqa: E501 - - _target_class_doc = "SomeEngineOrPool" - _dispatch_target = Pool - - @util.preload_module("sqlalchemy.engine") - @classmethod - def _accept_with( - cls, - target: Union[Pool, Type[Pool], Engine, Type[Engine]], - identifier: str, - ) -> Optional[Union[Pool, Type[Pool]]]: - if not typing.TYPE_CHECKING: - Engine = util.preloaded.engine.Engine - - if isinstance(target, type): - if issubclass(target, Engine): - return Pool - else: - assert issubclass(target, Pool) - return target - elif isinstance(target, Engine): - return target.pool - elif isinstance(target, Pool): - return target - elif hasattr(target, "_no_async_engine_events"): - target._no_async_engine_events() - else: - return None - - @classmethod - def _listen( - cls, - event_key: event._EventKey[Pool], - **kw: Any, - ) -> None: - target = event_key.dispatch_target - - kw.setdefault("asyncio", target._is_asyncio) - - event_key.base_listen(**kw) - - def connect( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - ) -> None: - """Called at the moment a particular DBAPI connection is first - created for a given :class:`_pool.Pool`. - - This event allows one to capture the point directly after which - the DBAPI module-level ``.connect()`` method has been used in order - to produce a new DBAPI connection. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - """ - - def first_connect( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - ) -> None: - """Called exactly once for the first time a DBAPI connection is - checked out from a particular :class:`_pool.Pool`. - - The rationale for :meth:`_events.PoolEvents.first_connect` - is to determine - information about a particular series of database connections based - on the settings used for all connections. Since a particular - :class:`_pool.Pool` - refers to a single "creator" function (which in terms - of a :class:`_engine.Engine` - refers to the URL and connection options used), - it is typically valid to make observations about a single connection - that can be safely assumed to be valid about all subsequent - connections, such as the database version, the server and client - encoding settings, collation settings, and many others. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - """ - - def checkout( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - connection_proxy: PoolProxiedConnection, - ) -> None: - """Called when a connection is retrieved from the Pool. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - :param connection_proxy: the :class:`.PoolProxiedConnection` object - which will proxy the public interface of the DBAPI connection for the - lifespan of the checkout. - - If you raise a :class:`~sqlalchemy.exc.DisconnectionError`, the current - connection will be disposed and a fresh connection retrieved. - Processing of all checkout listeners will abort and restart - using the new connection. - - .. seealso:: :meth:`_events.ConnectionEvents.engine_connect` - - a similar event - which occurs upon creation of a new :class:`_engine.Connection`. - - """ - - def checkin( - self, - dbapi_connection: Optional[DBAPIConnection], - connection_record: ConnectionPoolEntry, - ) -> None: - """Called when a connection returns to the pool. - - Note that the connection may be closed, and may be None if the - connection has been invalidated. ``checkin`` will not be called - for detached connections. (They do not return to the pool.) - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - """ - - @event._legacy_signature( - "2.0", - ["dbapi_connection", "connection_record"], - lambda dbapi_connection, connection_record, reset_state: ( - dbapi_connection, - connection_record, - ), - ) - def reset( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - reset_state: PoolResetState, - ) -> None: - """Called before the "reset" action occurs for a pooled connection. - - This event represents - when the ``rollback()`` method is called on the DBAPI connection - before it is returned to the pool or discarded. - A custom "reset" strategy may be implemented using this event hook, - which may also be combined with disabling the default "reset" - behavior using the :paramref:`_pool.Pool.reset_on_return` parameter. - - The primary difference between the :meth:`_events.PoolEvents.reset` and - :meth:`_events.PoolEvents.checkin` events are that - :meth:`_events.PoolEvents.reset` is called not just for pooled - connections that are being returned to the pool, but also for - connections that were detached using the - :meth:`_engine.Connection.detach` method as well as asyncio connections - that are being discarded due to garbage collection taking place on - connections before the connection was checked in. - - Note that the event **is not** invoked for connections that were - invalidated using :meth:`_engine.Connection.invalidate`. These - events may be intercepted using the :meth:`.PoolEvents.soft_invalidate` - and :meth:`.PoolEvents.invalidate` event hooks, and all "connection - close" events may be intercepted using :meth:`.PoolEvents.close`. - - The :meth:`_events.PoolEvents.reset` event is usually followed by the - :meth:`_events.PoolEvents.checkin` event, except in those - cases where the connection is discarded immediately after reset. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - :param reset_state: :class:`.PoolResetState` instance which provides - information about the circumstances under which the connection - is being reset. - - .. versionadded:: 2.0 - - .. seealso:: - - :ref:`pool_reset_on_return` - - :meth:`_events.ConnectionEvents.rollback` - - :meth:`_events.ConnectionEvents.commit` - - """ - - def invalidate( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - exception: Optional[BaseException], - ) -> None: - """Called when a DBAPI connection is to be "invalidated". - - This event is called any time the - :meth:`.ConnectionPoolEntry.invalidate` method is invoked, either from - API usage or via "auto-invalidation", without the ``soft`` flag. - - The event occurs before a final attempt to call ``.close()`` on the - connection occurs. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - :param exception: the exception object corresponding to the reason - for this invalidation, if any. May be ``None``. - - .. seealso:: - - :ref:`pool_connection_invalidation` - - """ - - def soft_invalidate( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - exception: Optional[BaseException], - ) -> None: - """Called when a DBAPI connection is to be "soft invalidated". - - This event is called any time the - :meth:`.ConnectionPoolEntry.invalidate` - method is invoked with the ``soft`` flag. - - Soft invalidation refers to when the connection record that tracks - this connection will force a reconnect after the current connection - is checked in. It does not actively close the dbapi_connection - at the point at which it is called. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - :param exception: the exception object corresponding to the reason - for this invalidation, if any. May be ``None``. - - """ - - def close( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - ) -> None: - """Called when a DBAPI connection is closed. - - The event is emitted before the close occurs. - - The close of a connection can fail; typically this is because - the connection is already closed. If the close operation fails, - the connection is discarded. - - The :meth:`.close` event corresponds to a connection that's still - associated with the pool. To intercept close events for detached - connections use :meth:`.close_detached`. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - """ - - def detach( - self, - dbapi_connection: DBAPIConnection, - connection_record: ConnectionPoolEntry, - ) -> None: - """Called when a DBAPI connection is "detached" from a pool. - - This event is emitted after the detach occurs. The connection - is no longer associated with the given connection record. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - :param connection_record: the :class:`.ConnectionPoolEntry` managing - the DBAPI connection. - - """ - - def close_detached(self, dbapi_connection: DBAPIConnection) -> None: - """Called when a detached DBAPI connection is closed. - - The event is emitted before the close occurs. - - The close of a connection can fail; typically this is because - the connection is already closed. If the close operation fails, - the connection is discarded. - - :param dbapi_connection: a DBAPI connection. - The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. - - """ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/pool/impl.py b/venv/lib/python3.11/site-packages/sqlalchemy/pool/impl.py deleted file mode 100644 index 157455c..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/pool/impl.py +++ /dev/null @@ -1,581 +0,0 @@ -# pool/impl.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php - - -"""Pool implementation classes. - -""" -from __future__ import annotations - -import threading -import traceback -import typing -from typing import Any -from typing import cast -from typing import List -from typing import Optional -from typing import Set -from typing import Type -from typing import TYPE_CHECKING -from typing import Union -import weakref - -from .base import _AsyncConnDialect -from .base import _ConnectionFairy -from .base import _ConnectionRecord -from .base import _CreatorFnType -from .base import _CreatorWRecFnType -from .base import ConnectionPoolEntry -from .base import Pool -from .base import PoolProxiedConnection -from .. import exc -from .. import util -from ..util import chop_traceback -from ..util import queue as sqla_queue -from ..util.typing import Literal - -if typing.TYPE_CHECKING: - from ..engine.interfaces import DBAPIConnection - - -class QueuePool(Pool): - """A :class:`_pool.Pool` - that imposes a limit on the number of open connections. - - :class:`.QueuePool` is the default pooling implementation used for - all :class:`_engine.Engine` objects other than SQLite with a ``:memory:`` - database. - - The :class:`.QueuePool` class **is not compatible** with asyncio and - :func:`_asyncio.create_async_engine`. The - :class:`.AsyncAdaptedQueuePool` class is used automatically when - using :func:`_asyncio.create_async_engine`, if no other kind of pool - is specified. - - .. seealso:: - - :class:`.AsyncAdaptedQueuePool` - - """ - - _is_asyncio = False # type: ignore[assignment] - - _queue_class: Type[sqla_queue.QueueCommon[ConnectionPoolEntry]] = ( - sqla_queue.Queue - ) - - _pool: sqla_queue.QueueCommon[ConnectionPoolEntry] - - def __init__( - self, - creator: Union[_CreatorFnType, _CreatorWRecFnType], - pool_size: int = 5, - max_overflow: int = 10, - timeout: float = 30.0, - use_lifo: bool = False, - **kw: Any, - ): - r""" - Construct a QueuePool. - - :param creator: a callable function that returns a DB-API - connection object, same as that of :paramref:`_pool.Pool.creator`. - - :param pool_size: The size of the pool to be maintained, - defaults to 5. This is the largest number of connections that - will be kept persistently in the pool. Note that the pool - begins with no connections; once this number of connections - is requested, that number of connections will remain. - ``pool_size`` can be set to 0 to indicate no size limit; to - disable pooling, use a :class:`~sqlalchemy.pool.NullPool` - instead. - - :param max_overflow: The maximum overflow size of the - pool. When the number of checked-out connections reaches the - size set in pool_size, additional connections will be - returned up to this limit. When those additional connections - are returned to the pool, they are disconnected and - discarded. It follows then that the total number of - simultaneous connections the pool will allow is pool_size + - `max_overflow`, and the total number of "sleeping" - connections the pool will allow is pool_size. `max_overflow` - can be set to -1 to indicate no overflow limit; no limit - will be placed on the total number of concurrent - connections. Defaults to 10. - - :param timeout: The number of seconds to wait before giving up - on returning a connection. Defaults to 30.0. This can be a float - but is subject to the limitations of Python time functions which - may not be reliable in the tens of milliseconds. - - :param use_lifo: use LIFO (last-in-first-out) when retrieving - connections instead of FIFO (first-in-first-out). Using LIFO, a - server-side timeout scheme can reduce the number of connections used - during non-peak periods of use. When planning for server-side - timeouts, ensure that a recycle or pre-ping strategy is in use to - gracefully handle stale connections. - - .. versionadded:: 1.3 - - .. seealso:: - - :ref:`pool_use_lifo` - - :ref:`pool_disconnects` - - :param \**kw: Other keyword arguments including - :paramref:`_pool.Pool.recycle`, :paramref:`_pool.Pool.echo`, - :paramref:`_pool.Pool.reset_on_return` and others are passed to the - :class:`_pool.Pool` constructor. - - """ - - Pool.__init__(self, creator, **kw) - self._pool = self._queue_class(pool_size, use_lifo=use_lifo) - self._overflow = 0 - pool_size - self._max_overflow = -1 if pool_size == 0 else max_overflow - self._timeout = timeout - self._overflow_lock = threading.Lock() - - def _do_return_conn(self, record: ConnectionPoolEntry) -> None: - try: - self._pool.put(record, False) - except sqla_queue.Full: - try: - record.close() - finally: - self._dec_overflow() - - def _do_get(self) -> ConnectionPoolEntry: - use_overflow = self._max_overflow > -1 - - wait = use_overflow and self._overflow >= self._max_overflow - try: - return self._pool.get(wait, self._timeout) - except sqla_queue.Empty: - # don't do things inside of "except Empty", because when we say - # we timed out or can't connect and raise, Python 3 tells - # people the real error is queue.Empty which it isn't. - pass - if use_overflow and self._overflow >= self._max_overflow: - if not wait: - return self._do_get() - else: - raise exc.TimeoutError( - "QueuePool limit of size %d overflow %d reached, " - "connection timed out, timeout %0.2f" - % (self.size(), self.overflow(), self._timeout), - code="3o7r", - ) - - if self._inc_overflow(): - try: - return self._create_connection() - except: - with util.safe_reraise(): - self._dec_overflow() - raise - else: - return self._do_get() - - def _inc_overflow(self) -> bool: - if self._max_overflow == -1: - self._overflow += 1 - return True - with self._overflow_lock: - if self._overflow < self._max_overflow: - self._overflow += 1 - return True - else: - return False - - def _dec_overflow(self) -> Literal[True]: - if self._max_overflow == -1: - self._overflow -= 1 - return True - with self._overflow_lock: - self._overflow -= 1 - return True - - def recreate(self) -> QueuePool: - self.logger.info("Pool recreating") - return self.__class__( - self._creator, - pool_size=self._pool.maxsize, - max_overflow=self._max_overflow, - pre_ping=self._pre_ping, - use_lifo=self._pool.use_lifo, - timeout=self._timeout, - recycle=self._recycle, - echo=self.echo, - logging_name=self._orig_logging_name, - reset_on_return=self._reset_on_return, - _dispatch=self.dispatch, - dialect=self._dialect, - ) - - def dispose(self) -> None: - while True: - try: - conn = self._pool.get(False) - conn.close() - except sqla_queue.Empty: - break - - self._overflow = 0 - self.size() - self.logger.info("Pool disposed. %s", self.status()) - - def status(self) -> str: - return ( - "Pool size: %d Connections in pool: %d " - "Current Overflow: %d Current Checked out " - "connections: %d" - % ( - self.size(), - self.checkedin(), - self.overflow(), - self.checkedout(), - ) - ) - - def size(self) -> int: - return self._pool.maxsize - - def timeout(self) -> float: - return self._timeout - - def checkedin(self) -> int: - return self._pool.qsize() - - def overflow(self) -> int: - return self._overflow if self._pool.maxsize else 0 - - def checkedout(self) -> int: - return self._pool.maxsize - self._pool.qsize() + self._overflow - - -class AsyncAdaptedQueuePool(QueuePool): - """An asyncio-compatible version of :class:`.QueuePool`. - - This pool is used by default when using :class:`.AsyncEngine` engines that - were generated from :func:`_asyncio.create_async_engine`. It uses an - asyncio-compatible queue implementation that does not use - ``threading.Lock``. - - The arguments and operation of :class:`.AsyncAdaptedQueuePool` are - otherwise identical to that of :class:`.QueuePool`. - - """ - - _is_asyncio = True # type: ignore[assignment] - _queue_class: Type[sqla_queue.QueueCommon[ConnectionPoolEntry]] = ( - sqla_queue.AsyncAdaptedQueue - ) - - _dialect = _AsyncConnDialect() - - -class FallbackAsyncAdaptedQueuePool(AsyncAdaptedQueuePool): - _queue_class = sqla_queue.FallbackAsyncAdaptedQueue - - -class NullPool(Pool): - """A Pool which does not pool connections. - - Instead it literally opens and closes the underlying DB-API connection - per each connection open/close. - - Reconnect-related functions such as ``recycle`` and connection - invalidation are not supported by this Pool implementation, since - no connections are held persistently. - - The :class:`.NullPool` class **is compatible** with asyncio and - :func:`_asyncio.create_async_engine`. - - """ - - def status(self) -> str: - return "NullPool" - - def _do_return_conn(self, record: ConnectionPoolEntry) -> None: - record.close() - - def _do_get(self) -> ConnectionPoolEntry: - return self._create_connection() - - def recreate(self) -> NullPool: - self.logger.info("Pool recreating") - - return self.__class__( - self._creator, - recycle=self._recycle, - echo=self.echo, - logging_name=self._orig_logging_name, - reset_on_return=self._reset_on_return, - pre_ping=self._pre_ping, - _dispatch=self.dispatch, - dialect=self._dialect, - ) - - def dispose(self) -> None: - pass - - -class SingletonThreadPool(Pool): - """A Pool that maintains one connection per thread. - - Maintains one connection per each thread, never moving a connection to a - thread other than the one which it was created in. - - .. warning:: the :class:`.SingletonThreadPool` will call ``.close()`` - on arbitrary connections that exist beyond the size setting of - ``pool_size``, e.g. if more unique **thread identities** - than what ``pool_size`` states are used. This cleanup is - non-deterministic and not sensitive to whether or not the connections - linked to those thread identities are currently in use. - - :class:`.SingletonThreadPool` may be improved in a future release, - however in its current status it is generally used only for test - scenarios using a SQLite ``:memory:`` database and is not recommended - for production use. - - The :class:`.SingletonThreadPool` class **is not compatible** with asyncio - and :func:`_asyncio.create_async_engine`. - - - Options are the same as those of :class:`_pool.Pool`, as well as: - - :param pool_size: The number of threads in which to maintain connections - at once. Defaults to five. - - :class:`.SingletonThreadPool` is used by the SQLite dialect - automatically when a memory-based database is used. - See :ref:`sqlite_toplevel`. - - """ - - _is_asyncio = False # type: ignore[assignment] - - def __init__( - self, - creator: Union[_CreatorFnType, _CreatorWRecFnType], - pool_size: int = 5, - **kw: Any, - ): - Pool.__init__(self, creator, **kw) - self._conn = threading.local() - self._fairy = threading.local() - self._all_conns: Set[ConnectionPoolEntry] = set() - self.size = pool_size - - def recreate(self) -> SingletonThreadPool: - self.logger.info("Pool recreating") - return self.__class__( - self._creator, - pool_size=self.size, - recycle=self._recycle, - echo=self.echo, - pre_ping=self._pre_ping, - logging_name=self._orig_logging_name, - reset_on_return=self._reset_on_return, - _dispatch=self.dispatch, - dialect=self._dialect, - ) - - def dispose(self) -> None: - """Dispose of this pool.""" - - for conn in self._all_conns: - try: - conn.close() - except Exception: - # pysqlite won't even let you close a conn from a thread - # that didn't create it - pass - - self._all_conns.clear() - - def _cleanup(self) -> None: - while len(self._all_conns) >= self.size: - c = self._all_conns.pop() - c.close() - - def status(self) -> str: - return "SingletonThreadPool id:%d size: %d" % ( - id(self), - len(self._all_conns), - ) - - def _do_return_conn(self, record: ConnectionPoolEntry) -> None: - try: - del self._fairy.current - except AttributeError: - pass - - def _do_get(self) -> ConnectionPoolEntry: - try: - if TYPE_CHECKING: - c = cast(ConnectionPoolEntry, self._conn.current()) - else: - c = self._conn.current() - if c: - return c - except AttributeError: - pass - c = self._create_connection() - self._conn.current = weakref.ref(c) - if len(self._all_conns) >= self.size: - self._cleanup() - self._all_conns.add(c) - return c - - def connect(self) -> PoolProxiedConnection: - # vendored from Pool to include the now removed use_threadlocal - # behavior - try: - rec = cast(_ConnectionFairy, self._fairy.current()) - except AttributeError: - pass - else: - if rec is not None: - return rec._checkout_existing() - - return _ConnectionFairy._checkout(self, self._fairy) - - -class StaticPool(Pool): - """A Pool of exactly one connection, used for all requests. - - Reconnect-related functions such as ``recycle`` and connection - invalidation (which is also used to support auto-reconnect) are only - partially supported right now and may not yield good results. - - The :class:`.StaticPool` class **is compatible** with asyncio and - :func:`_asyncio.create_async_engine`. - - """ - - @util.memoized_property - def connection(self) -> _ConnectionRecord: - return _ConnectionRecord(self) - - def status(self) -> str: - return "StaticPool" - - def dispose(self) -> None: - if ( - "connection" in self.__dict__ - and self.connection.dbapi_connection is not None - ): - self.connection.close() - del self.__dict__["connection"] - - def recreate(self) -> StaticPool: - self.logger.info("Pool recreating") - return self.__class__( - creator=self._creator, - recycle=self._recycle, - reset_on_return=self._reset_on_return, - pre_ping=self._pre_ping, - echo=self.echo, - logging_name=self._orig_logging_name, - _dispatch=self.dispatch, - dialect=self._dialect, - ) - - def _transfer_from(self, other_static_pool: StaticPool) -> None: - # used by the test suite to make a new engine / pool without - # losing the state of an existing SQLite :memory: connection - def creator(rec: ConnectionPoolEntry) -> DBAPIConnection: - conn = other_static_pool.connection.dbapi_connection - assert conn is not None - return conn - - self._invoke_creator = creator - - def _create_connection(self) -> ConnectionPoolEntry: - raise NotImplementedError() - - def _do_return_conn(self, record: ConnectionPoolEntry) -> None: - pass - - def _do_get(self) -> ConnectionPoolEntry: - rec = self.connection - if rec._is_hard_or_soft_invalidated(): - del self.__dict__["connection"] - rec = self.connection - - return rec - - -class AssertionPool(Pool): - """A :class:`_pool.Pool` that allows at most one checked out connection at - any given time. - - This will raise an exception if more than one connection is checked out - at a time. Useful for debugging code that is using more connections - than desired. - - The :class:`.AssertionPool` class **is compatible** with asyncio and - :func:`_asyncio.create_async_engine`. - - """ - - _conn: Optional[ConnectionPoolEntry] - _checkout_traceback: Optional[List[str]] - - def __init__(self, *args: Any, **kw: Any): - self._conn = None - self._checked_out = False - self._store_traceback = kw.pop("store_traceback", True) - self._checkout_traceback = None - Pool.__init__(self, *args, **kw) - - def status(self) -> str: - return "AssertionPool" - - def _do_return_conn(self, record: ConnectionPoolEntry) -> None: - if not self._checked_out: - raise AssertionError("connection is not checked out") - self._checked_out = False - assert record is self._conn - - def dispose(self) -> None: - self._checked_out = False - if self._conn: - self._conn.close() - - def recreate(self) -> AssertionPool: - self.logger.info("Pool recreating") - return self.__class__( - self._creator, - echo=self.echo, - pre_ping=self._pre_ping, - recycle=self._recycle, - reset_on_return=self._reset_on_return, - logging_name=self._orig_logging_name, - _dispatch=self.dispatch, - dialect=self._dialect, - ) - - def _do_get(self) -> ConnectionPoolEntry: - if self._checked_out: - if self._checkout_traceback: - suffix = " at:\n%s" % "".join( - chop_traceback(self._checkout_traceback) - ) - else: - suffix = "" - raise AssertionError("connection is already checked out" + suffix) - - if not self._conn: - self._conn = self._create_connection() - - self._checked_out = True - if self._store_traceback: - self._checkout_traceback = traceback.format_stack() - return self._conn -- cgit v1.2.3