| Crates.io | polite |
| lib.rs | polite |
| version | 0.1.3 |
| created_at | 2025-08-25 20:59:58.468286+00 |
| updated_at | 2025-08-26 21:49:39.191955+00 |
| description | rusqlite × Polars bridge library |
| homepage | https://github.com/lmmx/polite |
| repository | https://github.com/lmmx/polite |
| max_upload_size | |
| id | 1810145 |
| size | 271,781 |
The core rusqlite × Polars bridge.
polite makes it easy to move data between SQLite databases and Polars DataFrames.
DataFrames (to_dataframe) via ConnectorX.DataFrames into SQLite tables (from_dataframe).=0.4.39 due to an upstream Arrow/Polars issue
(this will be removed once the conflict is resolved there).INTEGER → Polars Int64
REAL → Polars Float64
TEXT → Polars StringString.DataFrame format.⚠️ Notes on SQLite backends
polite uses ConnectorX for bulk reads into Polars..sqlite, .db) are required.:memory:) are not supported — use a tempfile if you don’t want persistence.💡 All of these functions are also available via
use polite::prelude::*;
The two basic functions provided by the library are:
to_dataframe(db_path, sql) – run a query and return a DataFrame.from_dataframe(&conn, table, &df) – write a DataFrame into a table.
Takes an open rusqlite::Connection, the table name to write to, and your DataFrame.polite also provides a couple of convenience wrappers
(with simplified string errors and without connection handling):
save_dataframe(db_path, table, &df)
Opens a connection and writes the DataFrame in one step.
Creates and closes its own connection; use this for one-off saves.
load_dataframe(db_path, sql)
Wraps to_dataframe but adds context to errors (e.g. "Failed to load DataFrame from demo.db: no such table: users").
This makes it clearer where the failure came from, especially if you’re working with multiple databases.
These helpers don’t add new capabilities beyond the core API, but they provide more ergonomic errors.
The raw API (to_dataframe, from_dataframe) exposes detailed error variants (Query, Arrow, Polars, rusqlite, etc.), which is useful if you want to distinguish exactly what failed.
The convenience wrappers (load_dataframe, save_dataframe) normalize those into a single error variant per operation:
load_dataframe always yields PoliteError::Loadsave_dataframe always yields PoliteError::SaveQuery, Arrow, ArrowToPolars variants of PoliteError, rusqlite::Error etc.to_dataframe / from_dataframe for finer control and granular error inspection.In practice, wrappers are the recommended default for most use cases. Drop down to the raw API when you want maximum control.
use polite::prelude::*;
use polars::prelude::*;
fn main() -> anyhow::Result<(), String> {
// Open (or create) a SQLite database
let db_path = "polite.db";
let conn = connect_sqlite(Some(db_path))?;
execute_query(&conn, "CREATE TABLE friends_made (id INTEGER, name TEXT)")?;
let nobody = load_dataframe(db_path, "SELECT * FROM friends_made")?;
println!("🤓 I am making friends in SQLite! I don't have any there yet...\n{nobody:?}");
// Create a table to keep your friends' names in
execute_query(&conn, "INSERT INTO friends_made VALUES (1, 'Alice')")?;
execute_query(&conn, "INSERT INTO friends_made VALUES (2, 'Bob')")?;
execute_query(&conn, "INSERT INTO friends_made VALUES (3, 'Charlie')")?;
// Query your friends back into a Polars DataFrame
let dbf = to_dataframe(db_path, "SELECT * FROM friends_made")?;
println!("🪄 I have lovingly restored my friends into a Polars DataFrame:\n{dbf:?}");
// Add some more friends directly from a Polars DataFrame
let polars_friends = df! {
"id" => [4_i64, 5], // careful with dtypes: Polars will use i32 by default here!
"name" => ["Dora", "Eve"],
}?;
from_dataframe(&conn, "cool_friends", &polars_friends)?;
println!("🆒 My friends from Polars are now my friends in SQLite:\n{polars_friends:?}");
// Combine both tables into one DataFrame.
// ⚠️ If the `cool_friends.id` column was created as `Int32` in Polars,
// SQLite may widen or nullify values when UNIONing with `friends_made.id`
// (which is `INTEGER` = i64). Use `_i64` suffix in Polars literals to match.
let all_friends = load_dataframe(
db_path,
"SELECT * FROM friends_made UNION ALL SELECT * FROM cool_friends ORDER BY id",
)?;
println!("🎉 All my friends are politely gathered in a DataFrame:\n{all_friends:?}");
Ok(())
}
🤓 I am making friends in SQLite! I don't have any there yet...
shape: (0, 2)
┌─────┬──────┐
│ id ┆ name │
│ --- ┆ --- │
│ i64 ┆ str │
╞═════╪══════╡
└─────┴──────┘
🪄 I have lovingly restored my friends into a Polars DataFrame:
shape: (3, 2)
┌─────┬─────────┐
│ id ┆ name │
│ --- ┆ --- │
│ i64 ┆ str │
╞═════╪═════════╡
│ 1 ┆ Alice │
│ 2 ┆ Bob │
│ 3 ┆ Charlie │
└─────┴─────────┘
🆒 My friends from Polars are now my friends in SQLite:
shape: (2, 2)
┌─────┬──────┐
│ id ┆ name │
│ --- ┆ --- │
│ i64 ┆ str │
╞═════╪══════╡
│ 4 ┆ Dora │
│ 5 ┆ Eve │
└─────┴──────┘
🎉 All my friends are politely gathered in a DataFrame:
shape: (5, 2)
┌─────┬─────────┐
│ id ┆ name │
│ --- ┆ --- │
│ i64 ┆ str │
╞═════╪═════════╡
│ 1 ┆ Alice │
│ 2 ┆ Bob │
│ 3 ┆ Charlie │
│ 4 ┆ Dora │
│ 5 ┆ Eve │
└─────┴─────────┘
Note that the type system used by rusqlite via ConnectorX is as shown
here
Licensed under the MIT License. See LICENSE for details.