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 e95fb58  [Python] Distribute drivers as Python packages (#57)
e95fb58 is described below

commit e95fb587c79abb699f9bf50d7d41ed2f9b7dfac6
Author: David Li <[email protected]>
AuthorDate: Wed Aug 31 09:29:08 2022 -0400

    [Python] Distribute drivers as Python packages (#57)
    
    * [Python] Bundle Postgres driver as a Python package
    
    * [Python] Bundle Postgres driver without Cython
    
    * [Python] Add more tests
    
    * Fix authors, remove redundant block
---
 .isort.cfg                                         |   2 +-
 c/driver_manager/adbc_driver_manager.cc            |  72 +++++++---
 c/driver_manager/adbc_driver_manager.h             |  32 +++++
 c/drivers/postgres/postgres.cc                     |  19 ++-
 c/drivers/postgres/statement.cc                    |   2 +-
 .../adbc_driver_manager/_lib.pyx                   |  24 +++-
 .../adbc_driver_manager/dbapi.py                   |   6 +-
 python/adbc_driver_manager/pyproject.toml          |   2 +-
 .../adbc_driver_postgres/.gitignore                |   6 +-
 .../adbc_driver_postgres/__init__.py               |  14 +-
 .../adbc_driver_postgres/dbapi.py                  | 116 ++++++++++++++++
 .isort.cfg => python/adbc_driver_postgres/build.py |  21 ++-
 python/adbc_driver_postgres/poetry.lock            | 154 +++++++++++++++++++++
 .../pyproject.toml                                 |  22 ++-
 python/adbc_driver_postgres/requirements-dev.txt   |  70 ++++++++++
 .../adbc_driver_postgres/tests/__init__.py         |   4 -
 .../adbc_driver_postgres/tests/test_dbapi.py       |  23 ++-
 .../adbc_driver_postgres/tests/test_lowlevel.py    |  28 +++-
 18 files changed, 551 insertions(+), 66 deletions(-)

diff --git a/.isort.cfg b/.isort.cfg
index c614890..8ab8074 100644
--- a/.isort.cfg
+++ b/.isort.cfg
@@ -16,5 +16,5 @@
 # under the License.
 
 [settings]
-known_first_party = adbc_driver_manager
+known_first_party = adbc_driver_manager, adbc_driver_postgres
 profile = black
diff --git a/c/driver_manager/adbc_driver_manager.cc 
b/c/driver_manager/adbc_driver_manager.cc
index 4bac39a..54f7aa9 100644
--- a/c/driver_manager/adbc_driver_manager.cc
+++ b/c/driver_manager/adbc_driver_manager.cc
@@ -65,6 +65,11 @@ void SetError(struct AdbcError* error, const std::string& 
message) {
 
 // Default stubs
 
+AdbcStatusCode DatabaseSetOption(struct AdbcDatabase* database, const char* 
key,
+                                 const char* value, struct AdbcError* error) {
+  return ADBC_STATUS_NOT_IMPLEMENTED;
+}
+
 AdbcStatusCode ConnectionCommit(struct AdbcConnection*, struct AdbcError* 
error) {
   return ADBC_STATUS_NOT_IMPLEMENTED;
 }
@@ -147,6 +152,7 @@ struct TempDatabase {
   std::string driver;
   // Default name (see adbc.h)
   std::string entrypoint = "AdbcDriverInit";
+  AdbcDriverInitFunc init_func = nullptr;
 };
 
 /// Temporary state while the database is being configured.
@@ -209,7 +215,7 @@ static AdbcStatusCode ReleaseDriver(struct AdbcDriver* 
driver, struct AdbcError*
 
 AdbcStatusCode AdbcDatabaseNew(struct AdbcDatabase* database, struct 
AdbcError* error) {
   // Allocate a temporary structure to store options pre-Init
-  database->private_data = new TempDatabase;
+  database->private_data = new TempDatabase();
   database->private_driver = nullptr;
   return ADBC_STATUS_OK;
 }
@@ -231,14 +237,27 @@ AdbcStatusCode AdbcDatabaseSetOption(struct AdbcDatabase* 
database, const char*
   return ADBC_STATUS_OK;
 }
 
+AdbcStatusCode AdbcDriverManagerDatabaseSetInitFunc(struct AdbcDatabase* 
database,
+                                                    AdbcDriverInitFunc 
init_func,
+                                                    struct AdbcError* error) {
+  if (database->private_driver) {
+    return ADBC_STATUS_INVALID_STATE;
+  }
+
+  TempDatabase* args = reinterpret_cast<TempDatabase*>(database->private_data);
+  args->init_func = init_func;
+  return ADBC_STATUS_OK;
+}
+
 AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase* database, struct 
AdbcError* error) {
   if (!database->private_data) {
     SetError(error, "Must call AdbcDatabaseNew first");
     return ADBC_STATUS_INVALID_STATE;
   }
   TempDatabase* args = reinterpret_cast<TempDatabase*>(database->private_data);
-  if (args->driver.empty()) {
-    // Don't delete args here; caller should still call AdbcDatabaseRelease
+  if (args->init_func) {
+    // Do nothing
+  } else if (args->driver.empty()) {
     SetError(error, "Must provide 'driver' parameter");
     return ADBC_STATUS_INVALID_ARGUMENT;
   }
@@ -246,10 +265,20 @@ AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase* 
database, struct AdbcError*
   database->private_driver = new AdbcDriver;
   std::memset(database->private_driver, 0, sizeof(AdbcDriver));
   size_t initialized = 0;
-  AdbcStatusCode status =
-      AdbcLoadDriver(args->driver.c_str(), args->entrypoint.c_str(), 
ADBC_VERSION_0_0_1,
-                     database->private_driver, &initialized, error);
+  AdbcStatusCode status;
+  // So we don't confuse a driver into thinking it's initialized already
+  database->private_data = nullptr;
+  if (args->init_func) {
+    status = AdbcLoadDriverFromInitFunc(args->init_func, ADBC_VERSION_0_0_1,
+                                        database->private_driver, 
&initialized, error);
+  } else {
+    status =
+        AdbcLoadDriver(args->driver.c_str(), args->entrypoint.c_str(), 
ADBC_VERSION_0_0_1,
+                       database->private_driver, &initialized, error);
+  }
   if (status != ADBC_STATUS_OK) {
+    // Restore private_data so it will be released by AdbcDatabaseRelease
+    database->private_data = args;
     if (database->private_driver->release) {
       database->private_driver->release(database->private_driver, error);
     }
@@ -585,16 +614,6 @@ const char* AdbcStatusCodeMessage(AdbcStatusCode code) {
 AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint,
                               size_t count, struct AdbcDriver* driver,
                               size_t* initialized, struct AdbcError* error) {
-#define FILL_DEFAULT(DRIVER, STUB) \
-  if (!DRIVER->STUB) {             \
-    DRIVER->STUB = &STUB;          \
-  }
-#define CHECK_REQUIRED(DRIVER, STUB)                                           
\
-  if (!DRIVER->STUB) {                                                         
\
-    SetError(error, "Driver does not implement required function Adbc" #STUB); 
\
-    return ADBC_STATUS_INTERNAL;                                               
\
-  }
-
   AdbcDriverInitFunc init_func;
   std::string error_message;
 
@@ -695,11 +714,23 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, 
const char* entrypoint,
 
 #endif  // defined(_WIN32)
 
+  return AdbcLoadDriverFromInitFunc(init_func, count, driver, initialized, 
error);
+}
+
+AdbcStatusCode AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, size_t 
count,
+                                          struct AdbcDriver* driver, size_t* 
initialized,
+                                          struct AdbcError* error) {
+#define FILL_DEFAULT(DRIVER, STUB) \
+  if (!DRIVER->STUB) {             \
+    DRIVER->STUB = &STUB;          \
+  }
+#define CHECK_REQUIRED(DRIVER, STUB)                                           
\
+  if (!DRIVER->STUB) {                                                         
\
+    SetError(error, "Driver does not implement required function Adbc" #STUB); 
\
+    return ADBC_STATUS_INTERNAL;                                               
\
+  }
+
   auto result = init_func(count, driver, initialized, error);
-#if defined(_WIN32)
-  driver->private_manager = new ManagerDriverState{handle, driver->release};
-  driver->release = &ReleaseDriver;
-#endif  // defined(_WIN32)
   if (result != ADBC_STATUS_OK) {
     return result;
   }
@@ -707,6 +738,7 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, 
const char* entrypoint,
   CHECK_REQUIRED(driver, DatabaseNew);
   CHECK_REQUIRED(driver, DatabaseInit);
   CHECK_REQUIRED(driver, DatabaseRelease);
+  FILL_DEFAULT(driver, DatabaseSetOption);
 
   CHECK_REQUIRED(driver, ConnectionGetInfo);
   CHECK_REQUIRED(driver, ConnectionNew);
diff --git a/c/driver_manager/adbc_driver_manager.h 
b/c/driver_manager/adbc_driver_manager.h
index bdc25a2..e8958c6 100644
--- a/c/driver_manager/adbc_driver_manager.h
+++ b/c/driver_manager/adbc_driver_manager.h
@@ -49,6 +49,38 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const 
char* entrypoint,
                               size_t count, struct AdbcDriver* driver,
                               size_t* initialized, struct AdbcError* error);
 
+/// \brief Common entry point for drivers via the driver manager.
+///
+/// The driver manager can fill in default implementations of some
+/// ADBC functions for drivers. Drivers must implement a minimum level
+/// of functionality for this to be possible, however, and some
+/// functions must be implemented by the driver.
+///
+/// \param[in] entrypoint The entrypoint to call.
+/// \param[in] count The number of entries to initialize. Provides
+///   backwards compatibility if the struct definition is changed.
+/// \param[out] driver The table of function pointers to initialize.
+/// \param[out] initialized How much of the table was actually
+///   initialized (can be less than count).
+/// \param[out] error An optional location to return an error message
+///   if necessary.
+ADBC_EXPORT
+AdbcStatusCode AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, size_t 
count,
+                                          struct AdbcDriver* driver, size_t* 
initialized,
+                                          struct AdbcError* error);
+
+/// \brief Set the AdbcDriverInitFunc to use.
+///
+/// This is an extension to the ADBC API. The driver manager shims
+/// the AdbcDatabase* functions to allow you to specify the
+/// driver/entrypoint dynamically. This function lets you set the
+/// entrypoint explicitly, for applications that can dynamically
+/// load drivers on their own.
+ADBC_EXPORT
+AdbcStatusCode AdbcDriverManagerDatabaseSetInitFunc(struct AdbcDatabase* 
database,
+                                                    AdbcDriverInitFunc 
init_func,
+                                                    struct AdbcError* error);
+
 /// \brief Get a human-friendly description of a status code.
 ADBC_EXPORT
 const char* AdbcStatusCodeMessage(AdbcStatusCode code);
diff --git a/c/drivers/postgres/postgres.cc b/c/drivers/postgres/postgres.cc
index f4c624f..46b553d 100644
--- a/c/drivers/postgres/postgres.cc
+++ b/c/drivers/postgres/postgres.cc
@@ -25,6 +25,7 @@
 #include "connection.h"
 #include "database.h"
 #include "statement.h"
+#include "util.h"
 
 using adbcpq::PostgresConnection;
 using adbcpq::PostgresDatabase;
@@ -50,6 +51,7 @@ using adbcpq::PostgresStatement;
 // AdbcDatabase
 
 namespace {
+using adbcpq::SetError;
 AdbcStatusCode PostgresDatabaseInit(struct AdbcDatabase* database,
                                     struct AdbcError* error) {
   if (!database || !database->private_data) return ADBC_STATUS_INVALID_STATE;
@@ -59,7 +61,14 @@ AdbcStatusCode PostgresDatabaseInit(struct AdbcDatabase* 
database,
 
 AdbcStatusCode PostgresDatabaseNew(struct AdbcDatabase* database,
                                    struct AdbcError* error) {
-  if (!database || database->private_data) return ADBC_STATUS_INVALID_STATE;
+  if (!database) {
+    SetError(error, "database must not be null");
+    return ADBC_STATUS_INVALID_STATE;
+  }
+  if (database->private_data) {
+    SetError(error, "database is already initialized");
+    return ADBC_STATUS_INVALID_STATE;
+  }
   auto impl = std::make_shared<PostgresDatabase>();
   database->private_data = new std::shared_ptr<PostgresDatabase>(impl);
   return ADBC_STATUS_OK;
@@ -115,7 +124,7 @@ AdbcStatusCode PostgresConnectionCommit(struct 
AdbcConnection* connection,
 
 AdbcStatusCode PostgresConnectionGetInfo(struct AdbcConnection* connection,
                                          uint32_t* info_codes, size_t 
info_codes_length,
-                                         struct AdbcStatement* statement,
+                                         struct ArrowArrayStream* stream,
                                          struct AdbcError* error) {
   return ADBC_STATUS_NOT_IMPLEMENTED;
   // if (!statement->private_data) return ADBC_STATUS_INVALID_STATE;
@@ -206,9 +215,9 @@ AdbcStatusCode AdbcConnectionCommit(struct AdbcConnection* 
connection,
 
 AdbcStatusCode AdbcConnectionGetInfo(struct AdbcConnection* connection,
                                      uint32_t* info_codes, size_t 
info_codes_length,
-                                     struct AdbcStatement* statement,
+                                     struct ArrowArrayStream* stream,
                                      struct AdbcError* error) {
-  return PostgresConnectionGetInfo(connection, info_codes, info_codes_length, 
statement,
+  return PostgresConnectionGetInfo(connection, info_codes, info_codes_length, 
stream,
                                    error);
 }
 
@@ -438,7 +447,7 @@ AdbcStatusCode AdbcDriverInit(size_t count, struct 
AdbcDriver* driver,
   driver->DatabaseSetOption = PostgresDatabaseSetOption;
 
   driver->ConnectionCommit = PostgresConnectionCommit;
-  // driver->ConnectionGetInfo = PostgresConnectionGetInfo;
+  driver->ConnectionGetInfo = PostgresConnectionGetInfo;
   // driver->ConnectionGetObjects = PostgresConnectionGetObjects;
   driver->ConnectionGetTableSchema = PostgresConnectionGetTableSchema;
   // driver->ConnectionGetTableTypes = PostgresConnectionGetTableTypes;
diff --git a/c/drivers/postgres/statement.cc b/c/drivers/postgres/statement.cc
index 9ad3d72..bb4a031 100644
--- a/c/drivers/postgres/statement.cc
+++ b/c/drivers/postgres/statement.cc
@@ -171,7 +171,7 @@ uint64_t ToNetworkInt64(int64_t v) { return 
htobe64(static_cast<uint64_t>(v)); }
 }  // namespace
 
 int TupleReader::GetSchema(struct ArrowSchema* out) {
-  if (!result_) {
+  if (!schema_.release) {
     last_error_ = "[libpq] Result set was already consumed or freed";
     return EINVAL;
   }
diff --git a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx 
b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx
index c104371..5d27a3f 100644
--- a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx
+++ b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx
@@ -91,6 +91,9 @@ cdef extern from "adbc.h" nogil:
         char[5] sqlstate
         CAdbcErrorRelease release
 
+    cdef struct CAdbcDriver"AdbcDriver":
+        pass
+
     cdef struct CAdbcDatabase"AdbcDatabase":
         void* private_data
 
@@ -116,6 +119,12 @@ cdef extern from "adbc.h" nogil:
     CAdbcStatusCode AdbcDatabaseInit(CAdbcDatabase* database, CAdbcError* 
error)
     CAdbcStatusCode AdbcDatabaseRelease(CAdbcDatabase* database, CAdbcError* 
error)
 
+    ctypedef void (*CAdbcDriverInitFunc "AdbcDriverInitFunc")(size_t, 
CAdbcDriver*, size_t*, CAdbcError*)
+    CAdbcStatusCode AdbcDriverManagerDatabaseSetInitFunc(
+        CAdbcDatabase* database,
+        CAdbcDriverInitFunc init_func,
+        CAdbcError* error)
+
     CAdbcStatusCode AdbcConnectionCommit(
         CAdbcConnection* connection,
         CAdbcError* error)
@@ -454,11 +463,16 @@ cdef class AdbcDatabase(_AdbcHandle):
         check_error(status, &c_error)
 
         for key, value in kwargs.items():
-            key = key.encode("utf-8")
-            value = value.encode("utf-8")
-            c_key = key
-            c_value = value
-            status = AdbcDatabaseSetOption(&self.database, c_key, c_value, 
&c_error)
+            if key == "init_func":
+                status = AdbcDriverManagerDatabaseSetInitFunc(
+                    &self.database, <CAdbcDriverInitFunc> (<uintptr_t> value), 
&c_error)
+            else:
+                key = key.encode("utf-8")
+                value = value.encode("utf-8")
+                c_key = key
+                c_value = value
+                status = AdbcDatabaseSetOption(
+                    &self.database, c_key, c_value, &c_error)
             check_error(status, &c_error)
 
         with nogil:
diff --git a/python/adbc_driver_manager/adbc_driver_manager/dbapi.py 
b/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
index 440737f..d18df6e 100644
--- a/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
+++ b/python/adbc_driver_manager/adbc_driver_manager/dbapi.py
@@ -301,7 +301,11 @@ class Cursor(_Closeable):
         if operation != self._last_query:
             self._last_query = operation
             self._stmt.set_sql_query(operation)
-            self._stmt.prepare()
+            try:
+                self._stmt.prepare()
+            except NotSupportedError:
+                # Not all drivers support it
+                pass
 
         if parameters:
             rb = pyarrow.record_batch(
diff --git a/python/adbc_driver_manager/pyproject.toml 
b/python/adbc_driver_manager/pyproject.toml
index 618025b..9424bb2 100644
--- a/python/adbc_driver_manager/pyproject.toml
+++ b/python/adbc_driver_manager/pyproject.toml
@@ -19,7 +19,7 @@
 name = "adbc_driver_manager"
 version = "0.0.1-alpha.1"
 description = ""
-authors = ["David Li <[email protected]>"]
+authors = ["Apache Arrow Developers <[email protected]>"]
 license = "Apache-2.0"
 homepage = "https://arrow.apache.org";
 repository = "https://github.com/apache/arrow-adbc";
diff --git a/.isort.cfg b/python/adbc_driver_postgres/.gitignore
similarity index 92%
copy from .isort.cfg
copy to python/adbc_driver_postgres/.gitignore
index c614890..126488c 100644
--- a/.isort.cfg
+++ b/python/adbc_driver_postgres/.gitignore
@@ -15,6 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[settings]
-known_first_party = adbc_driver_manager
-profile = black
+adbc_driver_postgres/*.c
+adbc_driver_postgres/*.cpp
+build/
diff --git a/.isort.cfg 
b/python/adbc_driver_postgres/adbc_driver_postgres/__init__.py
similarity index 68%
copy from .isort.cfg
copy to python/adbc_driver_postgres/adbc_driver_postgres/__init__.py
index c614890..a2342bf 100644
--- a/.isort.cfg
+++ b/python/adbc_driver_postgres/adbc_driver_postgres/__init__.py
@@ -15,6 +15,14 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[settings]
-known_first_party = adbc_driver_manager
-profile = black
+import importlib.resources
+
+import adbc_driver_manager
+
+
+def connect(uri: str) -> adbc_driver_manager.AdbcDatabase:
+    """Create a low level ADBC connection to Postgres."""
+    with importlib.resources.path(
+        __package__, "libadbc_driver_postgres.so"
+    ) as entrypoint:
+        return adbc_driver_manager.AdbcDatabase(driver=str(entrypoint), 
uri=uri)
diff --git a/python/adbc_driver_postgres/adbc_driver_postgres/dbapi.py 
b/python/adbc_driver_postgres/adbc_driver_postgres/dbapi.py
new file mode 100644
index 0000000..5047045
--- /dev/null
+++ b/python/adbc_driver_postgres/adbc_driver_postgres/dbapi.py
@@ -0,0 +1,116 @@
+# 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 adbc_driver_manager
+import adbc_driver_manager.dbapi
+import adbc_driver_postgres
+
+__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
+# XXX: Postgres doesn't fit any of the param styles
+# We'll need some Python-side wrangling specific to this driver
+paramstyle = "pyformat"
+
+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: str) -> "Connection":
+    """Connect to Postgres via ADBC."""
+    db = None
+    conn = None
+
+    try:
+        db = adbc_driver_postgres.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/.isort.cfg b/python/adbc_driver_postgres/build.py
similarity index 65%
copy from .isort.cfg
copy to python/adbc_driver_postgres/build.py
index c614890..748d1ea 100644
--- a/.isort.cfg
+++ b/python/adbc_driver_postgres/build.py
@@ -1,3 +1,5 @@
+#!/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
@@ -15,6 +17,19 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[settings]
-known_first_party = adbc_driver_manager
-profile = black
+import os
+import shutil
+from pathlib import Path
+
+
+def build(setup_kwargs):
+    """Configure build for Poetry."""
+    library = os.environ.get("ADBC_POSTGRES_LIBRARY")
+    if not library:
+        raise ValueError("Must provide ADBC_POSTGRES_LIBRARY")
+
+    shutil.copy(
+        library, 
Path("./adbc_driver_postgres/libadbc_driver_postgres.so").resolve()
+    )
+
+    setup_kwargs["zip_safe"] = False
diff --git a/python/adbc_driver_postgres/poetry.lock 
b/python/adbc_driver_postgres/poetry.lock
new file mode 100644
index 0000000..914bc04
--- /dev/null
+++ b/python/adbc_driver_postgres/poetry.lock
@@ -0,0 +1,154 @@
+[[package]]
+name = "atomicwrites"
+version = "1.4.1"
+description = "Atomic file writes."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "attrs"
+version = "22.1.0"
+description = "Classes Without Boilerplate"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest 
(>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", 
"furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest 
(>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", 
"cloudpickle"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest 
(>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"]
+
+[[package]]
+name = "colorama"
+version = "0.4.5"
+description = "Cross-platform colored terminal text."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "packaging"
+version = "21.3"
+description = "Core utilities for Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+
+[[package]]
+name = "pluggy"
+version = "1.0.0"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+testing = ["pytest-benchmark", "pytest"]
+dev = ["tox", "pre-commit"]
+
+[[package]]
+name = "py"
+version = "1.11.0"
+description = "library with cross-python path, ini-parsing, io, code, log 
facilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute 
parsing grammars"
+category = "dev"
+optional = false
+python-versions = ">=3.6.8"
+
+[package.extras]
+diagrams = ["railroad-diagrams", "jinja2"]
+
+[[package]]
+name = "pytest"
+version = "7.1.2"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+py = ">=1.8.2"
+tomli = ">=1.0.0"
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments 
(>=2.7.2)", "requests", "xmlschema"]
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[metadata]
+lock-version = "1.1"
+python-versions = ">=3.8"
+content-hash = 
"28b7035287b9941bba6efb45d5fe98e6b0b4f0c0677b0ed243ca3ad03a3bc0a4"
+
+[metadata.files]
+atomicwrites = [
+    {file = "atomicwrites-1.4.1.tar.gz", hash = 
"sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
+]
+attrs = [
+    {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = 
"sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
+    {file = "attrs-22.1.0.tar.gz", hash = 
"sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
+]
+colorama = [
+    {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = 
"sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
+    {file = "colorama-0.4.5.tar.gz", hash = 
"sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
+]
+iniconfig = [
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = 
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = 
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
+packaging = [
+    {file = "packaging-21.3-py3-none-any.whl", hash = 
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+    {file = "packaging-21.3.tar.gz", hash = 
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+]
+pluggy = [
+    {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = 
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
+    {file = "pluggy-1.0.0.tar.gz", hash = 
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
+]
+py = [
+    {file = "py-1.11.0-py2.py3-none-any.whl", hash = 
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+    {file = "py-1.11.0.tar.gz", hash = 
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
+]
+pyparsing = [
+    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = 
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+    {file = "pyparsing-3.0.9.tar.gz", hash = 
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+pytest = [
+    {file = "pytest-7.1.2-py3-none-any.whl", hash = 
"sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
+    {file = "pytest-7.1.2.tar.gz", hash = 
"sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
+]
+tomli = [
+    {file = "tomli-2.0.1-py3-none-any.whl", hash = 
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+    {file = "tomli-2.0.1.tar.gz", hash = 
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
diff --git a/python/adbc_driver_manager/pyproject.toml 
b/python/adbc_driver_postgres/pyproject.toml
similarity index 74%
copy from python/adbc_driver_manager/pyproject.toml
copy to python/adbc_driver_postgres/pyproject.toml
index 618025b..7b1c3c5 100644
--- a/python/adbc_driver_manager/pyproject.toml
+++ b/python/adbc_driver_postgres/pyproject.toml
@@ -16,32 +16,28 @@
 # under the License.
 
 [tool.poetry]
-name = "adbc_driver_manager"
-version = "0.0.1-alpha.1"
+name = "adbc_driver_postgres"
+version = "0.0.0-alpha0"
 description = ""
-authors = ["David Li <[email protected]>"]
+authors = ["Apache Arrow Developers <[email protected]>"]
 license = "Apache-2.0"
 homepage = "https://arrow.apache.org";
 repository = "https://github.com/apache/arrow-adbc";
+include = [
+    {path = "adbc_driver_postgres/*.so", format = "wheel"},
+]
 
 [tool.poetry.build]
 script = "build.py"
 
 [tool.poetry.dependencies]
-pandas = { version = ">=1.2,<2", optional = true }
-pyarrow = { version = ">=8.0.0", optional = true }
+# XXX: setuptools/build.py don't like path dependencies
+# adbc_driver_manager = { path = "../adbc_driver_manager/", develop = false }
 python = ">=3.8"
 
 [tool.poetry.dev-dependencies]
-Cython = "^0.29.32"
-pandas = ">=1.2"
-pyarrow = ">=8.0.0"
 pytest = "^7.1.2"
-setuptools = "^63.4.0"
-
-[tool.poetry.extras]
-dbapi = ["pyarrow"]
 
 [build-system]
-requires = ["Cython", "poetry-core>=1.0.0", "setuptools"]
+requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
diff --git a/python/adbc_driver_postgres/requirements-dev.txt 
b/python/adbc_driver_postgres/requirements-dev.txt
new file mode 100644
index 0000000..6f1365f
--- /dev/null
+++ b/python/adbc_driver_postgres/requirements-dev.txt
@@ -0,0 +1,70 @@
+atomicwrites==1.4.1; python_version >= "3.7" and python_full_version < "3.0.0" 
and sys_platform == "win32" or sys_platform == "win32" and python_version >= 
"3.7" and python_full_version >= "3.4.0" \
+    
--hash=sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11
+attrs==22.1.0; python_version >= "3.7" \
+    
--hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c \
+    
--hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6
+colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and 
sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" 
and python_full_version >= "3.5.0" \
+    
--hash=sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da \
+    
--hash=sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4
+cython==0.29.32; (python_version >= "2.6" and python_full_version < "3.0.0") 
or (python_full_version >= "3.3.0") \
+    
--hash=sha256:39afb4679b8c6bf7ccb15b24025568f4f9b4d7f9bf3cbd981021f542acecd75b \
+    
--hash=sha256:dbee03b8d42dca924e6aa057b836a064c769ddfd2a4c2919e65da2c8a362d528 \
+    
--hash=sha256:5ba622326f2862f9c1f99ca8d47ade49871241920a352c917e16861e25b0e5c3 \
+    
--hash=sha256:e6ffa08aa1c111a1ebcbd1cf4afaaec120bc0bbdec3f2545f8bb7d3e8e77a1cd \
+    
--hash=sha256:97335b2cd4acebf30d14e2855d882de83ad838491a09be2011745579ac975833 \
+    
--hash=sha256:06be83490c906b6429b4389e13487a26254ccaad2eef6f3d4ee21d8d3a4aaa2b \
+    
--hash=sha256:eefd2b9a5f38ded8d859fe96cc28d7d06e098dc3f677e7adbafda4dcdd4a461c \
+    
--hash=sha256:5514f3b4122cb22317122a48e175a7194e18e1803ca555c4c959d7dfe68eaf98 \
+    
--hash=sha256:656dc5ff1d269de4d11ee8542f2ffd15ab466c447c1f10e5b8aba6f561967276 \
+    
--hash=sha256:cdf10af3e2e3279dc09fdc5f95deaa624850a53913f30350ceee824dc14fc1a6 \
+    
--hash=sha256:3875c2b2ea752816a4d7ae59d45bb546e7c4c79093c83e3ba7f4d9051dd02928 \
+    
--hash=sha256:79e3bab19cf1b021b613567c22eb18b76c0c547b9bc3903881a07bfd9e7e64cf \
+    
--hash=sha256:b0595aee62809ba353cebc5c7978e0e443760c3e882e2c7672c73ffe46383673 \
+    
--hash=sha256:0ea8267fc373a2c5064ad77d8ff7bf0ea8b88f7407098ff51829381f8ec1d5d9 \
+    
--hash=sha256:c8e8025f496b5acb6ba95da2fb3e9dacffc97d9a92711aacfdd42f9c5927e094 \
+    
--hash=sha256:afbce249133a830f121b917f8c9404a44f2950e0e4f5d1e68f043da4c2e9f457 \
+    
--hash=sha256:513e9707407608ac0d306c8b09d55a28be23ea4152cbd356ceaec0f32ef08d65 \
+    
--hash=sha256:e83228e0994497900af954adcac27f64c9a57cd70a9ec768ab0cb2c01fd15cf1 \
+    
--hash=sha256:ea1dcc07bfb37367b639415333cfbfe4a93c3be340edf1db10964bc27d42ed64 \
+    
--hash=sha256:8669cadeb26d9a58a5e6b8ce34d2c8986cc3b5c0bfa77eda6ceb471596cb2ec3 \
+    
--hash=sha256:ed087eeb88a8cf96c60fb76c5c3b5fb87188adee5e179f89ec9ad9a43c0c54b3 \
+    
--hash=sha256:3f85eb2343d20d91a4ea9cf14e5748092b376a64b7e07fc224e85b2753e9070b \
+    
--hash=sha256:63b79d9e1f7c4d1f498ab1322156a0d7dc1b6004bf981a8abda3f66800e140cd \
+    
--hash=sha256:e1958e0227a4a6a2c06fd6e35b7469de50adf174102454db397cec6e1403cce3 \
+    
--hash=sha256:856d2fec682b3f31583719cb6925c6cdbb9aa30f03122bcc45c65c8b6f515754 \
+    
--hash=sha256:479690d2892ca56d34812fe6ab8f58e4b2e0129140f3d94518f15993c40553da \
+    
--hash=sha256:67fdd2f652f8d4840042e2d2d91e15636ba2bcdcd92e7e5ffbc68e6ef633a754 \
+    
--hash=sha256:4a4b03ab483271f69221c3210f7cde0dcc456749ecf8243b95bc7a701e5677e0 \
+    
--hash=sha256:40eff7aa26e91cf108fd740ffd4daf49f39b2fdffadabc7292b4b7dc5df879f0 \
+    
--hash=sha256:0bbc27abdf6aebfa1bce34cd92bd403070356f28b0ecb3198ff8a182791d58b9 \
+    
--hash=sha256:cddc47ec746a08603037731f5d10aebf770ced08666100bd2cdcaf06a85d4d1b \
+    
--hash=sha256:eca3065a1279456e81c615211d025ea11bfe4e19f0c5650b859868ca04b3fcbd \
+    
--hash=sha256:d968ffc403d92addf20b68924d95428d523436adfd25cf505d427ed7ba3bee8b \
+    
--hash=sha256:f3fd44cc362eee8ae569025f070d56208908916794b6ab21e139cea56470a2b3 \
+    
--hash=sha256:b6da3063c5c476f5311fd76854abae6c315f1513ef7d7904deed2e774623bbb9 \
+    
--hash=sha256:061e25151c38f2361bc790d3bcf7f9d9828a0b6a4d5afa56fbed3bd33fb2373a \
+    
--hash=sha256:f9944013588a3543fca795fffb0a070a31a243aa4f2d212f118aa95e69485831 \
+    
--hash=sha256:07d173d3289415bb496e72cb0ddd609961be08fe2968c39094d5712ffb78672b \
+    
--hash=sha256:eeb475eb6f0ccf6c039035eb4f0f928eb53ead88777e0a760eccb140ad90930b \
+    
--hash=sha256:8733cf4758b79304f2a4e39ebfac5e92341bce47bcceb26c1254398b2f8c1af7
+iniconfig==1.1.1; python_version >= "3.7" \
+    
--hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \
+    
--hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32
+packaging==21.3; python_version >= "3.7" \
+    
--hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 \
+    
--hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb
+pluggy==1.0.0; python_version >= "3.7" \
+    
--hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 \
+    
--hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159
+py==1.11.0; python_version >= "3.7" and python_full_version < "3.0.0" or 
python_full_version >= "3.5.0" and python_version >= "3.7" \
+    
--hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 \
+    
--hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719
+pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.7" \
+    
--hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc \
+    
--hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb
+pytest==7.1.2; python_version >= "3.7" \
+    
--hash=sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c \
+    
--hash=sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45
+tomli==2.0.1; python_version >= "3.7" \
+    
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+    
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
diff --git a/.isort.cfg b/python/adbc_driver_postgres/tests/__init__.py
similarity index 92%
copy from .isort.cfg
copy to python/adbc_driver_postgres/tests/__init__.py
index c614890..13a8339 100644
--- a/.isort.cfg
+++ b/python/adbc_driver_postgres/tests/__init__.py
@@ -14,7 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-[settings]
-known_first_party = adbc_driver_manager
-profile = black
diff --git a/.isort.cfg b/python/adbc_driver_postgres/tests/test_dbapi.py
similarity index 63%
copy from .isort.cfg
copy to python/adbc_driver_postgres/tests/test_dbapi.py
index c614890..74fd2c8 100644
--- a/.isort.cfg
+++ b/python/adbc_driver_postgres/tests/test_dbapi.py
@@ -15,6 +15,23 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[settings]
-known_first_party = adbc_driver_manager
-profile = black
+import os
+
+import pytest
+
+from adbc_driver_postgres import dbapi
+
+
[email protected]
+def postgres():
+    postgres_uri = os.environ.get("ADBC_POSTGRES_TEST_URI")
+    if not postgres_uri:
+        pytest.skip("Set ADBC_POSTGRES_TEST_URI to run tests")
+    with dbapi.connect(postgres_uri) as conn:
+        yield conn
+
+
+def test_query_trivial(postgres):
+    with postgres.cursor() as cur:
+        cur.execute("SELECT 1")
+        assert cur.fetchone() == (1,)
diff --git a/.isort.cfg b/python/adbc_driver_postgres/tests/test_lowlevel.py
similarity index 52%
copy from .isort.cfg
copy to python/adbc_driver_postgres/tests/test_lowlevel.py
index c614890..0bd925c 100644
--- a/.isort.cfg
+++ b/python/adbc_driver_postgres/tests/test_lowlevel.py
@@ -15,6 +15,28 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[settings]
-known_first_party = adbc_driver_manager
-profile = black
+import os
+
+import pyarrow
+import pytest
+
+import adbc_driver_manager
+import adbc_driver_postgres
+
+
[email protected]
+def postgres():
+    postgres_uri = os.environ.get("ADBC_POSTGRES_TEST_URI")
+    if not postgres_uri:
+        pytest.skip("Set ADBC_POSTGRES_TEST_URI to run tests")
+    with adbc_driver_postgres.connect(postgres_uri) as db:
+        with adbc_driver_manager.AdbcConnection(db) as conn:
+            yield conn
+
+
+def test_query_trivial(postgres):
+    with adbc_driver_manager.AdbcStatement(postgres) as stmt:
+        stmt.set_sql_query("SELECT 1")
+        stream, _ = stmt.execute_query()
+        reader = pyarrow.RecordBatchReader._import_from_c(stream.address)
+        assert reader.read_all()

Reply via email to