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
2 changes: 0 additions & 2 deletions .flake8

This file was deleted.

29 changes: 29 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Lint

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff

- name: Run ruff linter
run: ruff check .

- name: Run ruff formatter check
run: ruff format --check .
40 changes: 40 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
default_stages: [pre-commit]

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.5
hooks:
- id: ruff
name: Check 'ruff linter' passes
args: [--fix, --exit-non-zero-on-fix]

- id: ruff-format
name: Check 'ruff format' passes

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
name: Remove trailing whitespace
args: [--markdown-linebreak-ext=md]

- id: end-of-file-fixer
name: Check files end in a newline character

- id: check-ast
name: Check files parse as valid Python

- id: check-json
name: Validate JSON syntax

- id: check-yaml
name: Validate YAML syntax

- id: check-merge-conflict
name: Check for merge conflict markers

- id: debug-statements
name: Check for debug breakpoints

- id: check-added-large-files
name: Check for large files
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,35 @@ The lib requires python3.6 or greater to work. In order to install it run
$ python3 -m pip install graphql-schema-diff
```

## Development Setup
If you want to contribute to this project, follow these steps to set up your development environment:

```bash
# Clone the repository
$ git clone https://github.com/octoenergy/graphql-schema-diff.git
$ cd graphql-schema-diff

# Install the package in development mode with dev dependencies
$ pip install -e ".[dev]"

# Install pre-commit hooks (recommended)
$ pre-commit install
```

The pre-commit hooks will automatically run `ruff` linting and formatting checks before each commit.
You can also run the checks manually:

```bash
# Run all pre-commit hooks on all files
$ pre-commit run --all-files

# Run ruff linter
$ ruff check .

