cassis-cli 2.1.0__tar.gz → 2.2.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.
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/PKG-INFO +7 -3
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/README.md +6 -2
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/api.py +65 -1
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/issues.py +14 -5
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/schema.py +97 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/pyproject.toml +1 -1
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/LICENSE +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/NOTICE +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/__init__.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/common.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/eval.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/guide.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/main.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/ontology.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/ontology_design_guide.md +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/projects.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/schema_plan.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/status.py +0 -0
- {cassis_cli-2.1.0 → cassis_cli-2.2.0}/cassis_cli/verify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cassis-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Validate, test and evaluate your Cassis ontology from your terminal, then publish it
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -35,10 +35,10 @@ Validate, test and evaluate your ontology from your terminal, then publish it. T
|
|
|
35
35
|
- `cassis ontology test` runs individual questions through the text-to-SQL agent using your local ontology files, so you can check that a change actually works (e.g. a new column gets picked) — where `eval run` only checks for regressions on existing eval cases.
|
|
36
36
|
- `cassis eval add-case` adds a gold question/SQL case to the project's eval suite — after fixing an ontology issue, add the question users were failing on so `eval run` guards it from regressing.
|
|
37
37
|
- `cassis eval list-cases` and `cassis eval delete-case` maintain the suite: list the current cases with their ids, and prune one that is stale or wrong (e.g. its gold SQL encodes a definition the ontology has since changed).
|
|
38
|
-
- `cassis schema plan <ddl>` (or `--warehouse` on a project connected to a warehouse) previews what a schema update would change before anything is applied: the source schema diff, the ontology changes Cassis will make (every change on a table placed in the ontology, with everything a drop takes with it) and warnings, terraform-style. `cassis schema apply <ddl>` (or `--plan <id>`) writes the resulting ontology files into the local checkout, app untouched, for review with `git diff`. `cassis schema push <ddl> [--publish]` pushes the new schema and the local ontology to the app (`--yes` in CI). The file speaks only for the schemas it contains — pass `--complete` when it is the project's complete source schema so schemas absent from it are treated as dropped. With `--warehouse` the server introspects the connected warehouse instead of parsing a file; the plan is always whole-source.
|
|
38
|
+
- `cassis schema plan <ddl>` (or `--warehouse` on a project connected to a warehouse) previews what a schema update would change before anything is applied: the source schema diff, the ontology changes Cassis will make (every change on a table placed in the ontology, with everything a drop takes with it) and warnings, terraform-style. `cassis schema apply <ddl>` (or `--plan <id>`) writes the resulting ontology files into the local checkout, app untouched, for review with `git diff`. `cassis schema push <ddl> [--publish]` pushes the new schema and the local ontology to the app (`--yes` in CI). The file speaks only for the schemas it contains — pass `--complete` when it is the project's complete source schema so schemas absent from it are treated as dropped. With `--warehouse` the server introspects the connected warehouse instead of parsing a file; the plan is always whole-source. `cassis schema plan <ddl> --dry-run` is the prepare-ahead variant: the plan is computed synchronously and nothing is kept in Cassis (no plan to apply or resume, the current plan untouched), so a dbt model or migration still in a PR can be planned against safely; `--write-checkout` writes the ontology files it would produce into the checkout, to commit alongside the schema change.
|
|
39
39
|
- `cassis projects list` lists the projects your API key can reach — id (what `--project` and `CASSIS_PROJECT_ID` take), name, published ontology version, and data-source dialect — so a pipeline or agent can discover the project id from the terminal instead of fishing it out of a webapp URL.
|
|
40
40
|
- `cassis status` shows the project's published version (number, label, git commit), whether unpublished changes await publication, the git-sync binding, a schema plan waiting to be applied, and how your local git HEAD relates to the published commit (in sync / N commits ahead / diverged). `cassis status --watch` polls until the published commit matches your local HEAD — e.g. right after merging a PR whose CI publishes the ontology — instead of watching the GitHub Actions tab.
|
|
41
|
-
- `cassis issues` triages the issues Cassis raised on the project — what it found wrong while answering questions (an ontology gap, missing data) — without leaving the checkout: `issues list` (filterable by status, impact and
|
|
41
|
+
- `cassis issues` triages the issues Cassis raised on the project — what it found wrong while answering questions (an ontology gap, missing data) — without leaving the checkout: `issues list` (filterable by status, impact, cause and ontology domain, and showing each issue's domain so you can work through one domain at a time), `issues show <id>` for the diagnosis, suggested action and the occurrences behind it, `issues evidence <id> <occurrence-id>` for what the agent actually saw, and `issues resolve` / `dismiss` / `reopen` once you've acted on it.
|
|
42
42
|
- `cassis verify` runs the full local gate in one verb — `ontology fmt --check`, `ontology check`, `eval run` — stopping at the first failure. One command in a checkout ("is this change safe to merge?"), one job in CI. `--no-eval` skips the eval suite.
|
|
43
43
|
|
|
44
44
|
## Install
|
|
@@ -140,6 +140,7 @@ cassis schema plan schema.sql --complete
|
|
|
140
140
|
cassis schema apply schema.sql --complete # writes cassis/ locally
|
|
141
141
|
cassis schema push schema.sql --complete --yes # schema + ontology to the app
|
|
142
142
|
cassis schema plan --warehouse # warehouse-connected projects: introspect instead
|
|
143
|
+
cassis schema plan future.sql --dry-run --write-checkout # plan a not-yet-deployed DDL, keep nothing server-side
|
|
143
144
|
|
|
144
145
|
# List the projects the API key can reach (id, name, published version, dialect):
|
|
145
146
|
cassis projects list
|
|
@@ -153,6 +154,9 @@ cassis issues analyze
|
|
|
153
154
|
cassis issues list --status open
|
|
154
155
|
cassis issues show 019f0000-0000-7000-8000-0000000000e1
|
|
155
156
|
|
|
157
|
+
# Work one ontology domain at a time (nested domains included):
|
|
158
|
+
cassis issues list --domain sales
|
|
159
|
+
|
|
156
160
|
# Read what the agent saw for one occurrence (ids from `issues show`):
|
|
157
161
|
cassis issues evidence 019f0000-0000-7000-8000-0000000000e1 019f0000-0000-7000-8000-0000000000c1
|
|
158
162
|
|
|
@@ -13,10 +13,10 @@ Validate, test and evaluate your ontology from your terminal, then publish it. T
|
|
|
13
13
|
- `cassis ontology test` runs individual questions through the text-to-SQL agent using your local ontology files, so you can check that a change actually works (e.g. a new column gets picked) — where `eval run` only checks for regressions on existing eval cases.
|
|
14
14
|
- `cassis eval add-case` adds a gold question/SQL case to the project's eval suite — after fixing an ontology issue, add the question users were failing on so `eval run` guards it from regressing.
|
|
15
15
|
- `cassis eval list-cases` and `cassis eval delete-case` maintain the suite: list the current cases with their ids, and prune one that is stale or wrong (e.g. its gold SQL encodes a definition the ontology has since changed).
|
|
16
|
-
- `cassis schema plan <ddl>` (or `--warehouse` on a project connected to a warehouse) previews what a schema update would change before anything is applied: the source schema diff, the ontology changes Cassis will make (every change on a table placed in the ontology, with everything a drop takes with it) and warnings, terraform-style. `cassis schema apply <ddl>` (or `--plan <id>`) writes the resulting ontology files into the local checkout, app untouched, for review with `git diff`. `cassis schema push <ddl> [--publish]` pushes the new schema and the local ontology to the app (`--yes` in CI). The file speaks only for the schemas it contains — pass `--complete` when it is the project's complete source schema so schemas absent from it are treated as dropped. With `--warehouse` the server introspects the connected warehouse instead of parsing a file; the plan is always whole-source.
|
|
16
|
+
- `cassis schema plan <ddl>` (or `--warehouse` on a project connected to a warehouse) previews what a schema update would change before anything is applied: the source schema diff, the ontology changes Cassis will make (every change on a table placed in the ontology, with everything a drop takes with it) and warnings, terraform-style. `cassis schema apply <ddl>` (or `--plan <id>`) writes the resulting ontology files into the local checkout, app untouched, for review with `git diff`. `cassis schema push <ddl> [--publish]` pushes the new schema and the local ontology to the app (`--yes` in CI). The file speaks only for the schemas it contains — pass `--complete` when it is the project's complete source schema so schemas absent from it are treated as dropped. With `--warehouse` the server introspects the connected warehouse instead of parsing a file; the plan is always whole-source. `cassis schema plan <ddl> --dry-run` is the prepare-ahead variant: the plan is computed synchronously and nothing is kept in Cassis (no plan to apply or resume, the current plan untouched), so a dbt model or migration still in a PR can be planned against safely; `--write-checkout` writes the ontology files it would produce into the checkout, to commit alongside the schema change.
|
|
17
17
|
- `cassis projects list` lists the projects your API key can reach — id (what `--project` and `CASSIS_PROJECT_ID` take), name, published ontology version, and data-source dialect — so a pipeline or agent can discover the project id from the terminal instead of fishing it out of a webapp URL.
|
|
18
18
|
- `cassis status` shows the project's published version (number, label, git commit), whether unpublished changes await publication, the git-sync binding, a schema plan waiting to be applied, and how your local git HEAD relates to the published commit (in sync / N commits ahead / diverged). `cassis status --watch` polls until the published commit matches your local HEAD — e.g. right after merging a PR whose CI publishes the ontology — instead of watching the GitHub Actions tab.
|
|
19
|
-
- `cassis issues` triages the issues Cassis raised on the project — what it found wrong while answering questions (an ontology gap, missing data) — without leaving the checkout: `issues list` (filterable by status, impact and
|
|
19
|
+
- `cassis issues` triages the issues Cassis raised on the project — what it found wrong while answering questions (an ontology gap, missing data) — without leaving the checkout: `issues list` (filterable by status, impact, cause and ontology domain, and showing each issue's domain so you can work through one domain at a time), `issues show <id>` for the diagnosis, suggested action and the occurrences behind it, `issues evidence <id> <occurrence-id>` for what the agent actually saw, and `issues resolve` / `dismiss` / `reopen` once you've acted on it.
|
|
20
20
|
- `cassis verify` runs the full local gate in one verb — `ontology fmt --check`, `ontology check`, `eval run` — stopping at the first failure. One command in a checkout ("is this change safe to merge?"), one job in CI. `--no-eval` skips the eval suite.
|
|
21
21
|
|
|
22
22
|
## Install
|
|
@@ -118,6 +118,7 @@ cassis schema plan schema.sql --complete
|
|
|
118
118
|
cassis schema apply schema.sql --complete # writes cassis/ locally
|
|
119
119
|
cassis schema push schema.sql --complete --yes # schema + ontology to the app
|
|
120
120
|
cassis schema plan --warehouse # warehouse-connected projects: introspect instead
|
|
121
|
+
cassis schema plan future.sql --dry-run --write-checkout # plan a not-yet-deployed DDL, keep nothing server-side
|
|
121
122
|
|
|
122
123
|
# List the projects the API key can reach (id, name, published version, dialect):
|
|
123
124
|
cassis projects list
|
|
@@ -131,6 +132,9 @@ cassis issues analyze
|
|
|
131
132
|
cassis issues list --status open
|
|
132
133
|
cassis issues show 019f0000-0000-7000-8000-0000000000e1
|
|
133
134
|
|
|
135
|
+
# Work one ontology domain at a time (nested domains included):
|
|
136
|
+
cassis issues list --domain sales
|
|
137
|
+
|
|
134
138
|
# Read what the agent saw for one occurrence (ids from `issues show`):
|
|
135
139
|
cassis issues evidence 019f0000-0000-7000-8000-0000000000e1 019f0000-0000-7000-8000-0000000000c1
|
|
136
140
|
|
|
@@ -372,6 +372,10 @@ class SchemaPlanConflictError(ApiError):
|
|
|
372
372
|
"""409 from the schema-plan routes: a plan is already active, the project is connected, the plan is stale / expired / not ready."""
|
|
373
373
|
|
|
374
374
|
|
|
375
|
+
class SchemaPlanRejectedError(ApiError):
|
|
376
|
+
"""400 from the preview route: the DDL does not parse or reads as truncated (the user's file, not transport)."""
|
|
377
|
+
|
|
378
|
+
|
|
375
379
|
class SchemaPlanNotFoundError(ApiError):
|
|
376
380
|
"""404 with the server's exact detail `"Schema plan not found"` (a project-scope 404 is a different error)."""
|
|
377
381
|
|
|
@@ -462,6 +466,61 @@ def post_schema_plan_warehouse(
|
|
|
462
466
|
return _schema_plan_response(response, url)
|
|
463
467
|
|
|
464
468
|
|
|
469
|
+
def _schema_plan_preview_response(response: httpx.Response, url: str) -> dict[str, Any]:
|
|
470
|
+
if response.status_code == 400:
|
|
471
|
+
raise SchemaPlanRejectedError(str(_detail_or_text(response)))
|
|
472
|
+
_raise_for_schema_plan_status(response)
|
|
473
|
+
result = _parse_json_response(response, url)
|
|
474
|
+
if not isinstance(result, dict) or "document" not in result or not isinstance(result.get("files"), dict):
|
|
475
|
+
raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
|
|
476
|
+
return result
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def post_schema_plan_preview(
|
|
480
|
+
*,
|
|
481
|
+
api_url: str,
|
|
482
|
+
api_key: str,
|
|
483
|
+
project_id: str,
|
|
484
|
+
ddl: str,
|
|
485
|
+
complete_source: bool = False,
|
|
486
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
487
|
+
) -> dict[str, Any]:
|
|
488
|
+
"""POST /api/ci/projects/{project_id}/schema/plans/preview: plan + rendered tree, nothing persisted.
|
|
489
|
+
|
|
490
|
+
Synchronous: the server computes the plan in the request (no plan row, no
|
|
491
|
+
job), so the tree timeout applies. A DDL the server cannot parse, or that
|
|
492
|
+
reads as truncated, is a 400 → `SchemaPlanRejectedError`.
|
|
493
|
+
"""
|
|
494
|
+
url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/schema/plans/preview"
|
|
495
|
+
try:
|
|
496
|
+
with _client(timeout=ONTOLOGY_TREE_TIMEOUT_SECONDS, transport=transport) as client:
|
|
497
|
+
response = client.post(
|
|
498
|
+
url,
|
|
499
|
+
json={"ddl": ddl, "complete_source": complete_source},
|
|
500
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
501
|
+
)
|
|
502
|
+
except httpx.HTTPError as exc:
|
|
503
|
+
raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
|
|
504
|
+
return _schema_plan_preview_response(response, url)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def post_schema_plan_preview_warehouse(
|
|
508
|
+
*,
|
|
509
|
+
api_url: str,
|
|
510
|
+
api_key: str,
|
|
511
|
+
project_id: str,
|
|
512
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
513
|
+
) -> dict[str, Any]:
|
|
514
|
+
"""POST /api/ci/projects/{project_id}/schema/plans/preview/warehouse: the warehouse twin of the preview."""
|
|
515
|
+
url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/schema/plans/preview/warehouse"
|
|
516
|
+
try:
|
|
517
|
+
with _client(timeout=ONTOLOGY_TREE_TIMEOUT_SECONDS, transport=transport) as client:
|
|
518
|
+
response = client.post(url, headers={"Authorization": f"Bearer {api_key}"})
|
|
519
|
+
except httpx.HTTPError as exc:
|
|
520
|
+
raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
|
|
521
|
+
return _schema_plan_preview_response(response, url)
|
|
522
|
+
|
|
523
|
+
|
|
465
524
|
def get_schema_plan(
|
|
466
525
|
*,
|
|
467
526
|
api_url: str,
|
|
@@ -790,11 +849,16 @@ def get_issues(
|
|
|
790
849
|
status: Optional[str] = None,
|
|
791
850
|
impact: Optional[str] = None,
|
|
792
851
|
cause: Optional[str] = None,
|
|
852
|
+
domain: Optional[str] = None,
|
|
793
853
|
transport: Optional[httpx.BaseTransport] = None,
|
|
794
854
|
) -> list[dict[str, Any]]:
|
|
795
855
|
"""GET /api/ci/projects/{project_id}/issues and return the issue list."""
|
|
796
856
|
url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/issues"
|
|
797
|
-
params = {
|
|
857
|
+
params = {
|
|
858
|
+
key: value
|
|
859
|
+
for key, value in (("status", status), ("impact", impact), ("cause", cause), ("domain", domain))
|
|
860
|
+
if value
|
|
861
|
+
}
|
|
798
862
|
result = _get_issue_json(url, api_key, transport, params=params)
|
|
799
863
|
if not isinstance(result, list) or not all(isinstance(issue, dict) and "id" in issue for issue in result):
|
|
800
864
|
raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
|
|
@@ -117,6 +117,9 @@ def list_issues(
|
|
|
117
117
|
status: Optional[str] = typer.Option(None, "--status", help=f"Filter by status ({', '.join(STATUSES)})."),
|
|
118
118
|
impact: Optional[str] = typer.Option(None, "--impact", help=f"Filter by impact ({', '.join(IMPACTS)})."),
|
|
119
119
|
cause: Optional[str] = typer.Option(None, "--cause", help=f"Filter by cause ({', '.join(CAUSES)})."),
|
|
120
|
+
domain: Optional[str] = typer.Option(
|
|
121
|
+
None, "--domain", help="Filter by ontology domain path; nested domains included."
|
|
122
|
+
),
|
|
120
123
|
path: Path = _PATH_OPTION,
|
|
121
124
|
project_id: Optional[str] = _PROJECT_OPTION,
|
|
122
125
|
api_key: Optional[str] = _API_KEY_OPTION,
|
|
@@ -126,9 +129,11 @@ def list_issues(
|
|
|
126
129
|
) -> None:
|
|
127
130
|
"""List the project's issues, prioritized by impact then recurrence.
|
|
128
131
|
|
|
129
|
-
Prints each issue's id, impact, occurrence count, status
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
Prints each issue's id, impact, occurrence count, status, primary ontology
|
|
133
|
+
domain (`-` when Cassis could not attach one) and title; the id is what
|
|
134
|
+
`cassis issues show`, `resolve`, `dismiss` and `reopen` take. Triage one
|
|
135
|
+
domain at a time with `--domain`, which covers its nested domains too.
|
|
136
|
+
Exits 0 on success, 2 on usage errors, 3 on transport/API errors.
|
|
132
137
|
"""
|
|
133
138
|
status = _validate_choice(status, STATUSES, "--status")
|
|
134
139
|
impact = _validate_choice(impact, IMPACTS, "--impact")
|
|
@@ -144,6 +149,7 @@ def list_issues(
|
|
|
144
149
|
status=status,
|
|
145
150
|
impact=impact,
|
|
146
151
|
cause=cause,
|
|
152
|
+
domain=domain,
|
|
147
153
|
)
|
|
148
154
|
except (AuthError, ApiError) as exc:
|
|
149
155
|
raise api_failure(exc) from exc
|
|
@@ -153,14 +159,16 @@ def list_issues(
|
|
|
153
159
|
raise typer.Exit(EXIT_OK)
|
|
154
160
|
|
|
155
161
|
if not issues:
|
|
156
|
-
typer.echo("No issues match." if (status or impact or cause) else "No issues on this project.")
|
|
162
|
+
typer.echo("No issues match." if (status or impact or cause or domain) else "No issues on this project.")
|
|
157
163
|
raise typer.Exit(EXIT_OK)
|
|
158
164
|
|
|
159
165
|
for issue in issues:
|
|
160
166
|
occurrences = issue.get("occurrence_count_cache") or 0
|
|
167
|
+
# Primary domain only; --json carries the whole list.
|
|
168
|
+
domains = issue.get("domains") or []
|
|
161
169
|
typer.echo(
|
|
162
170
|
f"{issue.get('id')} {issue.get('impact')} x{occurrences} "
|
|
163
|
-
f"{issue.get('status')} {one_line(issue.get('title'))}"
|
|
171
|
+
f"{issue.get('status')} {domains[0] if domains else '-'} {one_line(issue.get('title'))}"
|
|
164
172
|
)
|
|
165
173
|
raise typer.Exit(EXIT_OK)
|
|
166
174
|
|
|
@@ -201,6 +209,7 @@ def show(
|
|
|
201
209
|
f"{issue.get('status')} {issue.get('impact')} {issue.get('cause')} "
|
|
202
210
|
f"{issue.get('occurrence_count_cache', len(occurrences))} occurrence(s)"
|
|
203
211
|
)
|
|
212
|
+
_field("Domains", ", ".join(issue.get("domains") or []) or None)
|
|
204
213
|
_field("Description", issue.get("description"), blank_line=True)
|
|
205
214
|
_field("Suggested action", issue.get("suggested_action"), blank_line=True)
|
|
206
215
|
typer.echo("")
|
|
@@ -21,6 +21,7 @@ from cassis_cli.api import (
|
|
|
21
21
|
ApiError,
|
|
22
22
|
AuthError,
|
|
23
23
|
SchemaPlanConflictError,
|
|
24
|
+
SchemaPlanRejectedError,
|
|
24
25
|
UploadValidationError,
|
|
25
26
|
get_ontology_export,
|
|
26
27
|
get_schema_export,
|
|
@@ -29,6 +30,8 @@ from cassis_cli.api import (
|
|
|
29
30
|
post_ontology_import,
|
|
30
31
|
post_schema_plan,
|
|
31
32
|
post_schema_plan_apply,
|
|
33
|
+
post_schema_plan_preview,
|
|
34
|
+
post_schema_plan_preview_warehouse,
|
|
32
35
|
post_schema_plan_warehouse,
|
|
33
36
|
)
|
|
34
37
|
from cassis_cli.common import (
|
|
@@ -229,6 +232,17 @@ def plan(
|
|
|
229
232
|
timeout: float = _TIMEOUT_OPTION,
|
|
230
233
|
json_output: bool = _JSON_OPTION,
|
|
231
234
|
out: Optional[Path] = _OUT_OPTION,
|
|
235
|
+
dry_run: bool = typer.Option(
|
|
236
|
+
False,
|
|
237
|
+
"--dry-run",
|
|
238
|
+
help="Compute the plan in the request and keep nothing server-side: no plan to apply or resume, "
|
|
239
|
+
"the project's current plan untouched. For a DDL not deployed to the warehouse yet.",
|
|
240
|
+
),
|
|
241
|
+
write_checkout: bool = typer.Option(
|
|
242
|
+
False,
|
|
243
|
+
"--write-checkout",
|
|
244
|
+
help="With --dry-run: also write the ontology files the plan would produce under <path>/<base-path>.",
|
|
245
|
+
),
|
|
232
246
|
) -> None:
|
|
233
247
|
"""Preview what a schema update would change. Nothing is applied.
|
|
234
248
|
|
|
@@ -241,11 +255,46 @@ def plan(
|
|
|
241
255
|
whole-source. Exits 0 when the plan is ready (even when it is empty), 1
|
|
242
256
|
when the plan failed (unparseable or truncated DDL, unreachable
|
|
243
257
|
warehouse), 2 on usage errors, 3 on transport errors or a timeout.
|
|
258
|
+
|
|
259
|
+
--dry-run is the prepare-ahead gesture: the plan is computed synchronously
|
|
260
|
+
and nothing is kept server-side, so it works for a schema change that is
|
|
261
|
+
still a PR (a dbt model, a migration) and leaves the project's current plan
|
|
262
|
+
alone. --write-checkout then writes the resulting ontology files into the
|
|
263
|
+
checkout, to commit next to the schema change; nothing is pushed.
|
|
244
264
|
"""
|
|
245
265
|
api_key = require_api_key(api_key)
|
|
246
266
|
_require_one_source(ddl_file, warehouse)
|
|
267
|
+
if write_checkout and not dry_run:
|
|
268
|
+
typer.secho(
|
|
269
|
+
"--write-checkout needs --dry-run (use `cassis schema apply` otherwise).", fg=typer.colors.RED, err=True
|
|
270
|
+
)
|
|
271
|
+
raise typer.Exit(EXIT_USAGE)
|
|
247
272
|
resolved_project = resolve_project_id(project_id, path / base_path, quiet=json_output)
|
|
248
273
|
assert resolved_project is not None
|
|
274
|
+
if dry_run:
|
|
275
|
+
preview = _preview(
|
|
276
|
+
ddl_file,
|
|
277
|
+
warehouse=warehouse,
|
|
278
|
+
api_url=api_url,
|
|
279
|
+
api_key=api_key,
|
|
280
|
+
project_id=resolved_project,
|
|
281
|
+
complete_source=complete_source,
|
|
282
|
+
json_output=json_output,
|
|
283
|
+
out=out,
|
|
284
|
+
)
|
|
285
|
+
if write_checkout:
|
|
286
|
+
ontology_dir = path / base_path.strip().strip("/")
|
|
287
|
+
written, deleted, _kept = _write_checkout(ontology_dir, preview["files"], json_output=json_output)
|
|
288
|
+
typer.secho(
|
|
289
|
+
f"✓ Wrote {len(written)} ontology file(s) into {ontology_dir}"
|
|
290
|
+
+ (f", deleted {len(deleted)} stale file(s)" if deleted else "")
|
|
291
|
+
+ ". The app is unchanged.",
|
|
292
|
+
fg=typer.colors.GREEN,
|
|
293
|
+
err=json_output,
|
|
294
|
+
)
|
|
295
|
+
if json_output:
|
|
296
|
+
typer.echo(json.dumps(preview, indent=2))
|
|
297
|
+
raise typer.Exit(EXIT_OK)
|
|
249
298
|
record = _plan(
|
|
250
299
|
ddl_file,
|
|
251
300
|
warehouse=warehouse,
|
|
@@ -724,6 +773,54 @@ def _plan(
|
|
|
724
773
|
return record
|
|
725
774
|
|
|
726
775
|
|
|
776
|
+
def _preview(
|
|
777
|
+
ddl_file: Optional[Path],
|
|
778
|
+
*,
|
|
779
|
+
warehouse: bool,
|
|
780
|
+
api_url: str,
|
|
781
|
+
api_key: str,
|
|
782
|
+
project_id: str,
|
|
783
|
+
complete_source: bool,
|
|
784
|
+
json_output: bool,
|
|
785
|
+
out: Optional[Path],
|
|
786
|
+
) -> "dict[str, Any]":
|
|
787
|
+
"""Compute a dry-run plan for `ddl_file` (or the warehouse), render it. Return the preview record."""
|
|
788
|
+
try:
|
|
789
|
+
if warehouse:
|
|
790
|
+
preview = post_schema_plan_preview_warehouse(api_url=api_url, api_key=api_key, project_id=project_id)
|
|
791
|
+
else:
|
|
792
|
+
assert ddl_file is not None
|
|
793
|
+
preview = post_schema_plan_preview(
|
|
794
|
+
api_url=api_url,
|
|
795
|
+
api_key=api_key,
|
|
796
|
+
project_id=project_id,
|
|
797
|
+
ddl=_read_ddl(ddl_file),
|
|
798
|
+
complete_source=complete_source,
|
|
799
|
+
)
|
|
800
|
+
except (SchemaPlanRejectedError, SchemaPlanConflictError) as exc:
|
|
801
|
+
typer.secho(f"Plan failed: {exc}", fg=typer.colors.RED, err=True)
|
|
802
|
+
raise typer.Exit(EXIT_VALIDATION_FAILED) from exc
|
|
803
|
+
except (AuthError, ApiError) as exc:
|
|
804
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
805
|
+
raise typer.Exit(EXIT_TRANSPORT) from exc
|
|
806
|
+
if out is not None:
|
|
807
|
+
try:
|
|
808
|
+
out.write_text(json.dumps(preview, indent=2) + "\n", encoding="utf-8")
|
|
809
|
+
except OSError as exc:
|
|
810
|
+
typer.secho(f"Could not write {out}: {exc}", fg=typer.colors.RED, err=True)
|
|
811
|
+
raise typer.Exit(EXIT_USAGE) from exc
|
|
812
|
+
# The renderer reads a plan record; a preview is one without an id.
|
|
813
|
+
record = {"status": "ready", "document": preview.get("document"), "summary": preview.get("summary")}
|
|
814
|
+
render_plan(record, err=json_output)
|
|
815
|
+
for warning in preview.get("warnings") or []:
|
|
816
|
+
typer.secho(f" warning: {warning}", fg=typer.colors.YELLOW, err=True)
|
|
817
|
+
if plan_is_empty(record):
|
|
818
|
+
typer.secho("✓ Schema is up to date (dry run, nothing kept).", fg=typer.colors.GREEN, err=json_output)
|
|
819
|
+
else:
|
|
820
|
+
typer.secho("✓ Plan computed (dry run, nothing kept).", fg=typer.colors.GREEN, err=json_output)
|
|
821
|
+
return preview
|
|
822
|
+
|
|
823
|
+
|
|
727
824
|
def _explain_not_ready(record: "dict[str, Any]") -> None:
|
|
728
825
|
status = record.get("status")
|
|
729
826
|
error = record.get("error") or ""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|