Skip to content

Tree — App / Analysis / Dashboard / Sheet

Top-level tree nodes. App is the construction root; an app emits an Analysis and (optionally) a Dashboard mirroring it. Each carries a list of Sheet nodes; sheets carry visuals, filter groups, filter controls, and parameter controls.

structure

Structural tree types — GridSlot / Sheet / Analysis / Dashboard / App.

The skeleton the rest of the tree hangs off. Authors construct an App, attach an Analysis (which holds the sheet tree), optionally attach a Dashboard, and call app.emit_analysis() / app.emit_dashboard() to get the models.py instances ready for deploy.

LayoutNode

Bases: Protocol

Anything placeable in a sheet's grid layout.

Both typed visual subtypes (KPI / Table / BarChart / Sankey) and the typed TextBox wrapper satisfy this Protocol. Each exposes element_id (the layout slot's ElementId) and element_type ("VISUAL" or "TEXT_BOX") — the slot reads them off the node at emit time.

The Protocol means Sheet.place(node, ...) accepts both visuals and text boxes uniformly; QuickSight's two-list split (Visuals vs TextBoxes in SheetDefinition) stays an emit-time concern that callers never see.

element_id property

element_id: str

element_type property

element_type: GridLayoutElementType

GridSlot dataclass

One placement in a sheet's grid layout.

Holds an OBJECT reference to the placed LayoutNode. The element id and element type are read off the node at emit time — the slot is agnostic about whether it carries a visual or a text box.

element instance-attribute

element: LayoutNode

col_span instance-attribute

col_span: int

row_span instance-attribute

row_span: int

col_index instance-attribute

col_index: int

row_index class-attribute instance-attribute

row_index: int | None = None

__init__

__init__(element: LayoutNode, col_span: int, row_span: int, col_index: int, row_index: int | None = None) -> None

emit

Sheet dataclass

Tree node for one sheet on an Analysis / Dashboard.

Sheet has four concerns:

  1. Metadatasheet_id, name, title, description set at construction.
  2. Layout — visuals + text boxes placed in a grid. Accessed via sheet.layout: sheet.layout.row(height=...).add_kpi(width=, title=, values=, ...) for sequential rows, or sheet.layout. absolute(col_index=, row_index=, col_span=, row_span=).add_*(...) for explicit positioning. The layout's row tracks the column cursor so call sites don't compute col_index arithmetic by hand.
  3. Controls — parameter / filter controls live above/aside the canvas (NOT in the grid). Added directly on Sheet via sheet.add_parameter_dropdown(...) / add_parameter_slider / add_parameter_datetime_picker / add_filter_dropdown / add_filter_slider / add_filter_datetime_picker / add_filter_cross_sheet — one shortcut per control kind.
  4. Scope wiringsheet.scope(filter_group, [v1, v2]) scopes a filter group to specific visuals on this sheet.

emit() returns the SheetDefinition ready to drop into AnalysisDefinition.Sheets.

sheet_id instance-attribute

sheet_id: SheetId

name instance-attribute

name: str

title instance-attribute

title: str

description instance-attribute

description: str

visuals class-attribute instance-attribute

visuals: list[VisualLike] = field(default_factory=list[VisualLike])

parameter_controls class-attribute instance-attribute

parameter_controls: list[ParameterControlLike] = field(default_factory=list[ParameterControlLike])

filter_controls class-attribute instance-attribute

filter_controls: list[FilterControlLike] = field(default_factory=list[FilterControlLike])

text_boxes class-attribute instance-attribute

text_boxes: list[TextBox] = field(default_factory=list[TextBox])

grid_slots class-attribute instance-attribute

grid_slots: list[GridSlot] = field(default_factory=list['GridSlot'])

layout property

layout: 'SheetLayout'

L.1.21 — Layout namespace for grid placement.

Visuals + text boxes are added through the layout (one of: sheet.layout.row(height=...).add_kpi(width=..., ...) or sheet.layout.absolute(col_index=..., row_index=..., col_span=..., row_span=...).add_kpi(...)). The Sheet itself coordinates four concerns; layout is one of them, factored out so the call sites for "place a visual" don't crowd against "register a control" or "scope a filter group".

Lazily constructed; the same SheetLayout instance returns on subsequent accesses so the row cursor advances across calls.

__init__

__init__(sheet_id: SheetId, name: str, title: str, description: str, visuals: list[VisualLike] = list[VisualLike](), parameter_controls: list[ParameterControlLike] = list[ParameterControlLike](), filter_controls: list[FilterControlLike] = list[FilterControlLike](), text_boxes: list[TextBox] = list[TextBox](), grid_slots: list[GridSlot] = list['GridSlot']()) -> None

add_parameter_dropdown

add_parameter_dropdown(*, parameter: ParameterDeclLike, title: str, selectable_values: SelectableValues, type: Literal['SINGLE_SELECT', 'MULTI_SELECT'] = 'SINGLE_SELECT', hidden_select_all: bool = False, cascade_source: ParameterDropdown | None = None, cascade_match_column: Column | None = None, control_id: str | AutoResolved = AUTO) -> ParameterDropdown

Construct + register a parameter dropdown control on this sheet.

selectable_values is required: a parameter dropdown without a source list (StaticValues / LinkedValues) shows only the QuickSight empty-state "All" placeholder, so the user can't actually pick a value — the bound parameter stays unset and any CategoryFilter using it matches nothing. Caught the L1 Daily Statement account-dropdown footgun (v8.3.3 hotfix); the type makes it unrepresentable going forward.

add_parameter_slider

add_parameter_slider(*, parameter: ParameterDeclLike, title: str, minimum_value: float, maximum_value: float, step_size: float, control_id: str | AutoResolved = AUTO) -> ParameterSlider

Construct + register a parameter slider control on this sheet.

add_parameter_datetime_picker

add_parameter_datetime_picker(*, parameter: ParameterDeclLike, title: str, control_id: str | AutoResolved = AUTO) -> ParameterDateTimePicker

Construct + register a parameter datetime picker control.

add_parameter_text_field

add_parameter_text_field(*, parameter: ParameterDeclLike, title: str, control_id: str | AutoResolved = AUTO) -> ParameterTextField

Construct + register a free-text parameter input control.

Use when the parameter's option universe is unbounded / unknown at deploy time, or when LinkedValues / StaticValues paths fail (X.1.b L2FT cascade Value dropdown hit Sample values not found from QS's lazy sample-values fetch on cold per-CI-run dashboards). Text input has no equivalent fetch path.

