From 641eccd8dae6febaa7bd9dd2d0387701e85ffccb Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Sun, 24 Jul 2022 03:41:10 +0300 Subject: [PATCH 1/5] Add tests for all GraphQL spec features --- tests/test_arguments.py | 53 ++++++++++ tests/test_custom_scalar_types.py | 80 +++++++++++++++ tests/test_enumeration_types.py | 76 +++++++++++++++ tests/test_fields.py | 89 +++++++++++++++++ tests/test_inline_fragments.py | 98 +++++++++++++++++++ tests/test_operation_name.py | 20 ++++ tests/test_queries.py | 16 +++ tests/test_variables.py | 155 ++++++++++++++++++++++++++++++ 8 files changed, 587 insertions(+) create mode 100644 tests/test_arguments.py create mode 100644 tests/test_custom_scalar_types.py create mode 100644 tests/test_enumeration_types.py create mode 100644 tests/test_fields.py create mode 100644 tests/test_inline_fragments.py create mode 100644 tests/test_operation_name.py create mode 100644 tests/test_queries.py create mode 100644 tests/test_variables.py diff --git a/tests/test_arguments.py b/tests/test_arguments.py new file mode 100644 index 0000000..92e3aae --- /dev/null +++ b/tests/test_arguments.py @@ -0,0 +1,53 @@ +from pydantic import Field + +from pygraphic import GQLQuery + + +def test_creation_of_query_with_field_arguments(): + class Query(GQLQuery): + i: int = Field(ia=0) + f: float = Field(fa=0.0) + s: str = Field(sa="") + b: bool = Field(ba=False) + + Query(i=0, f=0.0, s="", b=False) + + +def test_generation_of_query_with_one_field_argument(): + class Query(GQLQuery): + i: int = Field(ia=0) + f: float = Field(fa=0.0) + s: str = Field(sa="foo") + b: bool = Field(ba=False) + + gql = Query.get_query_string(include_name=False) + assert gql == "\n".join( + ( + "query {", + " i(ia: 0)", + " f(fa: 0.0)", + ' s(sa: "foo")', + " b(ba: false)", + "}", + ) + ) + + +def test_generation_of_query_with_two_field_arguments(): + class Query(GQLQuery): + i: int = Field(ia=0, ib=1) + f: float = Field(fa=0.0, fb=1.1) + s: str = Field(sa="foo", sb="bar") + b: bool = Field(ba=False, bb=True) + + gql = Query.get_query_string(include_name=False) + assert gql == "\n".join( + ( + "query {", + " i(ia: 0, ib: 1)", + " f(fa: 0.0, fb: 1.1)", + ' s(sa: "foo", sb: "bar")', + " b(ba: false, bb: true)", + "}", + ) + ) diff --git a/tests/test_custom_scalar_types.py b/tests/test_custom_scalar_types.py new file mode 100644 index 0000000..6a24f46 --- /dev/null +++ b/tests/test_custom_scalar_types.py @@ -0,0 +1,80 @@ +from datetime import datetime +from uuid import UUID + +import pytest +from pydantic import Field + +from pygraphic import GQLQuery, GQLType, GQLVariables +from pygraphic.exceptions import QueryGenerationError +from pygraphic.types import register_graphql_type + + +def test_generation_of_query_with_custom_scalar_types(): + class Query(GQLQuery): + uuid: UUID + datetime: datetime + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n uuid\n datetime\n}" + + +def test_parsing_of_query_with_custom_scalar_types(): + class Query(GQLQuery): + uuid: UUID + datetime: datetime + + data = { + "uuid": "7a81e34d-ebe1-47eb-bae0-68ecc3ef174d", + "datetime": "2001-07-01 08:30:00", + } + result = Query.parse_obj(data) + assert result.uuid == UUID(hex="7a81e34d-ebe1-47eb-bae0-68ecc3ef174d") + assert result.datetime == datetime(year=2001, month=7, day=1, hour=8, minute=30) + + +def test_passing_custom_scalar_types_as_field_arguments(): + uu = UUID(hex="7a81e34d-ebe1-47eb-bae0-68ecc3ef174d") + dt = datetime(year=2001, month=7, day=1, hour=8, minute=30) + + class Query(GQLQuery): + foo: int = Field(uuid=uu) + bar: int = Field(datetime=dt) + + gql = Query.get_query_string(include_name=False) + assert gql == "\n".join( + ( + "query {", + ' foo(uuid: "7a81e34d-ebe1-47eb-bae0-68ecc3ef174d")', + ' bar(datetime: "2001-07-01 08:30:00")', + "}", + ) + ) + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_fail_of_using_custom_scalar_types_in_query_variables_without_registration(): + class Variables(GQLVariables): + uuid: UUID + datetime: datetime + + class Query(GQLQuery, variables=Variables): + pass + + with pytest.raises(TypeError): + Query.get_query_string(include_name=False) + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_using_custom_scalar_types_in_query_variables(): + class Variables(GQLVariables): + uuid: UUID + datetime: datetime + + class Query(GQLQuery, variables=Variables): + pass + + register_graphql_type("UUID", UUID) + register_graphql_type("DateTime", datetime) + + gql = Query.get_query_string(include_name=False) + assert gql == "query($uuid: UUID!, $datetime: DateTime!) {\n}" diff --git a/tests/test_enumeration_types.py b/tests/test_enumeration_types.py new file mode 100644 index 0000000..baa4cff --- /dev/null +++ b/tests/test_enumeration_types.py @@ -0,0 +1,76 @@ +from enum import Enum, auto +from typing import Optional + +import pytest +from pydantic import Field + +from pygraphic import GQLQuery, GQLVariables + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_generation_of_query_with_enum_field(): + class Foo(Enum): + BAR = auto() + + class Query(GQLQuery): + baz: Foo + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n baz\n}" + + +@pytest.mark.skip("Parsing enum from query field not yet implemented") +def test_parsing_enum_from_query_field(): + class Foo(Enum): + BAR = auto() + + class Query(GQLQuery): + baz: Foo + + data = { + "baz": "BAR", + } + result = Query.parse_obj(data) + + assert result.baz == Foo.BAR + + +def test_passing_enum_as_field_argument(): + class Foo(Enum): + BAR = auto() + + class Query(GQLQuery): + i: int = Field(ia=Foo.BAR) + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n i(ia: BAR)\n}" + + +@pytest.mark.skip("Passing enum as query variable not yet implemented") +def test_passing_enum_as_query_variable(): + class Foo(Enum): + BAR = auto() + + class Variables(GQLVariables): + baz: Foo + + class Query(GQLQuery, variables=Variables): + pass + + gql = Query.get_query_string(include_name=False) + assert gql == "query($baz: Foo) {\n}" + + +@pytest.mark.skip("Passing enum as query variable not yet implemented") +def test_passing_enum_as_default_query_variable(): + class Foo(Enum): + BAR = auto() + + class Variables(GQLVariables): + baz: Optional[Foo] = Field(default=Foo.BAR) + + class Query(GQLQuery, variables=Variables): + pass + + gql = Query.get_query_string(include_name=False) + assert gql == "query($baz: Foo = BAR) {\n}" diff --git a/tests/test_fields.py b/tests/test_fields.py new file mode 100644 index 0000000..d0cff11 --- /dev/null +++ b/tests/test_fields.py @@ -0,0 +1,89 @@ +from pygraphic import GQLQuery, GQLType + + +def test_creation_of_query_with_scalar_fields(): + class Query(GQLQuery): + i: int + f: float + s: str + b: bool + + Query(i=0, f=0.0, s="", b=False) + + +def test_generation_of_query_with_scalar_fields(): + class Query(GQLQuery): + i: int + f: float + s: str + b: bool + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n i\n f\n s\n b\n}" + + +def test_creation_of_query_with_custom_field_type(): + class Foo(GQLType): + i: int + f: float + s: str + b: bool + + class Query(GQLQuery): + foo: Foo + + Query(foo=Foo(i=0, f=0.0, s="", b=False)) + + +def test_generation_of_query_with_custom_field_type(): + class Foo(GQLType): + i: int + f: float + s: str + b: bool + + class Query(GQLQuery): + foo: Foo + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n foo {\n i\n f\n s\n b\n }\n}" + + +def test_creation_of_query_with_custom_nested_field_types(): + class Bar(GQLType): + f: float + + class Foo(GQLType): + i: int + bar: Bar + + class Query(GQLQuery): + foo: Foo + + Query(foo=Foo(i=0, bar=Bar(f=0.0))) + + +def test_generation_of_query_with_custom_nested_field_types(): + class Bar(GQLType): + f: float + + class Foo(GQLType): + i: int + bar: Bar + + class Query(GQLQuery): + foo: Foo + + gql = Query.get_query_string(include_name=False) + assert gql == "\n".join( + ( + "query {", + " foo {", + " i", + " bar {", + " f", + " }", + " }", + "}", + ) + ) diff --git a/tests/test_inline_fragments.py b/tests/test_inline_fragments.py new file mode 100644 index 0000000..7f28ea7 --- /dev/null +++ b/tests/test_inline_fragments.py @@ -0,0 +1,98 @@ +from pygraphic import GQLQuery, GQLType + + +def test_query_generation_with_one_inline_fragment(): + class Foo(GQLType): + i: int + + class Query(GQLQuery): + fragment: Foo | object + + gql = Query.get_query_string(include_name=False) + assert gql == "\n".join( + ( + "query {", + " fragment {", + " ... on Foo {", + " i", + " }", + " }", + "}", + ) + ) + + +def test_query_generation_with_two_inline_fragments(): + class Foo(GQLType): + i: int + + class Bar(GQLType): + s: float + + class Query(GQLQuery): + fragment: Foo | Bar + + gql = Query.get_query_string(include_name=False) + assert gql == "\n".join( + ( + "query {", + " fragment {", + " ... on Foo {", + " i", + " }", + " ... on Bar {", + " s", + " }", + " }", + "}", + ) + ) + + +def test_query_generation_with_two_inline_fragments_in_list(): + class Foo(GQLType): + i: int + + class Bar(GQLType): + s: str + + class Query(GQLQuery): + fragments: list[Foo | Bar] + + gql = Query.get_query_string(include_name=False) + assert gql == "\n".join( + ( + "query {", + " fragments {", + " ... on Foo {", + " i", + " }", + " ... on Bar {", + " s", + " }", + " }", + "}", + ) + ) + + +def test_query_parsing_with_two_inline_fragments_in_list(): + class Foo(GQLType): + i: int + + class Bar(GQLType): + s: str + + class Query(GQLQuery): + fragments: list[Foo | Bar] + + data = { + "fragments": [ + {"i": 0}, + {"s": "foo"}, + ] + } + + result = Query.parse_obj(data) + assert type(result.fragments[0]) is Foo + assert type(result.fragments[1]) is Bar diff --git a/tests/test_operation_name.py b/tests/test_operation_name.py new file mode 100644 index 0000000..6f95123 --- /dev/null +++ b/tests/test_operation_name.py @@ -0,0 +1,20 @@ +from pygraphic import GQLQuery + + +def test_generation_of_empty_named_query(): + class Query(GQLQuery): + pass + + gql = Query.get_query_string() + assert gql == "query Query {\n}" + + +def test_generation_of_named_query_with_scalar_fields(): + class Query(GQLQuery): + i: int + f: float + s: str + b: bool + + gql = Query.get_query_string() + assert gql == "query Query {\n i\n f\n s\n b\n}" diff --git a/tests/test_queries.py b/tests/test_queries.py new file mode 100644 index 0000000..42584ef --- /dev/null +++ b/tests/test_queries.py @@ -0,0 +1,16 @@ +from pygraphic import GQLQuery + + +def test_creation_of_empty_query(): + class Query(GQLQuery): + pass + + Query() + + +def test_generation_of_empty_unnamed_query(): + class Query(GQLQuery): + pass + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n}" diff --git a/tests/test_variables.py b/tests/test_variables.py new file mode 100644 index 0000000..b9f3378 --- /dev/null +++ b/tests/test_variables.py @@ -0,0 +1,155 @@ +from typing import Optional + +import pytest +from pydantic import Field + +from pygraphic import GQLQuery, GQLVariables + + +def test_creation_of_empty_variables(): + class Variables(GQLVariables): + pass + + Variables() + + +def test_creation_of_empty_query_with_empty_variables(): + class Variables(GQLVariables): + pass + + class Query(GQLQuery, variables=Variables): + pass + + Query() + + +def test_creation_of_variables(): + class Variables(GQLVariables): + i: int + f: float + s: Optional[str] + b: Optional[bool] + + Variables(i=0, f=0.0, s="foo", b=None) + + +def test_generation_of_variables(): + class Variables(GQLVariables): + i: int + f: float + s: Optional[str] + b: Optional[bool] + + variables = Variables(i=0, f=0.0, s="foo", b=None) + json = variables.json() + assert json == '{"i": 0, "f": 0.0, "s": "foo", "b": null}' + + +def test_creation_of_query_with_variables(): + class Variables(GQLVariables): + i: int + f: float + s: Optional[str] + b: Optional[bool] + + class Query(GQLQuery, variables=Variables): + pass + + Query() + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_generation_of_unnamed_query_with_empty_variables(): + class Variables(GQLVariables): + pass + + class Query(GQLQuery, variables=Variables): + pass + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n}" + + +def test_generation_of_named_query_with_empty_variables(): + class Variables(GQLVariables): + pass + + class Query(GQLQuery, variables=Variables): + pass + + gql = Query.get_query_string() + assert gql == "query Query {\n}" + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_generation_of_unnamed_query_with_variables(): + class Variables(GQLVariables): + i: int + f: float + s: Optional[str] + b: Optional[bool] + + class Query(GQLQuery, variables=Variables): + pass + + gql = Query.get_query_string(include_name=False) + assert gql == "query($i: Int!, $f: Float!, $s: String, $b: Boolean) {\n}" + + +def test_generation_of_named_query_with_variables(): + class Variables(GQLVariables): + i: int + f: float + s: Optional[str] + b: Optional[bool] + + class Query(GQLQuery, variables=Variables): + pass + + gql = Query.get_query_string() + assert gql == "query Query($i: Int!, $f: Float!, $s: String, $b: Boolean) {\n}" + + +@pytest.mark.skip("Default GraphQL arguments are not yet implemented") +def test_generation_of_query_with_default_arguments(): + class Variables(GQLVariables): + i: int = Field(default=0) + f: float = Field(default=0.0) + s: Optional[str] = Field(default="foo") + b: Optional[bool] = Field(default=None) + + class Query(GQLQuery, variables=Variables): + pass + + # Notice that non-nullable variables cannot have default values in GraphQL. + gql = Query.get_query_string(include_name=False) + assert gql == ( + 'query($i: Int!, $f: Float!, $s: String = "foo", $b: Boolean = null) {\n}' + ) + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_passing_variables_as_field_arguments(): + class Variables(GQLVariables): + i: int + f: float + s: Optional[str] + b: Optional[bool] + + class Query(GQLQuery, variables=Variables): + ii: int = Field(ia=Variables.i) + ff: float = Field(fa=Variables.f) + ss: str = Field(sa=Variables.s) + bb: bool = Field(ba=Variables.b) + + gql = Query.get_query_string(include_name=False) + assert gql == "\n".join( + ( + "query($i: Int!, $f: Float!, $s: String, $b: Boolean) {", + " ii(ia: $i)", + " ff(fa: $f)", + " ss(sa: $s)", + " bb(ba: $b)", + "}", + ) + ) From 8a027343ffb045eebaec7b15b7850ed429f34cea Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Sun, 24 Jul 2022 03:41:26 +0300 Subject: [PATCH 2/5] Enable test coverage reports --- .vscode/settings.json | 6 +++- poetry.lock | 78 ++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a9fde1..14d50ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,9 @@ "python.analysis.typeCheckingMode": "basic", "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - "python.testing.pytestArgs": ["tests"], + "python.testing.pytestArgs": [ + "tests", + "--cov=pygraphic", + "--cov-report=term-missing" + ], } diff --git a/poetry.lock b/poetry.lock index 52a650b..87bf792 100644 --- a/poetry.lock +++ b/poetry.lock @@ -98,6 +98,20 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "coverage" +version = "6.4.2" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "flake8" version = "4.0.1" @@ -327,6 +341,21 @@ tomli = ">=1.0.0" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"] + [[package]] name = "pyyaml" version = "6.0" @@ -423,7 +452,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "8b4246c8974519f463f12717f68dd7cc7f3fbb2940b9513c54f829f22153ad34" +content-hash = "747683b0f575d97bd60b9aa398e5d34601d3771c451d3d1716a5e51410595da0" [metadata.files] atomicwrites = [] @@ -473,6 +502,49 @@ colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] +coverage = [ + {file = "coverage-6.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e"}, + {file = "coverage-6.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c"}, + {file = "coverage-6.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8"}, + {file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39"}, + {file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0"}, + {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee"}, + {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d"}, + {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc"}, + {file = "coverage-6.4.2-cp310-cp310-win32.whl", hash = "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386"}, + {file = "coverage-6.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0"}, + {file = "coverage-6.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46"}, + {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07"}, + {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039"}, + {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996"}, + {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f"}, + {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e"}, + {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083"}, + {file = "coverage-6.4.2-cp37-cp37m-win32.whl", hash = "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7"}, + {file = "coverage-6.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120"}, + {file = "coverage-6.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"}, + {file = "coverage-6.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32"}, + {file = "coverage-6.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae"}, + {file = "coverage-6.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8"}, + {file = "coverage-6.4.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1"}, + {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63"}, + {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933"}, + {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de"}, + {file = "coverage-6.4.2-cp38-cp38-win32.whl", hash = "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783"}, + {file = "coverage-6.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6"}, + {file = "coverage-6.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f"}, + {file = "coverage-6.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f"}, + {file = "coverage-6.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe"}, + {file = "coverage-6.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29"}, + {file = "coverage-6.4.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55"}, + {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b"}, + {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978"}, + {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c"}, + {file = "coverage-6.4.2-cp39-cp39-win32.whl", hash = "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd"}, + {file = "coverage-6.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf"}, + {file = "coverage-6.4.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97"}, + {file = "coverage-6.4.2.tar.gz", hash = "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe"}, +] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, @@ -596,6 +668,10 @@ pyparsing = [ {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] pytest = [] +pytest-cov = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, diff --git a/pyproject.toml b/pyproject.toml index 785be42..b4ffdaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ isort = "^5.10.1" pytest = "^7.1.2" requests = "^2.28.1" types-requests = "^2.28.3" +pytest-cov = "^3.0.0" [tool.poetry-dynamic-versioning] enable = true From 06fc8799c50a3e5e2d6a317597ebe3d3d9bf7843 Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Sun, 24 Jul 2022 04:04:51 +0300 Subject: [PATCH 3/5] Add tests for automatic case conversion --- tests/test_case_conversion.py | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/test_case_conversion.py diff --git a/tests/test_case_conversion.py b/tests/test_case_conversion.py new file mode 100644 index 0000000..daa7435 --- /dev/null +++ b/tests/test_case_conversion.py @@ -0,0 +1,55 @@ +import pytest +from pydantic import Field + +from pygraphic import GQLQuery, GQLVariables + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_field_name_conversion(): + class Query(GQLQuery): + snake_case: bool + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n snakeCase\n}" + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_field_argument_conversion(): + class Query(GQLQuery): + i: int = Field(snake_case=False) + + gql = Query.get_query_string(include_name=False) + assert gql == "query {\n i(snakeCase: false)\n}" + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_query_generation_variable_conversion(): + class Variables(GQLVariables): + snake_case: bool + + class Query(GQLQuery, variables=Variables): + pass + + gql = Query.get_query_string(include_name=False) + assert gql == "query($snakeCase: Boolean!) {\n}" + + +def test_variable_generation_conversion(): + class Variables(GQLVariables): + snake_case: bool + + variables = Variables(snake_case=False) + json = variables.json() + assert json == '{"snakeCase": false}' + + +@pytest.mark.skip("Unnamed queries with parameters are not yet implemented") +def test_passing_variable_in_field_argument_conversion(): + class Variables(GQLVariables): + snake_case: bool + + class Query(GQLQuery, variables=Variables): + i: int = Field(camel_case=Variables.snake_case) + + gql = Query.get_query_string(include_name=False) + assert gql == "query($snakeCase: Boolean!) {\n i(camelCase: $snakeCase)\n}" From d9d7fb1d8130eb3e91b06c1a807b68873cf2cbe3 Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Sun, 24 Jul 2022 04:18:38 +0300 Subject: [PATCH 4/5] Upload coverage reports in test workflows --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb9af2d..b6e6218 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,6 +45,8 @@ jobs: poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics - name: Run pytest - run: poetry run pytest + run: poetry run pytest --cov=pygraphic --cov-report=term-missing:skip-covered --cov-report=xml tests env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload coverage report + uses: codecov/codecov-action@v3.1.0 From 8c880cce07602561e35a6217fe4009044ba2e1d5 Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Sun, 24 Jul 2022 05:10:17 +0300 Subject: [PATCH 5/5] Add badges to README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 4895e07..795697b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,18 @@ # pygraphic +

+ + Status + + + Version + + + Tests + + + Coverage + +

Client-side GraphQL query generator based on [pydantic].