# Run ruff formatter
$ ruff format .
```

## Usage
You can use this package as a lib or as a CLI. You can choose what better suits your needs

Expand Down
11 changes: 6 additions & 5 deletions pyproject.toml

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to modify some of the metadata in this file eg the version number, author, homepage ect to avoid confusion with the forked repo

Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ build-backend = "hatchling.build"
name = "graphql-schema-diff"
description = "Compare GraphQL Schemas"
readme = "README.md"
version = "1.2.4"
version = "1.2.4+kraken1"
authors = [
{ name = "Nahuel Ambrosini", email = "[email protected]" }
{ name = "Nahuel Ambrosini", email = "[email protected]" },
{ name = "Mattia Baldari", email = "[email protected]" }
Copy link

@TomasBayer TomasBayer Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐼 I know it's only temporary and not really important, but you'd usually keep the original author in here. authors is a list after all 😉

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really used to this, good you told me, didn't mean to be rude 😟

]
dependencies = [
"graphql-core>=3.0.1",
Expand All @@ -26,18 +27,18 @@ license = "GPL-3.0-or-later"
schemadiff = "schemadiff.__main__:cli"

[project.urls]
Homepage = "https://github.com/Ambro17/graphql-schema-diff"
Homepage = "https://github.com/octoenergy/graphql-schema-diff/"
Documentation = "https://ambro17.github.io/graphql-schema-diff/"

[project.optional-dependencies]
dev = [
"pytest",
"flake8",
"pytest",
"pytest-cov",
"codecov",
"pdoc3==0.9.1",
"hatch",
"ruff",
"pre-commit",
]

[tool.hatch.build.targets.wheel]
Expand Down
74 changes: 74 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
target-version = "py313"
line-length = 99
extend-exclude = [
".DS_Store",
".pytest_cache",
"__pycache__",
]

[format]
docstring-code-format = true

[lint]
select = ["ALL"]
ignore = [
"A001", # https://docs.astral.sh/ruff/rules/builtin-variable-shadowing/
"A002", # https://docs.astral.sh/ruff/rules/builtin-argument-shadowing/
"ANN", # Skip all type annotation rules
"ARG001", # https://docs.astral.sh/ruff/rules/unused-function-argument/
"ARG002", # https://docs.astral.sh/ruff/rules/unused-method-argument/
"C901", # https://docs.astral.sh/ruff/rules/complex-structure/
"COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/
"D100", # https://docs.astral.sh/ruff/rules/undocumented-public-module/
"D101", # https://docs.astral.sh/ruff/rules/undocumented-public-class/
"D102", # https://docs.astral.sh/ruff/rules/undocumented-public-method/
"D103", # https://docs.astral.sh/ruff/rules/undocumented-public-function/
"D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/
"D105", # https://docs.astral.sh/ruff/rules/undocumented-magic-method/
"D107", # https://docs.astral.sh/ruff/rules/undocumented-public-init/
"D203", # https://docs.astral.sh/ruff/rules/incorrect-blank-line-before-class/
"D205", # https://docs.astral.sh/ruff/rules/blank-line-after-summary/
"D212", # https://docs.astral.sh/ruff/rules/multi-line-summary-first-line/
"D400", # https://docs.astral.sh/ruff/rules/ends-in-period/
"D401", # https://docs.astral.sh/ruff/rules/non-imperative-mood/
"E741", # https://docs.astral.sh/ruff/rules/ambiguous-variable-name/
"E501", # https://docs.astral.sh/ruff/rules/line-too-long/
"EM101", # https://docs.astral.sh/ruff/rules/raw-string-in-exception/
"EM102", # https://docs.astral.sh/ruff/rules/f-string-in-exception/
"ERA001", # https://docs.astral.sh/ruff/rules/commented-out-code/
"FBT001", # https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument/
"FBT002", # https://docs.astral.sh/ruff/rules/boolean-default-value-positional-argument/
"FBT003", # https://docs.astral.sh/ruff/rules/boolean-positional-value-in-call/
"FIX002", # https://docs.astral.sh/ruff/rules/line-contains-todo/
"ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/
"ISC002", # https://docs.astral.sh/ruff/rules/multi-line-implicit-string-concatenation/
"N806", # https://docs.astral.sh/ruff/rules/non-lowercase-variable-in-function/
"N818", # https://docs.astral.sh/ruff/rules/error-suffix-on-exception-name/
"PLR0911", # https://docs.astral.sh/ruff/rules/too-many-return-statements/
"PLR0912", # https://docs.astral.sh/ruff/rules/too-many-branches/
"PLR0913", # https://docs.astral.sh/ruff/rules/too-many-arguments/
"PLR0915", # https://docs.astral.sh/ruff/rules/too-many-statements/
"PLR2004", # https://docs.astral.sh/ruff/rules/magic-value-comparison/
"PT018", # https://docs.astral.sh/ruff/rules/pytest-composite-assertion/
"PTH123", # https://docs.astral.sh/ruff/rules/builtin-open/
"RET504", # https://docs.astral.sh/ruff/rules/unnecessary-assign/
"RUF012", # https://docs.astral.sh/ruff/rules/mutable-class-default/
"RUF043", # https://docs.astral.sh/ruff/rules/pytest-raises-ambiguous-pattern/
"S101", # https://docs.astral.sh/ruff/rules/assert/
"S324", # https://docs.astral.sh/ruff/rules/hashlib-insecure-hash-function/
"SIM108", # https://docs.astral.sh/ruff/rules/if-else-block-instead-of-if-exp/
"TD002", # https://docs.astral.sh/ruff/rules/missing-todo-author/
"TD003", # https://docs.astral.sh/ruff/rules/missing-todo-link/
"TRY003", # https://docs.astral.sh/ruff/rules/raise-vanilla-args/
"UP035", # https://docs.astral.sh/ruff/rules/deprecated-import/
]

[lint.per-file-ignores]
"__init__.py" = [
"F401", # https://docs.astral.sh/ruff/rules/unused-import/
]
"**/tests/**" = [
"S105", # https://docs.astral.sh/ruff/rules/hardcoded-password-string/
"S106", # https://docs.astral.sh/ruff/rules/hardcoded-password-func-arg/
"SLF001", # https://docs.astral.sh/ruff/rules/private-member-access/
]
30 changes: 17 additions & 13 deletions schemadiff/__init__.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,48 @@
from typing import Union, List
from typing import List, Union

from graphql import GraphQLSchema as GQLSchema, is_schema
from graphql import GraphQLSchema as GQLSchema
from graphql import is_schema

from schemadiff.changes import Change
from schemadiff.diff.schema import Schema
from schemadiff.formatting import format_diff, print_diff
from schemadiff.schema_loader import SchemaLoader
from schemadiff.formatting import print_diff, format_diff
from schemadiff.validation import validate_changes


SDL = str # Alias for string describing schema through schema definition language


def diff(old_schema: Union[SDL, GQLSchema], new_schema: Union[SDL, GQLSchema]) -> List[Change]:
"""Compare two graphql schemas highlighting dangerous and breaking changes.
def diff(old_schema: SDL | GQLSchema, new_schema: SDL | GQLSchema) -> list[Change]:
"""
Compare two graphql schemas highlighting dangerous and breaking changes.

Returns:
changes (List[Change]): List of differences between both schemas with details about each change

"""
first = SchemaLoader.from_sdl(old_schema) if not is_schema(old_schema) else old_schema
second = SchemaLoader.from_sdl(new_schema) if not is_schema(new_schema) else new_schema
return Schema(first, second).diff()


def diff_from_file(schema_file: str, other_schema_file: str):
"""Compare two graphql schema files highlighting dangerous and breaking changes.
"""
Compare two graphql schema files highlighting dangerous and breaking changes.

Returns:
changes (List[Change]): List of differences between both schemas with details about each change

"""
first = SchemaLoader.from_file(schema_file)
second = SchemaLoader.from_file(other_schema_file)
return Schema(first, second).diff()


__all__ = [
'diff',
'diff_from_file',
'format_diff',
'print_diff',
'validate_changes',
'Change',
"Change",
"diff",
"diff_from_file",
"format_diff",
"print_diff",
"validate_changes",
]
81 changes: 52 additions & 29 deletions schemadiff/__main__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import sys
import argparse
import sys

from schemadiff.allow_list import read_allowed_changes
from schemadiff.diff.schema import Schema
from schemadiff.schema_loader import SchemaLoader
from schemadiff.formatting import print_diff, print_json
from schemadiff.schema_loader import SchemaLoader
from schemadiff.validation import rules_list, validate_changes


Expand All @@ -14,32 +14,55 @@ def cli():


def parse_args(arguments):
parser = argparse.ArgumentParser(description='Schema comparator')
parser.add_argument('-o', '--old-schema',
dest='old_schema',
type=argparse.FileType('r', encoding='UTF-8'),
help='Path to old graphql schema file',
required=True)
parser.add_argument('-n', '--new-schema',
dest='new_schema',
type=argparse.FileType('r', encoding='UTF-8'),
help='Path to new graphql schema file',
required=True)
parser.add_argument('-j', '--as-json',
action='store_true',
help='Output a detailed summary of changes in json format',
required=False)
parser.add_argument('-a', '--allow-list',
type=argparse.FileType('r', encoding='UTF-8'),
help='Path to the allowed list of changes')
parser.add_argument('-t', '--tolerant',
action='store_true',
help="Tolerant mode. Error out only if there's a breaking change but allow dangerous changes")
parser.add_argument('-s', '--strict',
action='store_true',
help="Strict mode. Error out on dangerous and breaking changes.")
parser.add_argument('-r', '--validation-rules', choices=rules_list(), nargs='*',
help="Evaluate rules mode. Error out on changes that fail some validation rule.")
parser = argparse.ArgumentParser(description="Schema comparator")
parser.add_argument(
"-o",
"--old-schema",
dest="old_schema",
type=argparse.FileType("r", encoding="UTF-8"),
help="Path to old graphql schema file",
required=True,
)
parser.add_argument(
"-n",
"--new-schema",
dest="new_schema",
type=argparse.FileType("r", encoding="UTF-8"),
help="Path to new graphql schema file",
required=True,
)
parser.add_argument(
"-j",
"--as-json",
action="store_true",
help="Output a detailed summary of changes in json format",
required=False,
)
parser.add_argument(
"-a",
"--allow-list",
type=argparse.FileType("r", encoding="UTF-8"),
help="Path to the allowed list of changes",
)
parser.add_argument(
"-t",
"--tolerant",
action="store_true",
help="Tolerant mode. Error out only if there's a breaking change but allow dangerous changes",
)
parser.add_argument(
"-s",
"--strict",
action="store_true",
help="Strict mode. Error out on dangerous and breaking changes.",
)
parser.add_argument(
"-r",
"--validation-rules",
choices=rules_list(),
nargs="*",
help="Evaluate rules mode. Error out on changes that fail some validation rule.",
)

return parser.parse_args(arguments)

Expand Down Expand Up @@ -79,5 +102,5 @@ def exit_code(changes, strict, some_change_is_restricted, tolerant) -> int:
return exit_code


if __name__ == '__main__':
if __name__ == "__main__":
sys.exit(cli())
Loading