add_filter_dropdown

add_filter_dropdown(*, filter: FilterLike, title: str, type: Literal['SINGLE_SELECT', 'MULTI_SELECT'] = 'MULTI_SELECT', selectable_values: SelectableValues | None = None, control_id: str | AutoResolved = AUTO) -> FilterDropdown

Construct + register a filter dropdown control on this sheet.

add_filter_slider

add_filter_slider(*, filter: FilterLike, title: str, minimum_value: float, maximum_value: float, step_size: float, type: Literal['SINGLE_POINT', 'RANGE'] = 'RANGE', control_id: str | AutoResolved = AUTO) -> FilterSlider

Construct + register a filter slider control on this sheet.

add_filter_datetime_picker

add_filter_datetime_picker(*, filter: FilterLike, title: str, type: Literal['SINGLE_VALUED', 'DATE_RANGE'] = 'DATE_RANGE', control_id: str | AutoResolved = AUTO) -> FilterDateTimePicker

Construct + register a filter datetime picker control.

add_filter_cross_sheet

add_filter_cross_sheet(*, filter: FilterLike, control_id: str | AutoResolved = AUTO) -> FilterCrossSheet

Construct + register a cross-sheet filter control on this sheet.

scope

scope(fg: FilterGroup, visuals: list[VisualLike]) -> FilterGroup

