Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 31 additions & 18 deletions docs-mintlify/docs/explore-analyze/workbooks/python-analysis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ title: Python analysis
description: Attach a Python script to a report to run forecasting, regression, cohort, and other analysis that SQL can't express, and save the result as a re-runnable report.
---

<Warning>

Python analysis is currently in preview, and the user experience and the script
contract may still change. Reach out to the [Cube support
team](/admin/account-billing/support) to activate this feature for your account.

</Warning>

A report can carry an attached **Python script** that transforms the report's SQL
result. The report's chart then renders the script's **output** instead of the raw
SQL rows. This turns an analysis that would otherwise scroll away in a chat
Expand Down Expand Up @@ -38,24 +46,24 @@ SQL report, that is usually correct behavior.

</Info>

### In a workbook
### From the toolbar

Open the tab menu and choose **Add Python**. Cube seeds a starter script and
reveals the code panel. **Remove Python** detaches the script, after which the tab
behaves like any SQL-backed report again.
Workbooks and [Explore](/docs/explore-analyze/explore) share the same flow.

Attaching Python clears any existing SQL result: a python-backed report renders its
last Python run, and a freshly attached script has none until you press **Run**.
Click **Python** in the toolbar to open the Python panel, then **Add script** to
attach one. Cube seeds a starter script and opens it on the **Script** tab.
Opening the panel does not attach anything by itself — only **Add script** does.

### In Explore
**Remove**, in the panel header, detaches the script, after which the report
behaves like any SQL-backed one again.

Use the **Add Python** button in the header, with the same **Remove Python**
counterpart.
Attaching Python clears any existing SQL result: a python-backed report renders its
last Python run, and a freshly attached script has none until you press **Run**.

This is only available on a **saved** [exploration](/docs/explore-analyze/explore).
On an unsaved one the button is disabled with the tooltip *"Save exploration before
adding Python."* — **Run** executes server-persisted code, so the analysis needs a
saved report to live on.
Attaching Python in Explore is only available on a **saved** exploration. On an
unsaved one the **Python** button is disabled with the tooltip *"Save the
exploration to add Python"* — **Run** executes server-persisted code, so the
analysis needs a saved report to live on.

## Writing the script

Expand All @@ -68,7 +76,7 @@ The script runs in a sandbox against a fixed contract:
A top-level dict or object is rejected — flatten any nested structure into one
array of uniform rows.

{/* TODO: screenshot — the Add Python tab menu entry */}
{/* TODO: screenshot — the Python panel's Add script button */}

## Python environment

Expand Down Expand Up @@ -100,7 +108,8 @@ adding packages to the environment is coming.

## Running and refreshing

The code panel is editable in place, with line numbers.
The panel's **Script** tab is editable in place, with line numbers. **Reset**
restores the starter template.

- **Edits do not run anything.** They save with the report, and the rendered result
keeps showing the previous run.
Expand All @@ -109,10 +118,14 @@ The code panel is editable in place, with line numbers.
after the last run. Run to refresh the saved result."*
- **Run** executes the stored script in the sandbox and persists the refreshed
result.
- The **Output** tab shows what the last run printed — the script's stdout and
stderr, so `print()` is how you inspect intermediate values. Both are captured up
to the cap in [Limits](#limits), so a chatty script gets truncated.
- The **input SQL panel is read-only** on a python report: that SQL is the
sandbox's input, not what gets charted.
- **A failed run keeps the previous chart.** The error and the script's
stdout/stderr surface in the UI while the last good result stays rendered.
sandbox's input, not what gets charted. It still offers the **Semantic SQL** and
**Generated SQL** tabs, both derived from that input query.
- **A failed run keeps the previous chart.** The error surfaces alongside the last
successful result, which stays rendered.

**Run is the only way the saved result changes.** Opening the report, reloading the
page, or viewing a dashboard never re-runs anything on its own.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ the math.

## Inspecting queries

Every query in the Semantic Query tab can be inspected as code. Open the SQL panel via the **SQL** button in the top-right toolbar to see the query that the workbook generates, switch between representations, and copy it for use elsewhere.
Every query in the Semantic Query tab can be inspected as code. Open the SQL panel via the **SQL** button in the toolbar, next to the Results and Chart tabs, to see the query that the workbook generates, switch between representations, and copy it for use elsewhere.

<Frame>
<img src="https://static.cube.dev/docs/explore-analyze/workbooks/inspect-query-tabs-v2.png" />
Expand Down
69 changes: 33 additions & 36 deletions rust/cubestore/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/cubestore/cubestore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ opentelemetry-otlp = { version = "0.26.0", default-features = false, features =
"trace", "metrics", "logs", "http-proto", "http-json", "reqwest-client", "tokio"
] }
opentelemetry-http = { version = "0.26.0", features = ["reqwest"] }
lru = "0.6.5"
lru = "0.18.2"
moka = { version = "0.10.1", features = ["future"] }
ctor = "0.1.20"
json = "0.12.4"
Expand Down
14 changes: 13 additions & 1 deletion rust/cubestore/cubestore/src/sql/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use futures::Future;
use log::trace;
use moka::future::{Cache, ConcurrentCacheExt, Iter};
use std::collections::{HashMap, HashSet};
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::{watch, Mutex};
Expand Down Expand Up @@ -120,7 +121,10 @@ impl SqlResultCache {
});

Self {
queue_cache: Mutex::new(lru::LruCache::new(queue_cache_max_capacity as usize)),
// `LruCache::new` takes NonZeroUsize since lru 0.9; a configured 0 would panic.
queue_cache: Mutex::new(lru::LruCache::new(
NonZeroUsize::new(queue_cache_max_capacity as usize).unwrap_or(NonZeroUsize::MIN),
)),
result_cache: cache_builder
.max_capacity(capacity_bytes)
.weigher(sql_result_cache_sizeof)
Expand Down Expand Up @@ -420,6 +424,14 @@ mod tests {
use std::sync::Arc;
use std::time::Duration;

/// A configured capacity of 0 must not panic on startup.
#[test]
fn queue_cache_capacity_zero_does_not_panic() {
let cache = SqlResultCache::new(1 << 20, Some(120), 0, None);

assert_eq!(cache.queue_cache.blocking_lock().cap().get(), 1);
}

#[tokio::test]
async fn simple() -> Result<(), CubeError> {
let cache = Arc::new(SqlResultCache::new(1 << 20, Some(120), 1000, None));
Expand Down
Loading