Metadata-Version: 2.4
Name: sleepybricks
Version: 1.7.1
Summary: Run databricks operations across multiple workspaces from a single terse CLI.
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Keywords: databricks,cli,workspaces,devops
Author: Isaac Yep
Author-email: anthonybenchyep@gmail.com
Requires-Python: >=3.11
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Utilities
Classifier: Topic :: Database
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: dev
Requires-Dist: black (>=24.10.0) ; extra == "dev"
Requires-Dist: databricks-sdk (>=0.57.0)
Requires-Dist: pytest (>=8.3.0) ; extra == "dev"
Requires-Dist: pyyaml (>=6.0)
Requires-Dist: shfmt-py (>=3.8.0) ; extra == "dev"
Requires-Dist: tabulate (>=0.9.0)
Requires-Dist: typer (>=0.16.0)
Project-URL: Changelog, https://github.com/anthonybench/bricks/releases
Project-URL: Documentation, https://github.com/anthonybench/bricks#readme
Project-URL: Homepage, https://github.com/anthonybench/bricks
Project-URL: Issues, https://github.com/anthonybench/bricks/issues
Project-URL: Source, https://github.com/anthonybench/bricks
Description-Content-Type: text/markdown

<div align="center">

# ⚡ sleepybricks

**Run databricks operations across many workspaces from one terse CLI.**

