Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new connect_args:preload_functions #911

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 43 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
Basic SQLAlchemy driver for [DuckDB](https://duckdb.org/)

<!--ts-->
* [duckdb_engine](#duckdb_engine)
* [Installation](#installation)
* [Usage](#usage)
* [Usage in IPython/Jupyter](#usage-in-ipythonjupyter)
* [Configuration](#configuration)
* [How to register a pandas DataFrame](#how-to-register-a-pandas-dataframe)
* [Things to keep in mind](#things-to-keep-in-mind)
* [Auto-incrementing ID columns](#auto-incrementing-id-columns)
* [Pandas read_sql() chunksize](#pandas-read_sql-chunksize)
* [Unsigned integer support](#unsigned-integer-support)
* [Alembic Integration](#alembic-integration)
* [Preloading extensions (experimental)](#preloading-extensions-experimental)
* [The name](#the-name)
- [duckdb\_engine](#duckdb_engine)
- [Installation](#installation)
- [Usage](#usage)
- [Usage in IPython/Jupyter](#usage-in-ipythonjupyter)
- [Configuration](#configuration)
- [How to register a pandas DataFrame](#how-to-register-a-pandas-dataframe)
- [Things to keep in mind](#things-to-keep-in-mind)
- [Auto-incrementing ID columns](#auto-incrementing-id-columns)
- [Pandas `read_sql()` chunksize](#pandas-read_sql-chunksize)
- [Unsigned integer support](#unsigned-integer-support)
- [Alembic Integration](#alembic-integration)
- [Preloading extensions (experimental)](#preloading-extensions-experimental)
- [Preloading user-defined functions (experimental)](#preloading-user-defined-functions-experimental)
- [The name](#the-name)

<!-- Created by https://github.com/ekalinin/github-markdown-toc -->
<!-- Added by: me, at: Wed 20 Sep 2023 12:44:27 AWST -->
Expand Down Expand Up @@ -180,6 +181,35 @@ create_engine(
)
```

## Preloading user-defined functions (experimental)

> DuckDB 0.8.1+ includes builtin support for user-defined function (UDF) , see [the extension documentation](https://duckdb.org/docs/archive/0.8.1/api/python/function) for more information.

Until the DuckDB python client allows you to natively preload functions, I've added experimental support via a `connect_args` parameter

```python
from sqlalchemy import create_engine

create_engine(
'duckdb:///:memory:',
connect_args={
"preload_functions": [
"/data/path/to/udf.py"
]
}
)
```

and here is a very simple `udf.py`,

```python
def add_built_in_type(x:int) -> int:
return x + 1

def create_functions(conn):
conn.create_function('add_built_in_type', add_built_in_type, ['BIGINT'], 'BIGINT', type='native')
```

## The name

Yes, I'm aware this package should be named `duckdb-driver` or something, I wasn't thinking when I named it and it's too hard to change the name now
21 changes: 21 additions & 0 deletions duckdb_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,27 @@
kwargs["use_native_hstore"] = False
super().__init__(*args, **kwargs)

def create_functions(self, path, conn):
"""
Create functions using the specified path and connection.

:param path: The path to the file containing the functions to be created.
:param conn: The connection to be used for creating the functions.
:return: None
"""
import importlib.util
import os

Check warning on line 259 in duckdb_engine/__init__.py

View check run for this annotation

Codecov / codecov/patch

duckdb_engine/__init__.py#L258-L259

Added lines #L258 - L259 were not covered by tests

filename = os.path.basename(path)
spec = importlib.util.spec_from_file_location(filename, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
module.create_functions(conn)

Check warning on line 265 in duckdb_engine/__init__.py

View check run for this annotation

Codecov / codecov/patch

duckdb_engine/__init__.py#L261-L265

Added lines #L261 - L265 were not covered by tests

def connect(self, *cargs: Any, **cparams: Any) -> "Connection":
core_keys = get_core_config()
preload_extensions = cparams.pop("preload_extensions", [])
preload_functions = cparams.pop("preload_functions", [])
config = cparams.setdefault("config", {})
config.update(cparams.pop("url_config", {}))

Expand All @@ -265,6 +283,9 @@
for extension in preload_extensions:
conn.execute(f"LOAD {extension}")

for function in preload_functions:
self.create_functions(function, conn)

Check warning on line 287 in duckdb_engine/__init__.py

View check run for this annotation

Codecov / codecov/patch

duckdb_engine/__init__.py#L287

Added line #L287 was not covered by tests

apply_config(self, conn, ext)

return ConnectionWrapper(conn)
Expand Down
Loading