Skip to content

Page

Page holds widgets, their layout and their actions, and goes on the dashboard with dachshund.include(). See the Pages guide.

import dachshund
from dachshund import Page
from dachshund.widgets import Number, TimeSeriesChart

page = Page("overview", title="Overview")

with page.row():
    with page.column(span=4):
        page.add(Number("errors"))
        page.add(Number("users"))
    page.add(TimeSeriesChart("latency"))

dachshund.include(page)

dachshund.layout.Page

Page(name, *, title=None, public=False)

A page of the dashboard, with its widgets, layout and actions.

Register on a page, then put it on the dashboard with dachshund.include(). See the Pages guide.

Example

page = Page("overview", title="Overview")

with page.row():
    page.add(Text("summary"))

with page.row():
    with page.column(span=4):
        page.add(Number("errors"))
        page.add(Number("users"))
    page.add(TimeSeriesChart("latency"))


@page.action("flush", label="Flush cache")
def flush(ctx): ...


dachshund.include(page)

Options

  • name (str) - The page's URL, /<name>. Can't contain / or start with _. Required.
  • title (str) - Shown in the navigation. Defaults to name, underscores as spaces.
  • public (bool) - Shown without the dashboard's authorize, see Authentication. Its tabs follow it. Defaults to False.

Layout

A page is rows on a 12-column grid, laid out with with blocks. Widgets live in rows: add() puts a widget in the innermost open block,

  • in a column(), stacked under the widgets already there
  • in a row(), as a column of its own

and raises outside of both. A full-width widget is a row with one widget.

A column's span is its width out of 12. Columns without one share what's left evenly.

Tabs

A page holds either widgets or tab()s. Its tabs are listed in a sidebar, and visiting the page shows the first one.

Source code in dachshund/layout.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def __init__(
    self, name: str, *, title: str | None = None, public: bool = False
) -> None:
    if not name.strip() or "/" in name:
        raise ValueError("A page name cannot be blank or contain '/'.")

    if name.startswith("_"):
        raise ValueError(
            f"Page names starting with '_' are dachshund's own, rename {name!r}."
        )

    self.name = name
    self.title = title
    self.public = public
    self.parent: Page | None = None
    self.rows: list[Row] = []
    self.widgets: dict[str, Widget] = {}
    self.actions: dict[str, Action] = {}
    self.tabs: dict[str, Page] = {}
    self.included = False
    self._builder = Builder(
        self, lambda page, widget: page._register(widget), Page._unregister
    )

name instance-attribute

name = name

title instance-attribute

title = title

public instance-attribute

public = public

parent instance-attribute

parent = None

rows instance-attribute

rows = []

widgets instance-attribute

widgets = {}

actions instance-attribute

actions = {}

tabs instance-attribute

tabs = {}

included instance-attribute

included = False

path property

path

Where the page lives, "parent/tab" for a tab.

row

row()

Open a row: widgets added until the block ends sit side by side.

Example
with page.row():
    page.add(Number("errors"))
    page.add(TimeSeriesChart("latency"))
Source code in dachshund/layout.py
261
262
263
264
265
266
267
268
269
270
271
272
def row(self) -> AbstractContextManager[Row]:
    """Open a row: widgets added until the block ends sit side by side.

    ## Example

    ```python
    with page.row():
        page.add(Number("errors"))
        page.add(TimeSeriesChart("latency"))
    ```
    """
    return self._builder.row()

column

column(*, span=None)

Open a column inside a row: widgets added until the block ends stack.

Options
  • span (int) - Columns wide, out of 12. Defaults to an even share of what the row's other columns leave.
Source code in dachshund/layout.py
274
275
276
277
278
279
280
281
282
def column(self, *, span: int | None = None) -> AbstractContextManager[Column]:
    """Open a column inside a row: widgets added until the block ends stack.

    ## Options

    - `span` (`int`) - Columns wide, out of 12. Defaults to an even share
      of what the row's other columns leave.
    """
    return self._builder.column(span)

tab

tab(name, *, title=None)

Open a tab: rows and widgets added until the block ends go on it.

A tab is a page nested under this one, at /<page>/<name>.

Options
  • name (str) - The tab's URL. Required.
  • title (str) - Shown in the sidebar. Defaults to name, underscores as spaces.
Source code in dachshund/layout.py
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def tab(
    self, name: str, *, title: str | None = None
) -> AbstractContextManager["Page"]:
    """Open a tab: rows and widgets added until the block ends go on it.

    A tab is a page nested under this one, at `/<page>/<name>`.

    ## Options

    - `name` (`str`) - The tab's URL. Required.
    - `title` (`str`) - Shown in the sidebar. Defaults to `name`, underscores
      as spaces.
    """
    return self._builder.tab(name, title)

add

add(widget)

Put a widget in the open row or column, see Layout.

Source code in dachshund/layout.py
299
300
301
def add(self, widget: Widget) -> None:
    """Put a widget in the open row or column, see [Layout](#layout)."""
    self._builder.add(widget)

action

action(name, *, label=None, confirm=False, public=False)

Register an action, see Dashboard.action.

Source code in dachshund/layout.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
def action(
    self,
    name: str,
    *,
    label: str | None = None,
    confirm: bool = False,
    public: bool = False,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Register an action, see [Dashboard.action][dachshund.registry.Dashboard.action]."""

    def decorator(handler: Callable[..., Any]) -> Callable[..., Any]:
        self._check_open()

        if name in self.actions:
            raise ValueError(f"An action named {name!r} already exists.")

        self.actions[name] = make_action(
            name, handler, label=label, confirm=confirm, public=public
        )
        return handler

    return decorator

every

every()

This page and its tabs, recursively.

Source code in dachshund/layout.py
342
343
344
def every(self) -> list["Page"]:
    """This page and its tabs, recursively."""
    return [self, *(page for tab in self.tabs.values() for page in tab.every())]

dachshund.layout.Row dataclass

Row(columns=list())

Columns side by side on a 12-column grid.

columns class-attribute instance-attribute

columns = field(default_factory=list)

spans

spans()

Each column's width out of 12. Unsized columns share what's left evenly.

Source code in dachshund/layout.py
33
34
35
36
37
38
39
40
41
42
43
44
def spans(self) -> list[int]:
    """Each column's width out of 12. Unsized columns share what's left evenly."""
    fixed = sum(column.span or 0 for column in self.columns)
    free = [
        index for index, column in enumerate(self.columns) if column.span is None
    ]
    share, extra = divmod(max(COLUMNS - fixed, len(free)), len(free) or 1)

    return [
        column.span or share + (1 if free.index(index) < extra else 0)
        for index, column in enumerate(self.columns)
    ]

check

check()
Source code in dachshund/layout.py
46
47
48
49
50
51
52
def check(self) -> None:
    fixed = sum(column.span or 0 for column in self.columns)

    if fixed > COLUMNS:
        raise ValueError(
            f"The row's columns span {fixed} columns, the grid has 12."
        )

dachshund.layout.Column dataclass

Column(names=list(), span=None)

Widgets stacked top to bottom in one column of a row.

Options

  • names (list[str]) - The widgets, top to bottom.
  • span (int) - Columns wide, out of 12. Defaults to None, an even share of what the row's other columns leave.

names class-attribute instance-attribute

names = field(default_factory=list)

span class-attribute instance-attribute

span = None