dash-observe 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. dash_observe-0.1.2/.editorconfig +15 -0
  2. dash_observe-0.1.2/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  3. dash_observe-0.1.2/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. dash_observe-0.1.2/.github/PULL_REQUEST_TEMPLATE.md +11 -0
  5. dash_observe-0.1.2/.github/workflows/ci.yml +53 -0
  6. dash_observe-0.1.2/.github/workflows/daily.yml +33 -0
  7. dash_observe-0.1.2/.github/workflows/release.yml +265 -0
  8. dash_observe-0.1.2/.gitignore +9 -0
  9. dash_observe-0.1.2/CHANGELOG.md +13 -0
  10. dash_observe-0.1.2/CLAUDE.md +46 -0
  11. dash_observe-0.1.2/CODE_OF_CONDUCT.md +51 -0
  12. dash_observe-0.1.2/CONTRIBUTING.md +48 -0
  13. dash_observe-0.1.2/LICENSE +201 -0
  14. dash_observe-0.1.2/PKG-INFO +118 -0
  15. dash_observe-0.1.2/README.md +80 -0
  16. dash_observe-0.1.2/RELEASE_NOTES.md +23 -0
  17. dash_observe-0.1.2/SECURITY.md +20 -0
  18. dash_observe-0.1.2/dashobserve/__init__.py +21 -0
  19. dash_observe-0.1.2/dashobserve/monitors.py +81 -0
  20. dash_observe-0.1.2/dashobserve/py.typed +0 -0
  21. dash_observe-0.1.2/dashobserve/runner.py +150 -0
  22. dash_observe-0.1.2/dashobserve/ui.py +110 -0
  23. dash_observe-0.1.2/docs/api/dashobserve.html +916 -0
  24. dash_observe-0.1.2/docs/api/index.html +7 -0
  25. dash_observe-0.1.2/docs/api/search.js +46 -0
  26. dash_observe-0.1.2/docs/screenshots/launch.png +0 -0
  27. dash_observe-0.1.2/marketplace/listing.json +13 -0
  28. dash_observe-0.1.2/marketplace/quickstart_notebook.py +37 -0
  29. dash_observe-0.1.2/pyproject.toml +45 -0
  30. dash_observe-0.1.2/tests/test_dashobserve.py +18 -0
  31. dash_observe-0.1.2/tests/test_monitors.py +120 -0
  32. dash_observe-0.1.2/tests/test_runner.py +37 -0
