cassis-cli 0.1.0__tar.gz → 0.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.
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: cassis-cli
3
+ Version: 0.2.0
4
+ Summary: Cassis CLI — run Cassis actions (ontology validation, upload and publish) from your CI pipelines
5
+ License: Proprietary
6
+ Keywords: cassis,ontology,ci,text-to-sql
7
+ Author: Cassis
8
+ Author-email: tech.admin@getcassis.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: License :: Other/Proprietary License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: httpx (>=0.24,<1.0)
18
+ Requires-Dist: typer (>=0.12,<1.0)
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Cassis CLI
22
+
23
+ Run Cassis actions from your CI pipelines:
24
+
25
+ - `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.
26
+ - `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.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install cassis-cli
32
+ ```
33
+
34
+ ## Setup
35
+
36
+ 1. Create an API key in Cassis under **Organization settings → API keys** (keys start with `sk-k6-`).
37
+ 2. Store it as a CI secret and expose it as `CASSIS_API_KEY`.
38
+ 3. For `upload`: find the project ID (UUID) in the project's URL and expose it as `CASSIS_PROJECT_ID` (or pass `--project`).
39
+
40
+ ## Usage
41
+
42
+ ```bash
43
+ # From the root of a repository synced with Cassis (contains the ontology export directory, cassis/ by default):
44
+ cassis ontology check
45
+
46
+ # Or point at the checkout explicitly:
47
+ cassis ontology check /path/to/checkout
48
+
49
+ # Upload the ontology to a project and publish it immediately:
50
+ cassis ontology upload --project 019f0000-0000-7000-8000-000000000000
51
+
52
+ # Upload without publishing (the tree becomes the project's unpublished ontology, to review in Cassis):
53
+ cassis ontology upload --project ... --no-publish
54
+
55
+ # Label the published version:
56
+ cassis ontology upload --project ... --label "release 1.2"
57
+
58
+ # Machine-readable output:
59
+ cassis ontology check --json
60
+ cassis ontology upload --project ... --json
61
+ ```
62
+
63
+ Configuration (flags take precedence over env vars):
64
+
65
+ | Flag | Env var | Default |
66
+ | ----------- | ---------------- | --------------------------- |
67
+ | `--api-key` | `CASSIS_API_KEY` | — (required) |
68
+ | `--api-url` | `CASSIS_API_URL` | `https://app.getcassis.com` |
69
+ | `--base-path` | `CASSIS_BASE_PATH` | `cassis` — must match the project's git-sync "Path" setting |
70
+ | `--project` (upload only) | `CASSIS_PROJECT_ID` | — (required) |
71
+
72
+ ### Exit codes
73
+
74
+ | Code | Meaning |
75
+ | ---- | ------------------------------------------------------------------------------ |
76
+ | 0 | Ontology is valid (check) / uploaded (upload) |
77
+ | 1 | Validation failed (check: findings printed; upload: nothing imported) |
78
+ | 2 | Usage error (missing API key or project, no ontology directory, unreadable file, tree over the size limits) |
79
+ | 3 | Transport/API error (unreachable API, invalid key, inaccessible project, unexpected response) |
80
+
81
+ Both commands accept up to 2000 YAML files / 5 MB total — far above real
82
+ ontologies (a few hundred small files). Beyond that the CLI fails fast with
83
+ exit 2 before uploading anything; double-check `--base-path` if you hit it.
84
+
85
+ `upload` replaces the project's entire ontology with the uploaded tree. A
86
+ never-published project always goes live immediately on first upload (even
87
+ with `--no-publish`), matching imports from the Cassis app. Publishing is
88
+ idempotent: re-uploading content identical to the published version reports
89
+ that version instead of creating a new one, so re-running the CI job on
90
+ unchanged files is a no-op.
91
+
92
+ ### GitHub Actions example
93
+
94
+ ```yaml
95
+ jobs:
96
+ ontology-check:
97
+ runs-on: ubuntu-latest
98
+ steps:
99
+ - uses: actions/checkout@v4
100
+ - uses: actions/setup-python@v5
101
+ with:
102
+ python-version: "3.12"
103
+ - run: pip install cassis-cli
104
+ - run: cassis ontology check
105
+ env:
106
+ CASSIS_API_KEY: ${{ secrets.CASSIS_API_KEY }}
107
+
108
+ ontology-publish:
109
+ runs-on: ubuntu-latest
110
+ if: github.ref == 'refs/heads/main'
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ - uses: actions/setup-python@v5
114
+ with:
115
+ python-version: "3.12"
116
+ - run: pip install cassis-cli
117
+ - run: cassis ontology upload
118
+ env:
119
+ CASSIS_API_KEY: ${{ secrets.CASSIS_API_KEY }}
120
+ CASSIS_PROJECT_ID: ${{ vars.CASSIS_PROJECT_ID }}
121
+ ```
122
+
123
+ ### GitLab CI example
124
+
125
+ ```yaml
126
+ ontology-check:
127
+ image: python:3.12-slim
128
+ script:
129
+ - pip install cassis-cli
130
+ - cassis ontology check
131
+ variables:
132
+ CASSIS_API_KEY: $CASSIS_API_KEY
133
+
134
+ ontology-publish:
135
+ image: python:3.12-slim
136
+ rules:
137
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
138
+ script:
139
+ - pip install cassis-cli
140
+ - cassis ontology upload
141
+ variables:
142
+ CASSIS_API_KEY: $CASSIS_API_KEY
143
+ CASSIS_PROJECT_ID: $CASSIS_PROJECT_ID
144
+ ```
145
+
@@ -0,0 +1,124 @@
1
+ # Cassis CLI
2
+
3
+ Run Cassis actions from your CI pipelines:
4
+
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.
6
+ - `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.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install cassis-cli
12
+ ```
13
+
14
+ ## Setup
15
+
16
+ 1. Create an API key in Cassis under **Organization settings → API keys** (keys start with `sk-k6-`).
17
+ 2. Store it as a CI secret and expose it as `CASSIS_API_KEY`.
18
+ 3. For `upload`: find the project ID (UUID) in the project's URL and expose it as `CASSIS_PROJECT_ID` (or pass `--project`).
19
+
20
+ ## Usage
21
+
22
+ ```bash
23
+ # From the root of a repository synced with Cassis (contains the ontology export directory, cassis/ by default):
24
+ cassis ontology check
25
+
26
+ # Or point at the checkout explicitly:
27
+ cassis ontology check /path/to/checkout
28
+
29
+ # Upload the ontology to a project and publish it immediately:
30
+ cassis ontology upload --project 019f0000-0000-7000-8000-000000000000
31
+
32
+ # Upload without publishing (the tree becomes the project's unpublished ontology, to review in Cassis):
33
+ cassis ontology upload --project ... --no-publish
34
+
35
+ # Label the published version:
36
+ cassis ontology upload --project ... --label "release 1.2"
37
+
38
+ # Machine-readable output:
39
+ cassis ontology check --json
40
+ cassis ontology upload --project ... --json
41
+ ```
42
+
43
+ Configuration (flags take precedence over env vars):
44
+
45
+ | Flag | Env var | Default |
46
+ | ----------- | ---------------- | --------------------------- |
47
+ | `--api-key` | `CASSIS_API_KEY` | — (required) |
48
+ | `--api-url` | `CASSIS_API_URL` | `https://app.getcassis.com` |
49
+ | `--base-path` | `CASSIS_BASE_PATH` | `cassis` — must match the project's git-sync "Path" setting |
50
+ | `--project` (upload only) | `CASSIS_PROJECT_ID` | — (required) |
51
+
52
+ ### Exit codes
53
+
54
+ | Code | Meaning |
55
+ | ---- | ------------------------------------------------------------------------------ |
56
+ | 0 | Ontology is valid (check) / uploaded (upload) |
57
+ | 1 | Validation failed (check: findings printed; upload: nothing imported) |
58
+ | 2 | Usage error (missing API key or project, no ontology directory, unreadable file, tree over the size limits) |
59
+ | 3 | Transport/API error (unreachable API, invalid key, inaccessible project, unexpected response) |
60
+
61
+ Both commands accept up to 2000 YAML files / 5 MB total — far above real
62
+ ontologies (a few hundred small files). Beyond that the CLI fails fast with
63
+ exit 2 before uploading anything; double-check `--base-path` if you hit it.
64
+
65
+ `upload` replaces the project's entire ontology with the uploaded tree. A
66
+ never-published project always goes live immediately on first upload (even
67
+ with `--no-publish`), matching imports from the Cassis app. Publishing is
68
+ idempotent: re-uploading content identical to the published version reports
69
+ that version instead of creating a new one, so re-running the CI job on
70
+ unchanged files is a no-op.
71
+
72
+ ### GitHub Actions example
73
+
74
+ ```yaml
75
+ jobs:
76
+ ontology-check:
77
+ runs-on: ubuntu-latest
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+ - uses: actions/setup-python@v5
81
+ with:
82
+ python-version: "3.12"
83
+ - run: pip install cassis-cli
84
+ - run: cassis ontology check
85
+ env:
86
+ CASSIS_API_KEY: ${{ secrets.CASSIS_API_KEY }}
87
+
88
+ ontology-publish:
89
+ runs-on: ubuntu-latest
90
+ if: github.ref == 'refs/heads/main'
91
+ steps:
92
+ - uses: actions/checkout@v4
93
+ - uses: actions/setup-python@v5
94
+ with:
95
+ python-version: "3.12"
96
+ - run: pip install cassis-cli
97
+ - run: cassis ontology upload
98
+ env:
99
+ CASSIS_API_KEY: ${{ secrets.CASSIS_API_KEY }}
100
+ CASSIS_PROJECT_ID: ${{ vars.CASSIS_PROJECT_ID }}
101
+ ```
102
+
103
+ ### GitLab CI example
104
+
105
+ ```yaml
106
+ ontology-check:
107
+ image: python:3.12-slim
108
+ script:
109
+ - pip install cassis-cli
110
+ - cassis ontology check
111
+ variables:
112
+ CASSIS_API_KEY: $CASSIS_API_KEY
113
+
114
+ ontology-publish:
115
+ image: python:3.12-slim
116
+ rules:
117
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
118
+ script:
119
+ - pip install cassis-cli
120
+ - cassis ontology upload
121
+ variables:
122
+ CASSIS_API_KEY: $CASSIS_API_KEY
123
+ CASSIS_PROJECT_ID: $CASSIS_PROJECT_ID
124
+ ```
@@ -1,3 +1,3 @@
1
1
  """Cassis CLI — run Cassis actions from your CI pipelines."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.0"
@@ -0,0 +1,113 @@
1
+ """Thin HTTP client for the Cassis API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ import httpx
8
+
9
+ DEFAULT_API_URL = "https://app.getcassis.com"
10
+ TIMEOUT_SECONDS = 60.0
11
+
12
+
13
+ class ApiError(Exception):
14
+ """Transport or HTTP-level failure talking to the Cassis API."""
15
+
16
+
17
+ class AuthError(ApiError):
18
+ """The API rejected the API key (401)."""
19
+
20
+
21
+ class UploadValidationError(ApiError):
22
+ """The API rejected the uploaded ontology tree as invalid (400)."""
23
+
24
+
25
+ def post_ontology_check(
26
+ *,
27
+ api_url: str,
28
+ api_key: str,
29
+ files: dict[str, str],
30
+ transport: Optional[httpx.BaseTransport] = None,
31
+ ) -> dict[str, Any]:
32
+ """POST the ontology tree to /api/ci/ontology-check and return the response body."""
33
+ url = api_url.rstrip("/") + "/api/ci/ontology-check"
34
+ try:
35
+ with httpx.Client(timeout=TIMEOUT_SECONDS, transport=transport) as client:
36
+ response = client.post(
37
+ url,
38
+ json={"files": files},
39
+ headers={"Authorization": f"Bearer {api_key}"},
40
+ )
41
+ except httpx.HTTPError as exc:
42
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
43
+
44
+ if response.status_code == 401:
45
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
46
+ if response.status_code >= 400:
47
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
48
+ try:
49
+ result = response.json()
50
+ except ValueError as exc: # json.JSONDecodeError — e.g. a proxy or portal answering HTML with a 200
51
+ raise ApiError(
52
+ f"The Cassis API at {url} returned a non-JSON response — check the API URL and any proxy in between."
53
+ ) from exc
54
+ if (
55
+ not isinstance(result, dict)
56
+ or not all(key in result for key in ("passed", "title", "summary"))
57
+ or not isinstance(result.get("findings"), list)
58
+ ):
59
+ raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
60
+ return result
61
+
62
+
63
+ def post_ontology_import(
64
+ *,
65
+ api_url: str,
66
+ api_key: str,
67
+ project_id: str,
68
+ files: dict[str, str],
69
+ publish: bool,
70
+ label: Optional[str] = None,
71
+ transport: Optional[httpx.BaseTransport] = None,
72
+ ) -> dict[str, Any]:
73
+ """POST the ontology tree to /api/ci/projects/{project_id}/ontology/import and return the response body."""
74
+ url = api_url.rstrip("/") + f"/api/ci/projects/{project_id}/ontology/import"
75
+ body: dict[str, Any] = {"files": files, "publish": publish}
76
+ if label is not None:
77
+ body["label"] = label
78
+ try:
79
+ with httpx.Client(timeout=TIMEOUT_SECONDS, transport=transport) as client:
80
+ response = client.post(
81
+ url,
82
+ json=body,
83
+ headers={"Authorization": f"Bearer {api_key}"},
84
+ )
85
+ except httpx.HTTPError as exc:
86
+ raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
87
+
88
+ if response.status_code == 401:
89
+ raise AuthError("The Cassis API rejected the API key (invalid or expired).")
90
+ if response.status_code == 400:
91
+ try:
92
+ detail = response.json().get("detail") or response.text[:500]
93
+ except ValueError:
94
+ detail = response.text[:500]
95
+ raise UploadValidationError(str(detail))
96
+ if response.status_code in (403, 404):
97
+ raise ApiError(
98
+ f"Project not found or not accessible (HTTP {response.status_code}). Check --project and that "
99
+ "the API key belongs to the project's organization and can edit it."
100
+ )
101
+ if response.status_code >= 400:
102
+ raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
103
+ try:
104
+ result = response.json()
105
+ except ValueError as exc: # json.JSONDecodeError — e.g. a proxy or portal answering HTML with a 200
106
+ raise ApiError(
107
+ f"The Cassis API at {url} returned a non-JSON response — check the API URL and any proxy in between."
108
+ ) from exc
109
+ if not isinstance(result, dict) or not all(
110
+ key in result for key in ("domain_count", "table_count", "join_count", "metric_count", "published_version")
111
+ ):
112
+ raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
113
+ return result
@@ -5,9 +5,17 @@ from __future__ import annotations
5
5
  import json
