From 75f5c891ee5fc0a574c7d6218eb0b63c1af40404 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 10:18:59 +0000 Subject: [PATCH 1/2] Add landing-page copy aligned with SparkProxy and Lakeflow APIs. Replace stale SparkMock examples with SparkProxy, configure()+dbutils.fs, LocalWorkflowRunner extra_globals, and current Python 3.10+ / JDK notes. Co-authored-by: gkaran184 --- docs/website-landing-copy.md | 102 ++++++++ docs/website/App.tsx | 469 +++++++++++++++++++++++++++++++++++ docs/website/index.html | 25 ++ 3 files changed, 596 insertions(+) create mode 100644 docs/website-landing-copy.md create mode 100644 docs/website/App.tsx create mode 100644 docs/website/index.html diff --git a/docs/website-landing-copy.md b/docs/website-landing-copy.md new file mode 100644 index 0000000..7f491db --- /dev/null +++ b/docs/website-landing-copy.md @@ -0,0 +1,102 @@ +# Landing page copy for [testbricks.netlify.app](https://testbricks.netlify.app/) + +Drop-in files for `engineeringmadness/testbricks-website` (this library repo cannot push that repository): + +- `docs/website/index.html` → website `index.html` +- `docs/website/App.tsx` → website `src/App.tsx` + +The live site still uses `SparkMock` and undersells Lakeflow / `dbutils` coverage from master. + +## Meta (`index.html`) + +**title:** `testbricks — Run Databricks workflows locally` + +**description / og:description:** + +> testbricks runs Databricks workflows on your laptop: SparkProxy maps `schema.table` to local CSV, a drop-in dbutils (widgets, fs, secrets, notebook, taskValues), and a workflow runner with DAG, run_if, and repair-and-rerun. + +## Hero + +**h1:** Run Databricks Workflows E2E / **in your Local Environment** (unchanged) + +**body:** + +> testbricks is a Python library with genuinely useful mocks — SparkProxy routes `spark.read.table` / `saveAsTable` to CSV on disk, a drop-in `dbutils` for widgets, files, secrets, notebooks, and task values, and a runner that executes a whole workflow JSON (DAG, `run_if`, and repair-and-rerun). No cluster. No waiting around. + +**install:** `pip install testbricks` + +**fine print:** `Python 3.10+ · JDK on PATH for PySpark · notebooks, scripts and CI` + +## Features + +**SparkProxy** + +> A SparkSession stand-in that speaks the same API. Catalog tables are `{base_path}/{schema}/{table}.csv`; file writes (`parquet` / `json` / `csv`) use native Spark under that folder. `format("delta").save` is parquet on disk — no cluster, no metastore. + +**Drop-in dbutils** + +> Widgets (including combobox, multiselect, getAll), fs (`ls` / `put` / `cp` / `mv` / `rm` / `mkdirs`), secrets, notebook (`run` / `exit`), `jobs.taskValues`, `library.restartPython`, and `data.summarize`. `%run`, `%sh`, and `%fs` magics work in notebooks the runner executes. + +**Workflow runner** + +> Parses a Databricks / Lakeflow workflow JSON, walks the task graph, and runs notebooks in order. Understands `run_if` and `depends_on` outcomes, `condition_task`, `for_each_task`, retries, taskValues, and repair-and-rerun (`only` / `from_task`). + +**Zero cluster time** + +> Iterate in seconds on your laptop. Debug with breakpoints, run it in CI, and keep your compute bill for the things that matter. (unchanged) + +## How it works + +1. **Swap in the mocks** — Use `SparkProxy` instead of a cluster `SparkSession`. Import `dbutils` from `testbricks.dbutils` (the runner injects it into notebooks). +2. **Drop your data in a folder** — CSV files at `{base_path}/{schema}/{table}.csv` stand in for Unity Catalog tables. Read, write, and inspect them with any tool you like. +3. **Run the whole workflow** — Hand the runner your workflow JSON. It walks the DAG, honors `run_if` / condition / for_each, and re-runs a subgraph with `only` or `from_task`. + +## Code examples (`SNIPPETS` in `App.tsx`) + +Rename the first tab from `SparkMock` to `SparkProxy`. Update the Prism comment that still says `SparkMock(...)`. + +### SparkProxy + +**blurb:** Point it at a folder. `schema.table` reads and writes land as `{schema}/{table}.csv` you can open anywhere. + +```python +from testbricks import SparkProxy + +spark = SparkProxy("./data") + +df = spark.read.option("header", "true").option("inferSchema", "true").table("bronze.customers") +df.write.mode("overwrite").saveAsTable("silver.customers_enriched") +``` + +### dbutils + +**blurb:** Drop-in Databricks `dbutils`: widgets, fs, secrets, notebook, and `jobs.taskValues`. Notebooks the runner executes get it automatically. + +```python +from testbricks.dbutils import configure, dbutils + +configure("./data") # same catalog root as SparkProxy + +dbutils.widgets.text("filter_country", "USA") +country = dbutils.widgets.get("filter_country") + +for info in dbutils.fs.ls("/"): + print(info.name, info.size) +``` + +### LocalWorkflowRunner + +**blurb:** Feed it an exported workflow JSON. It resolves the DAG and runs every notebook in order — including repair-and-rerun with `only` or `from_task`. + +```python +from testbricks import SparkProxy, LocalWorkflowRunner + +spark = SparkProxy("./data") +runner = LocalWorkflowRunner( + source_dir="./notebooks", + workflow_json_path="./workflow.json", + base_path="./data", +) +runner.run_workflow(extra_globals={"spark": spark}) +# runner.run_workflow(extra_globals={"spark": spark}, only=["build_summary"]) +``` diff --git a/docs/website/App.tsx b/docs/website/App.tsx new file mode 100644 index 0000000..8459320 --- /dev/null +++ b/docs/website/App.tsx @@ -0,0 +1,469 @@ +import { useState } from "react"; +import { Highlight, Prism, type PrismTheme } from "prism-react-renderer"; +import { + Check, + Copy, + Database, + FileSpreadsheet, + Github, + Laptop, + Package, + Star, + Waves, + Workflow, +} from "lucide-react"; + +const GITHUB_URL = "https://github.com/engineeringmadness/testbricks"; +const PYPI_URL = "https://pypi.org/project/testbricks/"; + +/* ---------------------------------- data --------------------------------- */ + +type Snippet = { + id: string; + label: string; + blurb: string; + code: string; +}; + +const SNIPPETS: Snippet[] = [ + { + id: "spark", + label: "SparkProxy", + blurb: + "Point it at a folder. schema.table reads and writes land as {schema}/{table}.csv you can open anywhere.", + code: `from testbricks import SparkProxy + +spark = SparkProxy("./data") + +df = spark.read.option("header", "true").option("inferSchema", "true").table("bronze.customers") +df.write.mode("overwrite").saveAsTable("silver.customers_enriched")`, + }, + { + id: "dbutils", + label: "dbutils", + blurb: + "Drop-in Databricks dbutils: widgets, fs, secrets, notebook, and jobs.taskValues. Notebooks the runner executes get it automatically.", + code: `from testbricks.dbutils import configure, dbutils + +configure("./data") # same catalog root as SparkProxy + +dbutils.widgets.text("filter_country", "USA") +country = dbutils.widgets.get("filter_country") + +for info in dbutils.fs.ls("/"): + print(info.name, info.size)`, + }, + { + id: "runner", + label: "LocalWorkflowRunner", + blurb: + "Feed it an exported workflow JSON. It resolves the DAG and runs every notebook in order — including repair-and-rerun with only or from_task.", + code: `from testbricks import SparkProxy, LocalWorkflowRunner + +spark = SparkProxy("./data") +runner = LocalWorkflowRunner( + source_dir="./notebooks", + workflow_json_path="./workflow.json", + base_path="./data", +) +runner.run_workflow(extra_globals={"spark": spark}) +# runner.run_workflow(extra_globals={"spark": spark}, only=["build_summary"])`, + }, +]; + +const FEATURES = [ + { + icon: Database, + title: "SparkProxy", + body: "A SparkSession stand-in that speaks the same API. Catalog tables are {base_path}/{schema}/{table}.csv; file writes (parquet / json / csv) use native Spark under that folder. format(\"delta\").save is parquet on disk — no cluster, no metastore.", + }, + { + icon: FileSpreadsheet, + title: "Drop-in dbutils", + body: "Widgets (including combobox, multiselect, getAll), fs (ls / put / cp / mv / rm / mkdirs), secrets, notebook (run / exit), jobs.taskValues, library.restartPython, and data.summarize. %run, %sh, and %fs magics work in notebooks the runner executes.", + }, + { + icon: Workflow, + title: "Workflow runner", + body: "Parses a Databricks / Lakeflow workflow JSON, walks the task graph, and runs notebooks in order. Understands run_if and depends_on outcomes, condition_task, for_each_task, retries, taskValues, and repair-and-rerun (only / from_task).", + }, + { + icon: Laptop, + title: "Zero cluster time", + body: "Iterate in seconds on your laptop. Debug with breakpoints, run it in CI, and keep your compute bill for the things that matter.", + }, +]; + +const STEPS = [ + { + n: "01", + title: "Swap in the mocks", + body: "Use SparkProxy instead of a cluster SparkSession. Import dbutils from testbricks.dbutils (the runner injects it into notebooks).", + }, + { + n: "02", + title: "Drop your data in a folder", + body: "CSV files at {base_path}/{schema}/{table}.csv stand in for Unity Catalog tables. Read, write, and inspect them with any tool you like.", + }, + { + n: "03", + title: "Run the whole workflow", + body: "Hand the runner your workflow JSON. It walks the DAG, honors run_if / condition / for_each, and re-runs a subgraph with only or from_task.", + }, +]; + +/* -------------------------------- helpers -------------------------------- */ + +function useCopy() { + const [copied, setCopied] = useState(null); + + function copy(value: string, key: string) { + void navigator.clipboard.writeText(value).then(() => { + setCopied(key); + window.setTimeout(() => setCopied(null), 1800); + }); + } + + return { copied, copy }; +} + +/* ------------------------------ code theme ------------------------------- */ + +// Prism's Python grammar only styles `def`/`class` names, so extend it to also +// highlight function/method calls (and constructor calls like `SparkProxy(...)`). +// Inserted after `keyword` so keywords such as `from`/`import` still take priority. +Prism.languages.insertBefore("python", "builtin", { + "function-call": { + pattern: /\b[A-Za-z_][A-Za-z0-9_]*(?=\s*\()/, + alias: "function", + }, +}); + +// Dark editor palette drawn from the Sandy Shore design tokens (teal, coral, sand). +const CODE_THEME: PrismTheme = { + plain: { + color: "#d7e5e2", + backgroundColor: "#0c1a1a", + }, + styles: [ + { + types: ["comment", "prolog", "cdata"], + style: { color: "#5f7a76", fontStyle: "italic" }, + }, + { + types: ["string", "char", "attr-value", "string-interpolation"], + style: { color: "#8fd6c5" }, + }, + { + types: ["keyword", "atrule"], + style: { color: "#f4a261" }, + }, + { + types: ["boolean", "constant"], + style: { color: "#ff9e7d" }, + }, + { + types: ["number"], + style: { color: "#ffb36b" }, + }, + { + types: ["function", "class-name", "builtin", "maybe-class-name"], + style: { color: "#ffd166" }, + }, + { + types: ["operator", "punctuation"], + style: { color: "#93a6a1" }, + }, + ], +}; + +function CodeBlock({ code }: { code: string }) { + return ( + + {({ style, tokens, getLineProps, getTokenProps }) => ( +
+          {tokens.map((line, i) => (
+            
+ {line.map((token, key) => ( + + ))} +
+ ))} +
+ )} +
+ ); +} + +/* -------------------------------- sections ------------------------------- */ + +function Nav() { + return ( +
+ +
+ ); +} + +function Hero() { + const { copied, copy } = useCopy(); + const install = "pip install testbricks"; + + return ( +
+
+

+ Run Databricks Workflows E2E + in your Local Environment +

+ +

+ testbricks is a Python library with genuinely useful mocks — SparkProxy routes{" "} + spark.read.table /{" "} + saveAsTable to CSV on + disk, a drop-in{" "} + dbutils for widgets, + files, secrets, notebooks, and task values, and a runner that executes a whole workflow + JSON (DAG,{" "} + run_if, and + repair-and-rerun). No cluster. No waiting around. +

+ +
+ + + + See it in action + +
+ +

+ Python 3.10+ · JDK on PATH for PySpark · notebooks, scripts and CI +

+
+
+ ); +} + +function Features() { + return ( +
+
+

+ Mocks that actually hold up +

+

+ Everything your notebook reaches for in a Databricks runtime, quietly reimplemented for a + machine that fits on your desk. +

+ +
+ {FEATURES.map((f) => ( +
+ + + +

{f.title}

+

{f.body}

+
+ ))} +
+
+
+ ); +} + +function CodeSection() { + const [active, setActive] = useState(SNIPPETS[0].id); + const { copied, copy } = useCopy(); + const snippet = SNIPPETS.find((s) => s.id === active) ?? SNIPPETS[0]; + + return ( +
+
+

+ Three imports, and you're local +

+

+ The same notebook code you'd ship to a job cluster, running on your machine. +

+ +
+
+ {SNIPPETS.map((s) => ( + + ))} +
+ +
+

{snippet.blurb}

+ +
+ + +
+
+
+ ); +} + +function HowItWorks() { + return ( +
+
+

How it works

+ +
    + {STEPS.map((s) => ( +
  1. + {s.n} +

    {s.title}

    +

    {s.body}

    +
  2. + ))} +
+ +
+ + + +

+ Love Testbricks? +

+ +
+
+
+ ); +} + +function Footer() { + return ( +
+
+ Made with care for Databricks users +
+
+ ); +} + +export default function App() { + return ( +
+
+ ); +} diff --git a/docs/website/index.html b/docs/website/index.html new file mode 100644 index 0000000..e21927b --- /dev/null +++ b/docs/website/index.html @@ -0,0 +1,25 @@ + + + + + + + testbricks — Run Databricks workflows locally + + + + + + + + +
+ + + From b64d06c2db69c97a560a6d94cf38dc1f09d29268 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 10:26:47 +0000 Subject: [PATCH 2/2] Simplify landing-page hero and feature copy. Keep code examples current (SparkProxy) without listing APIs in marketing prose. Co-authored-by: gkaran184 --- docs/website-landing-copy.md | 37 +++++++++++++++++++----------------- docs/website/App.tsx | 37 ++++++++++++++++-------------------- docs/website/index.html | 4 ++-- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/docs/website-landing-copy.md b/docs/website-landing-copy.md index 7f491db..e024e90 100644 --- a/docs/website-landing-copy.md +++ b/docs/website-landing-copy.md @@ -5,15 +5,19 @@ Drop-in files for `engineeringmadness/testbricks-website` (this library repo can - `docs/website/index.html` → website `index.html` - `docs/website/App.tsx` → website `src/App.tsx` -The live site still uses `SparkMock` and undersells Lakeflow / `dbutils` coverage from master. +The live site still uses the old `SparkMock` name in examples. Marketing copy stays high-level; the code tabs show the current API. ## Meta (`index.html`) **title:** `testbricks — Run Databricks workflows locally` -**description / og:description:** +**description:** -> testbricks runs Databricks workflows on your laptop: SparkProxy maps `schema.table` to local CSV, a drop-in dbutils (widgets, fs, secrets, notebook, taskValues), and a workflow runner with DAG, run_if, and repair-and-rerun. +> testbricks runs Databricks workflows on your laptop with a Spark proxy, a drop-in dbutils, and a workflow runner. + +**og:description:** + +> Run Databricks workflows on your laptop with SparkProxy, a dbutils mock, and a JSON workflow runner. ## Hero @@ -21,43 +25,43 @@ The live site still uses `SparkMock` and undersells Lakeflow / `dbutils` coverag **body:** -> testbricks is a Python library with genuinely useful mocks — SparkProxy routes `spark.read.table` / `saveAsTable` to CSV on disk, a drop-in `dbutils` for widgets, files, secrets, notebooks, and task values, and a runner that executes a whole workflow JSON (DAG, `run_if`, and repair-and-rerun). No cluster. No waiting around. +> testbricks is a Python library with genuinely useful mocks — a Spark proxy that reads and writes tables as CSV, a drop-in `dbutils` replacement, and a runner that executes a whole workflow JSON in dependency order. No cluster. No waiting around. **install:** `pip install testbricks` -**fine print:** `Python 3.10+ · JDK on PATH for PySpark · notebooks, scripts and CI` +**fine print:** `Python 3.10+ · works in notebooks, scripts and CI` ## Features **SparkProxy** -> A SparkSession stand-in that speaks the same API. Catalog tables are `{base_path}/{schema}/{table}.csv`; file writes (`parquet` / `json` / `csv`) use native Spark under that folder. `format("delta").save` is parquet on disk — no cluster, no metastore. +> A SparkSession stand-in that speaks the same API. Table reads and writes land as CSV files on disk — no cluster, no metastore, no waiting. **Drop-in dbutils** -> Widgets (including combobox, multiselect, getAll), fs (`ls` / `put` / `cp` / `mv` / `rm` / `mkdirs`), secrets, notebook (`run` / `exit`), `jobs.taskValues`, `library.restartPython`, and `data.summarize`. `%run`, `%sh`, and `%fs` magics work in notebooks the runner executes. +> Widgets, filesystem helpers, secrets, and more. Import it instead of the real thing and your notebook runs unchanged. **Workflow runner** -> Parses a Databricks / Lakeflow workflow JSON, walks the task graph, and runs notebooks in order. Understands `run_if` and `depends_on` outcomes, `condition_task`, `for_each_task`, retries, taskValues, and repair-and-rerun (`only` / `from_task`). +> Parses a Databricks workflow JSON, builds the task graph, and runs notebooks in the right order on your machine. **Zero cluster time** -> Iterate in seconds on your laptop. Debug with breakpoints, run it in CI, and keep your compute bill for the things that matter. (unchanged) +> Iterate in seconds on your laptop. Debug with breakpoints, run it in CI, and keep your compute bill for the things that matter. ## How it works -1. **Swap in the mocks** — Use `SparkProxy` instead of a cluster `SparkSession`. Import `dbutils` from `testbricks.dbutils` (the runner injects it into notebooks). -2. **Drop your data in a folder** — CSV files at `{base_path}/{schema}/{table}.csv` stand in for Unity Catalog tables. Read, write, and inspect them with any tool you like. -3. **Run the whole workflow** — Hand the runner your workflow JSON. It walks the DAG, honors `run_if` / condition / for_each, and re-runs a subgraph with `only` or `from_task`. +1. **Swap in the mocks** — Replace the Spark session and dbutils object with the testbricks equivalents at the top of your notebook. +2. **Drop your data in a folder** — CSV files in a base directory stand in for your tables. Read, write and inspect them with any tool you like. +3. **Run the whole workflow** — Hand the runner your workflow JSON and it walks the dependency graph, notebook by notebook, right on your machine. ## Code examples (`SNIPPETS` in `App.tsx`) -Rename the first tab from `SparkMock` to `SparkProxy`. Update the Prism comment that still says `SparkMock(...)`. +First tab label: `SparkProxy` (not `SparkMock`). ### SparkProxy -**blurb:** Point it at a folder. `schema.table` reads and writes land as `{schema}/{table}.csv` you can open anywhere. +**blurb:** Point it at a folder. Table reads and writes land as plain CSV files you can open anywhere. ```python from testbricks import SparkProxy @@ -70,7 +74,7 @@ df.write.mode("overwrite").saveAsTable("silver.customers_enriched") ### dbutils -**blurb:** Drop-in Databricks `dbutils`: widgets, fs, secrets, notebook, and `jobs.taskValues`. Notebooks the runner executes get it automatically. +**blurb:** A drop-in replacement for the Databricks dbutils object. Notebooks the runner executes get it automatically. ```python from testbricks.dbutils import configure, dbutils @@ -86,7 +90,7 @@ for info in dbutils.fs.ls("/"): ### LocalWorkflowRunner -**blurb:** Feed it an exported workflow JSON. It resolves the DAG and runs every notebook in order — including repair-and-rerun with `only` or `from_task`. +**blurb:** Feed it your exported workflow JSON. It resolves the task graph and runs every notebook in dependency order. ```python from testbricks import SparkProxy, LocalWorkflowRunner @@ -98,5 +102,4 @@ runner = LocalWorkflowRunner( base_path="./data", ) runner.run_workflow(extra_globals={"spark": spark}) -# runner.run_workflow(extra_globals={"spark": spark}, only=["build_summary"]) ``` diff --git a/docs/website/App.tsx b/docs/website/App.tsx index 8459320..298d86f 100644 --- a/docs/website/App.tsx +++ b/docs/website/App.tsx @@ -30,7 +30,7 @@ const SNIPPETS: Snippet[] = [ id: "spark", label: "SparkProxy", blurb: - "Point it at a folder. schema.table reads and writes land as {schema}/{table}.csv you can open anywhere.", + "Point it at a folder. Table reads and writes land as plain CSV files you can open anywhere.", code: `from testbricks import SparkProxy spark = SparkProxy("./data") @@ -42,7 +42,7 @@ df.write.mode("overwrite").saveAsTable("silver.customers_enriched")`, id: "dbutils", label: "dbutils", blurb: - "Drop-in Databricks dbutils: widgets, fs, secrets, notebook, and jobs.taskValues. Notebooks the runner executes get it automatically.", + "A drop-in replacement for the Databricks dbutils object. Notebooks the runner executes get it automatically.", code: `from testbricks.dbutils import configure, dbutils configure("./data") # same catalog root as SparkProxy @@ -57,7 +57,7 @@ for info in dbutils.fs.ls("/"): id: "runner", label: "LocalWorkflowRunner", blurb: - "Feed it an exported workflow JSON. It resolves the DAG and runs every notebook in order — including repair-and-rerun with only or from_task.", + "Feed it your exported workflow JSON. It resolves the task graph and runs every notebook in dependency order.", code: `from testbricks import SparkProxy, LocalWorkflowRunner spark = SparkProxy("./data") @@ -66,8 +66,7 @@ runner = LocalWorkflowRunner( workflow_json_path="./workflow.json", base_path="./data", ) -runner.run_workflow(extra_globals={"spark": spark}) -# runner.run_workflow(extra_globals={"spark": spark}, only=["build_summary"])`, +runner.run_workflow(extra_globals={"spark": spark})`, }, ]; @@ -75,17 +74,17 @@ const FEATURES = [ { icon: Database, title: "SparkProxy", - body: "A SparkSession stand-in that speaks the same API. Catalog tables are {base_path}/{schema}/{table}.csv; file writes (parquet / json / csv) use native Spark under that folder. format(\"delta\").save is parquet on disk — no cluster, no metastore.", + body: "A SparkSession stand-in that speaks the same API. Table reads and writes land as CSV files on disk — no cluster, no metastore, no waiting.", }, { icon: FileSpreadsheet, title: "Drop-in dbutils", - body: "Widgets (including combobox, multiselect, getAll), fs (ls / put / cp / mv / rm / mkdirs), secrets, notebook (run / exit), jobs.taskValues, library.restartPython, and data.summarize. %run, %sh, and %fs magics work in notebooks the runner executes.", + body: "Widgets, filesystem helpers, secrets, and more. Import it instead of the real thing and your notebook runs unchanged.", }, { icon: Workflow, title: "Workflow runner", - body: "Parses a Databricks / Lakeflow workflow JSON, walks the task graph, and runs notebooks in order. Understands run_if and depends_on outcomes, condition_task, for_each_task, retries, taskValues, and repair-and-rerun (only / from_task).", + body: "Parses a Databricks workflow JSON, builds the task graph, and runs notebooks in the right order on your machine.", }, { icon: Laptop, @@ -98,17 +97,17 @@ const STEPS = [ { n: "01", title: "Swap in the mocks", - body: "Use SparkProxy instead of a cluster SparkSession. Import dbutils from testbricks.dbutils (the runner injects it into notebooks).", + body: "Replace the Spark session and dbutils object with the testbricks equivalents at the top of your notebook.", }, { n: "02", title: "Drop your data in a folder", - body: "CSV files at {base_path}/{schema}/{table}.csv stand in for Unity Catalog tables. Read, write, and inspect them with any tool you like.", + body: "CSV files in a base directory stand in for your tables. Read, write and inspect them with any tool you like.", }, { n: "03", title: "Run the whole workflow", - body: "Hand the runner your workflow JSON. It walks the DAG, honors run_if / condition / for_each, and re-runs a subgraph with only or from_task.", + body: "Hand the runner your workflow JSON and it walks the dependency graph, notebook by notebook, right on your machine.", }, ]; @@ -261,15 +260,11 @@ function Hero() {

- testbricks is a Python library with genuinely useful mocks — SparkProxy routes{" "} - spark.read.table /{" "} - saveAsTable to CSV on - disk, a drop-in{" "} - dbutils for widgets, - files, secrets, notebooks, and task values, and a runner that executes a whole workflow - JSON (DAG,{" "} - run_if, and - repair-and-rerun). No cluster. No waiting around. + testbricks is a Python library with genuinely useful mocks — a Spark proxy that reads and + writes tables as CSV, a drop-in{" "} + dbutils replacement, and + a runner that executes a whole workflow JSON in dependency order. No cluster. No waiting + around.

@@ -299,7 +294,7 @@ function Hero() {

- Python 3.10+ · JDK on PATH for PySpark · notebooks, scripts and CI + Python 3.10+ · works in notebooks, scripts and CI

diff --git a/docs/website/index.html b/docs/website/index.html index e21927b..a8d4611 100644 --- a/docs/website/index.html +++ b/docs/website/index.html @@ -7,13 +7,13 @@ testbricks — Run Databricks workflows locally