[![PyPI](https://img.shields.io/pypi/v/sleepybricks.svg)](https://pypi.org/project/sleepybricks/)
[![Python](https://img.shields.io/pypi/pyversions/sleepybricks.svg)](https://pypi.org/project/sleepybricks/)
[![License](https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg)](LICENSE)

</div>

`sleepybricks` wraps the [databricks SDK](https://databricks-sdk-py.readthedocs.io/) with a [Typer](https://typer.tiangolo.com/) CLI so a single command fans out across all your workspaces — run SQL, trigger jobs, sync secrets, publish notebooks, audit groups, and more. It authenticates from your existing `~/.databrickscfg` and prints a tidy per-workspace table for every operation.

## Install

```sh
uv tool install sleepybricks     # or: pipx install sleepybricks
```

Optional, for the impatient: `alias bricks='sleepybricks'`.

## Authenticate

Profiles come from `~/.databrickscfg` — the same file the databricks CLI uses. Most commands take a comma-separated **profile list** (`dev,stg,us`); `groups-audit` is the account-scoped exception. A missing file or unknown profile fails fast with a clear message.

```sh
databricks configure --token     # once per workspace/profile
```

Output is a per-workspace table styled by your [config](#configure), decorated with a per-profile emoji + name. Add `--json` to any command for structured, pipeline-friendly output.

## Configure

`sleepybricks` is a _sleepy util_ and reads its settings from the shared `~/sleepyconfig/params.yml`, using the `bricks_` key prefix. If the file is absent it writes **only its own section** (below) and says so; if a value it needs is missing it prints this snippet and asks you to verify your config.

```yaml
# sleepybricks
bricks_table_style: simple                                  # any tabulate style (rounded_grid, github, …)
bricks_serverless_warehouse_name: <env>_serverless_warehouse # <env> → the active profile name
bricks_env_emojis:
  dev: "👩‍💻"
  stg: "🔧"
  us: "🇺🇸"
bricks_display_names:
  dev: "Development"
  stg: "Staging"
  us: "United States"
bricks_personal_cluster_name: personal_cluster              # ensured by update-clusters
bricks_personal_cluster_policy: "Personal Compute"          # must already exist in the workspace
```

- **`bricks_table_style`** — any [tabulate](https://pypi.org/project/tabulate/) table style.
- **`bricks_serverless_warehouse_name`** — SQL warehouse for `sql`; the `<env>` token becomes the active profile name (use a static value if it's the same everywhere).
- **`bricks_env_emojis`** / **`bricks_display_names`** — how each profile is labeled in output.
- **`bricks_personal_cluster_name`** / **`bricks_personal_cluster_policy`** — used only by `update-clusters`.

## Commands at a glance

| Command | What it does |
| --- | --- |
| [`sql`](#sql) | Run a SQL statement (or `.sql` file) on each workspace's serverless warehouse |
| [`run-job`](#run-job) | Trigger a job by exact name in each workspace |
| [`dash-links`](#dash-links) | Print a dashboard's published link in each workspace |
| [`groups-audit`](#groups-audit) | Audit **account-level** groups and their users |
| [`write-secret`](#write-secret--create-scope) / [`create-scope`](#write-secret--create-scope) | Manage secret scopes + secrets everywhere |
| [`pull-repo`](#pull-repo) | Pull the latest commit for a git repo in each workspace |
| [`put-file`](#put-file) | Upload a local file/notebook to each workspace |
| [`propagate-file`](#propagate-file) | Copy a file/notebook from one workspace to the others |
| [`create-workspace-folder`](#create-workspace-folder) | Make a workspace folder in each workspace |
| [`update-clusters`](#update-clusters) | Ensure a pinned, latest-LTS personal cluster in each workspace |
| [`snippets`](#snippets) | Local cheat sheets (`paths`, `workspaces`) |

---

## `sql`

Run one statement across workspaces on each one's serverless SQL warehouse (resolved from config).

```console
$ sleepybricks sql -s "select region, count(*) tenants from tenants group by region" dev,stg

─── 👩‍💻 Development (dev) ───
region     tenants
---------  -------
us-west-2     1043
eu-west-1      512

─── 🔧 Staging (stg) ───
region     tenants
---------  -------
us-west-2       12
```

**`--sum <column>`** — totals a column per workspace and across all of them:

```console
$ sleepybricks sql -s "select amount from invoices" dev,stg --sum amount

Workspace              sum(amount)
---------------------  -----------
👩‍💻 Development (dev)     182500.75
🔧 Staging (stg)             432.10
TOTAL                     182932.85
```

**`--out <path>`** — write every workspace's rows to one CSV (leading `profile` column; `.csv` enforced):

```console
$ sleepybricks sql -f ./audit.sql dev,stg --out ./audit

Workspace              Rows written
---------------------  ------------
👩‍💻 Development (dev)   1555
🔧 Staging (stg)          12

Wrote 1567 rows to audit.csv
```

Also: **`--as-sp <application_id>`** runs the query as a service principal (OAuth M2M; secret from `$SLEEPYBRICKS_SP_SECRET`) instead of you, and **`--json`** emits structured results. `--out` and `--sum` are mutually exclusive.

## `run-job`

Trigger a job by **exact, case-sensitive** name. Job names aren't unique — if more than one matches in a workspace, nothing runs there and the count is reported.

```console
$ sleepybricks run-job "Nightly ETL" dev,stg,eu

Workspace              Job ID  Status
---------------------  ------  -------------------------------------------
👩‍💻 Development (dev)   42      ✅ started: https://dev…/jobs/42/runs/9107
🔧 Staging (stg)        -       (not found)
🇺🇸 United States (eu)   -       ⚠️ 2 jobs share this name — not run
```

## `dash-links`

Find a dashboard's published link everywhere (case-sensitive; first match per workspace, with a warning on duplicates).

```console
$ sleepybricks dash-links "Triage Device Metrics" dev,eu

✨ Triage Device Metrics ✨

Workspace              Link
---------------------  ---------------------------------------------------
👩‍💻 Development (dev)   https://dev…/dashboardsv3/a1b2c3/published
🇺🇸 United States (eu)   https://eu…/dashboardsv3/d4e5f6/published
```

## `groups-audit`

Account-scoped (no profile list): list each account group with its users. The account profile is auto-detected from `~/.databrickscfg`. `--inactive` keeps only deactivated users (likely departed); `--users <emails>` filters to specific people. Either filter hides groups with no matches.

```console
$ sleepybricks groups-audit --inactive

=== Engineering (1) ===
Email          Display Name    Active
-------------  --------------  ------
bob@corp.com   Bob Stone       False

=== Data (1) ===
Email           Display Name    Active
--------------  --------------  ------
carol@corp.com  Carol Diaz      False
```

## `write-secret` / `create-scope`

Create secret scopes and write secrets across workspaces. The secret **value is never a CLI argument** — it's read from `$SLEEPYBRICKS_SECRET_STAGE` (keeps it out of shell history and `ps`).

```console
$ sleepybricks create-scope quant dev,stg

Workspace              Scope   Status
---------------------  ------  -----------------
👩‍💻 Development (dev)   quant   ✅ created
🔧 Staging (stg)        quant   ↩️ already existed

$ export SLEEPYBRICKS_SECRET_STAGE='s3cr3t-value'
$ sleepybricks write-secret quant.api_key dev,stg

Workspace              Scope   Key      Status
---------------------  ------  -------  ----------
👩‍💻 Development (dev)   quant   api_key  ✅ written
🔧 Staging (stg)        quant   api_key  ✅ written
```

## `pull-repo`

Pull the latest commit for a git repo (matched by name within its workspace path) in each workspace.

```console
$ sleepybricks pull-repo databricks_templates dev,stg

Workspace              Repo                                        Branch  Status
---------------------  ------------------------------------------  ------  ----------------
👩‍💻 Development (dev)   /Repos/dingus@company.com/databricks_…      main    ✅ pulled @ 3f9a1c2
🔧 Staging (stg)        /Repos/dingus@company.com/databricks_…      main    ✅ pulled @ 3f9a1c2
```

## `put-file`

Upload a local file/notebook to the same path in each workspace (overwrites). Notebook source (`.py`, `.sql`, `.scala`, `.r`, `.ipynb`) sent to a workspace path lands as a **notebook**; DBFS/`/FileStore` paths go to DBFS.

```console
$ sleepybricks put-file ./tools/audit.py /Users/dingus@company.com/Tools/audit dev,stg

Workspace              Target                                     Status
---------------------  -----------------------------------------  ----------------------
👩‍💻 Development (dev)   /Users/dingus@company.com/Tools/audit      ✅ uploaded → notebook
🔧 Staging (stg)        /Users/dingus@company.com/Tools/audit      ✅ uploaded → notebook
```

## `propagate-file`

Edit a notebook in one workspace, then push it to the others at the same path (overwrites; a notebook stays a notebook). Reports created vs. overwritten per target.

```console
$ sleepybricks propagate-file dev /Users/dingus@company.com/Tools/audit qa,stg

Propagating '/Users/dingus@company.com/Tools/audit' from 'dev' to 2 workspace(s)...

Workspace              Path                                       Status
---------------------  -----------------------------------------  --------------
🧪 QA (qa)              /Users/dingus@company.com/Tools/audit      ✅ created
🔧 Staging (stg)        /Users/dingus@company.com/Tools/audit      ✅ overwritten
```

## `create-workspace-folder`

Make a workspace folder in each workspace (idempotent — existing folders are skipped).

```console
$ sleepybricks create-workspace-folder /Shared/quant dev,stg

Workspace              Path            Status
---------------------  --------------  -----------------
👩‍💻 Development (dev)   /Shared/quant   ✅ created
🔧 Staging (stg)        /Shared/quant   ↩️ already existed
```

## `update-clusters`

Ensure a pinned, single-user, latest-LTS personal cluster (name + policy from config) in each workspace. Creates it when missing, rebuilds it when on an old runtime, or just (re)pins it when current.

```console
$ sleepybricks update-clusters dev,stg

Workspace              Cluster ID          Status
---------------------  ------------------  ------------------------------------------
👩‍💻 Development (dev)   0729-1a2b3c-abcd    ✅ created + pinned
🔧 Staging (stg)        0729-9z8y7x-wxyz    ✅ rebuilt to 15.4.x-scala2.12 (was 13.3…)
```

## `snippets`

Local cheat sheets — no workspace calls for `paths`; `workspaces` summarizes your configured profiles.

```console
$ sleepybricks snippets workspaces

Display Name    Profile  URL                                 Cloud  Region
--------------  -------  ----------------------------------  -----  ---------
Development     dev      https://dev…cloud.databricks.com     aws    us-west-2
Staging         stg      https://stg…cloud.databricks.com     aws    us-west-2

$ sleepybricks snippets paths     # workspace vs dbfs:/ vs /dbfs/ vs /Volumes reference
```

---

## Development

```sh
uv venv
uv pip install -e ".[dev]"
uv run pytest          # or ./tools/test.sh
```

Tests use fakes for the databricks client and never touch a real workspace.

## Documentation

- [Specification](docs/SPEC.md) — what the tool does
- [Project outline](docs/OUTLINE.md) — repository layout
- [Test drive](docs/test_drive.md) — setup, testing, and CLI usage
- [Adding a command](docs/new_command.md) — how to extend the CLI
- [Publishing](docs/publish.md) — release to PyPI

