Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,31 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: "Install dependencies"
run: pip install ".[dev]"

- name: Run tests
run: pytest --cov=. --cov-report html --cov-report term --cov-fail-under=25

schema-compatibility:
name: Check schema compatibility with demo server
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Install dependencies
run: pip install ".[dev]"

- name: Check schema compatibility
run: python scripts/check_schema_compatibility.py

conda-build:
name: Conda build check
runs-on: ubuntu-latest
Expand Down
6 changes: 2 additions & 4 deletions openhexa/cli/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from jinja2 import Template

from openhexa.cli.settings import settings
from openhexa.graphql import BaseOpenHexaClient
from openhexa.graphql import BUNDLED_SCHEMA_PATH, BaseOpenHexaClient
from openhexa.sdk.pipelines import get_local_workspace_config
from openhexa.sdk.pipelines.runtime import get_pipeline
from openhexa.utils import create_requests_session, stringcase
Expand Down Expand Up @@ -134,9 +134,7 @@ def _detect_graphql_breaking_changes_if_needed(token):

def _detect_graphql_breaking_changes(token):
"""Detect breaking changes between the schema referenced in the SDK and the server using graphql-core."""
stored_schema_obj = build_schema(
(Path(__file__).parent.parent / "graphql" / "schema.generated.graphql").open().read()
)
stored_schema_obj = build_schema(BUNDLED_SCHEMA_PATH.read_text())
server_schema_obj = build_client_schema(
_query_graphql(get_introspection_query(input_value_deprecation=True), token=token)
)
Expand Down
3 changes: 3 additions & 0 deletions openhexa/graphql/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""GraphQL package."""

from pathlib import Path

from .base_openhexa_client import BaseOpenHexaClient # noqa: F401 -> Expose base client class
from .graphql_client import * # noqa: F403 -> Expose autogenerated types

BUNDLED_SCHEMA_PATH = Path(__file__).parent / "schema.generated.graphql"
76 changes: 76 additions & 0 deletions scripts/check_schema_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python
"""
Check for breaking changes between the SDK's bundled GraphQL schema and live server schemas for our production and demo environments.

Exits 0 regardless of outcome. Emits ::warning:: annotations when running in GitHub Actions
so breaking changes are visible without affecting the job status.
"""

import os
from pathlib import Path

import requests
from graphql import build_client_schema, build_schema, get_introspection_query
from graphql.utilities import find_breaking_changes

from openhexa.graphql import BUNDLED_SCHEMA_PATH

GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true"
REPO_ROOT = Path(__file__).parent.parent
SCHEMA_RELATIVE_PATH = BUNDLED_SCHEMA_PATH.relative_to(REPO_ROOT)

URLS = [
"https://api.demo.openhexa.org",
"https://api.openhexa.org",
]


def fetch_server_schema(graphql_url: str):
"""Fetch the live GraphQL schema from the server via introspection."""
response = requests.post(
graphql_url,
json={"query": get_introspection_query(input_value_deprecation=True)},
timeout=30,
)
response.raise_for_status()
body = response.json()
if "errors" in body:
raise RuntimeError(f"Introspection query returned errors: {body['errors']}")
return build_client_schema(body["data"])


def check_url(stored_schema, url: str) -> list:
"""Check breaking changes for a single server URL. Returns the list of breaking changes."""
graphql_url = url.rstrip("/") + "/graphql/"
server_schema = fetch_server_schema(graphql_url)

breaking_changes = find_breaking_changes(stored_schema, server_schema)
if breaking_changes:
print(f" ⚠️ {len(breaking_changes)} breaking change(s) detected:")
for change in breaking_changes:
print(f" - {change.description}")
if GITHUB_ACTIONS:
print(f"::warning file={SCHEMA_RELATIVE_PATH},line=1,title=GraphQL schema breaking change ({url})::{change.description}")
else:
print(" ✅ No breaking changes detected.")
return breaking_changes


def main():
"""Execute main function."""
stored_schema = build_schema(BUNDLED_SCHEMA_PATH.read_text())

all_breaking_changes = []
for url in URLS:
all_breaking_changes.extend(check_url(stored_schema, url))

if all_breaking_changes:
print(
"\nThe server schema has diverged from openhexa/graphql/schema.generated.graphql."
"\nUpdate the bundled schema by copying the latest schema from the OpenHEXA monorepo"
" and re-running: python -m ariadne_codegen"
)


if __name__ == "__main__":
main()
Loading