L.1.21 — scope a filter group to specific visuals on this sheet.

Reads more naturally than fg.scope_visuals(sheet, visuals) since the sheet is the contextual subject. Runtime check stays — Python's type system can't track which Sheet instance a visual was registered on without dependent types.

find_visual

find_visual(*, title: str | None = None, title_contains: str | None = None, visual_id: VisualId | str | None = None) -> VisualLike

Look up a single visual on this sheet by title / partial title / visual id.

Designed for e2e + introspection: pass any of the three lookup keys and get the matching node back. Raises if no match or multiple matches — the API forces unambiguity at the call site so tests can rely on the result.

Auto-IDs (L.1.8.5) make this the right way to find a visual from outside the tree — IDs are not stable under tree restructuring, but titles + structural position are.

emit

emit() -> SheetDefinition

Row dataclass

One horizontal band in a Sheet's grid layout.

Tracks the column cursor as visuals + text boxes are added; refuses widths that overflow the 36-column grid. height becomes every visual's row_span; the column cursor advances by each visual's width (col_span). A new row from sheet.layout.row() lands below the previous row — the SheetLayout tracks the vertical cursor.

sheet instance-attribute

sheet: Sheet

height instance-attribute

height: int

row_index instance-attribute

row_index: int

__init__

__init__(sheet: Sheet, height: int, row_index: int) -> None

add_kpi

add_kpi(*, width: int, title: str, values: list[Measure] | None = None, subtitle: str | None = None, visual_id: VisualId | AutoResolved = AUTO) -> KPI

Construct + register + place a KPI in this row.

add_table

add_table(*, width: int, title: str, group_by: list[Dim] | None = None, values: list[Measure] | None = None, columns: list[Dim] | None = None, subtitle: str | None = None, sort_by: tuple[FieldRef, Literal['ASC', 'DESC']] | list[tuple[FieldRef, Literal['ASC', 'DESC']]] | None = None, actions: list[Action] | None = None, conditional_formatting: list[CellFormat] | None = None, visual_id: VisualId | AutoResolved = AUTO) -> Table

Construct + register + place a Table in this row.

Aggregated mode: pass group_by + values. Unaggregated mode (raw column display): pass columns. The two modes are mutually exclusive (Table.post_init enforces this).

add_bar_chart

add_bar_chart(*, width: int, title: str, category: list[Dim] | None = None, values: list[Measure] | None = None, colors: list[Dim] | None = None, subtitle: str | None = None, orientation: Literal['HORIZONTAL', 'VERTICAL'] | None = None, bars_arrangement: Literal['CLUSTERED', 'STACKED', 'STACKED_PERCENT'] | None = None, category_label: str | None = None, value_label: str | None = None, color_label: str | None = None, sort_by: tuple[FieldRef, Literal['ASC', 'DESC']] | None = None, actions: list[Action] | None = None, visual_id: VisualId | AutoResolved = AUTO) -> BarChart

Construct + register + place a BarChart in this row.

add_line_chart

add_line_chart(*, width: int, title: str, category: list[Dim] | None = None, values: list[Measure] | None = None, colors: list[Dim] | None = None, subtitle: str | None = None, chart_type: Literal['LINE', 'AREA', 'STACKED_AREA'] | None = None, category_label: str | None = None, value_label: str | None = None, sort_by: tuple[FieldRef, Literal['ASC', 'DESC']] | None = None, actions: list[Action] | None = None, visual_id: VisualId | AutoResolved = AUTO) -> LineChart

Construct + register + place a LineChart in this row.

add_sankey

add_sankey(*, width: int, title: str, source: Dim, target: Dim, weight: Measure, subtitle: str | None = None, items_limit: int | None = None, actions: list[Action] | None = None, visual_id: VisualId | AutoResolved = AUTO) -> Sankey

