Contracts and Guarantees
System contracts, lifecycle guarantees, and behavior specifications.
Data Source Lifecycle (v0.15)
Contract:
initialization → pipeline → data access
↓ ↓ ↓
__init__() get_processed_data()
(2 stages)
Pipeline Stages:
get_processed_data(params) → classify → build → transform → return
↓ ↓ ↓
Context Cache1 Cache2
Guarantees:
Staged Caching: Each pipeline stage caches independently
Cache Efficiency: Transform changes don’t trigger build stage
Cache Consistency: Same params → same cached data at each stage
Cache Expiration: Data refreshes after
cache_ttlsecondsError Handling:
DataLoadErrorraised on load failureThread Safety: Cache operations are thread-safe (diskcache)
Pre-conditions:
DataBuilder.build()must returnpd.DataFrameDataTransformer.transform()must returnpd.DataFrameCache directory must be writable (if specified)
Post-conditions:
get_processed_data()returns filtered DataFrame or empty DataFrameAll stages are properly cached
Data is consistent with input parameters
Block Lifecycle
Contract:
instantiation → registration → layout → callbacks → updates
↓ ↓ ↓ ↓ ↓
__init__() _register_ layout() bind_ update
state_ callbacks methods
interactions()
Guarantees:
Unique IDs: Each block has unique
block_idState Registration: Blocks register before callbacks
Theme Injection:
theme_configset beforelayout()calledCallback Order: Publishers registered before subscribers
Error Handling: Update methods never crash app (return fallback)
Pre-conditions:
block_idis unique across dashboarddatasourceis validDataSourceinstanceState IDs referenced in subscriptions exist
Post-conditions:
layout()returns valid Dash Component (single component, not composite)output_target()returns unique (id, property) tupleUpdates trigger correctly on state changes
Layout Height Contract (v0.15)
Contract:
Row (auto height) → Col (content height) → Card (natural size)
↓ ↓ ↓
Determined by Independent sizing Compact/tight
tallest child per block type no empty space
Guarantees:
Content-Driven Heights: Blocks size naturally to their content
No Fixed Heights: No hardcoded px values (responsive to content)
No Empty Space: Cards are compact (especially metrics)
Responsive Widths: Bootstrap breakpoints (xs, sm, md, lg) preserved
No Scrolling: Cards expand to show all content
Design Decision:
Blocks in the same row MAY have different heights based on content. This is industry standard (Grafana, Tableau, Looker, Metabase).
Rationale:
Bootstrap Flexbox align-items: stretch requires Row with defined height
to make columns equal height. With height: auto (content-driven),
equal heights require either:
CSS Grid (breaks responsive breakpoints)
JavaScript (complexity)
Fixed heights (empty space in compact blocks)
We choose content-driven sizing to preserve Bootstrap responsive behavior and avoid complexity.
Pre-conditions:
Row has
className="mb-4"for vertical spacing (auto-applied)Col has responsive width classes (xs, sm, md, lg) from user or defaults
Card has
className="h-100"to fill its column (applied by blocks)No fixed
heightorminHeightin inline styles
Post-conditions:
Cards are compact (tight fit around content)
Row height = max(card heights)
Columns may have different heights (content-driven)
Responsive breakpoints work correctly
Example:
# Metric (compact ~150px) + Chart (large ~500px) in same row
metric = SingleMetricBlock(...) # Compact card, natural height
chart = TypedChartBlock(...) # Larger card with graph
page = DashboardPage(..., blocks=[[metric, chart]])
# Result:
# - Row height = 500px (tallest child)
# - Metric card = 150px (compact, no empty space)
# - Chart card = 500px (natural graph size)
# - Both preserve responsive widths (col-md-4, col-md-8, etc.)
State Management Callbacks
Contract:
publisher change → callback trigger → subscribers update
↓ ↓ ↓
Input(...) function(*) Output(...)
Guarantees:
One Callback Per Block: Each block has exactly one callback
All Inputs Provided: Callback receives all subscribed state values
Positional Arguments: State values passed as positional args in registration order
Error Handling: Failed callbacks return safe fallback values
No Circular Dependencies: Detected and prevented at compile time
Pre-conditions:
No duplicate output targets (unless
allow_duplicate=True)All state IDs have publishers
Callback functions have correct signature
Post-conditions:
UI updates reflect all state changes
No dangling callbacks
Error states render safely
Cache Behavior (v0.15)
Contract:
get_processed_data(params) → classify → Stage 1 → Stage 2 → return
↓ ↓ ↓
Context Cache1 Cache2
Staged Caching Strategy:
# Stage 1: Built Data (cached by build params only)
cache_key_1 = f"built_{json(build_params)}"
# Stage 2: Transformed Data (cached by transform params)
cache_key_2 = f"transformed_{json(transform_params)}"
Guarantees:
Key Stability: Same params → same cache key (sorted JSON) per stage
TTL Enforcement: Expired entries refreshed automatically
Isolation: Different params → different cache entries
Persistence: Disk cache survives app restarts
Stage Independence: Transform changes don’t invalidate build cache
Efficiency: Only affected stages recompute
Cache Hit Scenarios:
Scenario |
Stage 1 (Build) |
Stage 2 (Transform) |
Performance |
Notes |
|---|---|---|---|---|
First load |
Miss |
Miss |
Full pipeline |
All stages run |
Same params |
Hit |
Hit |
Instant |
All cached |
Filter change |
Hit |
Miss |
Fast |
Only retransform |
Build change |
Miss |
Miss |
Full pipeline |
Rebuild needed |
Pre-conditions:
DataBuilder.build()must be implementedDataTransformer.transform()must be implementedBoth methods must return
pd.DataFrameCache directory exists and is writable (if specified)
Params are JSON-serializable
Post-conditions:
Cache hit at any stage → no recomputation of that stage
Cache miss at stage N → stages N, N+1 recompute
Expired entry → treated as cache miss
Both data attributes are populated:
_built_data,_transformed_data