Query a boring_semantic_layer DuckDB based semantic model directly from Excel.
DuckDB file
└── semantic_config.py (your model definitions)
└── FastAPI server (main.py)
└── Excel task pane (Office Add-in)
- Python 3.11+
- uv (
pip install uvorwinget install astral-sh.uv) - Microsoft Excel (Desktop)
git clone https://github.com/radders1981/bsl-excel
cd bsl-excel
uv syncuv run python scripts/generate_demo_db.pyThis creates data/tpch.duckdb with TPC-H sample data (~12 MB).
uv run python main.pyThe server starts at http://localhost:8000. Verify it's running:
curl http://localhost:8000/health
# {"status":"ok"}- Create a folder in a location that can be shared. Eg "C:\Users<UserName>\add_ins"
- Copy the manifest.xml file to this folder.
- Share the folder
- Open Excel. File -> Options -> Trust Centre -> Trust Center Settings
- Under "Trusted Add In Catalogs", Add the share location created above. This will be in the format //ComputerName/Users//add_ins". The file path starting with a a drive letter will not be accepted. Confirm, exit hte menu back to excel.
- Select the Developer tab (may need to enable it in options), select "Add Ins". Not "Excel Add Ins"
- There should be a "shared folder" option containing the BSL_Excel add in. Double Click, a new option will be added to the Home ribbon.
- Open the BSL Excel task pane
- Enter
http://localhost:8000as the server URL and click Connect - Select a model (e.g.
orders) - Check the dimensions and measures you want
- Optionally add a filter
- Click a cell in your sheet, then click Run Query → Sheet
Edit semantic_config.py. You need two things:
- An ibis DuckDB connection pointing at your
.duckdbfile - One or more
SemanticModelobjects collected into aMODELSdict
import ibis
from boring_semantic_layer import SemanticModel
conn = ibis.duckdb.connect("data/my_data.duckdb")
sales = SemanticModel(
table=conn.table("sales"),
dimensions={
"region": lambda t: t.region,
"category": lambda t: t.product_category,
"sale_date": lambda t: t.sale_date,
},
measures={
"revenue": lambda t: t.amount.sum(),
"order_count": lambda t: t.count(),
"avg_order": lambda t: t.amount.mean(),
},
)
MODELS = {"sales": sales}Restart the server after editing (Ctrl+C then uv run python main.py).
| Method | Path | Description |
|---|---|---|
| GET | /health |
Server health check |
| GET | /models |
List available model names |
| GET | /models/{name}/schema |
Get dimensions + measures for a model |
| POST | /query |
Execute a query, returns columns + rows |
{
"model": "orders",
"dimensions": ["order_date", "status"],
"measures": ["total_price", "order_count"],
"filters": [
{ "dimension": "status", "op": "eq", "value": "O" }
],
"limit": 1000
}Filter operators: eq neq gt gte lt lte contains
Interactive API docs available at http://localhost:8000/docs when the server is running.
bsl-excel/
├── main.py # Start the API server
├── semantic_config.py # Your semantic model definitions (edit this)
├── manifest.xml # Excel add-in manifest (sideload into Excel)
├── pyproject.toml # Python dependencies (managed by uv)
├── .python-version # Python version pin
│
├── app/
│ ├── api.py # FastAPI routes
│ └── loader.py # Loads semantic_config.py dynamically
│
├── data/
│ └── tpch.duckdb # Demo DuckDB database
│
├── docs/ # Static Excel task pane (GitHub Pages)
│ ├── index.html
│ ├── taskpane.js
│ └── taskpane.css
│
└── scripts/
└── generate_demo_db.py # One-time demo data generation
boring-semantic-layer >= 0.3.2fastapi >= 0.115uvicorn[standard] >= 0.30ibis-framework[duckdb] >= 9.0
All managed automatically by uv sync.
MIT