From: Denis Laxalde Date: Mon, 16 May 2022 17:16:53 +0000 (+0200) Subject: fix: only use covariant Row type variable X-Git-Tag: 3.1~81^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F306%2Fhead;p=thirdparty%2Fpsycopg.git fix: only use covariant Row type variable The real variance of Row type variable is covariant, per definition of RowMaker (RowMaker[B] is a subtype of RowMaker[A] if B is a subtype of A; similar to the Box type in [mypy documentation][]). By dropping the Row type variable from Cursor argument in RowFactory, we are now able to only use the covariant Row variable (previously Row_co, now Row). Indeed, RowFactory does not actually depend on Cursor on being parametrized on Row; rather it's the other way around (Cursor's Row type variable comes from its row factory). [mypy documentation]: https://mypy.readthedocs.io/en/latest/generics.html#variance-of-generic-types --- diff --git a/docs/advanced/rows.rst b/docs/advanced/rows.rst index 25e35d99a..c23efe5c5 100644 --- a/docs/advanced/rows.rst +++ b/docs/advanced/rows.rst @@ -86,7 +86,7 @@ Formally, these objects are represented by the `~psycopg.rows.RowFactory` and from psycopg import Cursor class DictRowFactory: - def __init__(self, cursor: Cursor[dict[str, Any]]): + def __init__(self, cursor: Cursor[Any]): self.fields = [c.name for c in cursor.description] def __call__(self, values: Sequence[Any]) -> dict[str, Any]: @@ -96,7 +96,7 @@ or as a plain function: .. code:: python - def dict_row_factory(cursor: Cursor[dict[str, Any]]) -> RowMaker[dict[str, Any]]: + def dict_row_factory(cursor: Cursor[Any]) -> RowMaker[dict[str, Any]]: fields = [c.name for c in cursor.description] def make_row(values: Sequence[Any]) -> dict[str, Any]: diff --git a/psycopg/psycopg/rows.py b/psycopg/psycopg/rows.py index 2f2d64728..9ca75d46c 100644 --- a/psycopg/psycopg/rows.py +++ b/psycopg/psycopg/rows.py @@ -17,15 +17,14 @@ if TYPE_CHECKING: from .cursor import BaseCursor, Cursor from .cursor_async import AsyncCursor -T = TypeVar("T") +T = TypeVar("T", covariant=True) # Row factories -Row = TypeVar("Row") -Row_co = TypeVar("Row_co", covariant=True) +Row = TypeVar("Row", covariant=True) -class RowMaker(Protocol[Row_co]): +class RowMaker(Protocol[Row]): """ Callable protocol taking a sequence of value and returning an object. @@ -37,7 +36,7 @@ class RowMaker(Protocol[Row_co]): Typically, `!RowMaker` functions are returned by `RowFactory`. """ - def __call__(self, __values: Sequence[Any]) -> Row_co: + def __call__(self, __values: Sequence[Any]) -> Row: ... @@ -55,7 +54,7 @@ class RowFactory(Protocol[Row]): use the values to create a dictionary for each record. """ - def __call__(self, __cursor: "Cursor[Row]") -> RowMaker[Row]: + def __call__(self, __cursor: "Cursor[Any]") -> RowMaker[Row]: ... @@ -64,7 +63,7 @@ class AsyncRowFactory(Protocol[Row]): Like `RowFactory`, taking an async cursor as argument. """ - def __call__(self, __cursor: "AsyncCursor[Row]") -> RowMaker[Row]: + def __call__(self, __cursor: "AsyncCursor[Any]") -> RowMaker[Row]: ... @@ -73,7 +72,7 @@ class BaseRowFactory(Protocol[Row]): Like `RowFactory`, taking either type of cursor as argument. """ - def __call__(self, __cursor: "BaseCursor[Any, Row]") -> RowMaker[Row]: + def __call__(self, __cursor: "BaseCursor[Any, Any]") -> RowMaker[Row]: ... @@ -92,7 +91,7 @@ database. """ -def tuple_row(cursor: "BaseCursor[Any, TupleRow]") -> "RowMaker[TupleRow]": +def tuple_row(cursor: "BaseCursor[Any, Any]") -> "RowMaker[TupleRow]": r"""Row factory to represent rows as simple tuples. This is the default factory, used when `~psycopg.Connection.connect()` or @@ -105,7 +104,7 @@ def tuple_row(cursor: "BaseCursor[Any, TupleRow]") -> "RowMaker[TupleRow]": return tuple -def dict_row(cursor: "BaseCursor[Any, DictRow]") -> "RowMaker[DictRow]": +def dict_row(cursor: "BaseCursor[Any, Any]") -> "RowMaker[DictRow]": """Row factory to represent rows as dictionaries. The dictionary keys are taken from the column names of the returned columns. @@ -123,7 +122,7 @@ def dict_row(cursor: "BaseCursor[Any, DictRow]") -> "RowMaker[DictRow]": def namedtuple_row( - cursor: "BaseCursor[Any, NamedTuple]", + cursor: "BaseCursor[Any, Any]", ) -> "RowMaker[NamedTuple]": """Row factory to represent rows as `~collections.namedtuple`. @@ -156,7 +155,7 @@ def class_row(cls: Type[T]) -> BaseRowFactory[T]: :rtype: `!Callable[[Cursor],` `RowMaker`\[~T]] """ - def class_row_(cur: "BaseCursor[Any, T]") -> "RowMaker[T]": + def class_row_(cur: "BaseCursor[Any, Any]") -> "RowMaker[T]": desc = cur.description if desc is None: return no_result diff --git a/tests/test_typing.py b/tests/test_typing.py index 6421db911..e4e55f639 100644 --- a/tests/test_typing.py +++ b/tests/test_typing.py @@ -385,7 +385,7 @@ class Thing: self.kwargs = kwargs def thing_row( - cur: Union[psycopg.Cursor[Thing], psycopg.AsyncCursor[Thing]], + cur: Union[psycopg.Cursor[Any], psycopg.AsyncCursor[Any]], ) -> Callable[[Sequence[Any]], Thing]: assert cur.description names = [d.name for d in cur.description]