Construct + register + place a Sankey in this row.

add_text_box

add_text_box(text_box: TextBox, *, width: int) -> TextBox

Register + place a pre-constructed TextBox in this row.

TextBox content is verbose XML (built via common/rich_text), so the analyst constructs the TextBox separately and passes it here. Uniqueness is by object identity — placing the same TextBox in two rows is a programmer error caught by the row- cursor advance (you'd be calling _consume twice).

AbsoluteSlot dataclass

One explicit-position slot. Use for layouts that don't fit the row pattern — overlapping visuals, asymmetric grids, off-grid positioning. One-shot: construct via sheet.layout.absolute( col_index=, row_index=, col_span=, row_span=) then chain a single add_<kind>(...) to fill it. Re-using an AbsoluteSlot for multiple visuals would emit duplicate slot positions and isn't supported.

sheet instance-attribute

sheet: Sheet

col_span instance-attribute

col_span: int

row_span instance-attribute

row_span: int

col_index instance-attribute

col_index: int

row_index instance-attribute

row_index: int | None

__init__

__init__(sheet: Sheet, col_span: int, row_span: int, col_index: int, row_index: int | None) -> None

add_kpi

add_kpi(*, title: str, values: list[Measure] | None = None, subtitle: str | None = None, visual_id: VisualId | AutoResolved = AUTO) -> KPI

add_table

add_table(*, title: str, group_by: list[Dim] | None = None, values: list[Measure] | None = None, columns: list[Dim] | None = None, subtitle: str | None = None, sort_by: tuple[FieldRef, Literal['ASC', 'DESC']] | list[tuple[FieldRef, Literal['ASC', 'DESC']]] | None = None, actions: list[Action] | None = None, conditional_formatting: list[CellFormat] | None = None, visual_id: VisualId | AutoResolved = AUTO) -> Table

add_bar_chart

add_bar_chart(*, title: str, category: list[Dim] | None = None, values: list[Measure] | None = None, subtitle: str | None = None, orientation: Literal['HORIZONTAL', 'VERTICAL'] | None = None, bars_arrangement: Literal['CLUSTERED', 'STACKED', 'STACKED_PERCENT'] | None = None, sort_by: tuple[FieldRef, Literal['ASC', 'DESC']] | None = None, actions: list[Action] | None = None, visual_id: VisualId | AutoResolved = AUTO) -> BarChart

add_line_chart

add_line_chart(*, title: str, category: list[Dim] | None = None, values: list[Measure] | None = None, colors: list[Dim] | None = None, subtitle: str | None = None, chart_type: Literal['LINE', 'AREA', 'STACKED_AREA'] | None = None, category_label: str | None = None, value_label: str | None = None, sort_by: tuple[FieldRef, Literal['ASC', 'DESC']] | None = None, actions: list[Action] | None = None, visual_id: VisualId | AutoResolved = AUTO) -> LineChart

add_sankey

add_sankey(*, title: str, source: Dim, target: Dim, weight: Measure, subtitle: str | None = None, items_limit: int | None = None, actions: list[Action] | None = None, visual_id: VisualId | AutoResolved = AUTO) -> Sankey

add_text_box

add_text_box(text_box: TextBox) -> TextBox

SheetLayout dataclass

Layout namespace on a Sheet — manages rows + absolute placements.

Tracks a vertical row cursor: each row(height=H) opens a new row at the current cursor and advances it by H for the next row. Absolute placements don't advance the cursor (they're independent of row flow).

sheet instance-attribute

sheet: Sheet

__init__

__init__(sheet: Sheet) -> None

row

row(*, height: int) -> Row

Open a new row at the current vertical cursor with the given height. The cursor advances by height so the next row() call lands below this one.

absolute

absolute(*, col_index: int, row_index: int | None = None, col_span: int, row_span: int) -> AbsoluteSlot