6
6
  from pathlib import Path
7
7
  from typing import Optional
8
+ from uuid import UUID
8
9
 
9
10
  import typer
10
- from cassis_cli.api import DEFAULT_API_URL, ApiError, AuthError, post_ontology_check
11
+ from cassis_cli.api import (
12
+ DEFAULT_API_URL,
13
+ ApiError,
14
+ AuthError,
15
+ UploadValidationError,
16
+ post_ontology_check,
17
+ post_ontology_import,
18
+ )
11
19
 
12
20
  app = typer.Typer(no_args_is_help=True, help="Ontology commands.")
13
21
 
@@ -46,38 +54,8 @@ def _collect_files(ontology_dir: Path) -> dict[str, str]:
46
54
  return files
47
55
 
48
56
 
49
- @app.command()
50
- def check(
51
- path: Path = typer.Argument(
52
- Path("."),
53
- help="Repository checkout root (the directory containing the ontology export path).",
54
- ),
55
- api_key: Optional[str] = typer.Option(
56
- None,
57
- "--api-key",
58
- envvar="CASSIS_API_KEY",
59
- help="Cassis API key (sk-k6-...). Create one in Organization settings -> API keys.",
60
- ),
61
- api_url: str = typer.Option(
62
- DEFAULT_API_URL,
63
- "--api-url",
64
- envvar="CASSIS_API_URL",
65
- help="Cassis API base URL.",
66
- ),
67
- base_path: str = typer.Option(
68
- DEFAULT_BASE_PATH,
69
- "--base-path",
70
- envvar="CASSIS_BASE_PATH",
71
- help="Repository directory the ontology is exported under (the project's git-sync Path setting).",
72
- ),
73
- json_output: bool = typer.Option(False, "--json", help="Print the raw JSON response."),
74
- ) -> None:
75
- """Validate the ontology files in a repository checkout.
76
-
77
- Runs the same checks as the Cassis GitHub PR check (YAML parsing,
78
- round-trip, import validation). Exits 0 when valid, 1 when validation
79
- fails, 2 on usage errors, 3 on transport/API errors.
80
- """
57
+ def _require_api_key(api_key: Optional[str]) -> str:
58
+ """Exit 2 (usage) when no API key was provided."""
81
59
  if not api_key:
82
60
  typer.secho(
83
61
  "No API key. Set CASSIS_API_KEY or pass --api-key "
@@ -86,7 +64,16 @@ def check(
86
64
  err=True,
87
65
  )
88
66
  raise typer.Exit(EXIT_USAGE)
67
+ return api_key
68
+
69
+
70
+ def _collect_tree(path: Path, base_path: str) -> "tuple[dict[str, str], str]":
71
+ """Resolve the ontology dir under the checkout and read its YAML tree.
89
72
 
73
+ Returns ``(files, normalized_base_path)``. Exits 2 (usage) on an empty or
74
+ missing dir, or a tree beyond the API request ceilings — all local checkout
75
+ problems, reported before anything is sent to the API.
76
+ """
90
77
  base_path = base_path.strip().strip("/")
91
78
  if not base_path:
92
79
  typer.secho("--base-path must not be empty.", fg=typer.colors.RED, err=True)
@@ -110,13 +97,50 @@ def check(
110
97
  total_bytes = sum(len(content.encode()) for content in files.values())
111
98
  if len(files) > MAX_FILES or total_bytes > MAX_TOTAL_BYTES:
112
99
  typer.secho(
113
- f"Ontology tree too large for the CI check: {len(files)} files / {total_bytes / (1024 * 1024):.1f} MB "
100
+ f"Ontology tree too large: {len(files)} files / {total_bytes / (1024 * 1024):.1f} MB "
114
101
  f"(limits: {MAX_FILES} files / {MAX_TOTAL_BYTES // (1024 * 1024)} MB). "
115
102
  "Check that --base-path points at the ontology directory, not a larger tree.",
116
103
  fg=typer.colors.RED,
117
104
  err=True,
118
105
  )
119
106
  raise typer.Exit(EXIT_USAGE)
107
+ return files, base_path
108
+
109
+
110
+ @app.command()
111
+ def check(
112
+ path: Path = typer.Argument(
113
+ Path("."),
114
+ help="Repository checkout root (the directory containing the ontology export path).",
115
+ ),
116
+ api_key: Optional[str] = typer.Option(
117
+ None,
118
+ "--api-key",
119
+ envvar="CASSIS_API_KEY",
120
+ help="Cassis API key (sk-k6-...). Create one in Organization settings -> API keys.",
121
+ ),
122
+ api_url: str = typer.Option(
123
+ DEFAULT_API_URL,
124
+ "--api-url",
125
+ envvar="CASSIS_API_URL",
126
+ help="Cassis API base URL.",
127
+ ),
128
+ base_path: str = typer.Option(
129
+ DEFAULT_BASE_PATH,
130
+ "--base-path",
131
+ envvar="CASSIS_BASE_PATH",
132
+ help="Repository directory the ontology is exported under (the project's git-sync Path setting).",
133
+ ),
134
+ json_output: bool = typer.Option(False, "--json", help="Print the raw JSON response."),
135
+ ) -> None:
136
+ """Validate the ontology files in a repository checkout.
137
+
138
+ Runs the same checks as the Cassis GitHub PR check (YAML parsing,
139
+ round-trip, import validation). Exits 0 when valid, 1 when validation
140
+ fails, 2 on usage errors, 3 on transport/API errors.
141
+ """
142
+ api_key = _require_api_key(api_key)
143
+ files, base_path = _collect_tree(path, base_path)
120
144
 
121
145
  try:
122
146
  result = post_ontology_check(api_url=api_url, api_key=api_key, files=files)
@@ -139,3 +163,94 @@ def check(
139
163
  typer.echo(f" {location}{finding.get('message', '')} ({finding.get('stage', '?')})")
140
164
 
141
165
  raise typer.Exit(EXIT_OK if result["passed"] else EXIT_VALIDATION_FAILED)
166
+
167
+
168
+ @app.command()
169
+ def upload(
170
+ path: Path = typer.Argument(
171
+ Path("."),
172
+ help="Repository checkout root (the directory containing the ontology export path).",
173
+ ),
174
+ project_id: str = typer.Option(
175
+ ...,
176
+ "--project",
177
+ envvar="CASSIS_PROJECT_ID",
178
+ help="Target Cassis project ID (UUID, shown in the project's URL).",
179
+ ),
180
+ api_key: Optional[str] = typer.Option(
181
+ None,
182
+ "--api-key",
183
+ envvar="CASSIS_API_KEY",
184
+ help="Cassis API key (sk-k6-...). Create one in Organization settings -> API keys.",
185
+ ),
186
+ api_url: str = typer.Option(
187
+ DEFAULT_API_URL,
188
+ "--api-url",
189
+ envvar="CASSIS_API_URL",
190
+ help="Cassis API base URL.",
191
+ ),
192
+ base_path: str = typer.Option(
193
+ DEFAULT_BASE_PATH,
194
+ "--base-path",
195
+ envvar="CASSIS_BASE_PATH",
196
+ help="Repository directory the ontology is exported under (the project's git-sync Path setting).",
197
+ ),
198
+ publish: bool = typer.Option(
199
+ True,
200
+ "--publish/--no-publish",
201
+ help="Publish the uploaded ontology immediately as a new version (default: publish).",
202
+ ),
203
+ label: Optional[str] = typer.Option(None, "--label", help="Label for the published version."),
204
+ json_output: bool = typer.Option(False, "--json", help="Print the raw JSON response."),
205
+ ) -> None:
206
+ """Upload the ontology files in a repository checkout to a Cassis project.
207
+
208
+ Replaces the project's unpublished ontology with the local tree (full
209
+ replace) and, unless --no-publish is passed, publishes it immediately as a
210
+ new version. Exits 0 on success, 1 when the tree fails validation, 2 on
211
+ usage errors, 3 on transport/API errors.
212
+ """
213
+ api_key = _require_api_key(api_key)
214
+ try:
215
+ UUID(project_id)
216
+ except ValueError:
217
+ typer.secho(f"--project must be a project ID (UUID), got {project_id!r}.", fg=typer.colors.RED, err=True)
218
+ raise typer.Exit(EXIT_USAGE)
219
+ files, base_path = _collect_tree(path, base_path)
220
+
221
+ try:
222
+ result = post_ontology_import(
223
+ api_url=api_url,
224
+ api_key=api_key,
225
+ project_id=project_id,
226
+ files=files,
227
+ publish=publish,
228
+ label=label,
229
+ )
230
+ except AuthError as exc:
231
+ typer.secho(str(exc), fg=typer.colors.RED, err=True)
232
+ raise typer.Exit(EXIT_TRANSPORT) from exc
233
+ except UploadValidationError as exc:
234
+ typer.secho("Ontology upload rejected:", fg=typer.colors.RED, bold=True, err=True)
235
+ typer.secho(str(exc), fg=typer.colors.RED, err=True)
236
+ raise typer.Exit(EXIT_VALIDATION_FAILED) from exc
237
+ except ApiError as exc:
238
+ typer.secho(str(exc), fg=typer.colors.RED, err=True)
239
+ raise typer.Exit(EXIT_TRANSPORT) from exc
240
+
241
+ if json_output:
242
+ typer.echo(json.dumps(result, indent=2))
243
+ else:
244
+ counts = (
245
+ f"{result['table_count']} tables, {result['domain_count']} domains, "
246
+ f"{result['join_count']} joins, {result['metric_count']} metrics"
247
+ )
248
+ version = result.get("published_version")
249
+ if version is not None:
250
+ typer.secho(f"✓ Ontology uploaded and published as v{version} ({counts}).", fg=typer.colors.GREEN)
251
+ else:
252
+ typer.secho(
253
+ f"✓ Ontology uploaded ({counts}). Not published — it is now the project's unpublished ontology.",
254
+ fg=typer.colors.GREEN,
255
+ )
256
+ raise typer.Exit(EXIT_OK)
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "cassis-cli"
3
- version = "0.1.0"
4
- description = "Cassis CLI — run Cassis actions (ontology validation) from your CI pipelines"
3
+ version = "0.2.0"
4
+ description = "Cassis CLI — run Cassis actions (ontology validation, upload and publish) from your CI pipelines"
5
5
  readme = "README.md"
6
6
  license = { text = "Proprietary" }
7
7
  authors = [{ name = "Cassis", email = "tech.admin@getcassis.com" }]
cassis_cli-0.1.0/PKG-INFO DELETED
@@ -1,98 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: cassis-cli
3
- Version: 0.1.0
4
- Summary: Cassis CLI — run Cassis actions (ontology validation) from your CI pipelines
5
- License: Proprietary
6
- Keywords: cassis,ontology,ci,text-to-sql
7
- Author: Cassis
8
- Author-email: tech.admin@getcassis.com
9
- Requires-Python: >=3.10,<4.0
10
- Classifier: License :: Other/Proprietary License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: Python :: 3.14
17
- Requires-Dist: httpx (>=0.24,<1.0)
18
- Requires-Dist: typer (>=0.12,<1.0)
19
- Description-Content-Type: text/markdown
20
-
21
- # Cassis CLI
22
-
23
- Run Cassis actions from your CI pipelines. The first command, `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.
24
-
25
- ## Install
26
-
27
- ```bash
28
- pip install cassis-cli
29
- ```
30
-
31
- ## Setup
32
-
33
- 1. Create an API key in Cassis under **Organization settings → API keys** (keys start with `sk-k6-`).
34
- 2. Store it as a CI secret and expose it as `CASSIS_API_KEY`.
35
-
36
- ## Usage
37
-
38
- ```bash
39
- # From the root of a repository synced with Cassis (contains the ontology export directory, cassis/ by default):
40
- cassis ontology check
41
-
42
- # Or point at the checkout explicitly:
43
- cassis ontology check /path/to/checkout
44
-
45
- # Machine-readable output:
46
- cassis ontology check --json
47
- ```
48
-
49
- Configuration (flags take precedence over env vars):
50
-
51
- | Flag | Env var | Default |
52
- | ----------- | ---------------- | --------------------------- |
53
- | `--api-key` | `CASSIS_API_KEY` | — (required) |
54
- | `--api-url` | `CASSIS_API_URL` | `https://app.getcassis.com` |
55
- | `--base-path` | `CASSIS_BASE_PATH` | `cassis` — must match the project's git-sync "Path" setting |
56
-
57
- ### Exit codes
58
-
59
- | Code | Meaning |
60
- | ---- | ------------------------------------------------------------------------------ |
61
- | 0 | Ontology is valid |
62
- | 1 | Validation failed (findings printed) |
63
- | 2 | Usage error (missing API key, no ontology directory, unreadable file, tree over the size limits) |
64
- | 3 | Transport/API error (unreachable API, invalid key, unexpected response) |
65
-
66
- The check accepts up to 2000 YAML files / 5 MB total — far above real
67
- ontologies (a few hundred small files). Beyond that the CLI fails fast with
68
- exit 2 before uploading anything; double-check `--base-path` if you hit it.
69
-
70
- ### GitHub Actions example
71
-
72
- ```yaml
73
- jobs:
74
- ontology-check:
75
- runs-on: ubuntu-latest
76
- steps:
77
- - uses: actions/checkout@v4
78
- - uses: actions/setup-python@v5
79
- with:
80
- python-version: "3.12"
81
- - run: pip install cassis-cli
82
- - run: cassis ontology check
83
- env:
84
- CASSIS_API_KEY: ${{ secrets.CASSIS_API_KEY }}
85
- ```
86
-
87
- ### GitLab CI example
88
-
89
- ```yaml
90
- ontology-check:
91
- image: python:3.12-slim
92
- script:
93
- - pip install cassis-cli
94
- - cassis ontology check
95
- variables:
96
- CASSIS_API_KEY: $CASSIS_API_KEY
97
- ```
98
-
@@ -1,77 +0,0 @@
1
- # Cassis CLI
2
-
3
- Run Cassis actions from your CI pipelines. The first command, `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.
4
-
5
- ## Install
6
-
7
- ```bash
8
- pip install cassis-cli
9
- ```
10
-
11
- ## Setup
12
-
13
- 1. Create an API key in Cassis under **Organization settings → API keys** (keys start with `sk-k6-`).
14
- 2. Store it as a CI secret and expose it as `CASSIS_API_KEY`.
15
-
16
- ## Usage
17
-
18
- ```bash
19
- # From the root of a repository synced with Cassis (contains the ontology export directory, cassis/ by default):
20
- cassis ontology check
21
-
22
- # Or point at the checkout explicitly:
23
- cassis ontology check /path/to/checkout
24
-
25
- # Machine-readable output:
26
- cassis ontology check --json
27
- ```
28
-
29
- Configuration (flags take precedence over env vars):
30
-
31
- | Flag | Env var | Default |
32
- | ----------- | ---------------- | --------------------------- |
33
- | `--api-key` | `CASSIS_API_KEY` | — (required) |
34
- | `--api-url` | `CASSIS_API_URL` | `https://app.getcassis.com` |
35
- | `--base-path` | `CASSIS_BASE_PATH` | `cassis` — must match the project's git-sync "Path" setting |
36
-
37
- ### Exit codes
38
-
39
- | Code | Meaning |
40
- | ---- | ------------------------------------------------------------------------------ |
41
- | 0 | Ontology is valid |
42
- | 1 | Validation failed (findings printed) |
43
- | 2 | Usage error (missing API key, no ontology directory, unreadable file, tree over the size limits) |
44
- | 3 | Transport/API error (unreachable API, invalid key, unexpected response) |
45
-
46
- The check accepts up to 2000 YAML files / 5 MB total — far above real
47
- ontologies (a few hundred small files). Beyond that the CLI fails fast with
48
- exit 2 before uploading anything; double-check `--base-path` if you hit it.
49
-
50
- ### GitHub Actions example
51
-
52
- ```yaml
53
- jobs:
54
- ontology-check:
55
- runs-on: ubuntu-latest
56
- steps:
57
- - uses: actions/checkout@v4
58
- - uses: actions/setup-python@v5
59
- with:
60
- python-version: "3.12"
61
- - run: pip install cassis-cli
62
- - run: cassis ontology check
63
- env:
64
- CASSIS_API_KEY: ${{ secrets.CASSIS_API_KEY }}
65
- ```
66
-
67
- ### GitLab CI example
68
-
69
- ```yaml
70
- ontology-check:
71
- image: python:3.12-slim
72
- script:
73
- - pip install cassis-cli
74
- - cassis ontology check
75
- variables:
76
- CASSIS_API_KEY: $CASSIS_API_KEY
77
- ```
@@ -1,56 +0,0 @@
1
- """Thin HTTP client for the Cassis API."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Any, Optional
6
-
7
- import httpx
8
-
9
- DEFAULT_API_URL = "https://app.getcassis.com"
10
- TIMEOUT_SECONDS = 60.0
11
-
12
-
13
- class ApiError(Exception):
14
- """Transport or HTTP-level failure talking to the Cassis API."""
15
-
16
-
17
- class AuthError(ApiError):
18
- """The API rejected the API key (401)."""
19
-
20
-
21
- def post_ontology_check(
22
- *,
23
- api_url: str,
24
- api_key: str,
25
- files: dict[str, str],
26
- transport: Optional[httpx.BaseTransport] = None,
27
- ) -> dict[str, Any]:
28
- """POST the ontology tree to /api/ci/ontology-check and return the response body."""
29
- url = api_url.rstrip("/") + "/api/ci/ontology-check"
30
- try:
31
- with httpx.Client(timeout=TIMEOUT_SECONDS, transport=transport) as client:
32
- response = client.post(
33
- url,
34
- json={"files": files},
35
- headers={"Authorization": f"Bearer {api_key}"},
36
- )
37
- except httpx.HTTPError as exc:
38
- raise ApiError(f"Could not reach the Cassis API at {url}: {exc}") from exc
39
-
40
- if response.status_code == 401:
41
- raise AuthError("The Cassis API rejected the API key (invalid or expired).")
42
- if response.status_code >= 400:
43
- raise ApiError(f"Cassis API returned HTTP {response.status_code}: {response.text[:500]}")
44
- try:
45
- result = response.json()
46
- except ValueError as exc: # json.JSONDecodeError — e.g. a proxy or portal answering HTML with a 200
47
- raise ApiError(
48
- f"The Cassis API at {url} returned a non-JSON response — check the API URL and any proxy in between."
49
- ) from exc
50
- if (
51
- not isinstance(result, dict)
52
- or not all(key in result for key in ("passed", "title", "summary"))
53
- or not isinstance(result.get("findings"), list)
54
- ):
55
- raise ApiError(f"Unexpected response shape from the Cassis API at {url}.")
56
- return result