Skip to content

Additional return types for psycopg2 connections #8528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 17, 2022
Merged

Additional return types for psycopg2 connections #8528

merged 9 commits into from
Aug 17, 2022

Conversation

OldSneerJaw
Copy link
Contributor

Adds correct return types for DB connection and cursor methods that deal with namedtuple and dict-like cursors. Currently the types are either incomplete (e.g. the connections' cursor methods do not specify return types) or wrong (e.g. DictCursor's fetch* methods should not return tuples, which is currently inherited from DictCursorBase).

@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@OldSneerJaw
Copy link
Contributor Author

I'm not sure of the best approach to solving the mypy errors in CI. The cases where it is reporting that an override's return types are incompatible with the super return types are how it is actually implemented in the underlying library's code (i.e. the stubs I've added merely reflect the reality of the code). Thoughts?

@Akuli
Copy link
Collaborator

Akuli commented Aug 11, 2022

Stubs should reflect what the library actually does. We use # type: ignore[override] to silence type checker errors in situations like this.

Comment on lines -40 to +52
def cursor(self, *args, **kwargs): ...
@overload
def cursor(self, name: str | bytes | None = ..., *, withhold: bool = ..., scrollable: bool | None = ...) -> DictCursor: ...
@overload
def cursor(
self,
name: str | bytes | None = ...,
*,
cursor_factory: Callable[..., _T_cur],
withhold: bool = ...,
scrollable: bool | None = ...,
) -> _T_cur: ...
@overload
def cursor(
self, name: str | bytes | None, cursor_factory: Callable[..., _T_cur], withhold: bool = ..., scrollable: bool | None = ...
) -> _T_cur: ...
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These overloads are consistent with the base class's definitions of the cursor method except that the default cursor_factory case has been updated to return DictCursor. This comment also applies to RealDictConnection and NamedTupleConnection.

Comment on lines +63 to +62
def fetchone(self) -> DictRow | None: ... # type: ignore[override]
def fetchmany(self, size: int | None = ...) -> list[DictRow]: ... # type: ignore[override]
def fetchall(self) -> list[DictRow]: ... # type: ignore[override]
def __next__(self) -> DictRow: ... # type: ignore[override]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return types aren't compatible with the base cursor class's definition, but that is consistent with the reality of the implementation. This comment also applies to the other modified cursor type defs.

@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@OldSneerJaw
Copy link
Contributor Author

All right. I have made several changes, including adding # type: ignore for the incompatible return types. I believe this PR is ready for review now.

class DictCursorBase(_cursor):
row_factory: Any
def __init__(self, *args, **kwargs) -> None: ...
def fetchone(self) -> tuple[Any, ...] | None: ...
def fetchmany(self, size: int | None = ...) -> list[tuple[Any, ...]]: ...
def fetchall(self) -> list[tuple[Any, ...]]: ...
def __iter__(self): ...
def __next__(self) -> tuple[Any, ...]: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for this change? Both __iter__ and __next__ are inherited from cursor, so we don't need them here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, you're right. in fact fetchone, fetchmany, fetchall and row_factory are all superfluous too. I'll clean them up.


class DictCursor(DictCursorBase):
def __init__(self, *args, **kwargs) -> None: ...
index: Any
def execute(self, query, vars: Any | None = ...): ...
def callproc(self, procname, vars: Any | None = ...): ...
def fetchone(self) -> DictRow | None: ... # type: ignore[override]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative could be to make the base class generic. But that may cause problems until we have TypeVar defaults.

def fetchmany(self, size: int | None = ...) -> list[tuple[Any, ...]]: ...
def fetchall(self) -> list[tuple[Any, ...]]: ...
def __iter__(self): ...
def fetchone(self) -> NamedTuple | None: ... # type: ignore[override]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning typing.NamedTuple is a little weird, but it apparently works with both pyright and mypy, so I suppose it's fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. But at least it offers a hint to the caller that they can use either list indexing or access the fields with dot-notation.

@github-actions

This comment has been minimized.

@OldSneerJaw
Copy link
Contributor Author

One new commit for review.

DB connection and cursor methods that deal with `dict`-style results now indicate the expected return types as implemented.
DB connection and cursor methods that deal with `namedtuple` results now indicate the expected return types as implemented.
As required by flake8, the `Iterator` referenced in psycopg2's `extras` module has been switched to the `collections.abc.Iterator[T]` variant.
The previous return type of `Self` is wrong; `cursor`'s `__iter__` method returns an `Iterator`.
The previous attempt to fix the return type of `cursor`'s (and subclasses) `__iter__` method was misguided; they should indeed return `Self`. It's the `__next__` methods that had to be updated to return the correct record/row instance type.
Provides full method signatures for the `cursor` methods of the `DictConnection`, `RealDictConnection` and `NamedTupleConnection` types. Notably this includes a default value for `cursor_factory` in each case, while preserving the option to override the parameter manually.
The return types of the `fetch*` and `__next__` methods of `DictCursor`, `RealDictCursor` and `NamedTupleCursor` are incompatible with the base `cursor` class's corresponding methods' return types. However, this does accurately reflect reality, so ignore the mypy errors in those cases.
As required by flake8, the `Callable` referenced in psycopg2's `extras` module has been switched to the `Callable` variant.
Several members in the `DictCursorBase` type definition were entirely unnecessary, so they have been removed. In addition, adds a type to the `size` param of the `cursor`'s `fetchmany` method.
@github-actions
Copy link
Contributor

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

@JelleZijlstra JelleZijlstra merged commit 234ef7e into python:master Aug 17, 2022
@OldSneerJaw OldSneerJaw deleted the psycopg2-connections branch August 18, 2022 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants