adbcbridge 0.1.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.
adbcbridge/__init__.py ADDED
@@ -0,0 +1,157 @@
1
+ # Copyright 2026 the adbcbridge authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ # SPDX-License-Identifier: Apache-2.0
16
+
17
+ """adbcbridge: use any ODBC data source as an ADBC (Arrow) database.
18
+
19
+ import adbcbridge
20
+
21
+ with adbcbridge.connect(uri="Driver=SQLite3;Database=my.db;") as conn:
22
+ with conn.cursor() as cur:
23
+ cur.execute("SELECT * FROM t")
24
+ table = cur.fetch_arrow_table()
25
+
26
+ This package is a thin convenience layer: it finds the adbcbridge shared
27
+ library (:func:`driver_path`) and hands it to ``adbc_driver_manager``. The
28
+ object it returns is a plain ``adbc_driver_manager.dbapi.Connection``.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from typing import TYPE_CHECKING, Any, Dict, Optional
34
+
35
+ from . import _preload
36
+ from ._locate import (
37
+ DriverNotFoundError,
38
+ OdbcDriver,
39
+ driver_path,
40
+ odbc_driver_library,
41
+ odbc_drivers,
42
+ odbcinst_ini,
43
+ )
44
+ from ._preload import preload_odbc_driver
45
+
46
+ if TYPE_CHECKING: # pragma: no cover - typing only
47
+ from adbc_driver_manager import dbapi as _dbapi
48
+
49
+ __all__ = [
50
+ "DriverNotFoundError",
51
+ "OdbcDriver",
52
+ "__version__",
53
+ "connect",
54
+ "driver_path",
55
+ "odbc_driver_library",
56
+ "odbc_drivers",
57
+ "odbcinst_ini",
58
+ "preload_odbc_driver",
59
+ ]
60
+
61
+ __version__ = "0.1.0"
62
+
63
+ # Local alias: the `driver_path` parameter of connect() shadows the function.
64
+ _find_driver = driver_path
65
+
66
+
67
+ def _dbapi_module():
68
+ """Import ``adbc_driver_manager.dbapi`` -- and, with it, pyarrow.
69
+
70
+ Deferred rather than done at import time so that ``import adbcbridge`` does
71
+ not pull pyarrow into the process: an ODBC driver that needs static
72
+ thread-local storage can then still be loaded first (see ``_preload``).
73
+ """
74
+ from adbc_driver_manager import dbapi
75
+
76
+ return dbapi
77
+
78
+
79
+ #: Prefix for this driver's own options, e.g. ``adbc.odbc.batch_size``.
80
+ OPTION_PREFIX = "adbc.odbc."
81
+
82
+ # Options the driver takes verbatim (they are ADBC-standard names, not ours).
83
+ _PLAIN_OPTIONS = frozenset({"uri", "dsn", "username", "password"})
84
+
85
+
86
+ def _option_key(name: str) -> str:
87
+ """``batch_size`` -> ``adbc.odbc.batch_size``; dotted names pass through."""
88
+ if "." in name or name in _PLAIN_OPTIONS:
89
+ return name
90
+ return OPTION_PREFIX + name
91
+
92
+
93
+ def _option_value(value: Any) -> str:
94
+ if isinstance(value, bool): # the driver parses "true"/"false"
95
+ return "true" if value else "false"
96
+ return str(value)
97
+
98
+
99
+ def connect(
100
+ uri: Optional[str] = None,
101
+ dsn: Optional[str] = None,
102
+ username: Optional[str] = None,
103
+ password: Optional[str] = None,
104
+ driver_path: Optional[str] = None, # noqa: A002 - matches the documented API
105
+ *,
106
+ autocommit: bool = False,
107
+ conn_kwargs: Optional[Dict[str, str]] = None,
108
+ **options: Any,
109
+ ) -> _dbapi.Connection:
110
+ """Connect to an ODBC data source and return a DBAPI 2.0 connection.
111
+
112
+ :param uri: full ODBC connection string, e.g.
113
+ ``"Driver=/usr/lib/x86_64-linux-gnu/odbc/libsqlite3odbc.so;Database=my.db;"``.
114
+ :param dsn: DSN name from ``odbc.ini`` (used instead of, or alongside, *uri*).
115
+ :param username: sent as ``UID=``.
116
+ :param password: sent as ``PWD=``.
117
+ :param driver_path: path to ``libadbc_driver_odbc.so``; defaults to
118
+ :func:`driver_path`. An ADBC driver manifest name such as ``"odbc"``
119
+ also works, since the value is passed straight to the driver manager.
120
+ :param autocommit: passed to the driver manager (default: transactional).
121
+ :param conn_kwargs: extra connection-level ADBC options.
122
+ :param options: further database options. A bare name is prefixed with
123
+ ``adbc.odbc.`` (``batch_size=4096`` sets ``adbc.odbc.batch_size``);
124
+ a dotted name is used as given. ``bool`` becomes ``"true"``/``"false"``.
125
+
126
+ The return value is an ``adbc_driver_manager.dbapi.Connection``, so
127
+ everything ADBC's DBAPI layer offers (``cursor()``, ``fetch_arrow_table()``,
128
+ ``adbc_ingest()``, ``adbc_get_objects()``, ...) is available.
129
+ """
130
+ if uri is None and dsn is None:
131
+ raise ValueError("connect() needs at least one of uri= or dsn=")
132
+
133
+ db_kwargs: Dict[str, str] = {}
134
+ if uri is not None:
135
+ db_kwargs["uri"] = uri
136
+ if dsn is not None:
137
+ db_kwargs["dsn"] = dsn
138
+ if username is not None:
139
+ db_kwargs["username"] = username
140
+ if password is not None:
141
+ db_kwargs["password"] = password
142
+ for key, value in options.items():
143
+ if value is None:
144
+ continue
145
+ db_kwargs[_option_key(key)] = _option_value(value)
146
+
147
+ driver = driver_path if driver_path is not None else _find_driver()
148
+ if _preload.preload_enabled():
149
+ # Before pyarrow comes in below. Best effort: a driver that will not
150
+ # load here still gets its real failure reported by the connection.
151
+ preload_odbc_driver(uri=uri, dsn=dsn)
152
+ return _dbapi_module().connect(
153
+ driver=driver,
154
+ db_kwargs=db_kwargs,
155
+ conn_kwargs=dict(conn_kwargs or {}),
156
+ autocommit=autocommit,
157
+ )
adbcbridge/__main__.py ADDED
@@ -0,0 +1,24 @@
1
+ # Copyright 2026 the adbcbridge authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ # SPDX-License-Identifier: Apache-2.0
16
+
17
+ """``python -m adbcbridge`` / the ``adbcbridge`` console script."""
18
+
19
+ import sys
20
+
21
+ from ._cli import main
22
+
23
+ if __name__ == "__main__":
24
+ sys.exit(main())
adbcbridge/_cli.py ADDED
@@ -0,0 +1,134 @@
1
+ # Copyright 2026 the adbcbridge authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ # SPDX-License-Identifier: Apache-2.0
16
+
17
+ """The ``adbcbridge`` command line tool."""
18
+
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import pathlib
23
+ import sys
24
+ from typing import List, Optional, Sequence
25
+
26
+ from . import __version__, connect
27
+ from ._locate import DriverNotFoundError, driver_path, odbc_drivers, odbcinst_ini
28
+
29
+
30
+ def _cmd_query(args: argparse.Namespace) -> int:
31
+ params: Optional[List[str]] = args.param or None
32
+ with connect(uri=args.connection_string, driver_path=args.driver_path,
33
+ autocommit=True) as conn:
34
+ with conn.cursor() as cur:
35
+ cur.execute(args.sql, parameters=params)
36
+ if not cur.description: # DDL/DML: no result set
37
+ affected = cur.rowcount
38
+ # ODBC reports -1 when a statement has no meaningful row count.
39
+ print("OK" if affected is None or affected < 0
40
+ else "rows affected: %d" % affected)
41
+ return 0
42
+ table = cur.fetch_arrow_table()
43
+ if args.limit is not None and table.num_rows > args.limit:
44
+ table = table.slice(0, args.limit)
45
+ if args.format == "csv":
46
+ import pyarrow.csv
47
+
48
+ pyarrow.csv.write_csv(table, sys.stdout.buffer)
49
+ elif args.format == "schema":
50
+ print(table.schema)
51
+ else:
52
+ print(table)
53
+ print("%d rows x %d columns" % (table.num_rows, table.num_columns))
54
+ return 0
55
+
56
+
57
+ def _cmd_drivers(args: argparse.Namespace) -> int:
58
+ ini = odbcinst_ini()
59
+ drivers = odbc_drivers()
60
+ if not drivers:
61
+ where = str(ini) if ini else "odbcinst -j / odbcinst.ini"
62
+ print("no ODBC drivers registered (%s)" % where, file=sys.stderr)
63
+ return 1
64
+ print("# %s" % ini)
65
+ width = max(len(d.name) for d in drivers)
66
+ for driver in drivers:
67
+ note = ""
68
+ if driver.path and not pathlib.Path(driver.path).is_file():
69
+ note = " (missing)"
70
+ print("%-*s %s%s" % (width, driver.name, driver.path or "-", note))
71
+ if args.verbose and driver.description:
72
+ print("%-*s %s" % (width, "", driver.description))
73
+ return 0
74
+
75
+
76
+ def _cmd_driver_path(args: argparse.Namespace) -> int:
77
+ print(driver_path())
78
+ return 0
79
+
80
+
81
+ def build_parser() -> argparse.ArgumentParser:
82
+ parser = argparse.ArgumentParser(
83
+ prog="adbcbridge",
84
+ description="Query any ODBC data source through adbcbridge (ADBC/Arrow).",
85
+ )
86
+ parser.add_argument("--version", action="version", version="adbcbridge " + __version__)
87
+ sub = parser.add_subparsers(dest="command", required=True)
88
+
89
+ query = sub.add_parser(
90
+ "query",
91
+ help="run a SQL statement and print the resulting Arrow table",
92
+ description="Run a SQL statement against an ODBC connection string.",
93
+ epilog='example: adbcbridge query "Driver=SQLite3;Database=my.db;" '
94
+ '"SELECT * FROM t"',
95
+ )
96
+ query.add_argument("connection_string", help="ODBC connection string (Driver=...;)")
97
+ query.add_argument("sql", help="SQL to execute")
98
+ query.add_argument("-p", "--param", action="append", metavar="VALUE",
99
+ help="value for a '?' parameter (repeat, in order); "
100
+ "always sent as a string")
101
+ query.add_argument("--driver-path", metavar="PATH",
102
+ help="path to libadbc_driver_odbc.so (default: auto-detect)")
103
+ query.add_argument("--limit", type=int, metavar="N", help="print at most N rows")
104
+ query.add_argument("--format", choices=("table", "csv", "schema"), default="table",
105
+ help="output format (default: table)")
106
+ query.set_defaults(func=_cmd_query)
107
+
108
+ drivers = sub.add_parser(
109
+ "drivers",
110
+ help="list the ODBC drivers registered on this machine",
111
+ description="List the ODBC drivers from odbcinst -j / odbcinst.ini.",
112
+ )
113
+ drivers.add_argument("-v", "--verbose", action="store_true",
114
+ help="also print each driver's description")
115
+ drivers.set_defaults(func=_cmd_drivers)
116
+
117
+ where = sub.add_parser(
118
+ "driver-path",
119
+ help="print the path of the adbcbridge shared library that would be used",
120
+ )
121
+ where.set_defaults(func=_cmd_driver_path)
122
+ return parser
123
+
124
+
125
+ def main(argv: Optional[Sequence[str]] = None) -> int:
126
+ args = build_parser().parse_args(argv)
127
+ try:
128
+ return args.func(args)
129
+ except DriverNotFoundError as exc:
130
+ print("adbcbridge: %s" % exc, file=sys.stderr)
131
+ return 2
132
+ except Exception as exc: # database errors: a traceback helps nobody here
133
+ print("adbcbridge: %s: %s" % (type(exc).__name__, exc), file=sys.stderr)
134
+ return 1