@@ -0,0 +1,15 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.{yml,yaml,json}]
12
+ indent_size = 2
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something doesn't work the way it should
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ **Describe the bug**
10
+ A clear description of what's wrong.
11
+
12
+ **To Reproduce**
13
+ ```python
14
+ from dashobserve import MonitorConfig, run_monitors
15
+ # minimal config that reproduces the issue
16
+ ```
17
+
18
+ **Expected behavior**
19
+ What you expected to happen instead.
20
+
21
+ **Environment**
22
+ - `dash-observe` version: `pip show dash-observe`
23
+ - Python version:
24
+ - Running in: Databricks notebook / Jupyter / other
25
+
26
+ **Additional context**
27
+ Anything else relevant (screenshots, stack trace, etc.).
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a new monitor type or capability
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+ ---
8
+
9
+ **What problem does this solve?**
10
+ What are you trying to monitor that you currently can't (or can only do awkwardly)?
11
+
12
+ **Proposed API**
13
+ ```python
14
+ from dashobserve import MonitorConfig
15
+ # what you'd want to be able to configure
16
+ ```
17
+
18
+ **Alternatives considered**
19
+ Any workarounds you're using today.
@@ -0,0 +1,11 @@
1
+ ## Summary
2
+
3
+ What does this PR change, and why?
4
+
5
+ ## Checklist
6
+
7
+ - [ ] Tests added/updated for any behavior change
8
+ - [ ] `pytest tests/ -v` passes locally
9
+ - [ ] `ruff check dashobserve/` passes locally
10
+ - [ ] `CLAUDE.md` updated if the module structure or a design rule changed
11
+ - [ ] No version bump included (the release workflow handles that)
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.11"
17
+ - run: pip install ruff
18
+ - run: ruff check dashobserve/
19
+
20
+ test:
21
+ runs-on: ubuntu-latest
22
+ needs: lint
23
+ strategy:
24
+ matrix:
25
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-python@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ - name: Install
32
+ run: pip install -e ".[dev]" pytest pytest-cov
33
+ - name: Test
34
+ run: pytest tests/ -v --cov=dashobserve --cov-report=xml
35
+ - name: Upload coverage
36
+ uses: codecov/codecov-action@v4
37
+ with:
38
+ files: coverage.xml
39
+
40
+ build:
41
+ runs-on: ubuntu-latest
42
+ needs: test
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ - uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.11"
48
+ - run: pip install hatch
49
+ - run: hatch build
50
+ - uses: actions/upload-artifact@v4
51
+ with:
52
+ name: dist
53
+ path: dist/
@@ -0,0 +1,33 @@
1
+ name: Daily Tests
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 6 * * *" # Every day 06:00 UTC — tests only, no commit
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ test:
10
+ name: Test (Python ${{ matrix.python-version }})
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ - name: Install
24
+ run: pip install -e ".[dev]" pytest pytest-cov
25
+
26
+ - name: Run tests
27
+ run: pytest tests/ -v --cov=dashobserve --cov-report=xml --cov-report=term-missing
28
+
29
+ - name: Upload coverage
30
+ uses: codecov/codecov-action@v4
31
+ with:
32
+ files: coverage.xml
33
+ fail_ci_if_error: false
@@ -0,0 +1,265 @@
1
+ name: Weekly Release
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 9 * * 1" # Every Monday 09:00 UTC
6
+ workflow_dispatch:
7
+ inputs:
8
+ release_note:
9
+ description: "Optional release note (shown in GitHub release body)"
10
+ required: false
11
+ default: ""
12
+
13
+ jobs:
14
+ # ── Gate: tests must pass ────────────────────────────────────────────────
15
+ test:
16
+ name: Test (Python ${{ matrix.python-version }})
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ fail-fast: true
20
+ matrix:
21
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+
29
+ - name: Install
30
+ run: pip install -e ".[dev]" pytest pytest-cov
31
+
32
+ - name: Run tests
33
+ run: pytest tests/ -v --cov=dashobserve --cov-report=xml --cov-report=term-missing
34
+
35
+ - name: Upload coverage
36
+ uses: codecov/codecov-action@v4
37
+ with:
38
+ files: coverage.xml
39
+ fail_ci_if_error: false
40
+
41
+ # ── Generate docs ────────────────────────────────────────────────────────
42
+ docs:
43
+ name: Generate API docs
44
+ runs-on: ubuntu-latest
45
+ needs: test
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+
49
+ - uses: actions/setup-python@v5
50
+ with:
51
+ python-version: "3.11"
52
+
53
+ - name: Install
54
+ run: pip install -e ".[dev]" pdoc
55
+
56
+ - name: Generate docs
57
+ run: |
58
+ pdoc dashobserve --output-dir docs/api --docformat google
59
+ echo "Docs generated at $(date -u)" > docs/api/.generated
60
+
61
+ - name: Upload docs artifact
62
+ uses: actions/upload-artifact@v4
63
+ with:
64
+ name: api-docs
65
+ path: docs/api/
66
+
67
+ # ── Release: tag, GitHub release, commit docs ────────────────────────────
68
+ release:
69
+ name: Bump version & release
70
+ runs-on: ubuntu-latest
71
+ needs: [test, docs]
72
+ permissions:
73
+ contents: write
74
+ pull-requests: write
75
+ outputs:
76
+ version: ${{ steps.bump.outputs.version }}
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+ with:
80
+ fetch-depth: 0
81
+
82
+ - uses: actions/setup-python@v5
83
+ with:
84
+ python-version: "3.11"
85
+
86
+ - name: Install build tools
87
+ run: pip install hatch pdoc
88
+
89
+ - name: Bump patch version
90
+ id: bump
91
+ run: |
92
+ current=$(hatch version)
93
+ hatch version patch
94
+ new=$(hatch version)
95
+ echo "version=$new" >> $GITHUB_OUTPUT
96
+ echo "prev_version=$current" >> $GITHUB_OUTPUT
97
+ echo "Bumped $current → $new"
98
+
99
+ - name: Regenerate docs into repo
100
+ run: |
101
+ pip install -e ".[dev]"
102
+ pdoc dashobserve --output-dir docs/api --docformat google
103
+
104
+ - name: Build wheel + sdist
105
+ run: hatch build
106
+
107
+ - name: Write release notes
108
+ env:
109
+ VERSION: ${{ steps.bump.outputs.version }}
110
+ PREV_VERSION: ${{ steps.bump.outputs.prev_version }}
111
+ RELEASE_NOTE: ${{ github.event.inputs.release_note }}
112
+ run: |
113
+ cat > RELEASE_NOTES.md << EOF
114
+ ## DashObserve v${VERSION}
115
+
116
+ **Released:** $(date -u '+%Y-%m-%d')
117
+ **Previous:** v${PREV_VERSION}
118
+
119
+ $( [ -n "${RELEASE_NOTE}" ] && echo "### Notes" && echo "${RELEASE_NOTE}" || true )
120
+
121
+ ### What's included
122
+ - All tests passing across Python 3.9, 3.10, 3.11, 3.12
123
+ - API documentation regenerated (see \`docs/api/\`)
124
+ - Published to PyPI and Databricks Marketplace
125
+
126
+ ### Install
127
+ \`\`\`bash
128
+ pip install dash-observe==${VERSION}
129
+ \`\`\`
130
+
131
+ ### Quick Start (Databricks notebook)
132
+ \`\`\`python
133
+ %pip install dash-observe==${VERSION}
134
+ import dashobserve
135
+ dashobserve.launch()
136
+ \`\`\`
137
+ EOF
138
+
139
+ - name: Commit version bump + docs to a release branch
140
+ env:
141
+ VERSION: ${{ steps.bump.outputs.version }}
142
+ run: |
143
+ git config user.name "github-actions[bot]"
144
+ git config user.email "github-actions[bot]@users.noreply.github.com"
145
+ # Clean up any orphaned tag/branch from a previous partial run
146
+ git push origin --delete "refs/tags/v${VERSION}" 2>/dev/null || true
147
+ git push origin --delete "release/v${VERSION}" 2>/dev/null || true
148
+ git tag -d "v${VERSION}" 2>/dev/null || true
149
+ git checkout -b "release/v${VERSION}"
150
+ git add .
151
+ git commit -m "release: v${VERSION} — tests passed, docs updated"
152
+ git tag "v${VERSION}"
153
+ git push origin "release/v${VERSION}"
154
+ git push origin "v${VERSION}"
155
+
156
+ - name: Create GitHub Release
157
+ uses: softprops/action-gh-release@v2
158
+ with:
159
+ tag_name: "v${{ steps.bump.outputs.version }}"
160
+ body_path: RELEASE_NOTES.md
161
+ files: dist/*
162
+
163
+ - name: Upload dist artifact for PyPI job
164
+ uses: actions/upload-artifact@v4
165
+ with:
166
+ name: dist
167
+ path: dist/
168
+
169
+ - name: Open and auto-merge release PR
170
+ continue-on-error: true
171
+ env:
172
+ GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
173
+ VERSION: ${{ steps.bump.outputs.version }}
174
+ run: |
175
+ gh pr create --base main --head "release/v${VERSION}" \
176
+ --title "release: v${VERSION}" \
177
+ --body "Automated release PR — tests passed, docs regenerated, version bumped to v${VERSION}." \
178
+ 2>/dev/null || true
179
+ gh pr merge "release/v${VERSION}" --merge --admin --delete-branch
180
+
181
+ # ── Publish to PyPI (Trusted Publisher / OIDC — no token needed) ─────────
182
+ publish-pypi:
183
+ name: Publish to PyPI
184
+ runs-on: ubuntu-latest
185
+ needs: release
186
+ permissions:
187
+ id-token: write # required for OIDC trusted publisher
188
+ environment:
189
+ name: pypi
190
+ url: https://pypi.org/project/dash-observe
191
+ steps:
192
+ - name: Download dist
193
+ uses: actions/download-artifact@v4
194
+ with:
195
+ name: dist
196
+ path: dist/
197
+
198
+ - name: Publish to PyPI
199
+ uses: pypa/gh-action-pypi-publish@release/v1
200
+
201
+ # ── Package for Databricks Marketplace ───────────────────────────────────
202
+ publish-databricks:
203
+ name: Package for Databricks Marketplace
204
+ runs-on: ubuntu-latest
205
+ needs: release
206
+ steps:
207
+ - uses: actions/checkout@v4
208
+ with:
209
+ ref: "v${{ needs.release.outputs.version }}"
210
+
211
+ - name: Download dist
212
+ uses: actions/download-artifact@v4
213
+ with:
214
+ name: dist
215
+ path: dist/
216
+
217
+ - name: Build Marketplace bundle
218
+ env:
219
+ VERSION: ${{ needs.release.outputs.version }}
220
+ run: |
221
+ mkdir -p marketplace-bundle/files
222
+
223
+ cp dist/*.whl marketplace-bundle/files/
224
+
225
+ cat > marketplace-bundle/files/DashObserve_Quickstart.py << NBEOF
226
+ # Databricks notebook source
227
+ # MAGIC %md
228
+ # MAGIC # DashObserve v${VERSION} — Data Observability
229
+ # MAGIC Install and launch the interactive UI.
230
+
231
+ # COMMAND ----------
232
+ # MAGIC %pip install dash-observe==${VERSION}
233
+
234
+ # COMMAND ----------
235
+ dbutils.library.restartPython()
236
+
237
+ # COMMAND ----------
238
+ import dashobserve
239
+ dashobserve.launch()
240
+ NBEOF
241
+
242
+ cat > marketplace-bundle/listing.json << LEOF
243
+ {
244
+ "listing_name": "DashObserve — Data Observability",
245
+ "version": "${VERSION}",
246
+ "short_description": "Freshness, volume, and schema-change monitoring for Databricks tables",
247
+ "long_description": "DashObserve provides an ipywidgets UI inside Databricks notebooks to monitor table freshness, row-count volume, and schema changes, persisting results to a Delta history table. No external service to deploy.",
248
+ "categories": ["Data Engineering", "Developer Tools"],
249
+ "tags": ["data-observability", "databricks", "unity-catalog", "pyspark"],
250
+ "provider": "dash-libs",
251
+ "documentation_url": "https://github.com/dash-libs/dash-observe",
252
+ "source_url": "https://github.com/dash-libs/dash-observe",
253
+ "pypi_package": "dash-observe==${VERSION}"
254
+ }
255
+ LEOF
256
+
257
+ cd marketplace-bundle && zip -r ../dashobserve-marketplace-${VERSION}.zip .
258
+ echo "Bundle created: dashobserve-marketplace-${VERSION}.zip"
259
+
260
+ - name: Upload Marketplace bundle artifact
261
+ uses: actions/upload-artifact@v4
262
+ with:
263
+ name: marketplace-bundle
264
+ path: dashobserve-marketplace-*.zip
265
+ retention-days: 90
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .coverage
7
+ coverage.xml
8
+ .pytest_cache/
9
+ .ruff_cache/
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ and this project uses patch-only [semantic versioning](https://semver.org/)
6
+ bumps via the automated release workflow.
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - Initial release: freshness, volume, and schema-change monitoring
12
+ (`MonitorConfig`, `run_monitors`, `MonitorReport`)
13
+ - Notebook UI built on `dashui` (`dashobserve.launch()`)
@@ -0,0 +1,46 @@
1
+ # CLAUDE.md — dash-observe
2
+
3
+ Part of the **Dashlibs** suite. Full suite-wide context (release process
4
+ gotchas, PR/review norms, repo list, rationale for past decisions):
5
+ see `~/dashlibs/CLAUDE.md`.
6
+
7
+ ## Purpose
8
+ Data observability for Databricks — Monte Carlo-style monitoring without leaving the
9
+ notebook. v1 covers freshness, volume, and schema-change monitoring. monitors.py=pure-logic
10
+ check functions (no Spark, fully unit-testable), runner.py=MonitorConfig/run_monitors()/Delta
11
+ history persistence (the Spark-touching glue).
12
+
13
+ ## Structure
14
+ - `/ui.py` — ipywidgets UI (built on `dashui`), `launch()` entrypoint
15
+ - `/monitors.py` — `check_freshness`, `check_volume`, `check_schema`/`diff_schema`, `MonitorResult`
16
+ - `/runner.py` — `MonitorConfig`, `MonitorReport`, `run_monitors()`
17
+ - `tests/` — pytest, no Spark dependency for unit tests
18
+
19
+ ## Key Design Rules
20
+ - Never import Spark at module level — always inside functions
21
+ - Keep check logic in `monitors.py` pure (plain Python in, plain Python out) so it's testable
22
+ without Spark; `runner.py` is the only place that touches `SparkSession`/Delta
23
+ - Volume baselines and schema snapshots are read from the *same* history Delta table that
24
+ `run_monitors()` writes to (`mode("append")`) — there's no separate baseline store
25
+ - UI widgets come from the shared `dashui` package (PyPI: `dash-uis`) — don't reimplement
26
+ headers/source pickers/output panels locally
27
+ - `launch()` is always the public entrypoint for business users
28
+
29
+ ## v1 scope vs. planned direction
30
+ v1 deliberately covers only freshness, volume, and schema-change — the most common pipeline
31
+ failure modes, fast to ship correctly with pure-Python, fully-tested check logic.
32
+
33
+ **Explicitly requested next direction (not yet built):** go deep on Unity Catalog / Databricks
34
+ SDK integration rather than staying with basic `spark.table()` calls — system tables,
35
+ `information_schema`, table properties/tags, audit logs, and especially **lineage** (UC's
36
+ native lineage APIs, possibly cross-referencing dash-relate's ontology). Be comprehensive here,
37
+ not conservative — this was explicit user direction, not a "nice to have."
38
+
39
+ ## CI
40
+ - `ci.yml` — PR gate: lint → test → build
41
+ - `daily.yml` — 06:00 UTC: tests + .health/log.txt commit
42
+ - `release.yml`— Monday 09:00 UTC: patch bump on a release branch → PR → GitHub release → PyPI
43
+
44
+ This repo has no branch protection configured yet, but the same norm applies regardless:
45
+ **every change goes through a PR with real human review before merging — no exceptions.**
46
+ Don't self-merge, don't push directly to `main`. Prefer small, targeted commits/PRs.
@@ -0,0 +1,51 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic
9
+ status, nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to a positive environment:
15
+
16
+ - Demonstrating empathy and kindness toward other people
17
+ - Being respectful of differing opinions, viewpoints, and experiences
18
+ - Giving and gracefully accepting constructive feedback
19
+ - Accepting responsibility and apologizing to those affected by our mistakes
20
+
21
+ Examples of unacceptable behavior:
22
+
23
+ - The use of sexualized language or imagery, and sexual attention of any kind
24
+ - Trolling, insulting or derogatory comments, and personal or political attacks
25
+ - Public or private harassment
26
+ - Publishing others' private information without explicit permission
27
+
28
+ ## Enforcement Responsibilities
29
+
30
+ Maintainers are responsible for clarifying and enforcing standards of
31
+ acceptable behavior and will take appropriate, fair corrective action in
32
+ response to any behavior they deem inappropriate.
33
+
34
+ ## Scope
35
+
36
+ This Code of Conduct applies within all community spaces, and also applies
37
+ when an individual is officially representing the community in public spaces.
38
+
39
+ ## Enforcement
40
+
41
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
42
+ reported to the maintainers at the contact listed in `pyproject.toml`. All
43
+ complaints will be reviewed and investigated promptly and fairly.
44
+
45
+ ## Attribution
46
+
47
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
48
+ version 2.1, available at
49
+ https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
50
+
51
+ [homepage]: https://www.contributor-covenant.org
@@ -0,0 +1,48 @@
1
+ # Contributing to dash-observe
2
+
3
+ Thanks for considering a contribution. This is a small, focused library — keep
4
+ changes scoped and avoid adding dependencies unless they're essential.
5
+
6
+ ## Development setup
7
+
8
+ ```bash
9
+ git clone https://github.com/dash-libs/dash-observe.git
10
+ cd dash-observe
11
+ python3 -m venv .venv && source .venv/bin/activate
12
+ pip install -e ".[dev]"
13
+ ```
14
+
15
+ ## Running tests
16
+
17
+ ```bash
18
+ pytest tests/ -v
19
+ ```
20
+
21
+ Tests must run without a Spark session or a live Databricks workspace — keep
22
+ any Spark/Databricks-specific code inside functions (never at module level),
23
+ and keep monitor check logic in `monitors.py` pure (plain Python in, plain
24
+ Python out) so it's fully testable without Spark. `runner.py` is the only
25
+ place that should touch `SparkSession`/Delta.
26
+
27
+ ## Linting
28
+
29
+ ```bash
30
+ ruff check dashobserve/
31
+ ```
32
+
33
+ CI runs lint → test (Python 3.9–3.12) → build on every PR; all three must pass.
34
+
35
+ ## Making a change
36
+
37
+ 1. Open an issue first for anything beyond a small fix, so we can agree on
38
+ the approach before you write code.
39
+ 2. Add or update tests for any behavior change.
40
+ 3. Keep `CLAUDE.md` in sync if you change the module structure or a design
41
+ rule documented there.
42
+ 4. Open a PR against `main`. The release workflow handles versioning —
43
+ don't bump the version yourself.
44
+
45
+ ## Reporting bugs / requesting features
46
+
47
+ Use the issue templates in `.github/ISSUE_TEMPLATE/`. For security issues,
48
+ see [SECURITY.md](SECURITY.md) instead of opening a public issue.