Skip to content

Conversation

@tilboerner
Copy link
Contributor

Fixes #15308.

row_factory, if not None, is a callable that receives a Cursor instance and a tuple of row values.

sqlite3.Row itself is such a callable, but claiming that it will be passed as an argument to the row_factory function is wrong, as it does not match documented and actual behavior.

Although not necessary (since it's covered by the Callable[...]), the corrected type hint leaves type[Row] in place as an explicit option to make this connection immediately obvious.

See
https://docs.python.org/3.14/library/sqlite3.html#sqlite3.Cursor.row_factory


I'm not sure about PR etiquette on this project, so this is my best effort after reading CONTRIBUTING.md and the contents of tests/. Happy to make any suggested corrections.

Tested with pre-commit run --all-files and python3 tests/runtests.py stdlib/sqlite3. (The latter produces unrelated stubtest errors for me, which are the same when running the command on the unmodified main branch.)

Manual test

Use the following Python snippet with valid code that produces a type checker false positive with the faulty stubs, but works with the proposed fix:

(.venv) ~/g/typeshed (main|✔) $ cat > /tmp/test.py
import sqlite3
from typing import Any

def my_factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> tuple[Any, ...]:
    print(type(cursor), type(row))
    return row

conn: sqlite3.Connection = sqlite3.connect(":memory:")
conn.row_factory = my_factory
print(conn.execute("SELECT 1").fetchall())

Output of the script itself shows a tuple is being passed:

(.venv) ~/g/typeshed (main|✔) $ python /tmp/test.py
<class 'sqlite3.Cursor'> <class 'tuple'>
[(1,)]

Unpatched false positive:

(.venv) ~/g/typeshed (main|✔) $ pyright /tmp/test.py
/tmp/test.py
  /tmp/test.py:9:20 - error: Cannot assign to attribute "row_factory" for class "Connection"
    Type "(cursor: Cursor, row: tuple[Any, ...]) -> tuple[Any, ...]" is not assignable to type "_RowFactoryOptions"
      Type "(cursor: Cursor, row: tuple[Any, ...]) -> tuple[Any, ...]" is not assignable to type "type[Row]"
      Type "(cursor: Cursor, row: tuple[Any, ...]) -> tuple[Any, ...]" is not assignable to type "(Cursor, Row) -> object"
        Parameter 2: type "Row" is incompatible with type "tuple[Any, ...]"
          "Row" is not assignable to "tuple[Any, ...]"
      "FunctionType" is not assignable to "None" (reportAttributeAccessIssue)
1 error, 0 warnings, 0 informations

With fix:

(.venv) ~/g/typeshed (main|✔) [0|1]$ git stash pop
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   stdlib/sqlite3/__init__.pyi

(.venv) ~/g/typeshed (main|✚1) $ git diff
diff --git a/stdlib/sqlite3/__init__.pyi b/stdlib/sqlite3/__init__.pyi
index de5309235..56bccb56d 100644
--- a/stdlib/sqlite3/__init__.pyi
+++ b/stdlib/sqlite3/__init__.pyi
@@ -222,5 +222,5 @@ _AdaptedInputData: TypeAlias = _SqliteData | Any
 _Parameters: TypeAlias = SupportsLenAndGetItem[_AdaptedInputData] | Mapping[str, _AdaptedInputData]
 # Controls the legacy transaction handling mode of sqlite3.
 _IsolationLevel: TypeAlias = Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None
-_RowFactoryOptions: TypeAlias = type[Row] | Callable[[Cursor, Row], object] | None
+_RowFactoryOptions: TypeAlias = type[Row] | Callable[[Cursor, tuple[Any, ...]], object] | None
	
(.venv) ~/g/typeshed (main|✚1) $ pyright /tmp/test.py
Config contains unrecognized setting "$schema".
0 errors, 0 warnings, 0 informations

`row_factory`, if not `None`, is a callable that receives a Cursor instance and a tuple of row values. 

`sqlite3.Row` itself is such a callable, but claiming that it will be passed as an argument to the row_factory function is wrong, as it does not match documented and actual behavior.

Although not necessary (since it's covered by the `Callable[...]`), the corrected type hint leaves `type[Row]` in place as an explicit option to make this connection immediately obvious.


See
https://docs.python.org/3.14/library/sqlite3.html#sqlite3.Cursor.row_factory
@github-actions
Copy link
Contributor

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

@tilboerner
Copy link
Contributor Author

Sorry, I made a manual edit in Github instead of pushing my local changes and introduced a syntax error. 🙄

A fix is in a follow-up commit. I don't want to unilaterally change git history, so please feel free to squash that. I'd also be happy to force-push a squashed version myself if you prefer.

@srittau
Copy link
Collaborator

srittau commented Jan 23, 2026

Sorry, I made a manual edit in Github instead of pushing my local changes and introduced a syntax error. 🙄

Please don't look at the commit history of some of my PRs ...

A fix is in a follow-up commit. I don't want to unilaterally change git history, so please feel free to squash that. I'd also be happy to force-push a squashed version myself if you prefer.

We actually prefer not force-pushing as it makes reviewing changes easier. All PRs get squash merged when ready.

Copy link
Collaborator

@srittau srittau left a comment

Choose a reason for hiding this comment

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

Thanks!

@srittau srittau merged commit 65ae6a2 into python:main Jan 23, 2026
63 checks passed
@tilboerner tilboerner deleted the patch-1 branch January 23, 2026 13:45
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.

Wrong type hint for row_factory in sqlite3.Connection and sqlit3.Cursor

2 participants