Code-first analytics dashboard platform — same data model as Metabase, Looker, PowerBI, Cube — but in Python, for agents, outputting Markdown
Project description
dashboardmd
Code-first analytics dashboard platform. Connectors for everything. Markdown output.
Universal data model — same concepts as Metabase, Looker, PowerBI, Cube — but in Python, for agents, with composable connectors that work together.
The Core Idea
Every analytics platform uses the same foundational concepts:
- Entities (tables/views with semantic meaning)
- Dimensions (attributes you group and filter by)
- Measures (aggregations you compute)
- Relationships (how entities join together)
- Queries (select measures + dimensions → get results)
- Dashboards (tiles bound to queries + global filters)
dashboardmd mirrors these concepts in Python. Connectors are the universal integration layer — whether your data comes from CSV files, REST APIs, BI platforms, or community packages, everything plugs into the same system.
Connectors Built-in
┌──────────────────────────┐ ┌───────────────────────┐
│ Data Connectors │ │ File Sources │
│ • Custom (your code) │ │ • CSV / Parquet / JSON│
│ • Community (pip) │ │ • DuckDB / SQLite │
│ • API / Callable │ │ • PostgreSQL / MySQL │
│ │ │ • pandas DataFrames │
│ BI Platform Connectors │ │ │
│ • MetabaseConnector │ │ Semantic Layer │
│ • LookMLConnector │ │ • Entity / Dimension │
│ • CubeConnector │ │ • Measure / Query │
│ • PowerBIConnector │ │ • Relationship │
└────────────┬─────────────┘ └───────────┬───────────┘
│ │
└───────────────┬───────────────┘
▼
┌──────────────────┐
│ dashboardmd │
│ Analyst │ ← DuckDB engine
│ analyst.use() │ ← composable connectors
└────────┬─────────┘
│
▼
┌──────────────────────────┐
│ Markdown Report │
│ (.md files) │
└──────────────────────────┘
Installation
pip install dashboardmd
With optional dependencies:
pip install "dashboardmd[pandas]" # DataFrame support
pip install "dashboardmd[plotting]" # Chart images via matplotlib
pip install "dashboardmd[all]" # Everything
Quick Start
Direct SQL with Analyst (maximum agent power)
The Analyst is the core of dashboardmd — a DuckDB-powered query engine that gives
AI agents full analytical SQL capabilities over any data source.
from dashboardmd import Analyst
# Create an analyst and register data sources
analyst = Analyst()
analyst.add("orders", "data/orders.csv")
analyst.add("customers", "data/customers.csv")
# Full DuckDB SQL — aggregations, window functions, CTEs, JOINs, pivots, regex...
result = analyst.sql("""
SELECT
c.segment,
COUNT(*) AS order_count,
SUM(o.amount) AS revenue,
AVG(o.amount) AS avg_order_value
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'completed'
GROUP BY 1
ORDER BY revenue DESC
""")
# Multiple output formats
result.show() # Print to stdout
result.to_markdown_table() # Render as Markdown table
result.df() # Return as pandas DataFrame
result.fetchall() # Raw tuples
result.scalar() # Single value
# Schema exploration (great for agents)
analyst.tables() # List all tables
analyst.schema("orders") # Column names and types
analyst.sample("orders", 5) # Preview rows
analyst.count("orders") # Row count
# Write results to Markdown
analyst.to_md("output/analysis.md", title="Revenue Analysis", queries=[
("By Segment", "SELECT segment, SUM(amount) FROM orders GROUP BY 1"),
("Top 10", "SELECT * FROM orders ORDER BY amount DESC LIMIT 10"),
])
Semantic Queries (BI-style)
Build on top of Analyst with entities, dimensions, and measures — same concepts as Looker, PowerBI, Cube, and Metabase.
from dashboardmd import Analyst, Entity, Dimension, Measure, Relationship
# Define entities (like Looker views, PowerBI tables, Cube cubes)
orders = Entity("orders", source="data/orders.csv", dimensions=[
Dimension("id", type="number", primary_key=True),
Dimension("date", type="time"),
Dimension("customer_id", type="number"),
Dimension("status", type="string"),
], measures=[
Measure("revenue", type="sum", sql="amount"),
Measure("count", type="count"),
])
# Register entities and query semantically
analyst = Analyst()
analyst.add_entity(orders)
result = analyst.query(measures=["orders.revenue"], dimensions=["orders.status"])
print(result.to_markdown_table())
Dashboards (structured reports)
from dashboardmd import Dashboard, Entity, Dimension, Measure, Relationship
orders = Entity("orders", source="data/orders.csv", dimensions=[
Dimension("id", type="number", primary_key=True),
Dimension("date", type="time"),
Dimension("customer_id", type="number"),
Dimension("status", type="string"),
], measures=[
Measure("revenue", type="sum", sql="amount", format="$,.0f"),
Measure("count", type="count"),
])
customers = Entity("customers", source="data/customers.csv", dimensions=[
Dimension("id", type="number", primary_key=True),
Dimension("name", type="string"),
Dimension("segment", type="string"),
])
rels = [
Relationship("orders", "customers", on=("customer_id", "id"), type="many_to_one"),
]
# Build the dashboard
dash = Dashboard(
title="Weekly Business Review",
entities=[orders, customers],
relationships=rels,
output="output/weekly.md",
)
dash.filter("date_range", dimension="orders.date", default="last_30_days")
dash.section("Key Metrics")
dash.tile("orders.revenue", compare="previous_period")
dash.tile("orders.count", compare="previous_period")
dash.section("Revenue by Segment")
dash.tile("orders.revenue", by="customers.segment", type="bar_chart")
# Raw SQL tiles — full DuckDB power inside dashboards
dash.section("Custom Analysis")
dash.tile_sql("Top Customers", """
SELECT c.name, SUM(o.amount) AS total
FROM orders o JOIN customers c ON o.customer_id = c.id
GROUP BY 1 ORDER BY 2 DESC LIMIT 5
""")
dash.save() # → output/weekly.md
Connectors
Connectors are the universal integration layer. A connector bundles everything needed to analyze a data domain: data sources, cleaning, entity definitions (dimensions/measures), relationships, and pre-built dashboard widgets.
Composable by design
Multiple connectors install into one Analyst and work together:
from dashboardmd import Analyst, Relationship
from dashboardmd.connectors import MetabaseConnector
analyst = Analyst()
# BI platform connector — import your existing Metabase model
analyst.use(MetabaseConnector(metabase_metadata))
# Community connector — pip install dashboardmd-github
analyst.use(GitHubConnector(token="...", repo="org/repo"))
# Custom connector — your own data
analyst.use(MyInternalConnector(api_key="..."))
# Cross-connector joins — link data across boundaries
analyst.add_relationship(Relationship(
"orders", "pull_requests", on=("id", "order_id"), type="one_to_many"
))
# Query across everything as one system
analyst.query(
measures=["orders.revenue"],
dimensions=["pull_requests.author"],
)
BI Platform Connectors (built-in)
Import data models from any BI platform. They implement the same
Connector interface, so they compose with data connectors:
from dashboardmd import Analyst
from dashboardmd.connectors import MetabaseConnector, LookMLConnector, CubeConnector, PowerBIConnector
# Import from Metabase
analyst = Analyst()
analyst.use(MetabaseConnector(metabase_metadata_dict))
# Import from LookML / Looker
analyst.use(LookMLConnector(lookml_model_dict))
# Import from Cube.js
analyst.use(CubeConnector(cube_schema_dict))
# Import from PowerBI
analyst.use(PowerBIConnector(powerbi_model_dict))
Custom Connectors
Build your own connector for any data domain:
from dashboardmd import Connector, DashboardWidget, Entity, Dimension, Measure
from dashboardmd.sources.base import SourceHandler
class MyAPISource(SourceHandler):
def __init__(self, url, token):
self.url = url
self.token = token
def register(self, conn, table_name):
rows = self._fetch() # your API logic
rows = self._clean(rows) # your cleaning logic
self._register_rows(conn, table_name, rows) # built-in helper
def describe(self):
return {"columns": []}
class MyConnector(Connector):
def __init__(self, api_key):
self.api_key = api_key
def name(self):
return "my_service"
def sources(self):
return {"events": MyAPISource("https://api.example.com/events", self.api_key)}
def entities(self):
return [Entity("events", dimensions=[
Dimension("id", type="number", primary_key=True),
Dimension("type", type="string"),
Dimension("timestamp", type="time"),
], measures=[
Measure("count", type="count"),
])]
def widgets(self):
return [DashboardWidget(
name="Overview",
title="Event Analytics",
requires=["events"],
build=self._build_overview,
)]
def _build_overview(self, dash):
dash.section("Events")
dash.tile("events.count", by="events.type")
# Use it
analyst = Analyst()
analyst.use(MyConnector(api_key="..."))
Multi-Connector Dashboards
Connectors contribute widgets to shared dashboards:
from dashboardmd import Analyst, Dashboard, Relationship
analyst = Analyst()
analyst.use(project_connector)
analyst.use(team_connector)
# Cross-connector relationship
analyst.add_relationship(Relationship(
"tasks", "team_members", on=("assignee", "username"), type="many_to_one"
))
# Build unified dashboard with widgets from both connectors
dash = Dashboard(title="Engineering Velocity", analyst=analyst, output="velocity.md")
project_connector.contribute_widgets(dash, ["Project Status"])
team_connector.contribute_widgets(dash, ["Team Overview"])
# Add custom cross-connector analysis
dash.section("Cross-Team Insights")
dash.tile_sql("Hours by Department", """
SELECT tm.department, SUM(tl.hours) as total
FROM time_logs tl JOIN team_members tm ON tl.username = tm.username
GROUP BY 1 ORDER BY 2 DESC
""")
dash.save()
Concept Mapping
| Concept | Looker | PowerBI | Cube | Metabase | dashboardmd |
|---|---|---|---|---|---|
| Logical table | View | Table | Cube | Table | Entity |
| Attribute | dimension | Column | dimension | Column | Dimension |
| Aggregation | measure | DAX Measure | measure | Metric | Measure |
| Table link | explore + join | Relationship | joins | FK | Relationship |
| Query | Explore query | Visual query | /load API | Question | Query |
| Dashboard | Dashboard + tiles | Report + visuals | (frontend) | Dashboard + cards | Dashboard + Tiles |
| Integration | — | — | — | — | Connector |
CLI
dashboardmd discover data/ # Auto-discover entities from data files
dashboardmd query data/ "SELECT COUNT(*) FROM orders" # Run SQL against data
dashboardmd run dashboard.py # Execute a dashboard script
Development
# Install in development mode
make install
# Run tests
make test
# Run linter + type checker + tests
make check
Project Status
dashboardmd is in early development (v0.1.0). See the design proposal for the full vision and implementation plan.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dashboardmd-0.1.0.tar.gz.
File metadata
- Download URL: dashboardmd-0.1.0.tar.gz
- Upload date:
- Size: 747.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
271d25ddcd3a7afa152a702d7fccc9f9df0d747386acf80ac486c7335303b20f
|
|
| MD5 |
f93aa00c1658445ae0c647b1a5c524ba
|
|
| BLAKE2b-256 |
8b3d1294eb3104b61483cdee4bdefa25d1406ea30fdb83363d8587ac4fbf6c8e
|
Provenance
The following attestation bundles were made for dashboardmd-0.1.0.tar.gz:
Publisher:
release.yml on minhlucvan/dashboardmd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dashboardmd-0.1.0.tar.gz -
Subject digest:
271d25ddcd3a7afa152a702d7fccc9f9df0d747386acf80ac486c7335303b20f - Sigstore transparency entry: 1095447060
- Sigstore integration time:
-
Permalink:
minhlucvan/dashboardmd@8e52cf229709cc9635984380c9947d1d16c38b7c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/minhlucvan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8e52cf229709cc9635984380c9947d1d16c38b7c -
Trigger Event:
push
-
Statement type:
File details
Details for the file dashboardmd-0.1.0-py3-none-any.whl.
File metadata
- Download URL: dashboardmd-0.1.0-py3-none-any.whl
- Upload date:
- Size: 44.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7cbe75f738ee6110977056c74da769c498d9228b5c9d9bb635255882275c9efe
|
|
| MD5 |
3d80df8b7c85b601880c4f144a5869d3
|
|
| BLAKE2b-256 |
ce4af66579e25ee9a73375cd7538d1b9bd98877d82754a10a6043d3bbb0da0c3
|
Provenance
The following attestation bundles were made for dashboardmd-0.1.0-py3-none-any.whl:
Publisher:
release.yml on minhlucvan/dashboardmd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dashboardmd-0.1.0-py3-none-any.whl -
Subject digest:
7cbe75f738ee6110977056c74da769c498d9228b5c9d9bb635255882275c9efe - Sigstore transparency entry: 1095447062
- Sigstore integration time:
-
Permalink:
minhlucvan/dashboardmd@8e52cf229709cc9635984380c9947d1d16c38b7c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/minhlucvan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8e52cf229709cc9635984380c9947d1d16c38b7c -
Trigger Event:
push
-
Statement type: