From f5af639838095b004fe314eb21ecb56bb66ccf70 Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Fri, 15 Jul 2022 22:24:29 +0300 Subject: [PATCH 1/8] Fix missing defaults export --- pygraphic/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygraphic/__init__.py b/pygraphic/__init__.py index 43684de..233f706 100644 --- a/pygraphic/__init__.py +++ b/pygraphic/__init__.py @@ -1,7 +1,7 @@ -from . import types +from . import defaults, types from ._gql_parameters import GQLParameters from ._gql_query import GQLQuery from ._gql_type import GQLType -__all__ = ["GQLParameters", "GQLType", "GQLQuery", "types"] +__all__ = ["defaults", "GQLParameters", "GQLType", "GQLQuery", "types"] From 48ca9d9451ac7c6b853a71c281d3128c156fb681 Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Fri, 15 Jul 2022 22:24:56 +0300 Subject: [PATCH 2/8] Fix missing README whitespace --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dded932..0399f8f 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ incorrectly. The API may change at any time. ## Example ### Server schema + ``` gql type User { id: int! From f3130228ecc7b304d632e9158c7991c648b4ffaf Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Fri, 15 Jul 2022 22:25:39 +0300 Subject: [PATCH 3/8] Fix redundant whitespace in error message --- pygraphic/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygraphic/types.py b/pygraphic/types.py index 04d5fab..657e41d 100644 --- a/pygraphic/types.py +++ b/pygraphic/types.py @@ -19,6 +19,6 @@ def class_to_graphql_type(python_class: type, allow_none: bool) -> str: return type_ + "!" except KeyError: raise KeyError( - f"Type '{python_class.__name__}' could not be converted to a GraphQL type. " + f"Type '{python_class.__name__}' could not be converted to a GraphQL type." "See pygraphic.types.register_graphql_type" ) From dc8d53bbe6a9943a87500c2140a09ad12efedd7e Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Fri, 15 Jul 2022 22:31:27 +0300 Subject: [PATCH 4/8] Fix remaining camelCase arguments --- tests/test_parametrized_query.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_parametrized_query.py b/tests/test_parametrized_query.py index cf986f2..827187a 100644 --- a/tests/test_parametrized_query.py +++ b/tests/test_parametrized_query.py @@ -16,7 +16,7 @@ def test_query_string_generation(): def test_local_query_execution(): query = GetUsersBornAfter.get_query_string() - variables = Parameters(bornAfter=date.fromtimestamp(0.0)) + variables = Parameters(born_after=date.fromtimestamp(0.0)) result = server_schema.execute_sync(query, json.loads(variables.json())) assert result.errors is None assert result.data is not None @@ -24,7 +24,7 @@ def test_local_query_execution(): def test_pydantic_object_parsing(): query = GetUsersBornAfter.get_query_string() - variables = Parameters(bornAfter=date.fromtimestamp(0.0)) + variables = Parameters(born_after=date.fromtimestamp(0.0)) result = server_schema.execute_sync(query, json.loads(variables.json())) assert type(result.data) is dict result = GetUsersBornAfter.parse_obj(result.data) From 5c833d707d84a08a36c602ef4f55db7991456e9c Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Fri, 15 Jul 2022 22:37:04 +0300 Subject: [PATCH 5/8] Fix missing GQLParameters constructor autocompletion --- pygraphic/_gql_parameters.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pygraphic/_gql_parameters.py b/pygraphic/_gql_parameters.py index db0e867..a6cb75c 100644 --- a/pygraphic/_gql_parameters.py +++ b/pygraphic/_gql_parameters.py @@ -4,14 +4,18 @@ import pydantic import pydantic.main +from pydantic.fields import Field, FieldInfo +from pydantic.main import __dataclass_transform__ from .defaults import default_alias_generator +@__dataclass_transform__(kw_only_default=True, field_descriptors=(Field, FieldInfo)) class ModelMetaclass(pydantic.main.ModelMetaclass): def __getattr__(cls, __name: str) -> Any: try: - return cls.__fields__[__name] + model: type[GQLParameters] = cls # type: ignore + return model.__fields__[__name] except KeyError: raise AttributeError( f"type object '{cls.__name__}' has no attribute '{__name}'" From f9790e09bc25d34036184510fca7c218c28a323c Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Fri, 15 Jul 2022 22:42:15 +0300 Subject: [PATCH 6/8] Fix missing @classmethod decorator --- pygraphic/_gql_query.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pygraphic/_gql_query.py b/pygraphic/_gql_query.py index 99ce001..0c6ac42 100644 --- a/pygraphic/_gql_query.py +++ b/pygraphic/_gql_query.py @@ -31,6 +31,7 @@ def _gen(): return "\n".join(_gen()) + @classmethod def __init_subclass__( cls, parameters: Optional[type[GQLParameters]] = None, From 7c2abc4fa61f65c616f20e663f50f871d6e12109 Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Fri, 15 Jul 2022 23:59:31 +0300 Subject: [PATCH 7/8] Make __parameters__ a pydantic's Config field --- pygraphic/_gql_parameters.py | 11 +++++------ pygraphic/_gql_query.py | 33 ++++++++++++++------------------- pygraphic/_gql_type.py | 10 +++++----- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/pygraphic/_gql_parameters.py b/pygraphic/_gql_parameters.py index a6cb75c..4d78bad 100644 --- a/pygraphic/_gql_parameters.py +++ b/pygraphic/_gql_parameters.py @@ -22,11 +22,10 @@ def __getattr__(cls, __name: str) -> Any: ) -class GQLParameters( - pydantic.BaseModel, - metaclass=ModelMetaclass, - alias_generator=default_alias_generator, - allow_population_by_field_name=True, -): +class GQLParameters(pydantic.BaseModel, metaclass=ModelMetaclass): def json(self, **kwargs: Any) -> str: return super().json(by_alias=True, **kwargs) + + class Config: + alias_generator = default_alias_generator + allow_population_by_field_name = True diff --git a/pygraphic/_gql_query.py b/pygraphic/_gql_query.py index 0c6ac42..90e15d3 100644 --- a/pygraphic/_gql_query.py +++ b/pygraphic/_gql_query.py @@ -1,27 +1,28 @@ -from typing import Any, Iterator, Optional +from __future__ import annotations + +from typing import Iterator, Optional + +import pydantic from ._gql_parameters import GQLParameters from ._gql_type import GQLType -from .defaults import default_alias_generator from .types import class_to_graphql_type -class GQLQuery( - GQLType, - alias_generator=default_alias_generator, - allow_population_by_field_name=True, -): - __parameters__ = None - +class GQLQuery(GQLType): @classmethod def get_query_string(cls, named: bool = True) -> str: - if not named and cls.__parameters__ is not None: + parameters: Optional[ + type[GQLParameters] + ] = cls.__config__.parameters # type: ignore + + if not named and parameters is not None: # TODO Find a better exception type raise Exception("Query with parameters must have a name") def _gen(): if named: - params = "".join(_gen_parameter_string(cls.__parameters__)) + params = "".join(_gen_parameter_string(parameters)) yield "query " + cls.__name__ + params + " {" else: yield "query {" @@ -31,14 +32,8 @@ def _gen(): return "\n".join(_gen()) - @classmethod - def __init_subclass__( - cls, - parameters: Optional[type[GQLParameters]] = None, - **pydantic_kwargs: Any, - ) -> None: - cls.__parameters__ = parameters - return super().__init_subclass__(**pydantic_kwargs) + class Config(pydantic.BaseConfig): + parameters: Optional[type[GQLParameters]] = None def _gen_parameter_string(parameters: Optional[type[GQLParameters]]) -> Iterator[str]: diff --git a/pygraphic/_gql_type.py b/pygraphic/_gql_type.py index 284c4ba..236ca64 100644 --- a/pygraphic/_gql_type.py +++ b/pygraphic/_gql_type.py @@ -9,11 +9,7 @@ from .defaults import default_alias_generator -class GQLType( - pydantic.BaseModel, - alias_generator=default_alias_generator, - allow_population_by_field_name=True, -): +class GQLType(pydantic.BaseModel): @classmethod def generate_query_lines(cls, nest_level: int = 0) -> Iterator[str]: fields = typing.get_type_hints(cls) @@ -36,6 +32,10 @@ def generate_query_lines(cls, nest_level: int = 0) -> Iterator[str]: yield " " * nest_level + field.alias + params continue + class Config: + alias_generator = default_alias_generator + allow_population_by_field_name = True + def _gen_parameter_string(parameters: dict[str, Any]) -> Iterator[str]: if not parameters: From 1002b1d7d61f6cb4c687212a05b6aa8dd7326a8f Mon Sep 17 00:00:00 2001 From: Dmitry Semenov Date: Sat, 16 Jul 2022 00:00:08 +0300 Subject: [PATCH 8/8] Rename a variable --- pygraphic/_gql_parameters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygraphic/_gql_parameters.py b/pygraphic/_gql_parameters.py index 4d78bad..6822536 100644 --- a/pygraphic/_gql_parameters.py +++ b/pygraphic/_gql_parameters.py @@ -14,8 +14,8 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): def __getattr__(cls, __name: str) -> Any: try: - model: type[GQLParameters] = cls # type: ignore - return model.__fields__[__name] + mcs: type[GQLParameters] = cls # type: ignore + return mcs.__fields__[__name] except KeyError: raise AttributeError( f"type object '{cls.__name__}' has no attribute '{__name}'"