-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcheck_schema_compatibility.py
More file actions
86 lines (69 loc) · 3.14 KB
/
Copy pathcheck_schema_compatibility.py
File metadata and controls
86 lines (69 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env python
"""
Check for breaking changes between the SDK's bundled GraphQL schema and the production server schema.
Exits with code 1 when breaking changes are found. Emits ::warning:: annotations when running
in GitHub Actions so breaking changes are visible in the PR.
"""
import os
import sys
from pathlib import Path
import requests
from graphql import GraphQLSchema, build_client_schema, build_schema, get_introspection_query
from graphql.type import specified_directives
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
try:
SCHEMA_RELATIVE_PATH = BUNDLED_SCHEMA_PATH.relative_to(REPO_ROOT)
except ValueError:
# When installed as a package, the schema lives in site-packages, not the repo.
SCHEMA_RELATIVE_PATH = Path("openhexa/graphql/schema.generated.graphql")
PRODUCTION_URL = "https://api.openhexa.org"
SPEC_DIRECTIVE_NAMES = {directive.name for directive in specified_directives}
def strip_spec_directives(schema: GraphQLSchema) -> GraphQLSchema:
"""Remove built-in spec directives (@deprecated, @skip, ...) so the diff only covers directives the API owns.
Built-in directives vary with the graphql-core version on each side of the comparison
and produce false-positive breaking changes.
"""
kwargs = schema.to_kwargs()
kwargs["directives"] = tuple(d for d in kwargs["directives"] if d.name not in SPEC_DIRECTIVE_NAMES)
return GraphQLSchema(**kwargs)
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 main():
"""Execute main function."""
stored_schema = build_schema(BUNDLED_SCHEMA_PATH.read_text(), assume_valid_sdl=True)
server_schema = fetch_server_schema(f"{PRODUCTION_URL}/graphql/")
breaking_changes = find_breaking_changes(
strip_spec_directives(stored_schema),
strip_spec_directives(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 ({PRODUCTION_URL})::{change.description}"
)
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"
)
sys.exit(1)
else:
print("✅ No breaking changes detected.")
if __name__ == "__main__":
main()