Data Adapters
Craftable includes a comprehensive set of adapters to convert various Python data structures into table format. All adapters return a tuple of (rows, headers) that can be directly used with get_table().
Overview
Each adapter function follows a consistent pattern:
- Returns:
tuple[list[list[Any]], list[str]]- (value rows, header row) - Tolerates missing/ragged data - fills gaps with
None - Supports optional column filtering via
columnsparameter - Zero dependencies for core adapters (numpy/pandas optional)
Available Adapters
from_dicts()
Convert a list of dictionaries to table format. Ideal for JSON-like data or API responses.
Features:
- Automatically discovers all keys across all dictionaries
- Tolerates missing keys (fills with None)
- Supports column ordering: "detect" (default) or "alpha"
- Optional first_only mode to ignore keys beyond first dict
from craftable import get_table
from craftable.adapters import from_dicts
data = [
{"name": "Alice", "age": 30, "city": "LA"},
{"name": "Bob", "age": 25}, # Missing 'city'
{"name": "Charlie", "city": "NYC"} # Missing 'age'
]
rows, headers = from_dicts(data)
print(get_table(rows, header_row=headers))
Output:
Column Filtering:
# Only show specific columns
rows, headers = from_dicts(data, columns=["name", "city"])
print(get_table(rows, header_row=headers))
Column Ordering:
# Order alphabetically
rows, headers = from_dicts(data, order="alpha")
# Use only keys from first dict (ignore "city" from Charlie)
rows, headers = from_dicts(data, first_only=True)
# headers: ["name", "age"]
# Combine: first dict keys only, sorted alphabetically
rows, headers = from_dicts(data, first_only=True, order="alpha")
from_mapping_of_lists()
Convert a dictionary-of-lists (columnar format) to table format. Common with pandas-style data.
Features:
- Handles ragged columns (different lengths) by padding with None
- Preserves insertion order of keys (Python 3.7+)
from craftable import get_table
from craftable.adapters import from_mapping_of_lists
data = {
"name": ["Alice", "Bob", "Charlie"],
"score": [95, 87, 92],
"grade": ["A", "B", "A"]
}
rows, headers = from_mapping_of_lists(data)
print(get_table(rows, header_row=headers))
Output:
Ragged Columns:
ragged = {
"name": ["Alice", "Bob"],
"score": [95, 87, 92] # Extra value
}
rows, headers = from_mapping_of_lists(ragged)
# Row 3: [None, 92]
from_dataclasses()
Convert dataclass instances to table format. Type-safe alternative to dictionaries.
Features:
- Automatically extracts field names
- Excludes private fields (starting with _) by default
- Supports column filtering
from dataclasses import dataclass
from craftable import get_table
from craftable.adapters import from_dataclasses
@dataclass
class Employee:
name: str
age: int
department: str
salary: float
employees = [
Employee("Alice", 30, "Engineering", 95000),
Employee("Bob", 25, "Marketing", 75000),
]
rows, headers = from_dataclasses(employees)
print(get_table(rows, header_row=headers))
Output:
name │ age │ department │ salary
───────┼─────┼─────────────┼─────────
Alice │ 30 │ Engineering │ 95000.0
Bob │ 25 │ Marketing │ 75000.0
Private Fields:
@dataclass
class Record:
name: str
_internal_id: int # Excluded by default
# Include private fields
rows, headers = from_dataclasses(data, include_private=True)
from_models()
Generic adapter for model instances (Pydantic, attrs, dataclasses, or plain objects).
Features:
- Auto-detects model type and uses appropriate conversion
- Supports Pydantic v1 (dict()) and v2 (model_dump())
- Falls back to __dict__ for plain classes
from craftable import get_table
from craftable.adapters import from_models
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
people = [Person("Alice", 30), Person("Bob", 25)]
rows, headers = from_models(people)
print(get_table(rows, header_row=headers))
Works with Pydantic:
from pydantic import BaseModel
class User(BaseModel):
username: str
email: str
users = [User(username="alice", email="alice@example.com")]
rows, headers = from_models(users)
from_records()
Convert an iterable of tuples/lists to table format with optional headers.
Features:
- Simple conversion for raw record data
- Auto-generates headers (col_0, col_1, ...) if not provided
from craftable import get_table
from craftable.adapters import from_records
data = [
("Alice", 30, "LA"),
("Bob", 25, "NYC")
]
rows, headers = from_records(data, columns=["name", "age", "city"])
print(get_table(rows, header_row=headers))
Output:
Auto-generated Headers:
from_sql()
Convert database query results to table format. Works with DB-API cursors.
Features: - Auto-detects column names from cursor description - Works with raw rows + description - Falls back to generic headers if no description
from craftable import get_table
from craftable.adapters import from_sql
# Using a cursor directly
cursor.execute("SELECT name, age, city FROM users")
rows, headers = from_sql(cursor)
print(get_table(rows, header_row=headers))
# Using rows + description
cursor.execute("SELECT * FROM products")
result_rows = cursor.fetchall()
rows, headers = from_sql(result_rows, description=cursor.description)
Column Filtering:
from_numpy()
Convert NumPy arrays to table format. Requires numpy to be installed.
Features: - Supports 1D and 2D arrays - Handles structured arrays (uses field names as headers) - Optional index column
import numpy as np
from craftable import get_table
from craftable.adapters import from_numpy
# 2D array
arr = np.array([[1, 2, 3], [4, 5, 6]])
rows, headers = from_numpy(arr)
# headers: ["0", "1", "2"]
# 1D array
arr = np.array([10, 20, 30])
rows, headers = from_numpy(arr)
# headers: ["value"]
# With index column
rows, headers = from_numpy(arr, include_index=True)
# headers: ["index", "value"]
Structured Arrays:
dtype = [('name', 'U10'), ('age', 'i4')]
arr = np.array([('Alice', 30), ('Bob', 25)], dtype=dtype)
rows, headers = from_numpy(arr)
# headers: ["name", "age"]
from_dataframe()
Convert Pandas or Polars DataFrames to table format. Requires respective library.
Features: - Auto-detects Pandas vs Polars - Optional index column - Column filtering support
import pandas as pd
from craftable import get_table
from craftable.adapters import from_dataframe
df = pd.DataFrame({
"name": ["Alice", "Bob"],
"score": [95, 87]
})
rows, headers = from_dataframe(df)
print(get_table(rows, header_row=headers))
Output:
With Index:
Column Filtering:
Common Patterns
Combining with Custom Formatting
Adapters return raw data - use get_table() parameters for formatting:
data = [
{"product": "Widget A", "price": 19.99, "stock": 150},
{"product": "Widget B", "price": 29.99, "stock": 75},
]
rows, headers = from_dicts(data)
table = get_table(
rows,
header_row=headers,
col_defs=[
"product:20", # Fixed width
"price:>8.2f", # Right-aligned, 2 decimals
"stock:>6d" # Right-aligned integer
]
)
Filtering and Transforming
# Filter at adapter level
rows, headers = from_dicts(data, columns=["name", "score"])
# Or filter with ColDef
rows, headers = from_dicts(data)
table = get_table(rows, header_row=headers, col_defs=["name", "score"])
Handling None Values
All adapters use None for missing values. You have two ways to customize the display:
1) Quick, global replacement per table
2) Per‑column replacement using ColDef.none_text
from craftable import get_table
from craftable import ColDefList
specs = ["name", "age"]
col_defs = ColDefList.parse(specs) # convenience parser
col_defs[0].none_text = "N/A" # only for the first column
col_defs[1].none_text = "--" # different placeholder for age
table = get_table(rows, header_row=headers, col_defs=col_defs)
Notes:
- Per‑column none_text overrides the global none_text parameter when both are provided.
- none_text is treated as normal text for width/truncation/wrapping.
Error Handling
Adapters validate input and raise appropriate errors:
TypeError- Wrong data type (e.g., non-dataclass passed tofrom_dataclasses())ImportError- Missing optional dependency (numpy/pandas)ValueError- Invalid data shape (e.g., 3D numpy array)
try:
rows, headers = from_numpy(array)
except ImportError:
print("NumPy not installed")
except ValueError as e:
print(f"Invalid array shape: {e}")
Performance Notes
- from_dicts() - Iterates all dicts to find headers (O(n*m) where m = avg keys)
- from_mapping_of_lists() - Most efficient for columnar data (O(n))
- from_dataclasses() - Uses
fields()introspection (O(1) per type) - from_numpy()/from_dataframe() - Delegates to library's
.tolist()methods
Craftable strives to optimize for speed and efficiency, but it necessarily must load multiple copies of a dataset into memory in order to generate a large table. For extremely large datasets (>100K rows), consider:
- Column filtering
- Pagination
- Using native library display methods
See Also
- Getting Started - Basic usage
- Formatting Guide - Column definitions and styling
- API Reference - Complete API documentation