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 b78ca94af fix(python/adbc_driver_manager): reimport PyArrow if 
installed mid-session (#4641)
b78ca94af is described below

commit b78ca94af4bc31be0433bcbf464573884faa1f74
Author: Bryce Mecum <[email protected]>
AuthorDate: Sun Aug 2 15:45:05 2026 -0700

    fix(python/adbc_driver_manager): reimport PyArrow if installed mid-session 
(#4641)
    
    Currently, with the dbapi module in the Python driver manager, if I am
    using a REPL or notebook session started without PyArrow installed, I
    have to restart my session to pick PyArrow up so I can use
    PyArrow-enabled methods. This is because the module attempts to import
    PyArrow at startup and sets a persistent flag that never changes after
    import.
    
    It would be nicer if we could rescue the user so they can install
    PyArrow in their environment while a REPL or notebook is running, make
    the call again, and have it work.
    
    Steps to reproduce:
    
    1. Create an environment without PyArrow:
    
    ```sh
    python3 -m venv .venv
    source .venv/bin/activate
    pip install adbc_driver_manager
    ```
    
    2. See we get the expected error when we try to use a PyArrow-enabled
    routine:
    
    ```python
    .venv $ python
    Python 3.14.0 (main, Oct 14 2025, 21:10:22) [Clang 20.1.4 ] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from adbc_driver_manager import dbapi
    >>> con = dbapi.connect("sqlite://:memory:")
    >>> cur = con.cursor()
    >>> cur.execute("SELECT 1").fetch_arrow_table()
    Traceback (most recent call last):
      File "<python-input-3>", line 1, in <module>
        cur.execute("SELECT 1").fetch_arrow_table()
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
      File 
"/Users/bryce/src/apache/arrow-adbc/test_env/.venv/lib/python3.14/site-packages/adbc_driver_manager/dbapi.py",
 line 1355, in fetch_arrow_table
        return self._results.fetch_arrow_table()
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
      File 
"/Users/bryce/src/apache/arrow-adbc/test_env/.venv/lib/python3.14/site-packages/adbc_driver_manager/dbapi.py",
 line 1532, in fetch_arrow_table
        return _blocking_call(self.reader.read_all, (), {}, self._stmt.cancel)
                              ^^^^^^^^^^^
      File 
"/Users/bryce/src/apache/arrow-adbc/test_env/.venv/lib/python3.14/site-packages/adbc_driver_manager/dbapi.py",
 line 1466, in reader
        _requires_pyarrow()
        ~~~~~~~~~~~~~~~~~^^
      File 
"/Users/bryce/src/apache/arrow-adbc/test_env/.venv/lib/python3.14/site-packages/adbc_driver_manager/dbapi.py",
 line 1588, in _requires_pyarrow
        raise ProgrammingError(
        ...<2 lines>...
        )
    adbc_driver_manager.ProgrammingError: This API requires PyArrow to be 
installed
    ```
    
    3. Try to fix it in another shell
    
    ```sh
    source .venv/bin/activate
    pip install pyarrow
    ```
    
    4. Run (2) again and get the same error
    
    The Python driver manager's dbapi module guards PyArrow-enabled routines
    with `_requires_pyarrow()`. This PR tweaks that helper and lets it
    attempt to re-import PyArrow but only if PyArrow wasn't enabled at
    import time.
    
    This PR was generated using an LLM.
---
 .../adbc_driver_manager/dbapi.py                   | 23 +++++++++----
 python/adbc_driver_manager/tests/test_dbapi.py     | 38 ++++++++++++++++++++++
 2 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/python/adbc_driver_manager/adbc_driver_manager/dbapi.py 
b/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
index d3cb15750..254ab34f5 100644
--- a/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
+++ b/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
@@ -69,6 +69,8 @@ if typing.TYPE_CHECKING:
     import pyarrow
     from typing_extensions import CapsuleType, Self
 
+    from ._reader import AdbcRecordBatchReader
+
 # ----------------------------------------------------------
 # Globals
 
@@ -1445,7 +1447,7 @@ class _RowIterator(_Closeable):
         self._stmt = stmt
         self._handle: Optional[_lib.ArrowArrayStreamHandle] = handle
         self._backend = dbapi_backend
-        self._reader: Optional["_reader.AdbcRecordBatchReader"] = None
+        self._reader: Optional["AdbcRecordBatchReader"] = None
         self._current_batch = None
         self._next_row = 0
         self._finished = False
@@ -1461,7 +1463,7 @@ class _RowIterator(_Closeable):
             handle.release()
 
     @property
-    def reader(self) -> "_reader.AdbcRecordBatchReader":
+    def reader(self) -> "AdbcRecordBatchReader":
         if self._reader is None:
             _requires_pyarrow()
             if self._handle is None:
@@ -1584,8 +1586,17 @@ def _is_arrow_data(data) -> bool:
 
 
 def _requires_pyarrow() -> None:
+    global _has_pyarrow, _reader, pyarrow
     if not _has_pyarrow:
-        raise ProgrammingError(
-            "This API requires PyArrow to be installed",
-            status_code=_lib.AdbcStatusCode.INVALID_STATE,
-        )
+        try:
+            import pyarrow
+            import pyarrow.dataset
+        except ImportError:
+            raise ProgrammingError(
+                "This API requires PyArrow to be installed",
+                status_code=_lib.AdbcStatusCode.INVALID_STATE,
+            )
+        from . import _reader as _reader_mod
+
+        _reader = _reader_mod
+        _has_pyarrow = True
diff --git a/python/adbc_driver_manager/tests/test_dbapi.py 
b/python/adbc_driver_manager/tests/test_dbapi.py
index 8653d94e8..d266dbd12 100644
--- a/python/adbc_driver_manager/tests/test_dbapi.py
+++ b/python/adbc_driver_manager/tests/test_dbapi.py
@@ -722,6 +722,44 @@ def test_close_connection_suppresses_cursor_close_error() 
-> None:
     assert real_cursor._closed
 
 
+def test_requires_pyarrow_late_install(monkeypatch) -> None:
+    """Test that _requires_pyarrow recovers if PyArrow becomes available
+    after import."""
+    # Simulate the state where PyArrow wasn't available at module load time
+    monkeypatch.setattr(dbapi, "_has_pyarrow", False)
+    monkeypatch.delattr(dbapi, "_reader", raising=False)
+    monkeypatch.delattr(dbapi, "pyarrow", raising=False)
+
+    # Should not raise because PyArrow is actually importable now
+    dbapi._requires_pyarrow()
+
+    # The flag should be updated so subsequent calls don't re-import
+    assert dbapi._has_pyarrow is True
+    # The _reader Cython extension should now be available at module level
+    assert hasattr(dbapi, "_reader")
+    assert hasattr(dbapi._reader, "AdbcRecordBatchReader")
+    # pyarrow must be in module globals for callers that use it directly
+    assert hasattr(dbapi, "pyarrow")
+
+
+def test_requires_pyarrow_truly_missing(monkeypatch) -> None:
+    """Test that _requires_pyarrow still raises when PyArrow is genuinely 
missing."""
+    import builtins
+
+    real_import = builtins.__import__
+
+    def mock_import(name, *args, **kwargs):
+        if name.startswith("pyarrow"):
+            raise ImportError("mocked")
+        return real_import(name, *args, **kwargs)
+
+    monkeypatch.setattr(dbapi, "_has_pyarrow", False)
+    monkeypatch.setattr(builtins, "__import__", mock_import)
+
+    with pytest.raises(dbapi.ProgrammingError, match="requires PyArrow"):
+        dbapi._requires_pyarrow()
+
+
 @pytest.mark.sqlite
 def test_connect(tmp_path: pathlib.Path, monkeypatch) -> None:
     with dbapi.connect(driver="adbc_driver_sqlite") as conn:

Reply via email to