cassis-cli 0.1.0__py3-none-any.whl

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/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Cassis CLI — run Cassis actions from your CI pipelines."""
2
+
3
+ __version__ = "0.1.0"
cassis_cli/api.py ADDED
@@ -0,0 +1,56 @@
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
cassis_cli/main.py ADDED
@@ -0,0 +1,23 @@
1
+ """Entry point for the `cassis` CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+ from cassis_cli import __version__
7
+ from cassis_cli.ontology import app as ontology_app
8
+
9
+ app = typer.Typer(
10
+ no_args_is_help=True,
11
+ help="Cassis CLI — run Cassis actions from your CI pipelines.",
12
+ )
13
+ app.add_typer(ontology_app, name="ontology")
14
+
15
+
16
+ @app.command()
17
+ def version() -> None:
18
+ """Print the CLI version."""
19
+ typer.echo(__version__)
20
+
21
+
22
+ if __name__ == "__main__":
23
+ app()
cassis_cli/ontology.py ADDED
@@ -0,0 +1,141 @@
1
+ """`cassis ontology` subcommands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import typer
10
+ from cassis_cli.api import DEFAULT_API_URL, ApiError, AuthError, post_ontology_check
11
+
12
+ app = typer.Typer(no_args_is_help=True, help="Ontology commands.")
13
+
14
+ # Repository directory the ontology tree is exported under. Must match the
15
+ # project's git-sync "Path" setting in Cassis (default "cassis").
16
+ DEFAULT_BASE_PATH = "cassis"
17
+
18
+ # Exit codes (documented in the README; stable contract for CI scripts).
19
+ EXIT_OK = 0
20
+ EXIT_VALIDATION_FAILED = 1
21
+ EXIT_USAGE = 2
22
+ EXIT_TRANSPORT = 3
23
+
24
+ # Request ceilings of POST /api/ci/ontology-check, mirrored so oversized trees
25
+ # fail fast with a clear message before any upload. Source of truth:
26
+ # backend/app/schemas/ci.py (the server's 422 remains the backstop).
27
+ MAX_FILES = 2000
28
+ MAX_TOTAL_BYTES = 5 * 1024 * 1024
29
+
30
+
31
+ def _collect_files(ontology_dir: Path) -> dict[str, str]:
32
+ """Read every YAML file under the ontology dir, keyed by posix relpath.
33
+
34
+ Exits 2 (usage) on an unreadable or non-UTF-8 file — a local checkout
35
+ problem, reported before anything is sent to the API.
36
+ """
37
+ files: dict[str, str] = {}
38
+ for pattern in ("**/*.yml", "**/*.yaml"):
39
+ for file in sorted(ontology_dir.glob(pattern)):
40
+ if file.is_file():
41
+ try:
42
+ files[file.relative_to(ontology_dir).as_posix()] = file.read_text(encoding="utf-8")
43
+ except (UnicodeDecodeError, OSError) as exc:
44
+ typer.secho(f"Cannot read {file}: {exc}", fg=typer.colors.RED, err=True)
45
+ raise typer.Exit(EXIT_USAGE) from exc
46
+ return files
47
+
48
+
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
+ """
81
+ if not api_key:
82
+ typer.secho(
83
+ "No API key. Set CASSIS_API_KEY or pass --api-key "
84
+ "(create one in Cassis under Organization settings -> API keys).",
85
+ fg=typer.colors.RED,
86
+ err=True,
87
+ )
88
+ raise typer.Exit(EXIT_USAGE)
89
+
90
+ base_path = base_path.strip().strip("/")
91
+ if not base_path:
92
+ typer.secho("--base-path must not be empty.", fg=typer.colors.RED, err=True)
93
+ raise typer.Exit(EXIT_USAGE)
94
+
95
+ ontology_dir = path / Path(base_path)
96
+ if not ontology_dir.is_dir():
97
+ typer.secho(
98
+ f"No {base_path}/ directory found under {path}. "
99
+ "If the project exports to a custom path, pass it with --base-path (or CASSIS_BASE_PATH).",
100
+ fg=typer.colors.RED,
101
+ err=True,
102
+ )
103
+ raise typer.Exit(EXIT_USAGE)
104
+
105
+ files = _collect_files(ontology_dir)
106
+ if not files:
107
+ typer.secho(f"No YAML files found under {ontology_dir}.", fg=typer.colors.RED, err=True)
108
+ raise typer.Exit(EXIT_USAGE)
109
+
110
+ total_bytes = sum(len(content.encode()) for content in files.values())
111
+ if len(files) > MAX_FILES or total_bytes > MAX_TOTAL_BYTES:
112
+ typer.secho(
113
+ f"Ontology tree too large for the CI check: {len(files)} files / {total_bytes / (1024 * 1024):.1f} MB "
114
+ f"(limits: {MAX_FILES} files / {MAX_TOTAL_BYTES // (1024 * 1024)} MB). "
115
+ "Check that --base-path points at the ontology directory, not a larger tree.",
116
+ fg=typer.colors.RED,
117
+ err=True,
118
+ )
119
+ raise typer.Exit(EXIT_USAGE)
120
+
121
+ try:
122
+ result = post_ontology_check(api_url=api_url, api_key=api_key, files=files)
123
+ except AuthError as exc:
124
+ typer.secho(str(exc), fg=typer.colors.RED, err=True)
125
+ raise typer.Exit(EXIT_TRANSPORT) from exc
126
+ except ApiError as exc:
127
+ typer.secho(str(exc), fg=typer.colors.RED, err=True)
128
+ raise typer.Exit(EXIT_TRANSPORT) from exc
129
+
130
+ if json_output:
131
+ typer.echo(json.dumps(result, indent=2))
132
+ elif result["passed"]:
133
+ typer.secho(f"✓ {result['summary']}", fg=typer.colors.GREEN)
134
+ else:
135
+ typer.secho(result["title"], fg=typer.colors.RED, bold=True)
136
+ typer.echo(result["summary"])
137
+ for finding in result["findings"]:
138
+ location = f"{base_path}/{finding.get('path')}: " if finding.get("path") else ""
139
+ typer.echo(f" {location}{finding.get('message', '')} ({finding.get('stage', '?')})")
140
+
141
+ raise typer.Exit(EXIT_OK if result["passed"] else EXIT_VALIDATION_FAILED)
@@ -0,0 +1,98 @@
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
+
@@ -0,0 +1,8 @@
1
+ cassis_cli/__init__.py,sha256=hJhksUVtIGULBAvWH4nYAhF_jhzyF6kdju2sHjlRvqI,87
2
+ cassis_cli/api.py,sha256=9Owpt3gRYUOgxcEP1hp50LF1zQ0ZITLpv3919hbsupk,1921
3
+ cassis_cli/main.py,sha256=w256-qwyygVNiHLhVB4CHEiTrEujG03-BXLv7KMzNoY,480
4
+ cassis_cli/ontology.py,sha256=gVw7SqKz-TTCKjC29doy2bONmbU8ts8YQ_KcK_6HX-M,5408
5
+ cassis_cli-0.1.0.dist-info/METADATA,sha256=nZIM7uVi9VIAiPbgHMuZwHwBkHHkkze_laCt1ts5CEg,3361
6
+ cassis_cli-0.1.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
7
+ cassis_cli-0.1.0.dist-info/entry_points.txt,sha256=G0RioVCAagpy_yI1oDl3ty_v0I7kxC5Tlr-ofIKqgOU,46
8
+ cassis_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.3.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ cassis=cassis_cli.main:app
3
+