You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by li...@apache.org on 2023/06/15 17:04:52 UTC

[arrow-adbc] branch main updated: test(python): set up pyright (#790)

This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 18e96c71 test(python): set up pyright (#790)
18e96c71 is described below

commit 18e96c7145377573ca78b73cb647013c47cff78f
Author: David Li <li...@gmail.com>
AuthorDate: Thu Jun 15 13:04:47 2023 -0400

    test(python): set up pyright (#790)
    
    Fixes #545.
---
 .github/workflows/native-unix.yml                  |   5 +-
 .pre-commit-config.yaml                            |   2 +-
 CONTRIBUTING.md                                    |  13 ++
 ci/conda_env_python.txt                            |   1 +
 ci/scripts/python_build.sh                         |   2 +-
 ci/scripts/python_typecheck.sh                     |  54 +++++++
 dev/release/rat_exclude_files.txt                  |  19 +--
 pyrightconfig.json                                 |   9 ++
 python/adbc_driver_flightsql/MANIFEST.in           |   1 +
 .../adbc_driver_flightsql/py.typed                 |   0
 .../adbc_driver_flightsql/tests/test_lowlevel.py   |   2 +-
 python/adbc_driver_manager/MANIFEST.in             |   1 +
 .../adbc_driver_manager/_lib.pyi                   | 165 +++++++++++++++++++++
 .../adbc_driver_manager/dbapi.py                   |  22 +--
 .../adbc_driver_manager/py.typed                   |   0
 python/adbc_driver_manager/tests/test_lowlevel.py  |   2 +-
 python/adbc_driver_postgresql/MANIFEST.in          |   1 +
 python/adbc_driver_postgresql/tests/test_dbapi.py  |   4 +-
 .../adbc_driver_postgresql/tests/test_lowlevel.py  |   2 +-
 python/adbc_driver_snowflake/MANIFEST.in           |   1 +
 .../adbc_driver_snowflake/py.typed                 |   0
 .../adbc_driver_snowflake/tests/test_lowlevel.py   |   2 +-
 python/adbc_driver_sqlite/MANIFEST.in              |   1 +
 .../adbc_driver_sqlite/adbc_driver_sqlite/py.typed |   0
 python/adbc_driver_sqlite/tests/test_lowlevel.py   |   2 +-
 25 files changed, 284 insertions(+), 27 deletions(-)

diff --git a/.github/workflows/native-unix.yml b/.github/workflows/native-unix.yml
index c2d489ee..c839cbbd 100644
--- a/.github/workflows/native-unix.yml
+++ b/.github/workflows/native-unix.yml
@@ -399,7 +399,6 @@ jobs:
           mamba install -c conda-forge \
             python=${{ matrix.python }} \
             --file ci/conda_env_cpp.txt \
-            --file ci/conda_env_docs.txt \
             --file ci/conda_env_python.txt
       - uses: actions/setup-go@v3
         with:
@@ -466,6 +465,10 @@ jobs:
         shell: bash -l {0}
         run: |
           env BUILD_ALL=0 BUILD_DRIVER_SNOWFLAKE=1 ./ci/scripts/python_test.sh "$(pwd)" "$(pwd)/build" "$HOME/local"
+      - name: Typecheck Python
+        shell: bash -l {0}
+        run: |
+          ./ci/scripts/python_typecheck.sh "$(pwd)"
 
   python-docs:
     name: "Documentation ${{ matrix.python }} (Conda/${{ matrix.os }})"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ab6d5519..82df862c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -68,7 +68,7 @@ repos:
     rev: 22.3.0
     hooks:
     - id: black
-      types_or: [python]
+      types_or: [pyi, python]
   - repo: https://github.com/PyCQA/flake8
     rev: 4.0.1
     hooks:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d174fe9e..bcd2315e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -190,6 +190,19 @@ $ pip install -e .[test]
 $ pytest -vvx
 ```
 
+Type checking is done with [pyright][pyright].  There is a script to
+run the type checker:
+
+```shell
+# Build native libraries first
+$ env ADBC_USE_ASAN=0 ADBC_USE_UBSAN=0 ./ci/scripts/cpp_build.sh $(pwd) $(pwd)/build
+# Install Python packages
+$ ./ci/scripts/python_build.sh $(pwd) $(pwd)/build
+# Run type checker
+$ ./ci/scripts/python_typecheck.sh $(pwd)
+```
+
+[pyright]: https://microsoft.github.io/pyright/
 [pytest]: https://docs.pytest.org/
 [setuptools]: https://setuptools.pypa.io/en/latest/index.html
 
diff --git a/ci/conda_env_python.txt b/ci/conda_env_python.txt
index b5348b04..63d30fc9 100644
--- a/ci/conda_env_python.txt
+++ b/ci/conda_env_python.txt
@@ -18,6 +18,7 @@
 Cython
 pandas
 pyarrow>=8.0.0
+pyright
 pytest
 setuptools
 
diff --git a/ci/scripts/python_build.sh b/ci/scripts/python_build.sh
index e7f5521f..a6aeb384 100755
--- a/ci/scripts/python_build.sh
+++ b/ci/scripts/python_build.sh
@@ -46,7 +46,7 @@ build_subproject() {
         export ADBC_SNOWFLAKE_LIBRARY="${install_dir}/lib/libadbc_driver_snowflake.${ADBC_LIBRARY_SUFFIX}"
     fi
 
-    python -m pip install -e "${source_dir}/python/${subproject}"
+    python -m pip install "${source_dir}/python/${subproject}"
 }
 
 main() {
diff --git a/ci/scripts/python_typecheck.sh b/ci/scripts/python_typecheck.sh
new file mode 100755
index 00000000..71649ca8
--- /dev/null
+++ b/ci/scripts/python_typecheck.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -e
+
+: ${BUILD_ALL:=1}
+: ${BUILD_DRIVER_FLIGHTSQL:=${BUILD_ALL}}
+: ${BUILD_DRIVER_MANAGER:=${BUILD_ALL}}
+: ${BUILD_DRIVER_POSTGRESQL:=${BUILD_ALL}}
+: ${BUILD_DRIVER_SQLITE:=${BUILD_ALL}}
+: ${BUILD_DRIVER_SNOWFLAKE:=${BUILD_ALL}}
+
+: ${PYRIGHT_OPTIONS:=""}
+
+main() {
+    local -r source_dir="${1}"
+
+    if [[ "${BUILD_DRIVER_FLIGHTSQL}" -gt 0 ]]; then
+        pyright ${PYRIGHT_OPTIONS} "${source_dir}/python/adbc_driver_flightsql"
+    fi
+
+    if [[ "${BUILD_DRIVER_MANAGER}" -gt 0 ]]; then
+        pyright ${PYRIGHT_OPTIONS} "${source_dir}/python/adbc_driver_manager"
+    fi
+
+    if [[ "${BUILD_DRIVER_POSTGRESQL}" -gt 0 ]]; then
+        pyright ${PYRIGHT_OPTIONS} "${source_dir}/python/adbc_driver_postgresql"
+    fi
+
+    if [[ "${BUILD_DRIVER_SQLITE}" -gt 0 ]]; then
+        pyright ${PYRIGHT_OPTIONS} "${source_dir}/python/adbc_driver_sqlite"
+    fi
+
+    if [[ "${BUILD_DRIVER_SNOWFLAKE}" -gt 0 ]]; then
+        pyright ${PYRIGHT_OPTIONS} "${source_dir}/python/adbc_driver_snowflake"
+    fi
+}
+
+main "$@"
diff --git a/dev/release/rat_exclude_files.txt b/dev/release/rat_exclude_files.txt
index e72973c4..5dce6858 100644
--- a/dev/release/rat_exclude_files.txt
+++ b/dev/release/rat_exclude_files.txt
@@ -1,8 +1,17 @@
 *.json
+*.Rproj
+*.Rd
+c/vendor/sqlite3/sqlite3.c
+c/vendor/sqlite3/sqlite3.h
 ci/conda/.ci_support/*
 ci/conda/.gitattributes
 ci/linux-packages/changelog
 ci/linux-packages/*.install
+csharp/Apache.Arrow.Adbc.sln
+csharp/src/Apache.Arrow.Adbc.FlightSql/Apache.Arrow.Adbc.FlightSql.csproj
+csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
+csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Apache.Arrow.Adbc.FlightSql.Tests.csproj
+csharp/test/Apache.Arrow.Adbc.Tests/Apache.Arrow.Adbc.Tests.csproj
 dev/release/rat_exclude_files.txt
 docs/source/format/*.drawio
 docs/source/format/*.svg
@@ -14,6 +23,7 @@ go/adbc/status_string.go
 go/adbc/infocode_string.go
 go/adbc/go.sum
 java/.mvn/jvm.config
+python/*/*/py.typed
 rat.txt
 r/adbcdrivermanager/DESCRIPTION
 r/adbcdrivermanager/NAMESPACE
@@ -29,12 +39,3 @@ r/adbcpostgresql/.Rbuildignore
 r/adbcsnowflake/DESCRIPTION
 r/adbcsnowflake/NAMESPACE
 r/adbcsnowflake/.Rbuildignore
-c/vendor/sqlite3/sqlite3.c
-c/vendor/sqlite3/sqlite3.h
-*.Rproj
-*.Rd
-csharp/Apache.Arrow.Adbc.sln
-csharp/src/Apache.Arrow.Adbc.FlightSql/Apache.Arrow.Adbc.FlightSql.csproj
-csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
-csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Apache.Arrow.Adbc.FlightSql.Tests.csproj
-csharp/test/Apache.Arrow.Adbc.Tests/Apache.Arrow.Adbc.Tests.csproj
diff --git a/pyrightconfig.json b/pyrightconfig.json
new file mode 100644
index 00000000..e78d074f
--- /dev/null
+++ b/pyrightconfig.json
@@ -0,0 +1,9 @@
+{
+    "exclude": [
+        "**/.asv/",
+        "**/_version.py",
+        "**/setup.py",
+        "**/build/",
+        "**/benchmarks/"
+    ]
+}
diff --git a/python/adbc_driver_flightsql/MANIFEST.in b/python/adbc_driver_flightsql/MANIFEST.in
index da9b1fb7..c30e0623 100644
--- a/python/adbc_driver_flightsql/MANIFEST.in
+++ b/python/adbc_driver_flightsql/MANIFEST.in
@@ -18,3 +18,4 @@
 # setuptools manifest
 
 include adbc_driver_flightsql/libadbc_driver_flightsql.so
+include adbc_driver_flightsql/py.typed
diff --git a/python/adbc_driver_flightsql/adbc_driver_flightsql/py.typed b/python/adbc_driver_flightsql/adbc_driver_flightsql/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/python/adbc_driver_flightsql/tests/test_lowlevel.py b/python/adbc_driver_flightsql/tests/test_lowlevel.py
index 203b95b1..149888d4 100644
--- a/python/adbc_driver_flightsql/tests/test_lowlevel.py
+++ b/python/adbc_driver_flightsql/tests/test_lowlevel.py
@@ -44,4 +44,4 @@ def test_options(dremio):
 
 
 def test_version():
-    assert adbc_driver_flightsql.__version__
+    assert adbc_driver_flightsql.__version__  # type:ignore
diff --git a/python/adbc_driver_manager/MANIFEST.in b/python/adbc_driver_manager/MANIFEST.in
index 75f4a0dd..fe985282 100644
--- a/python/adbc_driver_manager/MANIFEST.in
+++ b/python/adbc_driver_manager/MANIFEST.in
@@ -22,3 +22,4 @@ include NOTICE.txt
 include adbc_driver_manager/adbc.h
 include adbc_driver_manager/adbc_driver_manager.cc
 include adbc_driver_manager/adbc_driver_manager.h
+include adbc_driver_manager/py.typed
diff --git a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyi b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyi
new file mode 100644
index 00000000..8f107369
--- /dev/null
+++ b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyi
@@ -0,0 +1,165 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# NOTE: generated with mypy's stubgen, then hand-edited to fix things
+
+from typing import Any, ClassVar, Dict, List, Literal, Optional, Tuple, Union
+
+from typing import overload
+import enum
+import typing
+
+INGEST_OPTION_MODE: str
+INGEST_OPTION_MODE_APPEND: str
+INGEST_OPTION_MODE_CREATE: str
+INGEST_OPTION_TARGET_TABLE: str
+
+class AdbcConnection(_AdbcHandle):
+    def __init__(self, database: "AdbcDatabase", **kwargs: str) -> None: ...
+    def close(self) -> None: ...
+    def commit(self) -> None: ...
+    def get_info(
+        self, info_codes: Optional[List[Union[int, "AdbcInfoCode"]]] = None
+    ) -> "ArrowArrayStreamHandle": ...
+    def get_objects(
+        self,
+        depth: "GetObjectsDepth",
+        catalog: Optional[str] = None,
+        db_schema: Optional[str] = None,
+        table_name: Optional[str] = None,
+        table_types: Optional[List[str]] = None,
+        column_name: Optional[str] = None,
+    ) -> "ArrowArrayStreamHandle": ...
+    def get_table_schema(
+        self,
+        catalog: Optional[str],
+        db_schema: Optional[str],
+        table_name: str,
+    ) -> "ArrowSchemaHandle": ...
+    def get_table_types(self) -> "ArrowArrayStreamHandle": ...
+    def read_partition(self, partition: bytes) -> "ArrowArrayStreamHandle": ...
+    def rollback(self) -> None: ...
+    def set_autocommit(self, enabled: bool) -> None: ...
+    def set_options(self, **kwargs: str) -> None: ...
+
+class AdbcDatabase(_AdbcHandle):
+    def __init__(self, **kwargs: str) -> None: ...
+    def close(self) -> None: ...
+    def set_options(self, **kwargs: str) -> None: ...
+
+class AdbcInfoCode(enum.IntEnum):
+    DRIVER_ARROW_VERSION = ...
+    DRIVER_NAME = ...
+    DRIVER_VERSION = ...
+    VENDOR_ARROW_VERSION = ...
+    VENDOR_NAME = ...
+    VENDOR_VERSION = ...
+
+class AdbcStatement(_AdbcHandle):
+    def __init__(self, *args, **kwargs) -> None: ...
+    def bind(self, *args, **kwargs) -> Any: ...
+    def bind_stream(self, *args, **kwargs) -> Any: ...
+    def close(self) -> None: ...
+    def execute_partitions(self, *args, **kwargs) -> Any: ...
+    def execute_query(self, *args, **kwargs) -> Any: ...
+    def execute_update(self, *args, **kwargs) -> Any: ...
+    def get_parameter_schema(self, *args, **kwargs) -> Any: ...
+    def prepare(self, *args, **kwargs) -> Any: ...
+    def set_options(self, *args, **kwargs) -> Any: ...
+    def set_sql_query(self, *args, **kwargs) -> Any: ...
+    def set_substrait_plan(self, *args, **kwargs) -> Any: ...
+    def __reduce__(self) -> Any: ...
+    def __setstate__(self, state) -> Any: ...
+
+class AdbcStatusCode(enum.IntEnum):
+    ALREADY_EXISTS = ...
+    CANCELLED = ...
+    INTEGRITY = ...
+    INTERNAL = ...
+    INVALID_ARGUMENT = ...
+    INVALID_DATA = ...
+    INVALID_STATE = ...
+    IO = ...
+    NOT_FOUND = ...
+    NOT_IMPLEMENTED = ...
+    OK = ...
+    TIMEOUT = ...
+    UNAUTHENTICATED = ...
+    UNAUTHORIZED = ...
+    UNKNOWN = ...
+
+class ArrowArrayHandle:
+    address: Any
+
+class ArrowArrayStreamHandle:
+    address: Any
+
+class ArrowSchemaHandle:
+    address: Any
+
+class DataError(DatabaseError): ...
+class DatabaseError(Error): ...
+
+class Error(Exception):
+    status_code: AdbcStatusCode
+    vendor_code: Optional[int]
+    sqlstate: Optional[str]
+
+    def __init__(
+        self,
+        message: str,
+        *,
+        status_code: Union[int, AdbcStatusCode],
+        vendor_code: Optional[str] = None,
+        sqlstate: Optional[str] = None
+    ) -> None: ...
+
+class GetObjectsDepth(enum.IntEnum):
+    ALL = ...
+    CATALOGS = ...
+    COLUMNS = ...
+    DB_SCHEMAS = ...
+    TABLES = ...
+
+class IntegrityError(DatabaseError): ...
+class InterfaceError(Error): ...
+class InternalError(DatabaseError): ...
+
+class NotSupportedError(DatabaseError):
+    def __init__(
+        self,
+        message: str,
+        *,
+        vendor_code: Optional[str] = None,
+        sqlstate: Optional[str] = None
+    ) -> None: ...
+
+class OperationalError(DatabaseError): ...
+class ProgrammingError(DatabaseError): ...
+class Warning(UserWarning): ...
+
+class _AdbcHandle:
+    def __init__(self, *args, **kwargs) -> None: ...
+    def __enter__(self) -> Any: ...
+    def __exit__(self, type, value, traceback) -> Any: ...
+
+def _test_error(
+    status_code: Union[int, AdbcStatusCode],
+    message: str,
+    vendor_code: Optional[int],
+    sqlstate: Optional[str],
+) -> Error: ...
diff --git a/python/adbc_driver_manager/adbc_driver_manager/dbapi.py b/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
index 22fd3eb3..31e4392a 100644
--- a/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
+++ b/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
@@ -29,6 +29,7 @@ under pytest, or when the environment variable
 
 """
 
+import abc
 import datetime
 import os
 import threading
@@ -45,9 +46,8 @@ except ImportError as e:
 from . import _lib
 
 if typing.TYPE_CHECKING:
-    from typing import Self
-
     import pandas
+    from typing_extensions import Self
 
 # ----------------------------------------------------------
 # Globals
@@ -97,12 +97,12 @@ def DateFromTicks(ticks: int) -> Date:
     return Date(*time.localtime(ticks)[:3])
 
 
-def TimeFromTicks(ticks: int) -> Date:
+def TimeFromTicks(ticks: int) -> Time:
     """Construct a time value from a count of seconds."""
     return Time(*time.localtime(ticks)[3:6])
 
 
-def TimestampFromTicks(ticks: int) -> Date:
+def TimestampFromTicks(ticks: int) -> Timestamp:
     """Construct a timestamp value from a count of seconds."""
     return Timestamp(*time.localtime(ticks)[:6])
 
@@ -157,7 +157,7 @@ STRING = _TypeSet([pyarrow.string().id, pyarrow.large_string().id])
 def connect(
     *,
     driver: str,
-    entrypoint: str = None,
+    entrypoint: Optional[str] = None,
     db_kwargs: Optional[Dict[str, str]] = None,
     conn_kwargs: Optional[Dict[str, str]] = None,
     autocommit=False,
@@ -212,7 +212,7 @@ def connect(
 # Classes
 
 
-class _Closeable:
+class _Closeable(abc.ABC):
     """Base class providing context manager interface."""
 
     def __enter__(self) -> "Self":
@@ -221,6 +221,10 @@ class _Closeable:
     def __exit__(self, exc_type, exc_val, exc_tb) -> None:
         self.close()
 
+    @abc.abstractmethod
+    def close(self) -> None:
+        ...
+
 
 class _SharedDatabase(_Closeable):
     """A holder for a shared AdbcDatabase."""
@@ -662,7 +666,7 @@ class Cursor(_Closeable):
         self._bind(arrow_parameters)
         self._rowcount = self._stmt.execute_update()
 
-    def fetchone(self) -> tuple:
+    def fetchone(self) -> Optional[tuple]:
         """Fetch one row of the result."""
         if self._results is None:
             raise ProgrammingError(
@@ -949,7 +953,7 @@ class _RowIterator(_Closeable):
             for field in self._reader.schema
         ]
 
-    def fetchone(self):
+    def fetchone(self) -> Optional[tuple]:
         if self._current_batch is None or self._next_row >= len(self._current_batch):
             try:
                 while True:
@@ -961,7 +965,7 @@ class _RowIterator(_Closeable):
                 self._current_batch = None
                 self._finished = True
 
-        if self._finished:
+        if self._finished or self._current_batch is None:
             return None
 
         row = tuple(arr[self._next_row].as_py() for arr in self._current_batch.columns)
diff --git a/python/adbc_driver_manager/adbc_driver_manager/py.typed b/python/adbc_driver_manager/adbc_driver_manager/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/python/adbc_driver_manager/tests/test_lowlevel.py b/python/adbc_driver_manager/tests/test_lowlevel.py
index 819873e1..15d98e53 100644
--- a/python/adbc_driver_manager/tests/test_lowlevel.py
+++ b/python/adbc_driver_manager/tests/test_lowlevel.py
@@ -46,7 +46,7 @@ def _bind(stmt, batch):
 
 
 def test_version():
-    assert adbc_driver_manager.__version__
+    assert adbc_driver_manager.__version__  # type:ignore
 
 
 def test_database_init():
diff --git a/python/adbc_driver_postgresql/MANIFEST.in b/python/adbc_driver_postgresql/MANIFEST.in
index bba71c47..4e654b19 100644
--- a/python/adbc_driver_postgresql/MANIFEST.in
+++ b/python/adbc_driver_postgresql/MANIFEST.in
@@ -18,3 +18,4 @@
 # setuptools manifest
 
 include adbc_driver_postgresql/libadbc_driver_postgresql.so
+include adbc_driver_postgresql/py.typed
diff --git a/python/adbc_driver_postgresql/tests/test_dbapi.py b/python/adbc_driver_postgresql/tests/test_dbapi.py
index 16301083..8d9b09b5 100644
--- a/python/adbc_driver_postgresql/tests/test_dbapi.py
+++ b/python/adbc_driver_postgresql/tests/test_dbapi.py
@@ -15,13 +15,15 @@
 # specific language governing permissions and limitations
 # under the License.
 
+from typing import Generator
+
 import pytest
 
 from adbc_driver_postgresql import dbapi
 
 
 @pytest.fixture
-def postgres(postgres_uri: str) -> dbapi.Connection:
+def postgres(postgres_uri: str) -> Generator[dbapi.Connection, None, None]:
     with dbapi.connect(postgres_uri) as conn:
         yield conn
 
diff --git a/python/adbc_driver_postgresql/tests/test_lowlevel.py b/python/adbc_driver_postgresql/tests/test_lowlevel.py
index 587f7b84..d19022bc 100644
--- a/python/adbc_driver_postgresql/tests/test_lowlevel.py
+++ b/python/adbc_driver_postgresql/tests/test_lowlevel.py
@@ -38,7 +38,7 @@ def test_query_trivial(postgres):
 
 
 def test_version():
-    assert adbc_driver_postgresql.__version__
+    assert adbc_driver_postgresql.__version__  # type:ignore
 
 
 def test_failed_connection():
diff --git a/python/adbc_driver_snowflake/MANIFEST.in b/python/adbc_driver_snowflake/MANIFEST.in
index 144d50db..7f8d0a39 100644
--- a/python/adbc_driver_snowflake/MANIFEST.in
+++ b/python/adbc_driver_snowflake/MANIFEST.in
@@ -18,3 +18,4 @@
 # setuptools manifest
 
 include adbc_driver_snowflake/libadbc_driver_snowflake.so
+include adbc_driver_snowflake/py.typed
diff --git a/python/adbc_driver_snowflake/adbc_driver_snowflake/py.typed b/python/adbc_driver_snowflake/adbc_driver_snowflake/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/python/adbc_driver_snowflake/tests/test_lowlevel.py b/python/adbc_driver_snowflake/tests/test_lowlevel.py
index f155ddaf..6186f889 100644
--- a/python/adbc_driver_snowflake/tests/test_lowlevel.py
+++ b/python/adbc_driver_snowflake/tests/test_lowlevel.py
@@ -51,4 +51,4 @@ def test_options(snowflake):
 
 
 def test_version():
-    assert adbc_driver_snowflake.__version__
+    assert adbc_driver_snowflake.__version__  # type:ignore
diff --git a/python/adbc_driver_sqlite/MANIFEST.in b/python/adbc_driver_sqlite/MANIFEST.in
index f9de8e7b..95af26a2 100644
--- a/python/adbc_driver_sqlite/MANIFEST.in
+++ b/python/adbc_driver_sqlite/MANIFEST.in
@@ -18,3 +18,4 @@
 # setuptools manifest
 
 include adbc_driver_sqlite/libadbc_driver_sqlite.so
+include adbc_driver_sqlite/py.typed
diff --git a/python/adbc_driver_sqlite/adbc_driver_sqlite/py.typed b/python/adbc_driver_sqlite/adbc_driver_sqlite/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/python/adbc_driver_sqlite/tests/test_lowlevel.py b/python/adbc_driver_sqlite/tests/test_lowlevel.py
index 6ab0acb9..58359a11 100644
--- a/python/adbc_driver_sqlite/tests/test_lowlevel.py
+++ b/python/adbc_driver_sqlite/tests/test_lowlevel.py
@@ -51,4 +51,4 @@ def test_options(sqlite):
 
 
 def test_version():
-    assert adbc_driver_sqlite.__version__
+    assert adbc_driver_sqlite.__version__  # type:ignore