Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support non-nullable parameters in queries #4

Merged
merged 5 commits into from
Jul 15, 2022
Merged
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
3 changes: 2 additions & 1 deletion examples/parametrized_query/get_users_born_after.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from datetime import date
from typing import Optional

from pydantic import Field

Expand All @@ -13,7 +14,7 @@


class Parameters(GQLParameters):
bornAfter: date
bornAfter: Optional[date] = None


class User(GQLType):
Expand Down
Empty file.
18 changes: 18 additions & 0 deletions examples/required_parametrized_query/get_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from pydantic import Field

from pygraphic import GQLParameters, GQLQuery, GQLType


class Parameters(GQLParameters):
userId: int


class User(GQLType):
id: int
username: str


class GetUser(GQLQuery, parameters=Parameters):
user: User = Field(id=Parameters.userId)
25 changes: 25 additions & 0 deletions examples/required_parametrized_query/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json

from examples.server import server_schema

from .get_user import GetUser, Parameters


# Generate query string
gql = GetUser.get_query_string()
variables = Parameters(userId=1)

# Typically you would send an HTTP Post request to a remote server.
# For simplicity, this query is processed locally.
# We dump and load json here to convert parameters from python types to strings.
response = server_schema.execute_sync(gql, json.loads(variables.json()))

# Handle errors
if response.data is None:
raise Exception("Query failed", response.errors)

# Parse the data
result = GetUser.parse_obj(response.data)

# Print validated data
print(result.user)
4 changes: 4 additions & 0 deletions examples/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ def users(self, born_after: Optional[date] = None) -> list[User]:
return [user for user in _users if user.birthday > born_after]
return _users

@strawberry.field
def user(self, id: int) -> User:
return next(filter(lambda user: user.id == id, _users))


_users = [
User(
Expand Down
6 changes: 6 additions & 0 deletions golden_files/query_parametrized_required.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
query GetUser($userId: Int!) {
user(id: $userId) {
id
username
}
}
1 change: 1 addition & 0 deletions golden_files/server_schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ scalar Date

type Query {
users(bornAfter: Date = null): [User!]!
user(id: Int!): User!
}

type User {
Expand Down
3 changes: 1 addition & 2 deletions pygraphic/_gql_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ def __getattr__(cls, __name: str) -> Any:


class GQLParameters(pydantic.BaseModel, metaclass=ModelMetaclass):
def __str__(self):
return ""
pass
2 changes: 1 addition & 1 deletion pygraphic/_gql_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,5 @@ def _gen_parameter_string(parameters: Optional[type[GQLParameters]]) -> Iterator
yield "$"
yield name
yield ": "
yield class_to_graphql_type(field.type_)
yield class_to_graphql_type(field.type_, allow_none=field.allow_none)
yield ")"
8 changes: 6 additions & 2 deletions pygraphic/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ def register_graphql_type(graphql_type: str, python_class: type) -> None:
_mapping[python_class] = graphql_type


def class_to_graphql_type(python_class: type) -> str:
def class_to_graphql_type(python_class: type, allow_none: bool) -> str:
try:
return _mapping[python_class]
type_ = _mapping[python_class]
if allow_none:
return type_
else:
return type_ + "!"
except KeyError:
raise KeyError(
f"Type '{python_class.__name__}' could not be converted to a GraphQL type. "
Expand Down
14 changes: 10 additions & 4 deletions tests/test_parametrized_query.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import json
from datetime import date
from pathlib import Path

from examples.parametrized_query.get_users_born_after import GetUsersBornAfter
from examples.parametrized_query.get_users_born_after import (
GetUsersBornAfter,
Parameters,
)
from examples.server import server_schema


Expand All @@ -11,15 +16,16 @@ def test_query_string_generation():

def test_local_query_execution():
query = GetUsersBornAfter.get_query_string()
# variables = GetAllUsers.variables()
result = server_schema.execute_sync(query)
variables = Parameters(bornAfter=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


def test_pydantic_object_parsing():
query = GetUsersBornAfter.get_query_string()
result = server_schema.execute_sync(query)
variables = Parameters(bornAfter=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)

Expand Down
35 changes: 35 additions & 0 deletions tests/test_required_parametrized_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import json
from pathlib import Path

from examples.required_parametrized_query.get_user import GetUser, Parameters
from examples.server import server_schema


def test_query_string_generation():
expected = Path("golden_files", "query_parametrized_required.gql").read_text(
"utf-8"
)
assert GetUser.get_query_string() == expected


def test_local_query_execution():
query = GetUser.get_query_string()
variables = Parameters(userId=1)
result = server_schema.execute_sync(query, json.loads(variables.json()))
assert result.errors is None
assert result.data is not None


def test_pydantic_object_parsing():
query = GetUser.get_query_string()
variables = Parameters(userId=1)
result = server_schema.execute_sync(query, json.loads(variables.json()))
assert type(result.data) is dict
result = GetUser.parse_obj(result.data)


def test_example():
from examples.required_parametrized_query.main import result

assert type(result) is GetUser
assert result.user.id == 1
18 changes: 11 additions & 7 deletions tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@

def test_class_to_typename_crashes_on_unknown_class():
with pytest.raises(KeyError) as e_info:
class_to_graphql_type(object)
class_to_graphql_type(object, allow_none=True)


def test_class_to_typename_with_default_scalars():
assert class_to_graphql_type(int) == "Int"
assert class_to_graphql_type(float) == "Float"
assert class_to_graphql_type(str) == "String"
assert class_to_graphql_type(int) == "Int"
# assert class_to_graphql_type(str) == 'ID'
assert class_to_graphql_type(int, allow_none=True) == "Int"
assert class_to_graphql_type(float, allow_none=True) == "Float"
assert class_to_graphql_type(str, allow_none=True) == "String"
assert class_to_graphql_type(int, allow_none=True) == "Int"
# assert class_to_graphql_type(str, allow_none=True) == 'ID'


def test_class_to_typename_with_non_nullable():
assert class_to_graphql_type(int, allow_none=False) == "Int!"


def test_class_to_typename_with_registered_type():
register_graphql_type("CustomType", object)
assert class_to_graphql_type(object) == "CustomType"
assert class_to_graphql_type(object, allow_none=True) == "CustomType"