From: Daniele Varrazzo Date: Sat, 30 Dec 2023 00:30:39 +0000 (+0100) Subject: refactor: use typing.Self X-Git-Tag: 3.1.17~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F710%2Fhead;p=thirdparty%2Fpsycopg.git refactor: use typing.Self The object seems available for all the supported Python version and should avoid problems with PyRight (see #708). It is not a solution for #308 because we cannot use `Self[Row]`. --- diff --git a/docs/news.rst b/docs/news.rst index e7233696d..5fd7c5128 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -7,6 +7,16 @@ ``psycopg`` release notes ========================= +Future releases +--------------- + +Psycopg 3.1.17 (unreleased) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Use `typing.Self` as a more correct return value annotation of context + managers and other self-returning methods (see :ticket:`708`). + + Current release --------------- diff --git a/docs/news_pool.rst b/docs/news_pool.rst index 3f29ef97a..b95336e3d 100644 --- a/docs/news_pool.rst +++ b/docs/news_pool.rst @@ -7,6 +7,16 @@ ``psycopg_pool`` release notes ============================== +Future releases +--------------- + +psycopg_pool 3.1.10 (unreleased) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Use `typing.Self` as a more correct return value annotation of context + managers and other self-returning methods (see :ticket:`708`). + + Current release --------------- diff --git a/psycopg/psycopg/_compat.py b/psycopg/psycopg/_compat.py index 7dbae7968..994e86f0f 100644 --- a/psycopg/psycopg/_compat.py +++ b/psycopg/psycopg/_compat.py @@ -55,15 +55,16 @@ else: from typing_extensions import TypeGuard if sys.version_info >= (3, 11): - from typing import LiteralString + from typing import LiteralString, Self else: - from typing_extensions import LiteralString + from typing_extensions import LiteralString, Self __all__ = [ "Counter", "Deque", "LiteralString", "Protocol", + "Self", "TypeGuard", "ZoneInfo", "cache", diff --git a/psycopg/psycopg/_pipeline.py b/psycopg/psycopg/_pipeline.py index 3c73f7eea..7064f6038 100644 --- a/psycopg/psycopg/_pipeline.py +++ b/psycopg/psycopg/_pipeline.py @@ -6,13 +6,13 @@ commands pipeline management import logging from types import TracebackType -from typing import Any, List, Optional, Union, Tuple, Type, TypeVar, TYPE_CHECKING +from typing import Any, List, Optional, Union, Tuple, Type, TYPE_CHECKING from typing_extensions import TypeAlias from . import pq from . import errors as e from .abc import PipelineCommand, PQGen -from ._compat import Deque +from ._compat import Deque, Self from .pq.misc import connection_summary from ._encodings import pgconn_encoding from ._preparing import Key, Prepare @@ -218,7 +218,6 @@ class Pipeline(BasePipeline): __module__ = "psycopg" _conn: "Connection[Any]" - _Self = TypeVar("_Self", bound="Pipeline") def __init__(self, conn: "Connection[Any]") -> None: super().__init__(conn) @@ -233,7 +232,7 @@ class Pipeline(BasePipeline): except e._NO_TRACEBACK as ex: raise ex.with_traceback(None) - def __enter__(self: _Self) -> _Self: + def __enter__(self) -> Self: with self._conn.lock: self._conn.wait(self._enter_gen()) return self @@ -262,7 +261,6 @@ class AsyncPipeline(BasePipeline): __module__ = "psycopg" _conn: "AsyncConnection[Any]" - _Self = TypeVar("_Self", bound="AsyncPipeline") def __init__(self, conn: "AsyncConnection[Any]") -> None: super().__init__(conn) @@ -274,7 +272,7 @@ class AsyncPipeline(BasePipeline): except e._NO_TRACEBACK as ex: raise ex.with_traceback(None) - async def __aenter__(self: _Self) -> _Self: + async def __aenter__(self) -> Self: async with self._conn.lock: await self._conn.wait(self._enter_gen()) return self diff --git a/psycopg/psycopg/connection.py b/psycopg/psycopg/connection.py index a38a98fc3..163e9b865 100644 --- a/psycopg/psycopg/connection.py +++ b/psycopg/psycopg/connection.py @@ -28,7 +28,7 @@ from .rows import Row, RowFactory, tuple_row, TupleRow, args_row from .adapt import AdaptersMap from ._enums import IsolationLevel from .cursor import Cursor -from ._compat import LiteralString +from ._compat import LiteralString, Self from .pq.misc import connection_summary from .conninfo import make_conninfo, conninfo_to_dict, ConnectionInfo from .conninfo import conninfo_attempts, ConnDict, timeout_from_conninfo @@ -666,7 +666,6 @@ class Connection(BaseConnection[Row]): server_cursor_factory: Type[ServerCursor[Row]] row_factory: RowFactory[Row] _pipeline: Optional[Pipeline] - _Self = TypeVar("_Self", bound="Connection[Any]") def __init__( self, @@ -692,7 +691,9 @@ class Connection(BaseConnection[Row]): context: Optional[AdaptContext] = None, **kwargs: Union[None, int, str], ) -> "Connection[Row]": - # TODO: returned type should be _Self. See #308. + # TODO: returned type should be Self. See #308. + # Unfortunately we cannot use Self[Row] as Self is not parametric. + # https://peps.python.org/pep-0673/#use-in-generic-classes ... @overload @@ -720,7 +721,7 @@ class Connection(BaseConnection[Row]): cursor_factory: Optional[Type[Cursor[Row]]] = None, context: Optional[AdaptContext] = None, **kwargs: Any, - ) -> "Connection[Any]": + ) -> Self: """ Connect to a database server and return a new `Connection` instance. """ @@ -759,7 +760,7 @@ class Connection(BaseConnection[Row]): rv.prepare_threshold = prepare_threshold return rv - def __enter__(self: _Self) -> _Self: + def __enter__(self) -> Self: return self def __exit__( diff --git a/psycopg/psycopg/connection_async.py b/psycopg/psycopg/connection_async.py index 88544db99..0422159e5 100644 --- a/psycopg/psycopg/connection_async.py +++ b/psycopg/psycopg/connection_async.py @@ -9,7 +9,7 @@ import asyncio import logging from types import TracebackType from typing import Any, AsyncGenerator, AsyncIterator, List, Optional -from typing import Type, TypeVar, Union, cast, overload, TYPE_CHECKING +from typing import Type, Union, cast, overload, TYPE_CHECKING from contextlib import asynccontextmanager from . import pq @@ -20,6 +20,7 @@ from ._tpc import Xid from .rows import Row, AsyncRowFactory, tuple_row, TupleRow, args_row from .adapt import AdaptersMap from ._enums import IsolationLevel +from ._compat import Self from .conninfo import ConnDict, make_conninfo, conninfo_to_dict from .conninfo import conninfo_attempts_async, timeout_from_conninfo from ._pipeline import AsyncPipeline @@ -53,7 +54,6 @@ class AsyncConnection(BaseConnection[Row]): server_cursor_factory: Type[AsyncServerCursor[Row]] row_factory: AsyncRowFactory[Row] _pipeline: Optional[AsyncPipeline] - _Self = TypeVar("_Self", bound="AsyncConnection[Any]") def __init__( self, @@ -79,7 +79,9 @@ class AsyncConnection(BaseConnection[Row]): context: Optional[AdaptContext] = None, **kwargs: Union[None, int, str], ) -> "AsyncConnection[Row]": - # TODO: returned type should be _Self. See #308. + # TODO: returned type should be Self. See #308. + # Unfortunately we cannot use Self[Row] as Self is not parametric. + # https://peps.python.org/pep-0673/#use-in-generic-classes ... @overload @@ -107,7 +109,7 @@ class AsyncConnection(BaseConnection[Row]): row_factory: Optional[AsyncRowFactory[Row]] = None, cursor_factory: Optional[Type[AsyncCursor[Row]]] = None, **kwargs: Any, - ) -> "AsyncConnection[Any]": + ) -> Self: if sys.platform == "win32": loop = asyncio.get_running_loop() if isinstance(loop, asyncio.ProactorEventLoop): @@ -153,7 +155,7 @@ class AsyncConnection(BaseConnection[Row]): rv.prepare_threshold = prepare_threshold return rv - async def __aenter__(self: _Self) -> _Self: + async def __aenter__(self) -> Self: return self async def __aexit__( diff --git a/psycopg/psycopg/copy.py b/psycopg/psycopg/copy.py index 10701e148..7b41b288b 100644 --- a/psycopg/psycopg/copy.py +++ b/psycopg/psycopg/copy.py @@ -12,13 +12,13 @@ import threading from abc import ABC, abstractmethod from types import TracebackType from typing import Any, AsyncIterator, Dict, Generic, Iterator, List, Match, IO -from typing import Optional, Sequence, Tuple, Type, TypeVar, Union, TYPE_CHECKING +from typing import Optional, Sequence, Tuple, Type, Union, TYPE_CHECKING from . import pq from . import adapt from . import errors as e from .abc import Buffer, ConnectionType, PQGen, Transformer -from ._compat import create_task +from ._compat import create_task, Self from .pq.misc import connection_summary from ._cmodule import _psycopg from ._encodings import pgconn_encoding @@ -75,8 +75,6 @@ class BaseCopy(Generic[ConnectionType]): a file for later use. """ - _Self = TypeVar("_Self", bound="BaseCopy[Any]") - formatter: "Formatter" def __init__( @@ -237,7 +235,7 @@ class Copy(BaseCopy["Connection[Any]"]): self.writer = writer self._write = writer.write - def __enter__(self: BaseCopy._Self) -> BaseCopy._Self: + def __enter__(self) -> Self: self._enter() return self @@ -495,7 +493,7 @@ class AsyncCopy(BaseCopy["AsyncConnection[Any]"]): self.writer = writer self._write = writer.write - async def __aenter__(self: BaseCopy._Self) -> BaseCopy._Self: + async def __aenter__(self) -> Self: self._enter() return self diff --git a/psycopg/psycopg/crdb/connection.py b/psycopg/psycopg/crdb/connection.py index 451474b77..49b7d5ffa 100644 --- a/psycopg/psycopg/crdb/connection.py +++ b/psycopg/psycopg/crdb/connection.py @@ -10,6 +10,7 @@ from typing import Any, Optional, Type, Union, overload, TYPE_CHECKING from .. import errors as e from ..abc import AdaptContext from ..rows import Row, RowFactory, AsyncRowFactory, TupleRow +from .._compat import Self from ..conninfo import ConnectionInfo from ..connection import Connection from .._adapters_map import AdaptersMap @@ -95,7 +96,7 @@ class CrdbConnection(_CrdbConnectionMixin, Connection[Row]): ... @classmethod - def connect(cls, conninfo: str = "", **kwargs: Any) -> "CrdbConnection[Any]": + def connect(cls, conninfo: str = "", **kwargs: Any) -> Self: """ Connect to a database server and return a new `CrdbConnection` instance. """ @@ -142,10 +143,8 @@ class AsyncCrdbConnection(_CrdbConnectionMixin, AsyncConnection[Row]): ... @classmethod - async def connect( - cls, conninfo: str = "", **kwargs: Any - ) -> "AsyncCrdbConnection[Any]": - return await super().connect(conninfo, **kwargs) # type: ignore [no-any-return] + async def connect(cls, conninfo: str = "", **kwargs: Any) -> Self: + return await super().connect(conninfo, **kwargs) # type: ignore[no-any-return] class CrdbConnectionInfo(ConnectionInfo): diff --git a/psycopg/psycopg/cursor.py b/psycopg/psycopg/cursor.py index 349f6ef5c..4346657ce 100644 --- a/psycopg/psycopg/cursor.py +++ b/psycopg/psycopg/cursor.py @@ -7,7 +7,7 @@ psycopg cursor objects from functools import partial from types import TracebackType from typing import Any, Generic, Iterable, Iterator, List -from typing import Optional, NoReturn, Sequence, Tuple, Type, TypeVar +from typing import Optional, NoReturn, Sequence, Tuple, Type from typing import overload, TYPE_CHECKING from warnings import warn from contextlib import contextmanager @@ -19,6 +19,7 @@ from .abc import ConnectionType, Query, Params, PQGen from .copy import Copy, Writer as CopyWriter from .rows import Row, RowMaker, RowFactory from ._column import Column +from ._compat import Self from .pq.misc import connection_summary from ._queries import PostgresQuery, PostgresClientQuery from ._pipeline import Pipeline @@ -662,7 +663,6 @@ class BaseCursor(Generic[ConnectionType, Row]): class Cursor(BaseCursor["Connection[Any]", Row]): __module__ = "psycopg" __slots__ = () - _Self = TypeVar("_Self", bound="Cursor[Any]") @overload def __init__(self: "Cursor[Row]", connection: "Connection[Row]"): @@ -686,7 +686,7 @@ class Cursor(BaseCursor["Connection[Any]", Row]): super().__init__(connection) self._row_factory = row_factory or connection.row_factory - def __enter__(self: _Self) -> _Self: + def __enter__(self) -> Self: return self def __exit__( @@ -718,13 +718,13 @@ class Cursor(BaseCursor["Connection[Any]", Row]): return self._row_factory(self) def execute( - self: _Self, + self, query: Query, params: Optional[Params] = None, *, prepare: Optional[bool] = None, binary: Optional[bool] = None, - ) -> _Self: + ) -> Self: """ Execute a query or command to the database. """ diff --git a/psycopg/psycopg/cursor_async.py b/psycopg/psycopg/cursor_async.py index 58fce6420..842de0f70 100644 --- a/psycopg/psycopg/cursor_async.py +++ b/psycopg/psycopg/cursor_async.py @@ -6,7 +6,7 @@ psycopg async cursor objects from types import TracebackType from typing import Any, AsyncIterator, Iterable, List -from typing import Optional, Type, TypeVar, TYPE_CHECKING, overload +from typing import Optional, Type, TYPE_CHECKING, overload from contextlib import asynccontextmanager from . import pq @@ -15,6 +15,7 @@ from .abc import Query, Params from .copy import AsyncCopy, AsyncWriter as AsyncCopyWriter from .rows import Row, RowMaker, AsyncRowFactory from .cursor import BaseCursor +from ._compat import Self from ._pipeline import Pipeline if TYPE_CHECKING: @@ -26,7 +27,6 @@ ACTIVE = pq.TransactionStatus.ACTIVE class AsyncCursor(BaseCursor["AsyncConnection[Any]", Row]): __module__ = "psycopg" __slots__ = () - _Self = TypeVar("_Self", bound="AsyncCursor[Any]") @overload def __init__(self: "AsyncCursor[Row]", connection: "AsyncConnection[Row]"): @@ -50,7 +50,7 @@ class AsyncCursor(BaseCursor["AsyncConnection[Any]", Row]): super().__init__(connection) self._row_factory = row_factory or connection.row_factory - async def __aenter__(self: _Self) -> _Self: + async def __aenter__(self) -> Self: return self async def __aexit__( @@ -78,13 +78,13 @@ class AsyncCursor(BaseCursor["AsyncConnection[Any]", Row]): return self._row_factory(self) async def execute( - self: _Self, + self, query: Query, params: Optional[Params] = None, *, prepare: Optional[bool] = None, binary: Optional[bool] = None, - ) -> _Self: + ) -> Self: try: async with self._conn.lock: await self._conn.wait( diff --git a/psycopg/psycopg/pq/_debug.py b/psycopg/psycopg/pq/_debug.py index f86f3bdcb..1d7c01ac0 100644 --- a/psycopg/psycopg/pq/_debug.py +++ b/psycopg/psycopg/pq/_debug.py @@ -30,8 +30,9 @@ Suggested usage:: import inspect import logging -from typing import Any, Callable, Type, TypeVar, TYPE_CHECKING +from typing import Any, Callable, TypeVar, TYPE_CHECKING from functools import wraps +from .._compat import Self from . import PGconn from .misc import connection_summary @@ -47,7 +48,6 @@ logger = logging.getLogger("psycopg.debug") class PGconnDebug: """Wrapper for a PQconn logging all its access.""" - _Self = TypeVar("_Self", bound="PGconnDebug") _pgconn: "abc.PGconn" def __init__(self, pgconn: "abc.PGconn"): @@ -71,11 +71,11 @@ class PGconnDebug: logger.info("PGconn.%s <- %s", attr, value) @classmethod - def connect(cls: Type[_Self], conninfo: bytes) -> _Self: + def connect(cls, conninfo: bytes) -> Self: return cls(debugging(PGconn.connect)(conninfo)) @classmethod - def connect_start(cls: Type[_Self], conninfo: bytes) -> _Self: + def connect_start(cls, conninfo: bytes) -> Self: return cls(debugging(PGconn.connect_start)(conninfo)) @classmethod diff --git a/psycopg/psycopg/server_cursor.py b/psycopg/psycopg/server_cursor.py index 7a86e599d..013a10b8d 100644 --- a/psycopg/psycopg/server_cursor.py +++ b/psycopg/psycopg/server_cursor.py @@ -5,7 +5,7 @@ psycopg server-side cursor objects. # Copyright (C) 2020 The Psycopg Team from typing import Any, AsyncIterator, List, Iterable, Iterator -from typing import Optional, TypeVar, TYPE_CHECKING, overload +from typing import Optional, TYPE_CHECKING, overload from warnings import warn from . import pq @@ -14,6 +14,7 @@ from . import errors as e from .abc import ConnectionType, Query, Params, PQGen from .rows import Row, RowFactory, AsyncRowFactory from .cursor import BaseCursor, Cursor +from ._compat import Self from .generators import execute from .cursor_async import AsyncCursor @@ -211,7 +212,6 @@ class ServerCursorMixin(BaseCursor[ConnectionType, Row]): class ServerCursor(ServerCursorMixin["Connection[Any]", Row], Cursor[Row]): __module__ = "psycopg" __slots__ = () - _Self = TypeVar("_Self", bound="ServerCursor[Any]") @overload def __init__( @@ -270,13 +270,13 @@ class ServerCursor(ServerCursorMixin["Connection[Any]", Row], Cursor[Row]): super().close() def execute( - self: _Self, + self, query: Query, params: Optional[Params] = None, *, binary: Optional[bool] = None, **kwargs: Any, - ) -> _Self: + ) -> Self: """ Open a cursor to execute a query to the database. """ @@ -353,7 +353,6 @@ class AsyncServerCursor( ): __module__ = "psycopg" __slots__ = () - _Self = TypeVar("_Self", bound="AsyncServerCursor[Any]") @overload def __init__( @@ -409,13 +408,13 @@ class AsyncServerCursor( await super().close() async def execute( - self: _Self, + self, query: Query, params: Optional[Params] = None, *, binary: Optional[bool] = None, **kwargs: Any, - ) -> _Self: + ) -> Self: if kwargs: raise TypeError(f"keyword not supported: {list(kwargs)[0]}") if self._pgconn.pipeline_status: diff --git a/psycopg/psycopg/transaction.py b/psycopg/psycopg/transaction.py index fae3c2ab5..c6405aa43 100644 --- a/psycopg/psycopg/transaction.py +++ b/psycopg/psycopg/transaction.py @@ -7,12 +7,13 @@ Transaction context managers returned by Connection.transaction() import logging from types import TracebackType -from typing import Generic, Iterator, Optional, Type, Union, TypeVar, TYPE_CHECKING +from typing import Generic, Iterator, Optional, Type, Union, TYPE_CHECKING from . import pq from . import sql from . import errors as e from .abc import ConnectionType, PQGen +from ._compat import Self from .pq.misc import connection_summary if TYPE_CHECKING: @@ -235,14 +236,12 @@ class Transaction(BaseTransaction["Connection[Any]"]): __module__ = "psycopg" - _Self = TypeVar("_Self", bound="Transaction") - @property def connection(self) -> "Connection[Any]": """The connection the object is managing.""" return self._conn - def __enter__(self: _Self) -> _Self: + def __enter__(self) -> Self: with self._conn.lock: self._conn.wait(self._enter_gen()) return self @@ -267,13 +266,11 @@ class AsyncTransaction(BaseTransaction["AsyncConnection[Any]"]): __module__ = "psycopg" - _Self = TypeVar("_Self", bound="AsyncTransaction") - @property def connection(self) -> "AsyncConnection[Any]": return self._conn - async def __aenter__(self: _Self) -> _Self: + async def __aenter__(self) -> Self: async with self._conn.lock: await self._conn.wait(self._enter_gen()) return self diff --git a/psycopg_pool/psycopg_pool/_compat.py b/psycopg_pool/psycopg_pool/_compat.py index 9fb2b9b56..3f94efd61 100644 --- a/psycopg_pool/psycopg_pool/_compat.py +++ b/psycopg_pool/psycopg_pool/_compat.py @@ -32,9 +32,15 @@ if sys.version_info >= (3, 9): else: from typing import Counter, Deque +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + __all__ = [ "Counter", "Deque", + "Self", "Task", "create_task", ] diff --git a/psycopg_pool/psycopg_pool/pool.py b/psycopg_pool/psycopg_pool/pool.py index 8254c5934..6d0dcbab4 100644 --- a/psycopg_pool/psycopg_pool/pool.py +++ b/psycopg_pool/psycopg_pool/pool.py @@ -11,7 +11,7 @@ from time import monotonic from queue import Queue, Empty from types import TracebackType from typing import Any, Callable, Dict, Iterator, List -from typing import Optional, Sequence, Type, TypeVar +from typing import Optional, Sequence, Type from weakref import ref from contextlib import contextmanager @@ -22,14 +22,12 @@ from psycopg.pq import TransactionStatus from .base import ConnectionAttempt, BasePool from .sched import Scheduler from .errors import PoolClosed, PoolTimeout, TooManyRequests -from ._compat import Deque +from ._compat import Deque, Self logger = logging.getLogger("psycopg.pool") class ConnectionPool(BasePool[Connection[Any]]): - _Self = TypeVar("_Self", bound="ConnectionPool") - def __init__( self, conninfo: str = "", @@ -385,7 +383,7 @@ class ConnectionPool(BasePool[Connection[Any]]): timeout, ) - def __enter__(self: _Self) -> _Self: + def __enter__(self) -> Self: self.open() return self diff --git a/psycopg_pool/psycopg_pool/pool_async.py b/psycopg_pool/psycopg_pool/pool_async.py index e08f0c494..6454f47e6 100644 --- a/psycopg_pool/psycopg_pool/pool_async.py +++ b/psycopg_pool/psycopg_pool/pool_async.py @@ -10,7 +10,7 @@ from abc import ABC, abstractmethod from time import monotonic from types import TracebackType from typing import Any, AsyncIterator, Awaitable, Callable -from typing import Dict, List, Optional, Sequence, Type, TypeVar +from typing import Dict, List, Optional, Sequence, Type from weakref import ref from contextlib import asynccontextmanager @@ -21,14 +21,12 @@ from psycopg.pq import TransactionStatus from .base import ConnectionAttempt, BasePool from .sched import AsyncScheduler from .errors import PoolClosed, PoolTimeout, TooManyRequests -from ._compat import Task, create_task, Deque +from ._compat import Task, create_task, Deque, Self logger = logging.getLogger("psycopg.pool") class AsyncConnectionPool(BasePool[AsyncConnection[Any]]): - _Self = TypeVar("_Self", bound="AsyncConnectionPool") - def __init__( self, conninfo: str = "", @@ -333,7 +331,7 @@ class AsyncConnectionPool(BasePool[AsyncConnection[Any]]): timeout, ) - async def __aenter__(self: _Self) -> _Self: + async def __aenter__(self) -> Self: await self.open() return self diff --git a/psycopg_pool/setup.cfg b/psycopg_pool/setup.cfg index bc04073d6..a5344e294 100644 --- a/psycopg_pool/setup.cfg +++ b/psycopg_pool/setup.cfg @@ -44,7 +44,7 @@ python_requires = >= 3.7 packages = find: zip_safe = False install_requires = - typing-extensions >= 3.10 + typing-extensions >= 4.0 [options.package_data] psycopg_pool = py.typed