Open an explicit-position slot. Doesn't advance the row cursor — absolute placements are independent of row flow.

Analysis dataclass

Tree node for the Analysis-level structure.

analysis_id_suffix is the part the App's cfg.prefixed() will prepend to (e.g. "investigation-analysis" becomes "qs-gen-investigation-analysis"). Keeping the suffix on the tree node keeps the per-app naming under the tree's control while leaving the global resource-prefix in the Config.

emit_definition() returns the models.AnalysisDefinition — the App combines this with metadata (AwsAccountId, ThemeArn, Permissions, dataset declarations) to produce the full models.Analysis ready for deploy.

analysis_id_suffix instance-attribute

analysis_id_suffix: str

name instance-attribute

name: str

sheets class-attribute instance-attribute

sheets: list[Sheet] = field(default_factory=list['Sheet'])

parameters class-attribute instance-attribute

parameters: list[ParameterDeclLike] = field(default_factory=list[ParameterDeclLike])

filter_groups class-attribute instance-attribute

filter_groups: list[FilterGroup] = field(default_factory=list[FilterGroup])

calc_fields class-attribute instance-attribute

calc_fields: list[CalcField] = field(default_factory=list[CalcField])

__init__

__init__(analysis_id_suffix: str, name: str, sheets: list[Sheet] = list['Sheet'](), parameters: list[ParameterDeclLike] = list[ParameterDeclLike](), filter_groups: list[FilterGroup] = list[FilterGroup](), calc_fields: list[CalcField] = list[CalcField]()) -> None

add_sheet

add_sheet(sheet: Sheet) -> Sheet

add_parameter

add_parameter(param: T) -> T

Declare a parameter on this analysis.

Construction-time check: parameter names are unique within the analysis. Catches the silent shadow bug where two declarations share a Name and only one wins at deploy time. Generic over the concrete subtype so the returned ref keeps its type (StringParam / IntegerParam / DateTimeParam) (PEP 695).

add_filter_group

add_filter_group(fg: FilterGroup) -> FilterGroup

Register a filter group on this analysis.

