cassis-cli 2.4.0__tar.gz → 3.0.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.4.0 → cassis_cli-3.0.0}/PKG-INFO +7 -5
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/README.md +6 -4
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/api.py +8 -3
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/common.py +128 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/ontology.py +7 -2
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/schema.py +17 -8
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/pyproject.toml +1 -1
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/LICENSE +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/NOTICE +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/__init__.py +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/eval.py +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/guide.py +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/issues.py +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/main.py +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/ontology_design_guide.md +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/projects.py +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/schema_plan.py +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/status.py +0 -0
- {cassis_cli-2.4.0 → cassis_cli-3.0.0}/cassis_cli/verify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cassis-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.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
|
|
@@ -27,7 +27,7 @@ Validate, test and evaluate your ontology from your terminal, then publish it. T
|
|
|
27
27
|
- `cassis ontology check` validates the ontology files in your repository with the exact same checks as the Cassis GitHub PR check (YAML parsing, round-trip, import validation) — so you can gate merges in any CI system, not just GitHub. It then prints advisory **ontology quality warnings** for a tree that parsed — tables not assigned to any domain, joins/metrics pointing at unknown tables or columns, missing table/column descriptions (the same findings `ontology test` reports, without the agent run). In a checkout bound to a project (`project.yml`, `--project`, or `CASSIS_PROJECT_ID`), it also cross-checks the tree against the project's source schema: references to tables or columns the warehouse doesn't have print as **warnings** too — advisory only (the object may simply not be built or synced yet). Warnings never fail the check.
|
|
28
28
|
- `cassis schema pull` downloads the data source's full source schema (as Cassis last introspected it) into `<base-path>/.schema.json` — a **gitignored** local snapshot (the command maintains the ignore entry) with a `pulled_at` stamp. The warehouse stays authoritative; the snapshot is a cache for offline/bulk work — e.g. a coding agent grepping table and column names during a modeling pass instead of paging through the MCP `get_source_schema` tool. Re-run to refresh.
|
|
29
29
|
- `cassis ontology fmt` rewrites the ontology files in canonical form (think `black`/`gofmt` for the ontology), so hand or agent edits pass the round-trip check.
|
|
30
|
-
- `cassis ontology upload` uploads the ontology files to a Cassis project (full replace) and, by default, publishes them immediately as a new version — so a merge to your main branch can go live in one CI step.
|
|
30
|
+
- `cassis ontology upload` uploads the ontology files to a Cassis project (full replace) and, by default, publishes them immediately as a new version — so a merge to your main branch can go live in one CI step. It runs from a git checkout whose ontology files are committed, and the published version records that commit, so `cassis status` can tell whether a checkout matches what is live.
|
|
31
31
|
- `cassis ontology pull` downloads the project's unpublished ontology into your repository checkout (full sync — stale local ontology files are pruned), so you can start editing from the current state, or bootstrap a repo that isn't git-synced (e.g. Bitbucket). Pruning only deletes files that are tracked and unmodified in git (i.e. restorable with `git checkout`); untracked or locally modified files are kept and listed, and every deleted path is printed.
|
|
32
32
|
- `cassis ontology pull` and `cassis ontology fmt` also write `<base-path>/AGENTS.md`, the Cassis ontology modeling guide, into the checkout (default `cassis/AGENTS.md`) — a managed file (generated banner; the CLI overwrites local edits) so a repo-aware coding agent loads current Cassis modeling doctrine by convention. It sits inside the ontology directory but is not part of the ontology tree (which is the YAML files plus the domain Markdown files `domains/**/README.md`), so it is never uploaded, validated, or pruned. Commit it alongside your ontology changes. The guide text ships inside the CLI package, so its version tracks the **installed cassis-cli version** — upgrade the CLI (`pip install -U cassis-cli`) and re-run `fmt` to pick up doctrine updates; an unpinned `pip install cassis-cli` in CI gets them automatically. The banner stamps a doctrine version, and the CLI never *downgrades* the file: if the checkout's `AGENTS.md` was written by a newer doctrine (a newer CLI, or Cassis itself on a publish), `fmt`/`pull` leave it in place, print an upgrade notice, and `fmt --check` still passes.
|
|
33
33
|
- The CLI identifies itself to the API (`User-Agent: cassis-cli/<version>`), and successful API responses advertise the newest published version — when you are behind, commands print a one-line upgrade notice on stderr (purely informational; output and exit codes are unchanged).
|
|
@@ -79,7 +79,8 @@ cassis ontology check /path/to/checkout
|
|
|
79
79
|
# --no-prune keeps even the tracked stale files it would otherwise delete):
|
|
80
80
|
cassis ontology pull --project 019f0000-0000-7000-8000-000000000000
|
|
81
81
|
|
|
82
|
-
# Upload the ontology to a project and publish it immediately
|
|
82
|
+
# Upload the ontology to a project and publish it immediately (commit the
|
|
83
|
+
# changes under cassis/ first: uploads refuse uncommitted ontology files):
|
|
83
84
|
cassis ontology upload --project 019f0000-0000-7000-8000-000000000000
|
|
84
85
|
|
|
85
86
|
# Upload without publishing (the tree becomes the project's unpublished ontology, to review in Cassis):
|
|
@@ -140,6 +141,7 @@ cassis schema pull
|
|
|
140
141
|
# Preview, apply locally and push a schema update from a DDL file (DDL-only projects):
|
|
141
142
|
cassis schema plan schema.sql --complete
|
|
142
143
|
cassis schema apply schema.sql --complete # writes cassis/ locally
|
|
144
|
+
git add cassis && git commit -m "Apply schema update" # push needs the tree committed
|
|
143
145
|
cassis schema push schema.sql --complete --yes # schema + ontology to the app
|
|
144
146
|
cassis schema plan --warehouse # warehouse-connected projects: introspect instead
|
|
145
147
|
cassis schema plan future.sql --dry-run --write-checkout # plan a not-yet-deployed DDL, keep nothing server-side
|
|
@@ -216,7 +218,7 @@ cassis ontology fmt --check
|
|
|
216
218
|
| ---- | ------------------------------------------------------------------------------ |
|
|
217
219
|
| 0 | Ontology is valid (check) / pulled (pull) / uploaded (upload) / eval run completed all-passed (eval run) / every probe completed (test — whatever its outcome; probes are informational, don't gate CI on them) |
|
|
218
220
|
| 1 | Validation failed (check: findings printed; upload: nothing imported; eval run: invalid tree, failed cases, or failed/cancelled run; test: invalid tree or a probe failed; add-case: duplicate question or gold SQL that does not run; delete-case: no such case in the project; issues: no such issue or occurrence in the project; issues analyze: failed or cancelled analysis run; schema plan/apply: extraction is incomplete, the plan failed (unparseable or truncated DDL), is stale or expired, the apply failed, or the project won't accept it (a plan is being applied, a DDL was given for a warehouse-connected project, or --warehouse for a DDL-only one)) |
|
|
219
|
-
| 2 | Usage error (missing API key or project, no ontology directory, unreadable file, tree over the size limits, `eval run --branch` naming an ontology branch the project does not have) |
|
|
221
|
+
| 2 | Usage error (missing API key or project, no ontology directory, unreadable file, tree over the size limits, `eval run --branch` naming an ontology branch the project does not have, `upload` or `schema push` outside a git checkout or with uncommitted ontology files) |
|
|
220
222
|
| 3 | Transport/API error (unreachable API, invalid key, inaccessible project, unexpected response), another eval run or issue analysis already active, out of credits, or `--timeout` reached |
|
|
221
223
|
|
|
222
224
|
Commands that send the local tree (`check`, `fmt`, `upload`, `eval run`, `test`) accept up to
|
|
@@ -299,7 +301,7 @@ ontology-eval:
|
|
|
299
301
|
CASSIS_PROJECT_ID: $CASSIS_PROJECT_ID
|
|
300
302
|
|
|
301
303
|
ontology-publish:
|
|
302
|
-
image: python:3.12-slim
|
|
304
|
+
image: python:3.12 # not -slim: the upload needs git to record the commit
|
|
303
305
|
rules:
|
|
304
306
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
305
307
|
script:
|
|
@@ -5,7 +5,7 @@ Validate, test and evaluate your ontology from your terminal, then publish it. T
|
|
|
5
5
|
- `cassis ontology check` validates the ontology files in your repository with the exact same checks as the Cassis GitHub PR check (YAML parsing, round-trip, import validation) — so you can gate merges in any CI system, not just GitHub. It then prints advisory **ontology quality warnings** for a tree that parsed — tables not assigned to any domain, joins/metrics pointing at unknown tables or columns, missing table/column descriptions (the same findings `ontology test` reports, without the agent run). In a checkout bound to a project (`project.yml`, `--project`, or `CASSIS_PROJECT_ID`), it also cross-checks the tree against the project's source schema: references to tables or columns the warehouse doesn't have print as **warnings** too — advisory only (the object may simply not be built or synced yet). Warnings never fail the check.
|
|
6
6
|
- `cassis schema pull` downloads the data source's full source schema (as Cassis last introspected it) into `<base-path>/.schema.json` — a **gitignored** local snapshot (the command maintains the ignore entry) with a `pulled_at` stamp. The warehouse stays authoritative; the snapshot is a cache for offline/bulk work — e.g. a coding agent grepping table and column names during a modeling pass instead of paging through the MCP `get_source_schema` tool. Re-run to refresh.
|
|
7
7
|
- `cassis ontology fmt` rewrites the ontology files in canonical form (think `black`/`gofmt` for the ontology), so hand or agent edits pass the round-trip check.
|
|
8
|
-
- `cassis ontology upload` uploads the ontology files to a Cassis project (full replace) and, by default, publishes them immediately as a new version — so a merge to your main branch can go live in one CI step.
|
|
8
|
+
- `cassis ontology upload` uploads the ontology files to a Cassis project (full replace) and, by default, publishes them immediately as a new version — so a merge to your main branch can go live in one CI step. It runs from a git checkout whose ontology files are committed, and the published version records that commit, so `cassis status` can tell whether a checkout matches what is live.
|
|
9
9
|
- `cassis ontology pull` downloads the project's unpublished ontology into your repository checkout (full sync — stale local ontology files are pruned), so you can start editing from the current state, or bootstrap a repo that isn't git-synced (e.g. Bitbucket). Pruning only deletes files that are tracked and unmodified in git (i.e. restorable with `git checkout`); untracked or locally modified files are kept and listed, and every deleted path is printed.
|
|
10
10
|
- `cassis ontology pull` and `cassis ontology fmt` also write `<base-path>/AGENTS.md`, the Cassis ontology modeling guide, into the checkout (default `cassis/AGENTS.md`) — a managed file (generated banner; the CLI overwrites local edits) so a repo-aware coding agent loads current Cassis modeling doctrine by convention. It sits inside the ontology directory but is not part of the ontology tree (which is the YAML files plus the domain Markdown files `domains/**/README.md`), so it is never uploaded, validated, or pruned. Commit it alongside your ontology changes. The guide text ships inside the CLI package, so its version tracks the **installed cassis-cli version** — upgrade the CLI (`pip install -U cassis-cli`) and re-run `fmt` to pick up doctrine updates; an unpinned `pip install cassis-cli` in CI gets them automatically. The banner stamps a doctrine version, and the CLI never *downgrades* the file: if the checkout's `AGENTS.md` was written by a newer doctrine (a newer CLI, or Cassis itself on a publish), `fmt`/`pull` leave it in place, print an upgrade notice, and `fmt --check` still passes.
|
|
11
11
|
- The CLI identifies itself to the API (`User-Agent: cassis-cli/<version>`), and successful API responses advertise the newest published version — when you are behind, commands print a one-line upgrade notice on stderr (purely informational; output and exit codes are unchanged).
|
|
@@ -57,7 +57,8 @@ cassis ontology check /path/to/checkout
|
|
|
57
57
|
# --no-prune keeps even the tracked stale files it would otherwise delete):
|
|
58
58
|
cassis ontology pull --project 019f0000-0000-7000-8000-000000000000
|
|
59
59
|
|
|
60
|
-
# Upload the ontology to a project and publish it immediately
|
|
60
|
+
# Upload the ontology to a project and publish it immediately (commit the
|
|
61
|
+
# changes under cassis/ first: uploads refuse uncommitted ontology files):
|
|
61
62
|
cassis ontology upload --project 019f0000-0000-7000-8000-000000000000
|
|
62
63
|
|
|
63
64
|
# Upload without publishing (the tree becomes the project's unpublished ontology, to review in Cassis):
|
|
@@ -118,6 +119,7 @@ cassis schema pull
|
|
|
118
119
|
# Preview, apply locally and push a schema update from a DDL file (DDL-only projects):
|
|
119
120
|
cassis schema plan schema.sql --complete
|
|
120
121
|
cassis schema apply schema.sql --complete # writes cassis/ locally
|
|
122
|
+
git add cassis && git commit -m "Apply schema update" # push needs the tree committed
|
|
121
123
|
cassis schema push schema.sql --complete --yes # schema + ontology to the app
|
|
122
124
|
cassis schema plan --warehouse # warehouse-connected projects: introspect instead
|
|
123
125
|
cassis schema plan future.sql --dry-run --write-checkout # plan a not-yet-deployed DDL, keep nothing server-side
|
|
@@ -194,7 +196,7 @@ cassis ontology fmt --check
|
|
|
194
196
|
| ---- | ------------------------------------------------------------------------------ |
|
|
195
197
|
| 0 | Ontology is valid (check) / pulled (pull) / uploaded (upload) / eval run completed all-passed (eval run) / every probe completed (test — whatever its outcome; probes are informational, don't gate CI on them) |
|
|
196
198
|
| 1 | Validation failed (check: findings printed; upload: nothing imported; eval run: invalid tree, failed cases, or failed/cancelled run; test: invalid tree or a probe failed; add-case: duplicate question or gold SQL that does not run; delete-case: no such case in the project; issues: no such issue or occurrence in the project; issues analyze: failed or cancelled analysis run; schema plan/apply: extraction is incomplete, the plan failed (unparseable or truncated DDL), is stale or expired, the apply failed, or the project won't accept it (a plan is being applied, a DDL was given for a warehouse-connected project, or --warehouse for a DDL-only one)) |
|
|
197
|
-
| 2 | Usage error (missing API key or project, no ontology directory, unreadable file, tree over the size limits, `eval run --branch` naming an ontology branch the project does not have) |
|
|
199
|
+
| 2 | Usage error (missing API key or project, no ontology directory, unreadable file, tree over the size limits, `eval run --branch` naming an ontology branch the project does not have, `upload` or `schema push` outside a git checkout or with uncommitted ontology files) |
|
|
198
200
|
| 3 | Transport/API error (unreachable API, invalid key, inaccessible project, unexpected response), another eval run or issue analysis already active, out of credits, or `--timeout` reached |
|
|
199
201
|
|
|
200
202
|
Commands that send the local tree (`check`, `fmt`, `upload`, `eval run`, `test`) accept up to
|
|
@@ -277,7 +279,7 @@ ontology-eval:
|
|
|
277
279
|
CASSIS_PROJECT_ID: $CASSIS_PROJECT_ID
|
|
278
280
|
|
|
279
281
|
ontology-publish:
|
|
280
|
-
image: python:3.12-slim
|
|
282
|
+
image: python:3.12 # not -slim: the upload needs git to record the commit
|
|
281
283
|
rules:
|
|
282
284
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
283
285
|
script:
|
|
@@ -204,12 +204,17 @@ def post_ontology_import(
|
|
|
204
204
|
project_id: str,
|
|
205
205
|
files: dict[str, str],
|
|
206
206
|
publish: bool,
|
|
207
|
+
git_commit_sha: str,
|
|
207
208
|
label: Optional[str] = None,
|
|
208
209
|
transport: Optional[httpx.BaseTransport] = None,
|
|
209
210
|
) -> dict[str, Any]:
|
|
210
|
-
"""POST the ontology tree to /api/ci/projects/{project_id}/ontology/import and return the response body.
|
|
211
|
+
"""POST the ontology tree to /api/ci/projects/{project_id}/ontology/import and return the response body.
|
|
212
|
+
|
|
213
|
+
``git_commit_sha`` is the commit the files were read from, recorded on the
|
|
214
|
+
version the import publishes.
|
|
215
|
+
"""
|
|
211
216
|
url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/ontology/import"
|
|
212
|
-
body: dict[str, Any] = {"files": files, "publish": publish}
|
|
217
|
+
body: dict[str, Any] = {"files": files, "publish": publish, "git_commit_sha": git_commit_sha}
|
|
213
218
|
if label is not None:
|
|
214
219
|
body["label"] = label
|
|
215
220
|
try:
|
|
@@ -1028,7 +1033,7 @@ def post_ontology_test(
|
|
|
1028
1033
|
if response.status_code == 400:
|
|
1029
1034
|
raise OntologyTestValidationError(_detail_or_text(response))
|
|
1030
1035
|
if response.status_code == 402:
|
|
1031
|
-
raise ApiError("Your organization has run out of credits. Contact your administrator to top up.")
|
|
1036
|
+
raise ApiError("Your organization has run out of credits. Contact your Cassis administrator to top up.")
|
|
1032
1037
|
if response.status_code in (403, 404):
|
|
1033
1038
|
raise _project_scope_error(response)
|
|
1034
1039
|
if response.status_code >= 400:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
5
6
|
import os
|
|
6
7
|
import re
|
|
7
8
|
import subprocess
|
|
@@ -106,6 +107,133 @@ def git_file_states(directory: Path) -> Optional[tuple[set[str], set[str]]]:
|
|
|
106
107
|
return tracked, dirty
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
def require_committed_tree(path: Path, base_path: str, files: "dict[str, str]") -> str:
|
|
111
|
+
"""Return the ``HEAD`` commit that holds exactly the ontology ``files`` about to be uploaded.
|
|
112
|
+
|
|
113
|
+
An upload records this commit on the version it publishes, so the uploaded
|
|
114
|
+
files must be the ontology files of ``base_path`` at ``HEAD``: the same paths
|
|
115
|
+
with the same text, up to line endings (``collect_files`` reads CRLF as LF,
|
|
116
|
+
and line endings carry no ontology meaning). ``files`` is what ``collect_tree`` read, keyed by path
|
|
117
|
+
relative to the base path. Exits 2 (usage) when ``path`` is not a git checkout
|
|
118
|
+
with a commit, or when the two differ: a file modified, staged, renamed,
|
|
119
|
+
deleted, untracked, gitignored, missing from a sparse checkout or inside a
|
|
120
|
+
submodule. Files the upload does not send never block.
|
|
121
|
+
"""
|
|
122
|
+
ontology_dir = path / Path(base_path)
|
|
123
|
+
env = {**os.environ, "GIT_OPTIONAL_LOCKS": "0"}
|
|
124
|
+
|
|
125
|
+
def fail(reason: str) -> "typer.Exit":
|
|
126
|
+
typer.secho(
|
|
127
|
+
f"{reason} Upload the ontology from a git checkout of the repository that holds it: "
|
|
128
|
+
"Cassis records the commit each published version comes from.",
|
|
129
|
+
fg=typer.colors.RED,
|
|
130
|
+
err=True,
|
|
131
|
+
)
|
|
132
|
+
return typer.Exit(EXIT_USAGE)
|
|
133
|
+
|
|
134
|
+
def git(*args: str) -> str:
|
|
135
|
+
try:
|
|
136
|
+
proc = subprocess.run(
|
|
137
|
+
["git", *args],
|
|
138
|
+
cwd=ontology_dir,
|
|
139
|
+
env=env,
|
|
140
|
+
capture_output=True,
|
|
141
|
+
text=True,
|
|
142
|
+
errors="replace",
|
|
143
|
+
)
|
|
144
|
+
except OSError as exc:
|
|
145
|
+
raise fail(f"Could not run git ({exc}).") from exc
|
|
146
|
+
if proc.returncode != 0:
|
|
147
|
+
detail = proc.stderr.strip().splitlines()
|
|
148
|
+
raise fail(f"{path} is not a git checkout with a commit" + (f" (git: {detail[0]})." if detail else "."))
|
|
149
|
+
return proc.stdout
|
|
150
|
+
|
|
151
|
+
head = git("rev-parse", "--verify", "HEAD").strip()
|
|
152
|
+
# Run from ontology_dir, ls-tree lists paths relative to it. A submodule is a
|
|
153
|
+
# "commit" entry, so the files inside it count as not committed.
|
|
154
|
+
committed: "dict[str, str]" = {}
|
|
155
|
+
for entry in git("ls-tree", "-r", "-z", "HEAD", ".").split("\0"):
|
|
156
|
+
if not entry:
|
|
157
|
+
continue
|
|
158
|
+
meta, rel = entry.split("\t", 1)
|
|
159
|
+
_mode, kind, blob = meta.split()
|
|
160
|
+
if kind == "blob" and is_ontology_file(rel):
|
|
161
|
+
committed[rel] = blob
|
|
162
|
+
# Compare the content about to be uploaded, not the files on disk: a clean
|
|
163
|
+
# filter can make a working-tree file hash to its committed blob while the
|
|
164
|
+
# text read from it differs. Its blob id is computed here; a blob that does
|
|
165
|
+
# not match is accepted when it holds the same text up to line endings,
|
|
166
|
+
# which `collect_files` already reads as LF (a CRLF commit uploads as LF).
|
|
167
|
+
differing_blobs = {
|
|
168
|
+
p: committed[p] for p in files if p in committed and _blob_id(files[p], committed[p]) != committed[p]
|
|
169
|
+
}
|
|
170
|
+
committed_text = _read_blobs(ontology_dir, env, set(differing_blobs.values()), fail)
|
|
171
|
+
differing = sorted(
|
|
172
|
+
{p for p in files if p not in committed}
|
|
173
|
+
| {p for p, blob in differing_blobs.items() if _lf(committed_text.get(blob)) != _lf(files[p])}
|
|
174
|
+
| (committed.keys() - files.keys())
|
|
175
|
+
)
|
|
176
|
+
if differing:
|
|
177
|
+
shown = "\n".join(f" {base_path}/{p}" for p in differing[:20])
|
|
178
|
+
more = f"\n … and {len(differing) - 20} more" if len(differing) > 20 else ""
|
|
179
|
+
typer.secho(
|
|
180
|
+
f"Uncommitted changes under {base_path}/ (these files differ from HEAD):\n{shown}{more}\n"
|
|
181
|
+
f"Commit the changes under {base_path}/, then upload again.",
|
|
182
|
+
fg=typer.colors.RED,
|
|
183
|
+
err=True,
|
|
184
|
+
)
|
|
185
|
+
raise typer.Exit(EXIT_USAGE)
|
|
186
|
+
return head
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _blob_id(content: str, like: str) -> str:
|
|
190
|
+
"""The git blob id of ``content``, in the hash ``like`` (a blob id of the repository) uses."""
|
|
191
|
+
data = content.encode("utf-8")
|
|
192
|
+
digest = hashlib.sha256() if len(like) == 64 else hashlib.sha1()
|
|
193
|
+
digest.update(b"blob %d\0" % len(data) + data)
|
|
194
|
+
return digest.hexdigest()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _read_blobs(
|
|
198
|
+
cwd: Path, env: "dict[str, str]", blobs: "set[str]", fail: "Callable[[str], typer.Exit]"
|
|
199
|
+
) -> "dict[str, str]":
|
|
200
|
+
"""Read committed blobs in one ``git cat-file --batch``, keyed by blob id; non-UTF-8 blobs are left out."""
|
|
201
|
+
if not blobs:
|
|
202
|
+
return {}
|
|
203
|
+
try:
|
|
204
|
+
proc = subprocess.run(
|
|
205
|
+
["git", "cat-file", "--batch"],
|
|
206
|
+
cwd=cwd,
|
|
207
|
+
env=env,
|
|
208
|
+
input="".join(f"{blob}\n" for blob in sorted(blobs)).encode(),
|
|
209
|
+
capture_output=True,
|
|
210
|
+
)
|
|
211
|
+
except OSError as exc:
|
|
212
|
+
raise fail(f"Could not run git ({exc}).") from exc
|
|
213
|
+
if proc.returncode != 0:
|
|
214
|
+
raise fail(f"Could not read committed files (git: {proc.stderr.decode(errors='replace').strip()}).")
|
|
215
|
+
texts: "dict[str, str]" = {}
|
|
216
|
+
out, pos = proc.stdout, 0
|
|
217
|
+
while pos < len(out):
|
|
218
|
+
header_end = out.index(b"\n", pos)
|
|
219
|
+
fields = out[pos:header_end].split()
|
|
220
|
+
pos = header_end + 1
|
|
221
|
+
if len(fields) != 3: # "<id> missing"
|
|
222
|
+
continue
|
|
223
|
+
size = int(fields[2])
|
|
224
|
+
try:
|
|
225
|
+
texts[fields[0].decode()] = out[pos : pos + size].decode("utf-8")
|
|
226
|
+
except UnicodeDecodeError:
|
|
227
|
+
pass
|
|
228
|
+
pos += size + 1 # the content is followed by a newline
|
|
229
|
+
return texts
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _lf(text: Optional[str]) -> Optional[str]:
|
|
233
|
+
"""``text`` with CRLF and CR line endings read as LF, as ``Path.read_text`` reads them."""
|
|
234
|
+
return None if text is None else text.replace("\r\n", "\n").replace("\r", "\n")
|
|
235
|
+
|
|
236
|
+
|
|
109
237
|
def sync_ontology_tree(
|
|
110
238
|
ontology_dir: Path,
|
|
111
239
|
files: "dict[str, str]",
|
|
@@ -29,6 +29,7 @@ from cassis_cli.common import (
|
|
|
29
29
|
from cassis_cli.common import collect_tree as _collect_tree
|
|
30
30
|
from cassis_cli.common import is_legacy_domain_file as _is_legacy_domain_file
|
|
31
31
|
from cassis_cli.common import require_api_key as _require_api_key
|
|
32
|
+
from cassis_cli.common import require_committed_tree as _require_committed_tree
|
|
32
33
|
from cassis_cli.common import resolve_project_id as _resolve_project_id
|
|
33
34
|
from cassis_cli.common import sync_ontology_tree as _sync_ontology_tree
|
|
34
35
|
from cassis_cli.guide import DOCTRINE_VERSION, GUIDE_FILENAME, guide_status, refresh_guide
|
|
@@ -332,11 +333,14 @@ def upload(
|
|
|
332
333
|
|
|
333
334
|
Replaces the project's unpublished ontology with the local tree (full
|
|
334
335
|
replace) and, unless --no-publish is passed, publishes it immediately as a
|
|
335
|
-
new version.
|
|
336
|
-
|
|
336
|
+
new version. The checkout must be a git repository whose ontology files
|
|
337
|
+
match HEAD: the published version records that commit. Exits 0 on success,
|
|
338
|
+
1 when the tree fails validation, 2 on usage errors (including uncommitted
|
|
339
|
+
changes under the base path), 3 on transport/API errors.
|
|
337
340
|
"""
|
|
338
341
|
api_key = _require_api_key(api_key)
|
|
339
342
|
files, base_path = _collect_tree(path, base_path)
|
|
343
|
+
head = _require_committed_tree(path, base_path, files)
|
|
340
344
|
project_id = _resolve_project_id(project_id, path / Path(base_path))
|
|
341
345
|
|
|
342
346
|
try:
|
|
@@ -346,6 +350,7 @@ def upload(
|
|
|
346
350
|
project_id=project_id,
|
|
347
351
|
files=files,
|
|
348
352
|
publish=publish,
|
|
353
|
+
git_commit_sha=head,
|
|
349
354
|
label=label,
|
|
350
355
|
)
|
|
351
356
|
except AuthError as exc:
|
|
@@ -46,6 +46,7 @@ from cassis_cli.common import (
|
|
|
46
46
|
collect_tree,
|
|
47
47
|
poll_until,
|
|
48
48
|
require_api_key,
|
|
49
|
+
require_committed_tree,
|
|
49
50
|
resolve_project_id,
|
|
50
51
|
sync_ontology_tree,
|
|
51
52
|
)
|
|
@@ -434,15 +435,17 @@ def push(
|
|
|
434
435
|
Two steps, in order: the new schema (a DDL file, or the connected
|
|
435
436
|
warehouse with --warehouse) is planned and applied server-side (new
|
|
436
437
|
schema version, tracked schema updated, ontology edits the plan lists),
|
|
437
|
-
then the local ontology tree replaces the project's unpublished ontology
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
438
|
+
then the local ontology tree replaces the project's unpublished ontology.
|
|
439
|
+
Commit the tree `cassis schema apply` wrote, and any hand edits, before
|
|
440
|
+
pushing: the push refuses uncommitted changes under the base path, and a
|
|
441
|
+
published version records the commit. Pass --publish to publish it as a
|
|
442
|
+
new version. Exits 0 when pushed, 1 when the plan failed / is stale or the
|
|
443
|
+
upload was rejected, 2 on usage errors, 3 on transport errors.
|
|
442
444
|
"""
|
|
443
445
|
api_key = require_api_key(api_key)
|
|
444
446
|
_require_one_source(ddl_file, warehouse)
|
|
445
447
|
files, base_path = collect_tree(path, base_path)
|
|
448
|
+
head = require_committed_tree(path, base_path, files)
|
|
446
449
|
resolved_project = resolve_project_id(project_id, path / Path(base_path), quiet=json_output)
|
|
447
450
|
assert resolved_project is not None
|
|
448
451
|
|
|
@@ -506,7 +509,13 @@ def push(
|
|
|
506
509
|
typer.echo(f"Uploading {len(files)} ontology file(s)…", err=True)
|
|
507
510
|
try:
|
|
508
511
|
upload = post_ontology_import(
|
|
509
|
-
api_url=api_url,
|
|
512
|
+
api_url=api_url,
|
|
513
|
+
api_key=api_key,
|
|
514
|
+
project_id=resolved_project,
|
|
515
|
+
files=files,
|
|
516
|
+
publish=publish,
|
|
517
|
+
git_commit_sha=head,
|
|
518
|
+
label=label,
|
|
510
519
|
)
|
|
511
520
|
except UploadValidationError as exc:
|
|
512
521
|
typer.secho("Ontology upload rejected:", fg=typer.colors.RED, bold=True, err=True)
|
|
@@ -572,8 +581,8 @@ def _require_checkout_in_sync(
|
|
|
572
581
|
if len(differing) > 10:
|
|
573
582
|
typer.secho(f" … {len(differing) - 10} more", fg=typer.colors.RED, err=True)
|
|
574
583
|
typer.secho(
|
|
575
|
-
"Bring the checkout up to date first (`cassis ontology pull`), or
|
|
576
|
-
"(`cassis ontology upload --no-publish`), then apply again. `--force` overwrites the local files.",
|
|
584
|
+
"Bring the checkout up to date first (`cassis ontology pull`), or commit your local edits and push "
|
|
585
|
+
"them (`cassis ontology upload --no-publish`), then apply again. `--force` overwrites the local files.",
|
|
577
586
|
fg=typer.colors.RED,
|
|
578
587
|
err=True,
|
|
579
588
|
)
|
|
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
|