Skip to content

Commit

Permalink
bored-charts router
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverlambson committed Aug 22, 2024
1 parent 0e64157 commit e6a05c3
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 26 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Build easy, minimal, PDF-able data reports with markdown and python.
- [x] separate pyproject.toml for lib and example
- [x] publish to pypi
- [ ] less boilerplate to register figure endpoints
- [x] @bored_router.chart decorator
- [ ] allow decoration of functions that return figures directly
- [ ] make report_name path parameter optional
- [ ] deploy to [bored-charts-example.oliverlambson.com](https://bored-charts-example.oliverlambson.com)
- [ ] dashboard layout with grid layout
Expand Down
9 changes: 4 additions & 5 deletions bored-charts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,16 @@ pip install bored-charts uvicorn
from pathlib import Path

import plotly.express as px
from boredcharts import boredcharts
from boredcharts import BCRouter, boredcharts
from boredcharts.jinja import to_html
from fastapi import APIRouter
from fastapi.responses import HTMLResponse

pages = Path(__file__).parent.absolute() / "pages"
figure_router = APIRouter()
figure_router = BCRouter()


@figure_router.get("/report/{report_name}/figure/usa_population", name="usa_population")
async def usa_population(report_name: str) -> HTMLResponse:
@figure_router.chart("usa_population")
async def usa_population() -> HTMLResponse:
df = px.data.gapminder().query("country=='United States'")
fig = px.bar(df, x="year", y="pop")
return HTMLResponse(to_html(fig))
Expand Down
4 changes: 3 additions & 1 deletion bored-charts/boredcharts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
__version__ = "0.1.4"
__version__ = "0.2.0"

from boredcharts.router import BCRouter
from boredcharts.webapp import boredcharts

__all__ = [
"BCRouter",
"boredcharts",
]
12 changes: 6 additions & 6 deletions bored-charts/boredcharts/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def figure(
figure: str,
*,
css_class: str = "min-h-112 min-w-80",
**kwargs: dict[str, Any],
**kwargs: Any,
) -> Markup:
"""Jinja function to display a figure.
Expand All @@ -104,18 +104,18 @@ def figure(
{{ figure("example_figure_with_params", param_1="foo") }}
"""
report = context.resolve("report")
if isinstance(report, Undefined):
raise ValueError("report is not available in the context")
if not isinstance(report, str):
raise ValueError(f"report must be a string, got {type(report)}")
if not isinstance(report, Undefined):
if not isinstance(report, str):
raise ValueError(f"report must be a string, got {type(report)}")
kwargs.update(report_name=report)

request = context.resolve("request")
if isinstance(request, Undefined):
raise ValueError("request is not available in the context")
if not isinstance(request, Request):
raise ValueError(f"request must be a Request, got {type(request)}")

url = request.url_for(figure, report_name=report).include_query_params(**kwargs)
url = request.url_for(figure).include_query_params(**kwargs)

# note using dedent to return a valid root-level element
return Markup(
Expand Down
16 changes: 16 additions & 0 deletions bored-charts/boredcharts/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from collections.abc import Callable

from fastapi import APIRouter
from fastapi.types import DecoratedCallable


class BCRouter(APIRouter):
def chart(
self,
name: str,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
path = f"/figure/{name}"
return self.api_route(
path=path,
name=name,
)
14 changes: 5 additions & 9 deletions examples/full/bcexample/figures.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import matplotlib.pyplot as plt
import numpy as np
import plotly.express as px
from boredcharts import BCRouter
from boredcharts.jinja import to_html
from fastapi import APIRouter
from fastapi.responses import HTMLResponse
from plotly.graph_objects import Figure

router = APIRouter()
router = BCRouter()


async def example(report_name: str, country: str) -> Figure:
Expand All @@ -31,14 +31,12 @@ async def example(report_name: str, country: str) -> Figure:


# TODO: pass functions into framework, auto generate these routes
@router.get(
"/report/{report_name}/figure/example_simple_usa", name="example_simple_usa"
)
@router.chart("example_simple_usa")
async def fig_example_simple(report_name: str) -> HTMLResponse:
return HTMLResponse(to_html(await example(report_name, "United States")))


@router.get("/report/{report_name}/figure/example_params", name="example_params")
@router.chart("example_params")
async def fig_example(report_name: str, country: str) -> HTMLResponse:
return HTMLResponse(to_html(await example(report_name, country)))

Expand Down Expand Up @@ -101,9 +99,7 @@ async def elasticity_vs_profit(


# TODO: pass functions into framework, auto generate these routes
@router.get(
"/report/{report_name}/figure/elasticity_vs_profit", name="elasticity_vs_profit"
)
@router.chart("elasticity_vs_profit")
async def fig_elasticity_vs_profit(
report_name: str, margin: float | None = None
) -> HTMLResponse:
Expand Down
9 changes: 4 additions & 5 deletions examples/minimal/main.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
from pathlib import Path

import plotly.express as px
from boredcharts import boredcharts
from boredcharts import BCRouter, boredcharts
from boredcharts.jinja import to_html
from fastapi import APIRouter
from fastapi.responses import HTMLResponse

pages = Path(__file__).parent.absolute() / "pages"
figure_router = APIRouter()
figure_router = BCRouter()


@figure_router.get("/report/{report_name}/figure/usa_population", name="usa_population")
async def usa_population(report_name: str) -> HTMLResponse:
@figure_router.chart("usa_population")
async def usa_population() -> HTMLResponse:
df = px.data.gapminder().query("country=='United States'")
fig = px.bar(df, x="year", y="pop")
return HTMLResponse(to_html(fig))
Expand Down

0 comments on commit e6a05c3

Please sign in to comment.