Construction-time check: explicit filter group IDs are unique. (Auto-IDs are unique by construction — assigned from the index in the analysis's filter_groups list — so the check only applies when callers passed an explicit id.)

find_sheet

find_sheet(*, name: str | None = None, sheet_id: SheetId | str | None = None) -> Sheet

Look up a single sheet on this analysis by name or sheet id.

Raises on no-match or multi-match. Sheet IDs stay explicit (URL-facing per the L.1.8.5 mixed scheme) so passing sheet_id= is the most robust lookup; name= is the next-best for tests that don't want to hardcode IDs.

find_filter_group

find_filter_group(*, filter_group_id: FilterGroupId | str | None = None) -> FilterGroup

Look up a single filter group by id (auto or explicit).

find_calc_field

find_calc_field(*, name: str) -> CalcField

Look up a single calc field by name.

find_parameter

find_parameter(*, name: str) -> ParameterDeclLike

Look up a single parameter declaration by name.

add_calc_field

add_calc_field(calc: CalcField) -> CalcField

Register a calculated field on this analysis.

Construction-time check: calc field names are unique within the analysis. Two calc fields sharing a Name silently let one win at deploy time — same shadow-bug class as parameters / filter groups / datasets.

datasets

datasets() -> set[Dataset]

Walk the analysis tree and return every Dataset referenced by any visual, filter group, or registered calc field. Used by App.dataset_dependencies to derive the precise refresh set.

Visuals using the spike-shape VisualNode factory wrapper don't expose their dataset refs (the factory hides them). Typed Visual subtypes (KPI / Table / BarChart / Sankey) all expose datasets() and contribute. The spike-shape gap closes once apps port to typed subtypes (L.2/L.3/L.4).

Registered CalcFields contribute too — their Dataset ref becomes a dep even if no visual directly references the underlying columns.

calc_fields_referenced

calc_fields_referenced() -> set[CalcField]

Walk the analysis tree and return every CalcField referenced by any visual or filter group. Distinct from self.calc_fields (the registry): this returns only the calc fields actually used.

Catches "calc field declared but never used" (registered but not in this set) and "calc field used but not declared" (in this set but not in the registry — App.validate_calc_field references raises on emit).

emit_definition

emit_definition(*, datasets: list[Dataset]) -> AnalysisDefinition

Dashboard dataclass

Tree node for a Dashboard.

Carries an object reference to the Analysis whose definition this Dashboard publishes. dashboard_id_suffix follows the same pattern as Analysis.analysis_id_suffix — App's cfg.prefixed() prepends the project resource prefix.

analysis is the SAME tree node the App owns; the Dashboard re-emits the same definition the Analysis produces, which matches the existing build_dashboard(cfg) pattern in the per-app builders.

dashboard_id_suffix instance-attribute

dashboard_id_suffix: str

name instance-attribute

name: str

analysis instance-attribute

analysis: Analysis

__init__

__init__(dashboard_id_suffix: str, name: str, analysis: Analysis) -> None

App dataclass

Top-level tree node — coordinates an Analysis + Dashboard plus the deploy-time context (theme, dataset arns, permissions) drawn from the Config.

Authors construct an App, attach the Analysis (which holds the sheet tree), optionally attach the Dashboard (most apps do — they publish what they author), and call emit_analysis() / emit_dashboard() to get the models.py instances ready for deploy.

Datasets are registered on the App via add_dataset() and referenced from visuals / filters by object ref. At emit time the App walks the tree's dataset_dependencies() and includes only the datasets actually used in the emitted DataSetIdentifierDeclarations — selective by construction. Validation: if a visual or filter references a Dataset that isn't registered on the App, emit_analysis raises with the offending identifiers.

name instance-attribute

name: str

cfg instance-attribute

cfg: Config

analysis class-attribute instance-attribute

analysis: Analysis | None = None

dashboard class-attribute instance-attribute

dashboard: Dashboard | None = None

datasets class-attribute instance-attribute

datasets: list[Dataset] = field(default_factory=list[Dataset])

allow_bare_strings class-attribute instance-attribute

allow_bare_strings: bool = False

__init__

__init__(name: str, cfg: Config, analysis: Analysis | None = None, dashboard: Dashboard | None = None, datasets: list[Dataset] = list[Dataset](), allow_bare_strings: bool = False) -> None

set_analysis

set_analysis(analysis: Analysis) -> Analysis

create_dashboard

create_dashboard(*, dashboard_id_suffix: str, name: str) -> Dashboard

Construct + register a Dashboard against the App's already-set Analysis.

The App owns the Analysis already; this shortcut prevents the analysis-mismatch bug class by construction — there's no opening to pass a different Analysis.

add_dataset

add_dataset(dataset: Dataset) -> Dataset

Register a Dataset on the App.

Construction-time check: dataset identifiers are unique within the app. Catches the silent shadow bug where two registrations share an identifier and only one wins at deploy.

dataset_dependencies

dataset_dependencies() -> set[Dataset]

The set of Datasets referenced anywhere in the App's tree.

Walks the Analysis (sheets → visuals + filter_groups). Each typed Visual subtype + typed Filter wrapper exposes its own datasets() set; the App unions them.

Deployment side effect. This set drives: - selective deploy (only re-create / refresh the datasets downstream of an actual change), - matview REFRESH ordering (REFRESH only the matviews backing datasets that the changed deploy surface depends on).

Returns an empty set when the App has no Analysis.

find_sheet

find_sheet(*, name: str | None = None, sheet_id: SheetId | str | None = None) -> Sheet

Convenience pass-through to app.analysis.find_sheet(...).

emit_analysis

emit_analysis() -> Analysis

emit_dashboard

emit_dashboard() -> Dashboard