adbc-driver-spanner 0.5.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ """ADBC driver for Google Cloud Spanner.
2
+
3
+ This package bundles the prebuilt Spanner ADBC driver shared library and exposes
4
+ a thin Python wrapper around it. The heavy lifting lives in the Rust cdylib; this
5
+ module just locates the bundled library and hands it to ``adbc_driver_manager``,
6
+ which loads it over the ADBC C ABI.
7
+
8
+ For a DBAPI 2.0 (PEP 249) connection with pandas/polars/Arrow helpers, use
9
+ :func:`adbc_driver_spanner.dbapi.connect` instead of the low-level
10
+ :func:`connect` here.
11
+ """
12
+
13
+ import functools
14
+ import pathlib
15
+ import typing
16
+
17
+ import adbc_driver_manager
18
+
19
+ from ._version import __version__
20
+
21
+ __all__ = ["connect", "option_kwargs", "ENTRYPOINT", "__version__"]
22
+
23
+ #: C entrypoint exported by the shared library (see src/ffi.rs).
24
+ ENTRYPOINT = "AdbcSpannerInit"
25
+
26
+
27
+ def option_kwargs(
28
+ database: typing.Optional[str] = None,
29
+ *,
30
+ endpoint: typing.Optional[str] = None,
31
+ emulator: bool = False,
32
+ keyfile: typing.Optional[str] = None,
33
+ keyfile_json: typing.Optional[str] = None,
34
+ db_kwargs: typing.Optional[typing.Mapping[str, str]] = None,
35
+ ) -> typing.Dict[str, str]:
36
+ """Translate the friendly connection kwargs into ``adbc.spanner.*`` options.
37
+
38
+ Shared by :func:`connect` and :func:`adbc_driver_spanner.dbapi.connect` so the
39
+ two entry points map parameters identically. ``db_kwargs`` is an escape hatch
40
+ for raw option keys and is merged last.
41
+ """
42
+ options: typing.Dict[str, str] = {}
43
+ # Friendly kwargs -> the driver's option keys (see src/lib.rs).
44
+ if database is not None:
45
+ options["adbc.spanner.database"] = database
46
+ if endpoint is not None:
47
+ options["adbc.spanner.endpoint"] = endpoint
48
+ if emulator:
49
+ options["adbc.spanner.emulator"] = "true"
50
+ if keyfile is not None:
51
+ options["adbc.spanner.keyfile"] = keyfile
52
+ if keyfile_json is not None:
53
+ options["adbc.spanner.keyfile_json"] = keyfile_json
54
+ if db_kwargs:
55
+ options.update(db_kwargs)
56
+ return options
57
+
58
+
59
+ def connect(
60
+ database: typing.Optional[str] = None,
61
+ *,
62
+ endpoint: typing.Optional[str] = None,
63
+ emulator: bool = False,
64
+ keyfile: typing.Optional[str] = None,
65
+ keyfile_json: typing.Optional[str] = None,
66
+ db_kwargs: typing.Optional[typing.Mapping[str, str]] = None,
67
+ ) -> adbc_driver_manager.AdbcDatabase:
68
+ """Create a low-level ADBC database handle for Spanner.
69
+
70
+ Parameters
71
+ ----------
72
+ database:
73
+ Fully-qualified database path,
74
+ ``projects/<p>/instances/<i>/databases/<d>``.
75
+ endpoint:
76
+ Override the Spanner gRPC endpoint (e.g. an emulator ``host:port``).
77
+ emulator:
78
+ Use anonymous credentials and talk to the emulator. When
79
+ ``SPANNER_EMULATOR_HOST`` is set the driver detects the emulator on its
80
+ own, so this is only needed to force it explicitly.
81
+ keyfile / keyfile_json:
82
+ Service-account credentials, as a path or inline JSON. Omit both to use
83
+ Application Default Credentials.
84
+ db_kwargs:
85
+ Escape hatch for raw ``adbc.spanner.*`` option keys, merged last.
86
+
87
+ For a DBAPI 2.0 connection, prefer :func:`adbc_driver_spanner.dbapi.connect`.
88
+ """
89
+ options = option_kwargs(
90
+ database,
91
+ endpoint=endpoint,
92
+ emulator=emulator,
93
+ keyfile=keyfile,
94
+ keyfile_json=keyfile_json,
95
+ db_kwargs=db_kwargs,
96
+ )
97
+ # ** unpacking accepts the dotted, non-identifier option keys; they land in
98
+ # AdbcDatabase's **kwargs and are forwarded as ADBC options.
99
+ return adbc_driver_manager.AdbcDatabase(
100
+ driver=_driver_path(), entrypoint=ENTRYPOINT, **options
101
+ )
102
+
103
+
104
+ @functools.cache
105
+ def _driver_path() -> str:
106
+ """Absolute path to the shared library bundled in this wheel."""
107
+ here = pathlib.Path(__file__).resolve().parent
108
+ for name in ("libadbc_spanner.so", "libadbc_spanner.dylib", "adbc_spanner.dll"):
109
+ candidate = here / name
110
+ if candidate.is_file():
111
+ return str(candidate)
112
+ raise RuntimeError(
113
+ "adbc_driver_spanner: no bundled Spanner driver library found next to "
114
+ f"{here}. This usually means a source/sdist install without a matching "
115
+ "platform wheel; install a prebuilt wheel for your platform instead."
116
+ )
@@ -0,0 +1 @@
1
+ __version__ = "0.5.0"
Binary file
@@ -0,0 +1,58 @@
1
+ """DBAPI 2.0 (PEP 249) interface for the Spanner ADBC driver.
2
+
3
+ This is the layer most users want: it returns a standard DBAPI connection with
4
+ cursors, plus the ADBC Arrow extensions (``fetch_arrow_table``, ``fetch_df``,
5
+ ``adbc_ingest``) that pandas / polars / DuckDB consume directly.
6
+
7
+ import adbc_driver_spanner.dbapi as spanner
8
+ with spanner.connect(database="projects/p/instances/i/databases/d") as conn:
9
+ df = conn.cursor().execute("SELECT * FROM Singers").fetch_df()
10
+
11
+ Note: DBAPI is autocommit-off by default, which puts this driver into its
12
+ buffer-and-commit manual-transaction mode — call ``conn.commit()`` to apply DML.
13
+ Pass ``autocommit=True`` to keep the driver's default single-statement mode.
14
+ """
15
+
16
+ import typing
17
+
18
+ import adbc_driver_manager.dbapi
19
+
20
+ from . import ENTRYPOINT, _driver_path, option_kwargs
21
+
22
+ __all__ = ["connect"]
23
+
24
+
25
+ def connect(
26
+ database: typing.Optional[str] = None,
27
+ *,
28
+ endpoint: typing.Optional[str] = None,
29
+ emulator: bool = False,
30
+ keyfile: typing.Optional[str] = None,
31
+ keyfile_json: typing.Optional[str] = None,
32
+ db_kwargs: typing.Optional[typing.Mapping[str, str]] = None,
33
+ conn_kwargs: typing.Optional[typing.Mapping[str, str]] = None,
34
+ autocommit: bool = False,
35
+ ) -> adbc_driver_manager.dbapi.Connection:
36
+ """Open a DBAPI 2.0 connection to a Spanner database.
37
+
38
+ Accepts the same connection parameters as
39
+ :func:`adbc_driver_spanner.connect`; ``conn_kwargs`` sets raw
40
+ ``adbc.connection.*`` options and ``autocommit`` toggles PEP 249 autocommit.
41
+ """
42
+ options = option_kwargs(
43
+ database,
44
+ endpoint=endpoint,
45
+ emulator=emulator,
46
+ keyfile=keyfile,
47
+ keyfile_json=keyfile_json,
48
+ db_kwargs=db_kwargs,
49
+ )
50
+ # The driver manager builds and owns the database/connection handles here and
51
+ # tears them down if the connection fails, so no manual cleanup is needed.
52
+ return adbc_driver_manager.dbapi.connect(
53
+ driver=_driver_path(),
54
+ entrypoint=ENTRYPOINT,
55
+ db_kwargs=options,
56
+ conn_kwargs=conn_kwargs,
57
+ autocommit=autocommit,
58
+ )
File without changes
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: adbc-driver-spanner
3
+ Version: 0.5.0
4
+ Summary: ADBC (Arrow Database Connectivity) driver for Google Cloud Spanner
5
+ Author-email: Fredrik Fornwall <fredrik@fornwall.net>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/fornwall/adbc-spanner
8
+ Project-URL: Repository, https://github.com/fornwall/adbc-spanner
9
+ Keywords: adbc,arrow,spanner,database,gcp
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Rust
14
+ Classifier: Topic :: Database
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: adbc-driver-manager
18
+ Provides-Extra: dbapi
19
+ Requires-Dist: pyarrow>=8; extra == "dbapi"
20
+
21
+ # adbc-driver-spanner
22
+
23
+ A Python [ADBC](https://arrow.apache.org/adbc/) driver for **Google Cloud Spanner**.
24
+
25
+ It bundles the prebuilt native driver (a Rust cdylib) and exposes it through
26
+ [`adbc_driver_manager`](https://pypi.org/project/adbc-driver-manager/), so you
27
+ get a standard DBAPI 2.0 connection whose results come back as Apache Arrow —
28
+ ready for pandas, polars, DuckDB, or PyArrow with no per-row conversion.
29
+
30
+ ## Install
31
+
32
+ ```sh
33
+ pip install adbc-driver-spanner
34
+ # for the DataFrame / Arrow helpers used below:
35
+ pip install adbc-driver-spanner[dbapi] pandas
36
+ ```
37
+
38
+ Prebuilt wheels are published for Linux (x86-64, aarch64), macOS (arm64,
39
+ x86-64), and Windows (x86-64, arm64).
40
+
41
+ ## Quickstart
42
+
43
+ ```python
44
+ import adbc_driver_spanner.dbapi as spanner
45
+
46
+ with spanner.connect(
47
+ database="projects/my-project/instances/my-instance/databases/my-db",
48
+ ) as conn:
49
+ with conn.cursor() as cur:
50
+ cur.execute("SELECT SingerId, FirstName FROM Singers")
51
+ df = cur.fetch_df() # -> pandas.DataFrame
52
+ ```
53
+
54
+ ## Connection options
55
+
56
+ Options mirror the driver's `adbc.spanner.*` keys:
57
+
58
+ | kwarg | driver option |
59
+ | -------------- | --------------------------- |
60
+ | `database=` | `adbc.spanner.database` |
61
+ | `endpoint=` | `adbc.spanner.endpoint` |
62
+ | `emulator=` | `adbc.spanner.emulator` |
63
+ | `keyfile=` | `adbc.spanner.keyfile` |
64
+ | `keyfile_json=`| `adbc.spanner.keyfile_json` |
65
+
66
+ Credentials default to Application Default Credentials; pass `keyfile=` /
67
+ `keyfile_json=` for a service account, or point at the emulator:
68
+
69
+ ```python
70
+ # docs-test: skip
71
+ spanner.connect(database="projects/p/instances/i/databases/d",
72
+ endpoint="localhost:9010", emulator=True)
73
+ ```
74
+
75
+ ## Cookbook
76
+
77
+ Every snippet below is executed against the Spanner emulator in CI, so they stay
78
+ correct. They assume a `Singers(SingerId INT64, FirstName STRING)` table.
79
+
80
+ Two things to know:
81
+
82
+ - **DBAPI is autocommit-off by default**, so **DML and ingest need a
83
+ `conn.commit()`** (or pass `autocommit=True`). Reads need neither.
84
+ - The DataFrame / Arrow paths need the `[dbapi]` extra (pyarrow).
85
+
86
+ **pyarrow — zero-copy Arrow:**
87
+
88
+ ```python
89
+ import adbc_driver_spanner.dbapi as spanner
90
+
91
+ with spanner.connect(
92
+ database="projects/my-project/instances/my-instance/databases/my-db",
93
+ ) as conn:
94
+ with conn.cursor() as cur:
95
+ cur.execute("SELECT SingerId, FirstName FROM Singers ORDER BY SingerId")
96
+ table = cur.fetch_arrow_table() # -> pyarrow.Table
97
+ ```
98
+
99
+ **polars — read straight from the connection:**
100
+
101
+ ```python
102
+ import polars as pl
103
+ import adbc_driver_spanner.dbapi as spanner
104
+
105
+ with spanner.connect(
106
+ database="projects/my-project/instances/my-instance/databases/my-db",
107
+ ) as conn:
108
+ df = pl.read_database(
109
+ "SELECT SingerId, FirstName FROM Singers ORDER BY SingerId",
110
+ connection=conn, # an ADBC connection, not a URI
111
+ )
112
+ ```
113
+
114
+ **DuckDB — query the fetched Arrow table in-process:**
115
+
116
+ ```python
117
+ import duckdb
118
+ import adbc_driver_spanner.dbapi as spanner
119
+
120
+ with spanner.connect(
121
+ database="projects/my-project/instances/my-instance/databases/my-db",
122
+ ) as conn:
123
+ with conn.cursor() as cur:
124
+ cur.execute("SELECT SingerId, FirstName FROM Singers")
125
+ singers = cur.fetch_arrow_table()
126
+
127
+ # `singers` is a pyarrow.Table; DuckDB queries it by variable name, no copy.
128
+ top = duckdb.sql("SELECT COUNT(*) AS n, MIN(FirstName) AS first FROM singers").fetchone()
129
+ ```
130
+
131
+ **Insert a DataFrame (bulk ingest):**
132
+
133
+ ```python
134
+ import pandas as pd
135
+ import pyarrow as pa
136
+ import adbc_driver_spanner.dbapi as spanner
137
+
138
+ frame = pd.DataFrame({"SingerId": [10, 11], "FirstName": ["Carol", "Dave"]})
139
+
140
+ with spanner.connect(
141
+ database="projects/my-project/instances/my-instance/databases/my-db",
142
+ autocommit=True, # apply immediately; returns the row count
143
+ ) as conn:
144
+ with conn.cursor() as cur:
145
+ # The target table must already exist — only append mode is supported.
146
+ rows = cur.adbc_ingest("Singers", pa.Table.from_pandas(frame), mode="append")
147
+ ```
148
+
149
+ ## Partitioned reads and Data Boost
150
+
151
+ A large scan can be split into independent partitions and read in parallel —
152
+ optionally on Spanner's serverless [Data Boost] compute, so the work is isolated
153
+ from your provisioned instance. This uses the ADBC partitioned-execution
154
+ extension (`adbc_execute_partitions` / `adbc_read_partition`):
155
+
156
+ ```python
157
+ import adbc_driver_spanner.dbapi as spanner
158
+
159
+ with spanner.connect(
160
+ database="projects/my-project/instances/my-instance/databases/my-db",
161
+ ) as conn:
162
+ with conn.cursor() as cur:
163
+ # Optional statement options, set on the underlying ADBC statement:
164
+ cur.adbc_statement.set_options(**{
165
+ "adbc.spanner.data_boost_enabled": "true", # run on Data Boost
166
+ "adbc.spanner.max_partitions": "8", # cap the partition count
167
+ })
168
+ partitions, schema = cur.adbc_execute_partitions("SELECT SingerId FROM Singers")
169
+
170
+ # Each descriptor is opaque bytes; it can be shipped to another worker,
171
+ # process, or connection and read independently.
172
+ for token in partitions:
173
+ with conn.cursor() as cur:
174
+ cur.adbc_read_partition(token)
175
+ table = cur.fetch_arrow_table()
176
+ ...
177
+ ```
178
+
179
+ The Data Boost choice is baked into each descriptor, so it is honoured wherever
180
+ the partition is read. Only single-table scans are partitionable — queries with
181
+ an `ORDER BY` or aggregation are not.
182
+
183
+ [Data Boost]: https://cloud.google.com/spanner/docs/databoost/databoost-overview
184
+
185
+ ## How this package is built
186
+
187
+ The wheel is **data-only**: it does not compile anything at install time and
188
+ links nothing against Python. CI (`.github/workflows/libraries.yml`) builds the
189
+ native library per platform, drops it into `adbc_driver_spanner/`, and packages
190
+ a `py3-none-<platform>` wheel. See that workflow for the release wiring.
@@ -0,0 +1,9 @@
1
+ adbc_driver_spanner/__init__.py,sha256=2zV0i1fbiJyGuuYAAKTG504WIGZJ-wKPT9xdEtMb4rk,4209
2
+ adbc_driver_spanner/_version.py,sha256=LBK46heutvn3KmsCrKIYu8RQikbfnjZaj2xFrXaeCzQ,22
3
+ adbc_driver_spanner/adbc_spanner.dll,sha256=XGFb7Vll8Vtf2-73GmKUp3uS6Zczik9yW59SVY6lQMs,18796032
4
+ adbc_driver_spanner/dbapi.py,sha256=obq1KQu9EI727oWonG39VtEQ1sv6sNezGtReOVafvOA,2104
5
+ adbc_driver_spanner/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ adbc_driver_spanner-0.5.0.dist-info/METADATA,sha256=lZnZC_9gz__KRyJykOdXsSXmVg_NHhv28q-bIcuQIFA,6668
7
+ adbc_driver_spanner-0.5.0.dist-info/WHEEL,sha256=hVx9elvUDfBjRmbl8JwIcXXikst35RZTZK9nspfI_28,98
8
+ adbc_driver_spanner-0.5.0.dist-info/top_level.txt,sha256=OQpyk3MKXSGZElB1wpO5_JMeFvzz3zAHYza6ahRQmb4,20
9
+ adbc_driver_spanner-0.5.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
5
+
@@ -0,0 +1 @@
1
+ adbc_driver_spanner