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! 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"] diff --git a/pygraphic/_gql_parameters.py b/pygraphic/_gql_parameters.py index db0e867..6822536 100644 --- a/pygraphic/_gql_parameters.py +++ b/pygraphic/_gql_parameters.py @@ -4,25 +4,28 @@ 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] + mcs: type[GQLParameters] = cls # type: ignore + return mcs.__fields__[__name] except KeyError: raise AttributeError( f"type object '{cls.__name__}' has no attribute '{__name}'" ) -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 99ce001..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,13 +32,8 @@ def _gen(): return "\n".join(_gen()) - 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: 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" ) 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)