view-scheduler 0.4.0__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 (39) hide show
  1. view_scheduler-0.4.0/LICENSE +21 -0
  2. view_scheduler-0.4.0/PKG-INFO +230 -0
  3. view_scheduler-0.4.0/README.md +210 -0
  4. view_scheduler-0.4.0/pyproject.toml +41 -0
  5. view_scheduler-0.4.0/setup.cfg +4 -0
  6. view_scheduler-0.4.0/src/view_scheduler/__init__.py +34 -0
  7. view_scheduler-0.4.0/src/view_scheduler/advice.py +162 -0
  8. view_scheduler-0.4.0/src/view_scheduler/cli.py +71 -0
  9. view_scheduler-0.4.0/src/view_scheduler/client.py +222 -0
  10. view_scheduler-0.4.0/src/view_scheduler/config.py +29 -0
  11. view_scheduler-0.4.0/src/view_scheduler/gate.py +278 -0
  12. view_scheduler-0.4.0/src/view_scheduler/history.py +145 -0
  13. view_scheduler-0.4.0/src/view_scheduler/models.py +36 -0
  14. view_scheduler-0.4.0/src/view_scheduler/obs.py +38 -0
  15. view_scheduler-0.4.0/src/view_scheduler/recommend.py +174 -0
  16. view_scheduler-0.4.0/src/view_scheduler/report.py +159 -0
  17. view_scheduler-0.4.0/src/view_scheduler/run.py +51 -0
  18. view_scheduler-0.4.0/src/view_scheduler/walker.py +140 -0
  19. view_scheduler-0.4.0/src/view_scheduler.egg-info/PKG-INFO +230 -0
  20. view_scheduler-0.4.0/src/view_scheduler.egg-info/SOURCES.txt +37 -0
  21. view_scheduler-0.4.0/src/view_scheduler.egg-info/dependency_links.txt +1 -0
  22. view_scheduler-0.4.0/src/view_scheduler.egg-info/entry_points.txt +2 -0
  23. view_scheduler-0.4.0/src/view_scheduler.egg-info/requires.txt +5 -0
  24. view_scheduler-0.4.0/src/view_scheduler.egg-info/top_level.txt +1 -0
  25. view_scheduler-0.4.0/tests/test_advice.py +111 -0
  26. view_scheduler-0.4.0/tests/test_bundle.py +50 -0
  27. view_scheduler-0.4.0/tests/test_cli.py +101 -0
  28. view_scheduler-0.4.0/tests/test_client.py +83 -0
  29. view_scheduler-0.4.0/tests/test_fixtures.py +30 -0
  30. view_scheduler-0.4.0/tests/test_gate.py +398 -0
  31. view_scheduler-0.4.0/tests/test_history.py +189 -0
  32. view_scheduler-0.4.0/tests/test_oracle.py +149 -0
  33. view_scheduler-0.4.0/tests/test_plan.py +118 -0
  34. view_scheduler-0.4.0/tests/test_real_shapes.py +79 -0
  35. view_scheduler-0.4.0/tests/test_recommend.py +136 -0
  36. view_scheduler-0.4.0/tests/test_report.py +187 -0
  37. view_scheduler-0.4.0/tests/test_run.py +92 -0
  38. view_scheduler-0.4.0/tests/test_walker.py +94 -0
  39. view_scheduler-0.4.0/tests/test_write_slot.py +88 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kiran B
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: view-scheduler
3
+ Version: 0.4.0
4
+ Summary: Recommend when a Unity Catalog view can be materialized with every dependency already refreshed
5
+ Author: Kiran B
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/kiranb-1508/view-scheduler-docs
8
+ Keywords: databricks,unity-catalog,delta,scheduling
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: databricks-sdk>=0.30
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=7.0; extra == "dev"
18
+ Requires-Dist: build>=1.0; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # View Materialization Scheduler
22
+
23
+ Two questions, two tools.
24
+
25
+ - **When** should this view run? → `advise`. It reads history and suggests a time.
26
+ - **Can** it run right now? → `can_refresh`, the gate. It checks today's state.
27
+
28
+ ```
29
+ $ view-scheduler sales_gold.customer_360
30
+
31
+ VIEW sales_gold.customer_360
32
+ 8 dependencies · last 30 days · times Asia/Kolkata
33
+
34
+ SCHEDULE AT 06:25 safe — every job had finished, on all 30 days
35
+ EARLIER 05:45 one job had not finished yet, on 1 of 30 days
36
+ ```
37
+
38
+ ## Read this before you rely on it
39
+
40
+ **The recommendation is advisory.** It says when a view *usually* can run; it
41
+ does not check today. If a dependency runs later than it ever has, the job
42
+ starts anyway and publishes stale data, silently. Pair it with the gate —
43
+ `can_refresh` — which checks every dependency's actual last write before the
44
+ refresh runs. Set the schedule from history; guard the run with the gate.
45
+
46
+ **The arithmetic is still being validated.** A 30-day fixture rig
47
+ (`rig/`) is running now to check the day-spread logic against real multi-day
48
+ commit history. Until that completes, treat output as indicative. Hence `0.4.0`.
49
+
50
+ ## Install
51
+
52
+ ### Databricks (recommended)
53
+
54
+ Ship the wheel through a Unity Catalog Volume, so access is governed by UC
55
+ grants rather than by GitHub accounts.
56
+
57
+ Once, to create the shared location:
58
+
59
+ ```sql
60
+ CREATE VOLUME IF NOT EXISTS <catalog>.<schema>.libs;
61
+ GRANT READ VOLUME ON VOLUME <catalog>.<schema>.libs TO `<group>`;
62
+ ```
63
+
64
+ Build and upload a release:
65
+
66
+ ```bash
67
+ python -m build --wheel
68
+ databricks fs cp dist/view_scheduler-0.4.0-py3-none-any.whl \
69
+ dbfs:/Volumes/<catalog>/<schema>/libs/
70
+ ```
71
+
72
+ Keep old versions in the volume so running jobs can stay pinned.
73
+
74
+ Then, in a notebook:
75
+
76
+ ```python
77
+ %pip install /Volumes/<catalog>/<schema>/libs/view_scheduler-0.4.0-py3-none-any.whl
78
+ dbutils.library.restartPython()
79
+ ```
80
+
81
+ Or as a job library, including from a DAB:
82
+
83
+ ```yaml
84
+ tasks:
85
+ - task_key: recommend
86
+ libraries:
87
+ - whl: /Volumes/<catalog>/<schema>/libs/view_scheduler-0.4.0-py3-none-any.whl
88
+ ```
89
+
90
+ ### From source
91
+
92
+ For working on the package itself:
93
+
94
+ ```bash
95
+ pip install -e ".[dev]" # a checkout
96
+ pip install git+https://github.com/kiranb-1508/view-scheduler-docs.git
97
+ ```
98
+
99
+ ## Use
100
+
101
+ As a command:
102
+
103
+ ```bash
104
+ export WAREHOUSE_ID=<sql warehouse id>
105
+ view-scheduler catalog.schema.my_view
106
+ view-scheduler catalog.schema.my_view --days 60 --buffer 30 --quiet
107
+ ```
108
+
109
+ As a library:
110
+
111
+ ```python
112
+ from view_scheduler import advise
113
+
114
+ advice = advise("sales.gold.customer_360") # WAREHOUSE_ID from the env
115
+ advice.safe_time # datetime.time(6, 25) or None
116
+ advice.earlier_time # datetime.time(5, 45) or None
117
+ advice.next_day # True when the (+1 day) marker applies
118
+ advice.dependencies # one row per upstream job
119
+ advice.to_dict() # JSON, for a job log
120
+ print(advice) # the report
121
+ ```
122
+
123
+ `advise` prints the report and returns an `Advice`. Nothing is dropped
124
+ silently — anything unreadable comes back in `advice.unreadable` with a reason.
125
+
126
+ `analyze` is the low-level call. It returns a tuple
127
+ `(text, histories, skipped)` for code that needs the raw per-table history.
128
+
129
+ Four settings: `days_history` (30), `buffer_min` (15), `timezone`
130
+ (`Asia/Kolkata`), and `fresh_hours` (24) — the gate's window. There is a
131
+ standing rule to ask before adding a fifth.
132
+
133
+ ### Gate: is my view ready to refresh?
134
+
135
+ Before you materialize a view, ask whether its inputs are actually fresh. The
136
+ gate walks every dependency and checks that its last real write is inside a
137
+ rolling window (24 hours by default).
138
+
139
+ ```python
140
+ from view_scheduler import can_refresh
141
+
142
+ result = can_refresh("sales.gold.v_orders") # WAREHOUSE_ID from $env
143
+ if result.ready:
144
+ ...run the refresh...
145
+ else:
146
+ print(result) # says exactly what to fix
147
+ ```
148
+
149
+ A list works the same way. You get one verdict per view, and one overall:
150
+
151
+ ```python
152
+ result = can_refresh(["sales.gold.v_orders", "sales.gold.v_revenue"])
153
+ print(result.ready) # True only when every view is ready
154
+ print(result.blockers()) # ('sales.silver.daily_sales',)
155
+ print(result.to_dict()) # JSON, for a job log
156
+ ```
157
+
158
+ ```
159
+ GATE BLOCKED 2 views, window 24h, times Asia/Kolkata
160
+
161
+ READY sales.gold.v_orders
162
+ fresh sales.silver.daily_orders (MANAGED) last refresh 2026-10-07 04:00 IST (11h 48m ago)
163
+
164
+ BLOCKED sales.gold.v_revenue
165
+ fresh sales.silver.orders (MANAGED) last refresh 2026-10-07 04:00 IST (11h 48m ago)
166
+ stale sales.silver.daily_sales (MANAGED) last refresh 2026-10-05 04:00 IST (2d 11h ago) — older than the 24h window
167
+
168
+ To unblock, refresh:
169
+ sales.silver.daily_sales (needed by sales.gold.v_revenue)
170
+ ```
171
+
172
+ The gate decides; it never refreshes. It is read-only, like `analyze`. The rules
173
+ are the whole table:
174
+
175
+ | Case | Verdict |
176
+ |---|---|
177
+ | Wrote inside the window | fresh |
178
+ | Wrote before the window | stale — **blocks** |
179
+ | No qualifying write in its history | **blocks** |
180
+ | Cannot be read (missing, no permission) | **blocks** — the gate fails closed |
181
+ | Streaming table | checked from its history, like any table — one on a schedule is stale between runs |
182
+ | Materialized view | read from its pipeline event log |
183
+ | Function dependency (a UDF) | a note, not a blocker |
184
+
185
+ `Workspace(warehouse_id=...)` and `Config.fresh_hours` are optional. Without a
186
+ `Workspace`, the gate reads `WAREHOUSE_ID` from the environment, so the
187
+ one-liner above is enough inside a job.
188
+
189
+ **Permissions.** The caller needs `SELECT` on each dependency (for
190
+ `DESCRIBE HISTORY`) and `USE` on the catalogs and schemas involved. Materialized
191
+ views are read through their pipeline event log, which by default only the
192
+ pipeline's run-as user can read. Without that access the gate **blocks** the MV:
193
+ it reports `history unreadable (permission)`. The call itself does not raise.
194
+
195
+ ## Design docs
196
+
197
+ | File | What it is |
198
+ |---|---|
199
+ | [docs/01-plan.html](docs/01-plan.html) | The plan. Start here — output shape, what's built, what was deliberately cut |
200
+ | [docs/02-scheduler-design.html](docs/02-scheduler-design.html) | Scheduler design: architecture, code flow, four steps, worked example |
201
+ | [docs/03-impact-design.html](docs/03-impact-design.html) | Impact analysis: given a table, what depends on it. Separate tool, separate data source |
202
+ | [docs/04-library-flow.html](docs/04-library-flow.html) | Visual end-to-end flow: input, functions, branches, recommendation, and report |
203
+ | [docs/05-interactive-guide.html](docs/05-interactive-guide.html) | Interactive guide: clickable flow, a gate simulator, the design decisions, and the open findings |
204
+ | [docs/06-code-walkthrough.html](docs/06-code-walkthrough.html) | Code walkthrough: the module map, call traces, a class tour, and a searchable index of all 66 functions |
205
+ | [rig/README.md](rig/README.md) | The 30-day fixture rig and its checkpoints |
206
+ | [GENIE-PROMPT.md](GENIE-PROMPT.md) | Build prompt for Databricks Genie Code, phased with verification gates |
207
+
208
+ Section 08 of the scheduler design lists what the docs describe but nobody
209
+ built. The runtime gate is no longer on that list — it ships as `can_refresh`.
210
+
211
+ ## The two things most likely to bite
212
+
213
+ 1. **Operation class comes from `operationParameters.mode`, not the operation
214
+ name.** The commonest Delta operation name is plain `WRITE`; only its mode
215
+ says whether it replaced or appended. Classify by name and most tables read
216
+ as dormant — a confident wrong answer, not an error.
217
+ 2. **`DESCRIBE HISTORY` carries no partition values.** Verified against the docs.
218
+ Any design that attributes commits to business dates via partitions is built
219
+ on an input that does not exist.
220
+
221
+ ## Development
222
+
223
+ ```bash
224
+ pip install -e ".[dev]"
225
+ pytest
226
+ ```
227
+
228
+ 176 tests, all offline — no Spark session, no network, no credentials.
229
+ `tools/` holds diagnostic scripts that run against a live workspace;
230
+ `rig/` builds and checks the 30-day fixture. Neither ships in the wheel.
@@ -0,0 +1,210 @@
1
+ # View Materialization Scheduler
2
+
3
+ Two questions, two tools.
4
+
5
+ - **When** should this view run? → `advise`. It reads history and suggests a time.
6
+ - **Can** it run right now? → `can_refresh`, the gate. It checks today's state.
7
+
8
+ ```
9
+ $ view-scheduler sales_gold.customer_360
10
+
11
+ VIEW sales_gold.customer_360
12
+ 8 dependencies · last 30 days · times Asia/Kolkata
13
+
14
+ SCHEDULE AT 06:25 safe — every job had finished, on all 30 days
15
+ EARLIER 05:45 one job had not finished yet, on 1 of 30 days
16
+ ```
17
+
18
+ ## Read this before you rely on it
19
+
20
+ **The recommendation is advisory.** It says when a view *usually* can run; it
21
+ does not check today. If a dependency runs later than it ever has, the job
22
+ starts anyway and publishes stale data, silently. Pair it with the gate —
23
+ `can_refresh` — which checks every dependency's actual last write before the
24
+ refresh runs. Set the schedule from history; guard the run with the gate.
25
+
26
+ **The arithmetic is still being validated.** A 30-day fixture rig
27
+ (`rig/`) is running now to check the day-spread logic against real multi-day
28
+ commit history. Until that completes, treat output as indicative. Hence `0.4.0`.
29
+
30
+ ## Install
31
+
32
+ ### Databricks (recommended)
33
+
34
+ Ship the wheel through a Unity Catalog Volume, so access is governed by UC
35
+ grants rather than by GitHub accounts.
36
+
37
+ Once, to create the shared location:
38
+
39
+ ```sql
40
+ CREATE VOLUME IF NOT EXISTS <catalog>.<schema>.libs;
41
+ GRANT READ VOLUME ON VOLUME <catalog>.<schema>.libs TO `<group>`;
42
+ ```
43
+
44
+ Build and upload a release:
45
+
46
+ ```bash
47
+ python -m build --wheel
48
+ databricks fs cp dist/view_scheduler-0.4.0-py3-none-any.whl \
49
+ dbfs:/Volumes/<catalog>/<schema>/libs/
50
+ ```
51
+
52
+ Keep old versions in the volume so running jobs can stay pinned.
53
+
54
+ Then, in a notebook:
55
+
56
+ ```python
57
+ %pip install /Volumes/<catalog>/<schema>/libs/view_scheduler-0.4.0-py3-none-any.whl
58
+ dbutils.library.restartPython()
59
+ ```
60
+
61
+ Or as a job library, including from a DAB:
62
+
63
+ ```yaml
64
+ tasks:
65
+ - task_key: recommend
66
+ libraries:
67
+ - whl: /Volumes/<catalog>/<schema>/libs/view_scheduler-0.4.0-py3-none-any.whl
68
+ ```
69
+
70
+ ### From source
71
+
72
+ For working on the package itself:
73
+
74
+ ```bash
75
+ pip install -e ".[dev]" # a checkout
76
+ pip install git+https://github.com/kiranb-1508/view-scheduler-docs.git
77
+ ```
78
+
79
+ ## Use
80
+
81
+ As a command:
82
+
83
+ ```bash
84
+ export WAREHOUSE_ID=<sql warehouse id>
85
+ view-scheduler catalog.schema.my_view
86
+ view-scheduler catalog.schema.my_view --days 60 --buffer 30 --quiet
87
+ ```
88
+
89
+ As a library:
90
+
91
+ ```python
92
+ from view_scheduler import advise
93
+
94
+ advice = advise("sales.gold.customer_360") # WAREHOUSE_ID from the env
95
+ advice.safe_time # datetime.time(6, 25) or None
96
+ advice.earlier_time # datetime.time(5, 45) or None
97
+ advice.next_day # True when the (+1 day) marker applies
98
+ advice.dependencies # one row per upstream job
99
+ advice.to_dict() # JSON, for a job log
100
+ print(advice) # the report
101
+ ```
102
+
103
+ `advise` prints the report and returns an `Advice`. Nothing is dropped
104
+ silently — anything unreadable comes back in `advice.unreadable` with a reason.
105
+
106
+ `analyze` is the low-level call. It returns a tuple
107
+ `(text, histories, skipped)` for code that needs the raw per-table history.
108
+
109
+ Four settings: `days_history` (30), `buffer_min` (15), `timezone`
110
+ (`Asia/Kolkata`), and `fresh_hours` (24) — the gate's window. There is a
111
+ standing rule to ask before adding a fifth.
112
+
113
+ ### Gate: is my view ready to refresh?
114
+
115
+ Before you materialize a view, ask whether its inputs are actually fresh. The
116
+ gate walks every dependency and checks that its last real write is inside a
117
+ rolling window (24 hours by default).
118
+
119
+ ```python
120
+ from view_scheduler import can_refresh
121
+
122
+ result = can_refresh("sales.gold.v_orders") # WAREHOUSE_ID from $env
123
+ if result.ready:
124
+ ...run the refresh...
125
+ else:
126
+ print(result) # says exactly what to fix
127
+ ```
128
+
129
+ A list works the same way. You get one verdict per view, and one overall:
130
+
131
+ ```python
132
+ result = can_refresh(["sales.gold.v_orders", "sales.gold.v_revenue"])
133
+ print(result.ready) # True only when every view is ready
134
+ print(result.blockers()) # ('sales.silver.daily_sales',)
135
+ print(result.to_dict()) # JSON, for a job log
136
+ ```
137
+
138
+ ```
139
+ GATE BLOCKED 2 views, window 24h, times Asia/Kolkata
140
+
141
+ READY sales.gold.v_orders
142
+ fresh sales.silver.daily_orders (MANAGED) last refresh 2026-10-07 04:00 IST (11h 48m ago)
143
+
144
+ BLOCKED sales.gold.v_revenue
145
+ fresh sales.silver.orders (MANAGED) last refresh 2026-10-07 04:00 IST (11h 48m ago)
146
+ stale sales.silver.daily_sales (MANAGED) last refresh 2026-10-05 04:00 IST (2d 11h ago) — older than the 24h window
147
+
148
+ To unblock, refresh:
149
+ sales.silver.daily_sales (needed by sales.gold.v_revenue)
150
+ ```
151
+
152
+ The gate decides; it never refreshes. It is read-only, like `analyze`. The rules
153
+ are the whole table:
154
+
155
+ | Case | Verdict |
156
+ |---|---|
157
+ | Wrote inside the window | fresh |
158
+ | Wrote before the window | stale — **blocks** |
159
+ | No qualifying write in its history | **blocks** |
160
+ | Cannot be read (missing, no permission) | **blocks** — the gate fails closed |
161
+ | Streaming table | checked from its history, like any table — one on a schedule is stale between runs |
162
+ | Materialized view | read from its pipeline event log |
163
+ | Function dependency (a UDF) | a note, not a blocker |
164
+
165
+ `Workspace(warehouse_id=...)` and `Config.fresh_hours` are optional. Without a
166
+ `Workspace`, the gate reads `WAREHOUSE_ID` from the environment, so the
167
+ one-liner above is enough inside a job.
168
+
169
+ **Permissions.** The caller needs `SELECT` on each dependency (for
170
+ `DESCRIBE HISTORY`) and `USE` on the catalogs and schemas involved. Materialized
171
+ views are read through their pipeline event log, which by default only the
172
+ pipeline's run-as user can read. Without that access the gate **blocks** the MV:
173
+ it reports `history unreadable (permission)`. The call itself does not raise.
174
+
175
+ ## Design docs
176
+
177
+ | File | What it is |
178
+ |---|---|
179
+ | [docs/01-plan.html](docs/01-plan.html) | The plan. Start here — output shape, what's built, what was deliberately cut |
180
+ | [docs/02-scheduler-design.html](docs/02-scheduler-design.html) | Scheduler design: architecture, code flow, four steps, worked example |
181
+ | [docs/03-impact-design.html](docs/03-impact-design.html) | Impact analysis: given a table, what depends on it. Separate tool, separate data source |
182
+ | [docs/04-library-flow.html](docs/04-library-flow.html) | Visual end-to-end flow: input, functions, branches, recommendation, and report |
183
+ | [docs/05-interactive-guide.html](docs/05-interactive-guide.html) | Interactive guide: clickable flow, a gate simulator, the design decisions, and the open findings |
184
+ | [docs/06-code-walkthrough.html](docs/06-code-walkthrough.html) | Code walkthrough: the module map, call traces, a class tour, and a searchable index of all 66 functions |
185
+ | [rig/README.md](rig/README.md) | The 30-day fixture rig and its checkpoints |
186
+ | [GENIE-PROMPT.md](GENIE-PROMPT.md) | Build prompt for Databricks Genie Code, phased with verification gates |
187
+
188
+ Section 08 of the scheduler design lists what the docs describe but nobody
189
+ built. The runtime gate is no longer on that list — it ships as `can_refresh`.
190
+
191
+ ## The two things most likely to bite
192
+
193
+ 1. **Operation class comes from `operationParameters.mode`, not the operation
194
+ name.** The commonest Delta operation name is plain `WRITE`; only its mode
195
+ says whether it replaced or appended. Classify by name and most tables read
196
+ as dormant — a confident wrong answer, not an error.
197
+ 2. **`DESCRIBE HISTORY` carries no partition values.** Verified against the docs.
198
+ Any design that attributes commits to business dates via partitions is built
199
+ on an input that does not exist.
200
+
201
+ ## Development
202
+
203
+ ```bash
204
+ pip install -e ".[dev]"
205
+ pytest
206
+ ```
207
+
208
+ 176 tests, all offline — no Spark session, no network, no credentials.
209
+ `tools/` holds diagnostic scripts that run against a live workspace;
210
+ `rig/` builds and checks the 30-day fixture. Neither ships in the wheel.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "view-scheduler"
7
+ version = "0.4.0"
8
+ description = "Recommend when a Unity Catalog view can be materialized with every dependency already refreshed"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{ name = "Kiran B" }]
13
+ keywords = ["databricks", "unity-catalog", "delta", "scheduling"]
14
+
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ ]
20
+
21
+ dependencies = [
22
+ "databricks-sdk>=0.30",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/kiranb-1508/view-scheduler-docs"
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pytest>=7.0",
31
+ "build>=1.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ view-scheduler = "view_scheduler.cli:main"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ # src layout: only view_scheduler ships. rig/, tests/ and tools/ are repo-only
41
+ # and stay out of the wheel — `unzip -l dist/*.whl` is the check.
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ """View materialization scheduler.
2
+
3
+ Two tools for a Unity Catalog view:
4
+
5
+ * `advise` — suggest the time each day the view can run with every upstream
6
+ job already finished. Computed from history. ADVISORY.
7
+ * `can_refresh` — a runtime GATE. Right now, has every dependency of this
8
+ view refreshed recently enough? READY or BLOCKED.
9
+
10
+ READ ONLY. This package never writes, alters, optimizes, vacuums or drops a
11
+ user table. Metadata and history reads only.
12
+
13
+ `advise` predicts. `can_refresh` checks. Use the first to set the schedule,
14
+ the second to guard the run. See README.md.
15
+ """
16
+
17
+ from .advice import Advice, DependencyRun, advise
18
+ from .client import Workspace
19
+ from .config import Config
20
+ from .gate import Dependency, GateResult, ViewResult, can_refresh
21
+ from .run import analyze
22
+
23
+ __all__ = [
24
+ "advise",
25
+ "Advice",
26
+ "analyze",
27
+ "can_refresh",
28
+ "Config",
29
+ "Dependency",
30
+ "DependencyRun",
31
+ "GateResult",
32
+ "ViewResult",
33
+ "Workspace",
34
+ ]