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 2022/11/30 18:44:05 UTC

[arrow-adbc] branch main updated: feat(c/driver/sqlite): add Python SQLite driver bindings (#201)

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 ddd715a  feat(c/driver/sqlite): add Python SQLite driver bindings (#201)
ddd715a is described below

commit ddd715a443cf2e5edd15c639af3845afe81af4ee
Author: David Li <li...@gmail.com>
AuthorDate: Wed Nov 30 13:44:00 2022 -0500

    feat(c/driver/sqlite): add Python SQLite driver bindings (#201)
---
 .github/workflows/cpp.yml                          |  22 +++
 c/driver/sqlite/CMakeLists.txt                     |  15 +-
 c/driver/sqlite/sqlite.c                           |   1 +
 c/driver/sqlite/utils.h                            |  11 +-
 c/driver_manager/adbc_driver_manager.cc            |   8 +-
 ci/scripts/python_build.sh                         |  10 +-
 ci/scripts/python_sdist_build.sh                   |   5 +-
 ci/scripts/python_test.sh                          |   5 +
 ci/scripts/python_util.sh                          |  65 +++++--
 ci/scripts/python_wheel_unix_build.sh              |   9 +-
 ci/scripts/python_wheel_windows_build.bat          |  28 ++-
 ci/scripts/python_wheel_windows_test.bat           |   4 +-
 docs/source/cpp/driver/index.rst                   |   1 +
 docs/source/cpp/driver/postgres.rst                |   4 +-
 .../source/cpp/driver/{postgres.rst => sqlite.rst} |  28 +--
 docs/source/cpp/install.rst                        |  22 +++
 go/adbc/drivermgr/wrapper_sqlite_test.go           |   2 +-
 python/adbc_driver_postgres/README.md              |   6 +-
 .../adbc_driver_sqlite/.gitignore                  |  28 +--
 .../adbc_driver_sqlite/MANIFEST.in                 |  26 +--
 .../README.md                                      |  28 +--
 .../adbc_driver_sqlite/__init__.py                 |  33 ++--
 .../adbc_driver_sqlite/_static_version.py          |  33 ++--
 .../adbc_driver_sqlite/_version.py                 | 210 +++++++++++++++++++++
 .../adbc_driver_sqlite/adbc_driver_sqlite/dbapi.py | 117 ++++++++++++
 .../adbc_driver_sqlite/pyproject.toml              |  39 ++--
 python/adbc_driver_sqlite/setup.py                 |  70 +++++++
 .../adbc_driver_sqlite/tests/__init__.py           |  26 ---
 .../adbc_driver_sqlite/tests/test_dbapi.py         |  31 +--
 .../adbc_driver_sqlite/tests/test_lowlevel.py      |  35 ++--
 30 files changed, 666 insertions(+), 256 deletions(-)

diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml
index 4b47db3..df811c9 100644
--- a/.github/workflows/cpp.yml
+++ b/.github/workflows/cpp.yml
@@ -195,6 +195,20 @@ jobs:
           ctest --output-on-failure --no-tests=error
           if %errorlevel% neq 0 then exit /b %errorlevel%
           cd ..\..
+      - name: Build/Test SQLite Driver
+        shell: cmd /C call {0}
+        run: |
+          mkdir build\driver_sqlite
+          cd build\driver_sqlite
+          cmake -GNinja ..\..\c\driver\sqlite -DADBC_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=%CONDA_PREFIX% -DADBC_BUILD_SHARED=ON -DADBC_BUILD_STATIC=OFF
+          if %errorlevel% neq 0 then exit /b %errorlevel%
+          cmake --build .
+          dir
+          if %errorlevel% neq 0 then exit /b %errorlevel%
+          set PATH=%PATH%;${{ github.workspace }}\build\driver_sqlite
+          ctest --output-on-failure --no-tests=error
+          if %errorlevel% neq 0 then exit /b %errorlevel%
+          cd ..\..
       - name: Build/Test Python Driver Manager
         shell: cmd /C call {0}
         run: |
@@ -203,3 +217,11 @@ jobs:
           set PATH=%PATH%;${{ github.workspace }}\build\driver_sqlite
           python -m pytest -vv
           cd ..\..
+      - name: Build/Test Python Driver SQLite
+        shell: cmd /C call {0}
+        run: |
+          cd python\adbc_driver_sqlite
+          set ADBC_SQLITE_LIBRARY=${{ github.workspace }}\build\driver_sqlite\adbc_driver_sqlite.dll
+          pip install -e .
+          python -m pytest -vv
+          cd ..\..
diff --git a/c/driver/sqlite/CMakeLists.txt b/c/driver/sqlite/CMakeLists.txt
index 1a40a3f..a80fd0d 100644
--- a/c/driver/sqlite/CMakeLists.txt
+++ b/c/driver/sqlite/CMakeLists.txt
@@ -25,9 +25,16 @@ project(adbc_driver_sqlite
         VERSION "${ADBC_BASE_VERSION}"
         LANGUAGES CXX)
 include(CTest)
-find_package(PkgConfig)
 
-find_package(SQLite3 REQUIRED)
+if(WIN32)
+  # XXX: for now, assume vcpkg
+  find_package(unofficial-sqlite3 CONFIG REQUIRED)
+  set(SQLite3_LINK_LIBRARIES unofficial::sqlite3::sqlite3)
+  set(SQLite3_INCLUDE_DIRS)
+else()
+  find_package(SQLite3 REQUIRED)
+  set(SQLite3_LINK_LIBRARIES SQLite::SQLite3)
+endif()
 
 add_arrow_lib(adbc_driver_sqlite
               SOURCES
@@ -39,10 +46,10 @@ add_arrow_lib(adbc_driver_sqlite
               SHARED_LINK_FLAGS
               ${ADBC_LINK_FLAGS}
               SHARED_LINK_LIBS
-              SQLite::SQLite3
+              ${SQLite3_LINK_LIBRARIES}
               nanoarrow
               STATIC_LINK_LIBS
-              SQLite::SQLite3
+              ${SQLite3_LINK_LIBRARIES}
               nanoarrow
               ${LIBPQ_STATIC_LIBRARIES})
 include_directories(SYSTEM ${REPOSITORY_ROOT})
diff --git a/c/driver/sqlite/sqlite.c b/c/driver/sqlite/sqlite.c
index 233e5c1..82eac6c 100644
--- a/c/driver/sqlite/sqlite.c
+++ b/c/driver/sqlite/sqlite.c
@@ -1783,6 +1783,7 @@ AdbcStatusCode AdbcStatementExecutePartitions(struct AdbcStatement* statement,
 }  // NOLINT(whitespace/indent)
 // due to https://github.com/cpplint/cpplint/pull/189
 
+ADBC_EXPORT
 AdbcStatusCode AdbcDriverInit(int version, void* driver, struct AdbcError* error) {
   return SqliteDriverInit(version, driver, error);
 }
diff --git a/c/driver/sqlite/utils.h b/c/driver/sqlite/utils.h
index 5d65d88..3fb3b1e 100644
--- a/c/driver/sqlite/utils.h
+++ b/c/driver/sqlite/utils.h
@@ -21,9 +21,16 @@
 
 #include <adbc.h>
 
+#if defined(__GNUC__)
+#define SET_ERROR_ATTRIBUTE __attribute__((format(printf, 2, 3)))
+#else
+#define SET_ERROR_ATTRIBUTE
+#endif
+
 /// Set error details using a format string.
-void SetError(struct AdbcError* error, const char* format, ...)
-    __attribute__((format(printf, 2, 3)));
+void SetError(struct AdbcError* error, const char* format, ...) SET_ERROR_ATTRIBUTE;
+
+#undef SET_ERROR_ATTRIBUTE
 
 /// Wrap a single batch as a stream.
 AdbcStatusCode BatchToArrayStream(struct ArrowArray* values, struct ArrowSchema* schema,
diff --git a/c/driver_manager/adbc_driver_manager.cc b/c/driver_manager/adbc_driver_manager.cc
index 80b0358..236073b 100644
--- a/c/driver_manager/adbc_driver_manager.cc
+++ b/c/driver_manager/adbc_driver_manager.cc
@@ -665,7 +665,9 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint,
   void* load_handle = GetProcAddress(handle, entrypoint);
   init_func = reinterpret_cast<AdbcDriverInitFunc>(load_handle);
   if (!init_func) {
-    std::string message = "GetProcAddress() failed: ";
+    std::string message = "GetProcAddress(";
+    message += entrypoint;
+    message += ") failed: ";
     GetWinError(&message);
     if (!FreeLibrary(handle)) {
       message += "\nFreeLibrary() failed: ";
@@ -722,7 +724,9 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint,
 
   void* load_handle = dlsym(handle, entrypoint);
   if (!load_handle) {
-    std::string message = "dlsym() failed: ";
+    std::string message = "dlsym(";
+    message += entrypoint;
+    message += ") failed: ";
     message += dlerror();
     SetError(error, message);
     return ADBC_STATUS_INTERNAL;
diff --git a/ci/scripts/python_build.sh b/ci/scripts/python_build.sh
index 6d39a53..135f643 100755
--- a/ci/scripts/python_build.sh
+++ b/ci/scripts/python_build.sh
@@ -21,6 +21,7 @@ set -e
 : ${BUILD_ALL:=1}
 : ${BUILD_DRIVER_MANAGER:=${BUILD_ALL}}
 : ${BUILD_DRIVER_POSTGRES:=${BUILD_ALL}}
+: ${BUILD_DRIVER_SQLITE:=${BUILD_ALL}}
 
 if [[ $(uname) = "Darwin" ]]; then
     ADBC_LIBRARY_SUFFIX="dylib"
@@ -33,10 +34,13 @@ build_subproject() {
     local -r install_dir="${2}"
     local -r subproject="${3}"
 
-    if [[ "${subproject}" -eq "adbc_driver_postgres" ]]; then
+    if [[ "${subproject}" = "adbc_driver_postgres" ]]; then
         export ADBC_POSTGRES_LIBRARY="${install_dir}/lib/libadbc_driver_postgres.${ADBC_LIBRARY_SUFFIX}"
+    elif [[ "${subproject}" = "adbc_driver_sqlite" ]]; then
+        export ADBC_SQLITE_LIBRARY="${install_dir}/lib/libadbc_driver_sqlite.${ADBC_LIBRARY_SUFFIX}"
     fi
 
+    echo foo $subproject $ADBC_SQLITE_LIBRARY
     python -m pip install -e "${source_dir}/python/${subproject}"
 }
 
@@ -56,6 +60,10 @@ main() {
     if [[ "${BUILD_DRIVER_POSTGRES}" -gt 0 ]]; then
         build_subproject "${source_dir}" "${install_dir}" adbc_driver_postgres
     fi
+
+    if [[ "${BUILD_DRIVER_SQLITE}" -gt 0 ]]; then
+        build_subproject "${source_dir}" "${install_dir}" adbc_driver_sqlite
+    fi
 }
 
 main "$@"
diff --git a/ci/scripts/python_sdist_build.sh b/ci/scripts/python_sdist_build.sh
index ea33375..535331c 100755
--- a/ci/scripts/python_sdist_build.sh
+++ b/ci/scripts/python_sdist_build.sh
@@ -20,6 +20,9 @@
 set -ex
 
 source_dir=${1}
+script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+source "${script_dir}/python_util.sh"
 
 echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
 
@@ -30,7 +33,7 @@ pip install --upgrade pip setuptools
 # For drivers, which bundle shared libraries, defer that to install time
 export _ADBC_IS_SDIST=1
 
-for component in adbc_driver_manager adbc_driver_postgres; do
+for component in ${COMPONENTS}; do
     pushd ${source_dir}/python/$component
 
     echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
diff --git a/ci/scripts/python_test.sh b/ci/scripts/python_test.sh
index 17c56c1..c858420 100755
--- a/ci/scripts/python_test.sh
+++ b/ci/scripts/python_test.sh
@@ -21,6 +21,7 @@ set -e
 : ${BUILD_ALL:=1}
 : ${BUILD_DRIVER_MANAGER:=${BUILD_ALL}}
 : ${BUILD_DRIVER_POSTGRES:=${BUILD_ALL}}
+: ${BUILD_DRIVER_SQLITE:=${BUILD_ALL}}
 
 test_subproject() {
     local -r source_dir=${1}
@@ -53,6 +54,10 @@ main() {
     if [[ "${BUILD_DRIVER_POSTGRES}" -gt 0 ]]; then
         test_subproject "${source_dir}" "${install_dir}" adbc_driver_postgres
     fi
+
+    if [[ "${BUILD_DRIVER_SQLITE}" -gt 0 ]]; then
+        test_subproject "${source_dir}" "${install_dir}" adbc_driver_sqlite
+    fi
 }
 
 main "$@"
diff --git a/ci/scripts/python_util.sh b/ci/scripts/python_util.sh
index fb06d6c..bd2a01b 100644
--- a/ci/scripts/python_util.sh
+++ b/ci/scripts/python_util.sh
@@ -17,7 +17,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-COMPONENTS="adbc_driver_manager adbc_driver_postgres"
+COMPONENTS="adbc_driver_manager adbc_driver_postgres adbc_driver_sqlite"
 
 function build_drivers {
     local -r source_dir="$1"
@@ -30,21 +30,29 @@ function build_drivers {
     # Enable manifest mode
     : ${VCPKG_FEATURE_FLAGS:=manifests}
     # Add our custom triplets
-    : ${VCPKG_OVERLAY_TRIPLETS:="${source_dir}/ci/vcpkg/triplets/"}
+    export VCPKG_OVERLAY_TRIPLETS="${source_dir}/ci/vcpkg/triplets/"
 
     if [[ $(uname) == "Linux" ]]; then
         export ADBC_POSTGRES_LIBRARY=${build_dir}/lib/libadbc_driver_postgres.so
+        export ADBC_SQLITE_LIBRARY=${build_dir}/lib/libadbc_driver_sqlite.so
         export VCPKG_DEFAULT_TRIPLET="x64-linux-static-release"
+
+        # XXX: Patch the portfile
+        sed -i "s|include/postgresql/server/pg_config.h|include/server/pg_config.h|" \
+            "${VCPKG_ROOT}/ports/libpq/portfile.cmake"
     else # macOS
         export ADBC_POSTGRES_LIBRARY=${build_dir}/lib/libadbc_driver_postgres.dylib
+        export ADBC_SQLITE_LIBRARY=${build_dir}/lib/libadbc_driver_sqlite.dylib
         export VCPKG_DEFAULT_TRIPLET="x64-osx-static-release"
-    fi
 
-    echo ${VCPKG_DEFAULT_TRIPLET}
-
-    mkdir -p ${build_dir}
-    pushd ${build_dir}
+        # XXX: Patch the portfile
+        sed -i '.bak' "s|include/postgresql/server/pg_config.h|include/server/pg_config.h|" \
+            "${VCPKG_ROOT}/ports/libpq/portfile.cmake"
+    fi
 
+    echo "=== Building driver/postgres ==="
+    mkdir -p ${build_dir}/driver/postgres
+    pushd ${build_dir}/driver/postgres
     cmake \
         -G ${CMAKE_GENERATOR} \
         -DADBC_BUILD_SHARED=ON \
@@ -53,23 +61,44 @@ function build_drivers {
         -DCMAKE_INSTALL_PREFIX=${build_dir} \
         -DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \
         -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} \
+        -DVCPKG_OVERLAY_TRIPLETS="${VCPKG_OVERLAY_TRIPLETS}" \
+        -DVCPKG_TARGET_TRIPLET="${VCPKG_DEFAULT_TRIPLET}" \
         ${source_dir}/c/driver/postgres
     cmake --build . --target install -j
     popd
+
+    echo "=== Building driver/sqlite ==="
+    mkdir -p ${build_dir}/driver/sqlite
+    pushd ${build_dir}/driver/sqlite
+    cmake \
+        -G ${CMAKE_GENERATOR} \
+        -DADBC_BUILD_SHARED=ON \
+        -DADBC_BUILD_STATIC=OFF \
+        -DCMAKE_INSTALL_LIBDIR=lib \
+        -DCMAKE_INSTALL_PREFIX=${build_dir} \
+        -DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \
+        -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} \
+        -DVCPKG_OVERLAY_TRIPLETS="${VCPKG_OVERLAY_TRIPLETS}" \
+        -DVCPKG_TARGET_TRIPLET="${VCPKG_DEFAULT_TRIPLET}" \
+        ${source_dir}/c/driver/sqlite
+    cmake --build . --target install -j
+    popd
 }
 
 function test_packages {
-    python -c "
-import adbc_driver_manager
-import adbc_driver_manager.dbapi
-import adbc_driver_postgres
-import adbc_driver_postgres.dbapi
+    for component in ${COMPONENTS}; do
+        echo "=== Testing $component ==="
+
+        python -c "
+import $component
+import $component.dbapi
 "
 
-    # Will only run some smoke tests
-    # --import-mode required, else tries to import from the source dir instead of installed package
-    echo "=== Testing adbc_driver_manager ==="
-    python -m pytest -vvx --import-mode append -k "not sqlite" ${source_dir}/python/adbc_driver_manager/tests
-    echo "=== Testing adbc_driver_postgres ==="
-    python -m pytest -vvx --import-mode append ${source_dir}/python/adbc_driver_postgres/tests
+        # --import-mode required, else tries to import from the source dir instead of installed package
+        if [[ "${component}" = "adbc_driver_manager" ]]; then
+            python -m pytest -vvx --import-mode append -k "not sqlite" ${source_dir}/python/$component/tests
+        else
+            python -m pytest -vvx --import-mode append ${source_dir}/python/$component/tests
+        fi
+    done
 }
diff --git a/ci/scripts/python_wheel_unix_build.sh b/ci/scripts/python_wheel_unix_build.sh
index 58c4e61..616462c 100755
--- a/ci/scripts/python_wheel_unix_build.sh
+++ b/ci/scripts/python_wheel_unix_build.sh
@@ -62,18 +62,19 @@ function check_wheels {
     fi
 }
 
-echo "=== (${PYTHON_VERSION}) Building ADBC libpq driver ==="
-# Sets ADBC_POSTGRES_LIBRARY
+echo "=== (${PYTHON_VERSION}) Building C/C++ driver components ==="
+# Sets ADBC_POSTGRES_LIBRARY, ADBC_SQLITE_LIBRARY
 build_drivers "${source_dir}" "${build_dir}"
 
 # Check that we don't expose any unwanted symbols
 check_visibility $ADBC_POSTGRES_LIBRARY
+check_visibility $ADBC_SQLITE_LIBRARY
 
 # https://github.com/pypa/pip/issues/7555
 # Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip
+pip install --upgrade pip auditwheel
 
-for component in adbc_driver_manager adbc_driver_postgres; do
+for component in $COMPONENTS; do
     pushd ${source_dir}/python/$component
 
     echo "=== (${PYTHON_VERSION}) Clean build artifacts==="
diff --git a/ci/scripts/python_wheel_windows_build.bat b/ci/scripts/python_wheel_windows_build.bat
index 654dbfd..3dcea73 100644
--- a/ci/scripts/python_wheel_windows_build.bat
+++ b/ci/scripts/python_wheel_windows_build.bat
@@ -30,8 +30,10 @@ set VCPKG_TARGET_TRIPLET=x64-windows-static
 
 IF NOT DEFINED VCPKG_ROOT (echo "Must set VCPKG_ROOT" && exit /B 1)
 
-mkdir %build_dir%
-pushd %build_dir%
+%VCPKG_ROOT%\vcpkg install --triplet=%VCPKG_TARGET_TRIPLET% libpq sqlite3
+
+mkdir %build_dir%\postgres
+pushd %build_dir%\postgres
 
 cmake ^
       -G "%CMAKE_GENERATOR%" ^
@@ -50,9 +52,29 @@ set ADBC_POSTGRES_LIBRARY=%build_dir%\bin\adbc_driver_postgres.dll
 
 popd
 
+mkdir %build_dir%\sqlite
+pushd %build_dir%\sqlite
+
+cmake ^
+      -G "%CMAKE_GENERATOR%" ^
+      -DADBC_BUILD_SHARED=ON ^
+      -DADBC_BUILD_STATIC=OFF ^
+      -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% ^
+      -DCMAKE_INSTALL_PREFIX=%build_dir% ^
+      -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake ^
+      -DCMAKE_UNITY_BUILD=%CMAKE_UNITY_BUILD% ^
+      -DVCPKG_TARGET_TRIPLET=%VCPKG_TARGET_TRIPLET% ^
+      %source_dir%\c\driver\sqlite || exit /B 1
+cmake --build . --config %CMAKE_BUILD_TYPE% --target install -j || exit /B 1
+
+@REM XXX: CMake installs it to bin instead of lib for some reason
+set ADBC_SQLITE_LIBRARY=%build_dir%\bin\adbc_driver_sqlite.dll
+
+popd
+
 python -m pip install --upgrade pip delvewheel
 
-FOR %%c IN (adbc_driver_manager adbc_driver_postgres) DO (
+FOR %%c IN (adbc_driver_manager adbc_driver_postgres adbc_driver_sqlite) DO (
     pushd %source_dir%\python\%%c
 
     echo "=== (%PYTHON_VERSION%) Building %%c wheel ==="
diff --git a/ci/scripts/python_wheel_windows_test.bat b/ci/scripts/python_wheel_windows_test.bat
index 7273f35..19305ca 100644
--- a/ci/scripts/python_wheel_windows_test.bat
+++ b/ci/scripts/python_wheel_windows_test.bat
@@ -21,7 +21,7 @@ set source_dir=%1
 
 echo "=== (%PYTHON_VERSION%) Installing wheels ==="
 
-FOR %%c IN (adbc_driver_manager adbc_driver_postgres) DO (
+FOR %%c IN (adbc_driver_manager adbc_driver_postgres adbc_driver_sqlite) DO (
     FOR %%w IN (%source_dir%\python\%%c\dist\*.whl) DO (
         pip install --force-reinstall %%w || exit /B 1
     )
@@ -31,7 +31,7 @@ pip install pytest pyarrow pandas
 
 echo "=== (%PYTHON_VERSION%) Testing wheels ==="
 
-FOR %%c IN (adbc_driver_manager adbc_driver_postgres) DO (
+FOR %%c IN (adbc_driver_manager adbc_driver_postgres adbc_driver_sqlite) DO (
     echo "=== Testing %%c ==="
     python -c "import %%c" || exit /B 1
     python -c "import %%c.dbapi" || exit /B 1
diff --git a/docs/source/cpp/driver/index.rst b/docs/source/cpp/driver/index.rst
index 7a05c79..df901a7 100644
--- a/docs/source/cpp/driver/index.rst
+++ b/docs/source/cpp/driver/index.rst
@@ -27,3 +27,4 @@ protocols/databases.  More may be available from third parties.
 
    flight_sql
    postgres
+   sqlite
diff --git a/docs/source/cpp/driver/postgres.rst b/docs/source/cpp/driver/postgres.rst
index 6a8f0b4..eb2ee96 100644
--- a/docs/source/cpp/driver/postgres.rst
+++ b/docs/source/cpp/driver/postgres.rst
@@ -19,8 +19,8 @@
 libpq-based Driver
 ==================
 
-The Flight SQL Driver provides access to any database that supports
-the Postgres wire format.
+The Postgres driver provides access to any database that supports the
+Postgres wire format.
 
 Installation
 ============
diff --git a/docs/source/cpp/driver/postgres.rst b/docs/source/cpp/driver/sqlite.rst
similarity index 66%
copy from docs/source/cpp/driver/postgres.rst
copy to docs/source/cpp/driver/sqlite.rst
index 6a8f0b4..19788fa 100644
--- a/docs/source/cpp/driver/postgres.rst
+++ b/docs/source/cpp/driver/sqlite.rst
@@ -15,25 +15,27 @@
 .. specific language governing permissions and limitations
 .. under the License.
 
-==================
-libpq-based Driver
-==================
+=============
+SQLite Driver
+=============
 
-The Flight SQL Driver provides access to any database that supports
-the Postgres wire format.
+The SQLite driver provides access to any database that supports the
+Postgres wire format.
 
 Installation
 ============
 
-The libpq-based driver is shipped as a standalone library.  See
-:ref:`Installation <cpp-install-libpq>`.
+The SQLite driver is shipped as a standalone library.  See
+:ref:`Installation <cpp-install-sqlite>`.
 
 Usage
 =====
 
 To connect to a database, supply the "uri" parameter when constructing
-the :cpp:class:`AdbcDatabase`.  This should be a `connection URI
-<https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>`_.
+the :cpp:class:`AdbcDatabase`.  This should be a filename or `URI
+filename <https://www.sqlite.org/c3ref/open.html#urifilenamesinsqlite3open>`_.
+If omitted, it will default to an in-memory database, but one that is
+shared across all connections.
 
 .. tab-set::
 
@@ -47,7 +49,7 @@ the :cpp:class:`AdbcDatabase`.  This should be a `connection URI
          // Ignoring error handling
          struct AdbcDatabase database;
          AdbcDatabaseNew(&database, nullptr);
-         AdbcDatabaseSetOption(&database, "uri", "postgresql://localhost:5433", nullptr);
+         AdbcDatabaseSetOption(&database, "uri", "file:mydb.db", nullptr);
          AdbcDatabaseInit(&database, nullptr);
 
    .. tab-item:: Python
@@ -55,9 +57,7 @@ the :cpp:class:`AdbcDatabase`.  This should be a `connection URI
 
       .. code-block:: python
 
-         import adbc_driver_postgres.dbapi
+         import adbc_driver_sqlite.dbapi
 
-
-         uri = "postgresql://localhost:5433"
-         with adbc_driver_postgres.dbapi.connect(uri) as conn:
+         with adbc_driver_sqlite.dbapi.connect() as conn:
              pass
diff --git a/docs/source/cpp/install.rst b/docs/source/cpp/install.rst
index ee2a57b..0fad01a 100644
--- a/docs/source/cpp/install.rst
+++ b/docs/source/cpp/install.rst
@@ -81,6 +81,28 @@ Flight SQL Driver
 libpq-based Driver
 ==================
 
+.. tab-set::
+
+   .. tab-item:: C++
+      :sync: cpp
+
+      .. note:: Under construction
+
+   .. tab-item:: Python (pip)
+      :sync: python
+
+      .. note:: Under construction
+
+   .. tab-item:: Python (conda-forge)
+      :sync: python-conda-forge
+
+      .. note:: Under construction
+
+.. _cpp-install-sqlite:
+
+SQLite Driver
+=============
+
 .. tab-set::
 
    .. tab-item:: C++
diff --git a/go/adbc/drivermgr/wrapper_sqlite_test.go b/go/adbc/drivermgr/wrapper_sqlite_test.go
index ba3b277..8ec3af0 100644
--- a/go/adbc/drivermgr/wrapper_sqlite_test.go
+++ b/go/adbc/drivermgr/wrapper_sqlite_test.go
@@ -216,7 +216,7 @@ func TestDriverMgrCustomInitFunc(t *testing.T) {
 	var exp *adbc.Error
 	assert.ErrorAs(t, err, &exp)
 	assert.Equal(t, adbc.StatusInternal, exp.Code)
-	assert.Contains(t, exp.Msg, "dlsym() failed")
+	assert.Contains(t, exp.Msg, "dlsym(ThisSymbolDoesNotExist) failed")
 	switch runtime.GOOS {
 	case "darwin":
 		assert.Contains(t, exp.Msg, "ThisSymbolDoesNotExist): symbol not found")
diff --git a/python/adbc_driver_postgres/README.md b/python/adbc_driver_postgres/README.md
index b5dcf40..af697da 100644
--- a/python/adbc_driver_postgres/README.md
+++ b/python/adbc_driver_postgres/README.md
@@ -30,10 +30,8 @@ manager](../adbc_driver_manager/README.md) to provide a [DBAPI 2.0/PEP
 
 Dependencies: a build of the libpq driver.
 
-Set the environment variable `ADBC_POSTGRES_LIBRARY` to the directory
-containing `libadbc_driver_postgres.so` before running `pip install`.
-
-(This library does not yet support Windows/MacOS.)
+Set the environment variable `ADBC_POSTGRES_LIBRARY` to the path to
+`libadbc_driver_postgres.{dll,dylib,so}` before running `pip install`.
 
 See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details on the
 general build process.
diff --git a/ci/scripts/python_sdist_build.sh b/python/adbc_driver_sqlite/.gitignore
old mode 100755
new mode 100644
similarity index 53%
copy from ci/scripts/python_sdist_build.sh
copy to python/adbc_driver_sqlite/.gitignore
index ea33375..126488c
--- a/ci/scripts/python_sdist_build.sh
+++ b/python/adbc_driver_sqlite/.gitignore
@@ -1,5 +1,3 @@
-#!/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
@@ -17,26 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set -ex
-
-source_dir=${1}
-
-echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
-
-# https://github.com/pypa/pip/issues/7555
-# Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip setuptools
-
-# For drivers, which bundle shared libraries, defer that to install time
-export _ADBC_IS_SDIST=1
-
-for component in adbc_driver_manager adbc_driver_postgres; do
-    pushd ${source_dir}/python/$component
-
-    echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
-    # python -m build copies to a tempdir, so we can't reference other files in the repo
-    # https://github.com/pypa/pip/issues/5519
-    python setup.py sdist
-
-    popd
-done
+adbc_driver_postgres/*.c
+adbc_driver_postgres/*.cpp
+build/
diff --git a/ci/scripts/python_sdist_build.sh b/python/adbc_driver_sqlite/MANIFEST.in
old mode 100755
new mode 100644
similarity index 53%
copy from ci/scripts/python_sdist_build.sh
copy to python/adbc_driver_sqlite/MANIFEST.in
index ea33375..f9de8e7
--- a/ci/scripts/python_sdist_build.sh
+++ b/python/adbc_driver_sqlite/MANIFEST.in
@@ -1,5 +1,3 @@
-#!/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
@@ -17,26 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set -ex
-
-source_dir=${1}
-
-echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
-
-# https://github.com/pypa/pip/issues/7555
-# Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip setuptools
-
-# For drivers, which bundle shared libraries, defer that to install time
-export _ADBC_IS_SDIST=1
-
-for component in adbc_driver_manager adbc_driver_postgres; do
-    pushd ${source_dir}/python/$component
-
-    echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
-    # python -m build copies to a tempdir, so we can't reference other files in the repo
-    # https://github.com/pypa/pip/issues/5519
-    python setup.py sdist
+# setuptools manifest
 
-    popd
-done
+include adbc_driver_sqlite/libadbc_driver_sqlite.so
diff --git a/python/adbc_driver_postgres/README.md b/python/adbc_driver_sqlite/README.md
similarity index 60%
copy from python/adbc_driver_postgres/README.md
copy to python/adbc_driver_sqlite/README.md
index b5dcf40..88b0528 100644
--- a/python/adbc_driver_postgres/README.md
+++ b/python/adbc_driver_sqlite/README.md
@@ -17,10 +17,10 @@
   under the License.
 -->
 
-# ADBC libpq Driver for Python
+# ADBC SQLite Driver for Python
 
-This package contains bindings for the [libpq
-driver](../../c/driver/postgres/README.md), using the [driver
+This package contains bindings for the [SQLite
+driver](../../c/driver/sqlite/README.md), using the [driver
 manager](../adbc_driver_manager/README.md) to provide a [DBAPI 2.0/PEP
 249-compatible][dbapi] interface on top.
 
@@ -28,33 +28,19 @@ manager](../adbc_driver_manager/README.md) to provide a [DBAPI 2.0/PEP
 
 ## Building
 
-Dependencies: a build of the libpq driver.
+Dependencies: a build of the SQLite driver.
 
-Set the environment variable `ADBC_POSTGRES_LIBRARY` to the directory
-containing `libadbc_driver_postgres.so` before running `pip install`.
-
-(This library does not yet support Windows/MacOS.)
+Set the environment variable `ADBC_SQLITE_LIBRARY` to the path to
+`libadbc_driver_sqlite.{dll,dylib,so}` before running `pip install`.
 
 See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details on the
 general build process.
 
 ## Testing
 
-A running instance of Postgres is required.  For example, using Docker:
-
-```shell
-$ docker run -it --rm \
-    -e POSTGRES_PASSWORD=password \
-    -e POSTGRES_DB=tempdb \
-    -p 5432:5432 \
-    postgres
-```
-
-Then, to run the tests, set the environment variable specifying the
-Postgres URI before running tests:
+To run the tests, use pytest:
 
 ```shell
-$ export ADBC_POSTGRES_TEST_URI=postgres://localhost:5432/postgres?user=postgres&password=password
 $ pytest -vvx
 ```
 
diff --git a/ci/scripts/python_sdist_build.sh b/python/adbc_driver_sqlite/adbc_driver_sqlite/__init__.py
old mode 100755
new mode 100644
similarity index 53%
copy from ci/scripts/python_sdist_build.sh
copy to python/adbc_driver_sqlite/adbc_driver_sqlite/__init__.py
index ea33375..c01ea88
--- a/ci/scripts/python_sdist_build.sh
+++ b/python/adbc_driver_sqlite/adbc_driver_sqlite/__init__.py
@@ -1,5 +1,3 @@
-#!/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
@@ -17,26 +15,21 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set -ex
-
-source_dir=${1}
-
-echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
+import importlib.resources
+import typing
 
-# https://github.com/pypa/pip/issues/7555
-# Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip setuptools
+import adbc_driver_manager
 
-# For drivers, which bundle shared libraries, defer that to install time
-export _ADBC_IS_SDIST=1
+from ._version import __version__
 
-for component in adbc_driver_manager adbc_driver_postgres; do
-    pushd ${source_dir}/python/$component
+__all__ = ["connect", "__version__"]
 
-    echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
-    # python -m build copies to a tempdir, so we can't reference other files in the repo
-    # https://github.com/pypa/pip/issues/5519
-    python setup.py sdist
 
-    popd
-done
+def connect(uri: typing.Optional[str] = None) -> adbc_driver_manager.AdbcDatabase:
+    """Create a low level ADBC connection to SQLite."""
+    with importlib.resources.path(
+        __package__, "libadbc_driver_sqlite.so"
+    ) as entrypoint:
+        if uri is None:
+            return adbc_driver_manager.AdbcDatabase(driver=str(entrypoint))
+        return adbc_driver_manager.AdbcDatabase(driver=str(entrypoint), uri=uri)
diff --git a/ci/scripts/python_sdist_build.sh b/python/adbc_driver_sqlite/adbc_driver_sqlite/_static_version.py
old mode 100755
new mode 100644
similarity index 53%
copy from ci/scripts/python_sdist_build.sh
copy to python/adbc_driver_sqlite/adbc_driver_sqlite/_static_version.py
index ea33375..7ca903c
--- a/ci/scripts/python_sdist_build.sh
+++ b/python/adbc_driver_sqlite/adbc_driver_sqlite/_static_version.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env bash
-#
+# -*- coding: utf-8 -*-
 # 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
@@ -17,26 +16,16 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set -ex
-
-source_dir=${1}
-
-echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
+# Generated by miniver (CC0).
 
-# https://github.com/pypa/pip/issues/7555
-# Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip setuptools
-
-# For drivers, which bundle shared libraries, defer that to install time
-export _ADBC_IS_SDIST=1
-
-for component in adbc_driver_manager adbc_driver_postgres; do
-    pushd ${source_dir}/python/$component
+# This file is part of 'miniver': https://github.com/jbweston/miniver
+#
+# This file will be overwritten by setup.py when a source or binary
+# distribution is made.  The magic value "__use_git__" is interpreted by
+# _version.py.
 
-    echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
-    # python -m build copies to a tempdir, so we can't reference other files in the repo
-    # https://github.com/pypa/pip/issues/5519
-    python setup.py sdist
+version = "__use_git__"
 
-    popd
-done
+# These values are only set if the distribution was created with 'git archive'
+refnames = "$Format:%D$"
+git_hash = "$Format:%h$"
diff --git a/python/adbc_driver_sqlite/adbc_driver_sqlite/_version.py b/python/adbc_driver_sqlite/adbc_driver_sqlite/_version.py
new file mode 100644
index 0000000..09bfd6f
--- /dev/null
+++ b/python/adbc_driver_sqlite/adbc_driver_sqlite/_version.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+# 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.
+
+# Generated by miniver (CC0).
+
+import os
+
+# This file is part of 'miniver': https://github.com/jbweston/miniver
+#
+from collections import namedtuple
+
+Version = namedtuple("Version", ("release", "dev", "labels"))
+
+# No public API
+__all__ = []
+
+package_root = os.path.dirname(os.path.realpath(__file__))
+package_name = os.path.basename(package_root)
+
+STATIC_VERSION_FILE = "_static_version.py"
+
+
+def get_version(version_file=STATIC_VERSION_FILE):
+    version_info = get_static_version_info(version_file)
+    version = version_info["version"]
+    if version == "__use_git__":
+        version = get_version_from_git()
+        if not version:
+            version = get_version_from_git_archive(version_info)
+        if not version:
+            version = Version("unknown", None, None)
+        return pep440_format(version)
+    else:
+        return version
+
+
+def get_static_version_info(version_file=STATIC_VERSION_FILE):
+    version_info = {}
+    with open(os.path.join(package_root, version_file), "rb") as f:
+        exec(f.read(), {}, version_info)
+    return version_info
+
+
+def version_is_from_git(version_file=STATIC_VERSION_FILE):
+    return get_static_version_info(version_file)["version"] == "__use_git__"
+
+
+def pep440_format(version_info):
+    release, dev, labels = version_info
+
+    version_parts = [release]
+    if dev:
+        if release.endswith("-dev") or release.endswith(".dev"):
+            version_parts.append(dev)
+        else:  # prefer PEP440 over strict adhesion to semver
+            version_parts.append(".dev{}".format(dev))
+
+    if labels:
+        version_parts.append("+")
+        version_parts.append(".".join(labels))
+
+    return "".join(version_parts)
+
+
+def get_version_from_git():
+    import subprocess
+
+    # git describe --first-parent does not take into account tags from branches
+    # that were merged-in. The '--long' flag gets us the 'dev' version and
+    # git hash, '--always' returns the git hash even if there are no tags.
+    for opts in [["--first-parent"], []]:
+        try:
+            p = subprocess.Popen(
+                ["git", "describe", "--long", "--always"] + opts,
+                cwd=package_root,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+            )
+        except OSError:
+            return
+        if p.wait() == 0:
+            break
+    else:
+        return
+
+    description = (
+        p.communicate()[0]
+        .decode()
+        .strip("v")  # Tags can have a leading 'v', but the version should not
+        .rstrip("\n")
+        .rsplit("-", 3)  # Split the latest tag, commits since tag, and hash
+    )
+
+    try:
+        _, release, dev, git = description
+    except ValueError:  # No tags, only the git hash
+        # prepend 'g' to match with format returned by 'git describe'
+        git = "g{}".format(*description)
+        # XXX: assume version if not given
+        release = "0.0.0"
+        dev = None
+
+    labels = []
+    if dev == "0":
+        dev = None
+    else:
+        labels.append(git)
+
+    try:
+        p = subprocess.Popen(["git", "diff", "--quiet"], cwd=package_root)
+    except OSError:
+        labels.append("confused")  # This should never happen.
+    else:
+        if p.wait() == 1:
+            labels.append("dirty")
+
+    return Version(release, dev, labels)
+
+
+# TODO: change this logic when there is a git pretty-format
+#       that gives the same output as 'git describe'.
+#       Currently we can only tell the tag the current commit is
+#       pointing to, or its hash (with no version info)
+#       if it is not tagged.
+def get_version_from_git_archive(version_info):
+    try:
+        refnames = version_info["refnames"]
+        git_hash = version_info["git_hash"]
+    except KeyError:
+        # These fields are not present if we are running from an sdist.
+        # Execution should never reach here, though
+        return None
+
+    if git_hash.startswith("$Format") or refnames.startswith("$Format"):
+        # variables not expanded during 'git archive'
+        return None
+
+    VTAG = "tag: adbc-"
+    refs = set(r.strip() for r in refnames.split(","))
+    version_tags = set(r[len(VTAG) :] for r in refs if r.startswith(VTAG))
+    if version_tags:
+        release, *_ = sorted(version_tags)  # prefer e.g. "2.0" over "2.0rc1"
+        return Version(release, dev=None, labels=None)
+    else:
+        return Version("unknown", dev=None, labels=["g{}".format(git_hash)])
+
+
+__version__ = get_version()
+
+
+# The following section defines a 'get_cmdclass' function
+# that can be used from setup.py. The '__version__' module
+# global is used (but not modified).
+
+
+def _write_version(fname):
+    # This could be a hard link, so try to delete it first.  Is there any way
+    # to do this atomically together with opening?
+    try:
+        os.remove(fname)
+    except OSError:
+        pass
+    with open(fname, "w") as f:
+        f.write(
+            "# This file has been created by setup.py.\n"
+            "version = '{}'\n".format(__version__)
+        )
+
+
+def get_cmdclass(pkg_source_path):
+    from setuptools.command.build_py import build_py as build_py_orig
+    from setuptools.command.sdist import sdist as sdist_orig
+
+    class _build_py(build_py_orig):
+        def run(self):
+            super().run()
+
+            src_marker = "".join(["src", os.path.sep])
+
+            if pkg_source_path.startswith(src_marker):
+                path = pkg_source_path[len(src_marker) :]
+            else:
+                path = pkg_source_path
+            _write_version(os.path.join(self.build_lib, path, STATIC_VERSION_FILE))
+
+    class _sdist(sdist_orig):
+        def make_release_tree(self, base_dir, files):
+            super().make_release_tree(base_dir, files)
+            _write_version(os.path.join(base_dir, pkg_source_path, STATIC_VERSION_FILE))
+
+    return dict(sdist=_sdist, build_py=_build_py)
+
+
+if __name__ == "__main__":
+    print("Version: ", get_version())
diff --git a/python/adbc_driver_sqlite/adbc_driver_sqlite/dbapi.py b/python/adbc_driver_sqlite/adbc_driver_sqlite/dbapi.py
new file mode 100644
index 0000000..e85766f
--- /dev/null
+++ b/python/adbc_driver_sqlite/adbc_driver_sqlite/dbapi.py
@@ -0,0 +1,117 @@
+# 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.
+
+"""
+DBAPI 2.0-compatible facade for the ADBC libpq driver.
+"""
+
+import typing
+
+import adbc_driver_sqlite
+
+import adbc_driver_manager
+import adbc_driver_manager.dbapi
+
+__all__ = [
+    "BINARY",
+    "DATETIME",
+    "NUMBER",
+    "ROWID",
+    "STRING",
+    "Connection",
+    "Cursor",
+    "DataError",
+    "DatabaseError",
+    "Date",
+    "DateFromTicks",
+    "Error",
+    "IntegrityError",
+    "InterfaceError",
+    "InternalError",
+    "NotSupportedError",
+    "OperationalError",
+    "ProgrammingError",
+    "Time",
+    "TimeFromTicks",
+    "Timestamp",
+    "TimestampFromTicks",
+    "Warning",
+    "apilevel",
+    "connect",
+    "paramstyle",
+    "threadsafety",
+]
+
+# ----------------------------------------------------------
+# Globals
+
+apilevel = adbc_driver_manager.dbapi.apilevel
+threadsafety = adbc_driver_manager.dbapi.threadsafety
+paramstyle = "qmark"
+
+Warning = adbc_driver_manager.dbapi.Warning
+Error = adbc_driver_manager.dbapi.Error
+InterfaceError = adbc_driver_manager.dbapi.InterfaceError
+DatabaseError = adbc_driver_manager.dbapi.DatabaseError
+DataError = adbc_driver_manager.dbapi.DataError
+OperationalError = adbc_driver_manager.dbapi.OperationalError
+IntegrityError = adbc_driver_manager.dbapi.IntegrityError
+InternalError = adbc_driver_manager.dbapi.InternalError
+ProgrammingError = adbc_driver_manager.dbapi.ProgrammingError
+NotSupportedError = adbc_driver_manager.dbapi.NotSupportedError
+
+# ----------------------------------------------------------
+# Types
+
+Date = adbc_driver_manager.dbapi.Date
+Time = adbc_driver_manager.dbapi.Time
+Timestamp = adbc_driver_manager.dbapi.Timestamp
+DateFromTicks = adbc_driver_manager.dbapi.DateFromTicks
+TimeFromTicks = adbc_driver_manager.dbapi.TimeFromTicks
+TimestampFromTicks = adbc_driver_manager.dbapi.TimestampFromTicks
+STRING = adbc_driver_manager.dbapi.STRING
+BINARY = adbc_driver_manager.dbapi.BINARY
+NUMBER = adbc_driver_manager.dbapi.NUMBER
+DATETIME = adbc_driver_manager.dbapi.DATETIME
+ROWID = adbc_driver_manager.dbapi.ROWID
+
+# ----------------------------------------------------------
+# Functions
+
+
+def connect(uri: typing.Optional[str] = None) -> "Connection":
+    """Connect to SQLite via ADBC."""
+    db = None
+    conn = None
+
+    try:
+        db = adbc_driver_sqlite.connect(uri)
+        conn = adbc_driver_manager.AdbcConnection(db)
+        return adbc_driver_manager.dbapi.Connection(db, conn)
+    except Exception:
+        if conn:
+            conn.close()
+        if db:
+            db.close()
+        raise
+
+
+# ----------------------------------------------------------
+# Classes
+
+Connection = adbc_driver_manager.dbapi.Connection
+Cursor = adbc_driver_manager.dbapi.Cursor
diff --git a/ci/scripts/python_sdist_build.sh b/python/adbc_driver_sqlite/pyproject.toml
old mode 100755
new mode 100644
similarity index 53%
copy from ci/scripts/python_sdist_build.sh
copy to python/adbc_driver_sqlite/pyproject.toml
index ea33375..ea720f5
--- a/ci/scripts/python_sdist_build.sh
+++ b/python/adbc_driver_sqlite/pyproject.toml
@@ -1,5 +1,3 @@
-#!/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
@@ -17,26 +15,25 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set -ex
-
-source_dir=${1}
-
-echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
-
-# https://github.com/pypa/pip/issues/7555
-# Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip setuptools
+[project]
+name = "adbc_driver_sqlite"
+description = "An ADBC driver for working with SQLite."
+authors = [{name = "Apache Arrow Developers", email = "dev@arrow.apache.org"}]
+license = {text = "Apache-2.0"}
+requires-python = ">=3.8"
+dynamic = ["version"]
 
-# For drivers, which bundle shared libraries, defer that to install time
-export _ADBC_IS_SDIST=1
+[project.optional-dependencies]
+dbapi = ["pandas", "pyarrow>=8.0.0"]
+test = ["pandas", "pyarrow>=8.0.0", "pytest"]
 
-for component in adbc_driver_manager adbc_driver_postgres; do
-    pushd ${source_dir}/python/$component
+[project.urls]
+homepage = "https://arrow.apache.org"
+repository = "https://github.com/apache/arrow-adbc"
 
-    echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
-    # python -m build copies to a tempdir, so we can't reference other files in the repo
-    # https://github.com/pypa/pip/issues/5519
-    python setup.py sdist
+[build-system]
+requires = ["setuptools >= 61.0.0"]
+build-backend = "setuptools.build_meta"
 
-    popd
-done
+[tool.setuptools]
+include-package-data = true
diff --git a/python/adbc_driver_sqlite/setup.py b/python/adbc_driver_sqlite/setup.py
new file mode 100644
index 0000000..4f72ceb
--- /dev/null
+++ b/python/adbc_driver_sqlite/setup.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+# 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.
+
+import os
+import shutil
+from pathlib import Path
+
+from setuptools import setup
+
+source_root = Path(__file__).parent
+repo_root = source_root.joinpath("../../")
+
+# ------------------------------------------------------------
+# Resolve Shared Library
+
+library = os.environ.get("ADBC_SQLITE_LIBRARY")
+if not library:
+    if os.environ.get("_ADBC_IS_SDIST", "").strip().lower() in ("1", "true"):
+        print("Building sdist, not requiring ADBC_SQLITE_LIBRARY")
+    else:
+        raise ValueError("Must provide ADBC_SQLITE_LIBRARY")
+else:
+    target = source_root.joinpath(
+        "./adbc_driver_sqlite/libadbc_driver_sqlite.so"
+    ).resolve()
+    shutil.copy(library, target)
+
+# ------------------------------------------------------------
+# Resolve Version (miniver)
+
+
+def get_version_and_cmdclass(pkg_path):
+    """
+    Load version.py module without importing the whole package.
+
+    Template code from miniver.
+    """
+    from importlib.util import module_from_spec, spec_from_file_location
+
+    spec = spec_from_file_location("version", os.path.join(pkg_path, "_version.py"))
+    module = module_from_spec(spec)
+    spec.loader.exec_module(module)
+    return module.__version__, module.get_cmdclass(pkg_path)
+
+
+version, cmdclass = get_version_and_cmdclass("adbc_driver_sqlite")
+
+# ------------------------------------------------------------
+# Setup
+
+setup(
+    cmdclass=cmdclass,
+    version=version,
+)
diff --git a/ci/scripts/python_sdist_build.sh b/python/adbc_driver_sqlite/tests/__init__.py
old mode 100755
new mode 100644
similarity index 53%
copy from ci/scripts/python_sdist_build.sh
copy to python/adbc_driver_sqlite/tests/__init__.py
index ea33375..13a8339
--- a/ci/scripts/python_sdist_build.sh
+++ b/python/adbc_driver_sqlite/tests/__init__.py
@@ -1,5 +1,3 @@
-#!/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
@@ -16,27 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-set -ex
-
-source_dir=${1}
-
-echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
-
-# https://github.com/pypa/pip/issues/7555
-# Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip setuptools
-
-# For drivers, which bundle shared libraries, defer that to install time
-export _ADBC_IS_SDIST=1
-
-for component in adbc_driver_manager adbc_driver_postgres; do
-    pushd ${source_dir}/python/$component
-
-    echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
-    # python -m build copies to a tempdir, so we can't reference other files in the repo
-    # https://github.com/pypa/pip/issues/5519
-    python setup.py sdist
-
-    popd
-done
diff --git a/ci/scripts/python_sdist_build.sh b/python/adbc_driver_sqlite/tests/test_dbapi.py
old mode 100755
new mode 100644
similarity index 53%
copy from ci/scripts/python_sdist_build.sh
copy to python/adbc_driver_sqlite/tests/test_dbapi.py
index ea33375..74a221d
--- a/ci/scripts/python_sdist_build.sh
+++ b/python/adbc_driver_sqlite/tests/test_dbapi.py
@@ -1,5 +1,3 @@
-#!/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
@@ -17,26 +15,17 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set -ex
-
-source_dir=${1}
-
-echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
-
-# https://github.com/pypa/pip/issues/7555
-# Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip setuptools
+import pytest
+from adbc_driver_sqlite import dbapi
 
-# For drivers, which bundle shared libraries, defer that to install time
-export _ADBC_IS_SDIST=1
 
-for component in adbc_driver_manager adbc_driver_postgres; do
-    pushd ${source_dir}/python/$component
+@pytest.fixture
+def sqlite():
+    with dbapi.connect() as conn:
+        yield conn
 
-    echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
-    # python -m build copies to a tempdir, so we can't reference other files in the repo
-    # https://github.com/pypa/pip/issues/5519
-    python setup.py sdist
 
-    popd
-done
+def test_query_trivial(sqlite):
+    with sqlite.cursor() as cur:
+        cur.execute("SELECT 1")
+        assert cur.fetchone() == (1,)
diff --git a/ci/scripts/python_sdist_build.sh b/python/adbc_driver_sqlite/tests/test_lowlevel.py
old mode 100755
new mode 100644
similarity index 53%
copy from ci/scripts/python_sdist_build.sh
copy to python/adbc_driver_sqlite/tests/test_lowlevel.py
index ea33375..051676a
--- a/ci/scripts/python_sdist_build.sh
+++ b/python/adbc_driver_sqlite/tests/test_lowlevel.py
@@ -1,5 +1,3 @@
-#!/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
@@ -17,26 +15,27 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set -ex
+import adbc_driver_sqlite
+import pyarrow
+import pytest
 
-source_dir=${1}
+import adbc_driver_manager
 
-echo "=== (${PYTHON_VERSION}) Building ADBC sdists ==="
 
-# https://github.com/pypa/pip/issues/7555
-# Get the latest pip so we have in-tree-build by default
-pip install --upgrade pip setuptools
+@pytest.fixture
+def sqlite():
+    with adbc_driver_sqlite.connect() as db:
+        with adbc_driver_manager.AdbcConnection(db) as conn:
+            yield conn
 
-# For drivers, which bundle shared libraries, defer that to install time
-export _ADBC_IS_SDIST=1
 
-for component in adbc_driver_manager adbc_driver_postgres; do
-    pushd ${source_dir}/python/$component
+def test_query_trivial(sqlite):
+    with adbc_driver_manager.AdbcStatement(sqlite) as stmt:
+        stmt.set_sql_query("SELECT 1")
+        stream, _ = stmt.execute_query()
+        reader = pyarrow.RecordBatchReader._import_from_c(stream.address)
+        assert reader.read_all()
 
-    echo "=== (${PYTHON_VERSION}) Building $component sdist ==="
-    # python -m build copies to a tempdir, so we can't reference other files in the repo
-    # https://github.com/pypa/pip/issues/5519
-    python setup.py sdist
 
-    popd
-done
+def test_version():
+    assert adbc_driver_sqlite.__version__