gql-next
gql-next copied to clipboard
WIP - Feature/custom codec
Allow embedding GraphQL queries in Python code. For example:
GetFilm = gql'''
query GetFilm {
film(id: "1") {
title
director
}
}
'''
result = GetFilm.execute()
film = result.data.film
Should render all the relevant Operation class and result dataclasses and set GetFilm
to be the operation class:
# AUTOGENERATED file. Do not Change!
from functools import partial
from typing import Any, Callable, Mapping, List
from enum import Enum
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json
from gql.clients import Client, AsyncIOClient
from types import SimpleNamespace
class GetFilmNamespace(SimpleNamespace):
from datetime import datetime
from marshmallow import fields as marshmallow_fields
DATETIME_FIELD = field(metadata={
'dataclasses_json': {'encoder': datetime.isoformat, 'decoder': datetime.fromisoformat,
'mm_field': marshmallow_fields.DateTime(format='iso')}})
@dataclass_json
@dataclass
class GetFilm:
__QUERY__ = """
query GetFilm {
film(id: "1") {
title
director
}
}
"""
@dataclass_json
@dataclass
class GetFilmData():
@dataclass_json
@dataclass
class Film():
title: str
director: str
film: Film = None
data: GetFilmData = None
errors: Any = None
@classmethod
def execute(cls, on_before_callback: Callable[[Mapping[str, str], Mapping[str, str]], None] = None):
client = Client('schemaurl')
variables = None
response_text = client.call(cls.__QUERY__, variables=variables, on_before_callback=on_before_callback)
return cls.from_json(response_text)
@classmethod
async def execute_async(cls, on_before_callback: Callable[[Mapping[str, str], Mapping[str, str]], None] = None):
client = AsyncIOClient('schemaurl')
variables = None
response_text = await client.call(cls.__QUERY__, variables=variables, on_before_callback=on_before_callback)
return cls.from_json(response_text)
GetFilm = GetFilmNamespace.GetFilm
result = GetFilm.execute()
film = result.data.film
pls use a syntax that looks like python, so that various tools that highlight/parse python don't break:
Foo = gql(''' .... ''')
some parsers/tools/highlighters might freak out over gql'''
pattern.
(In practice, both my primary tools - Notepad++ and Pygmentize don't have any issues with gql'''
).
@aviv-ebates I thought about this too and checked the IDEs I have:
- VS code - No problem
- PyCharm - shows an error that can be easily dismissed
The con of using a function syntax is that it can be confused by the reader as a real function call. Having an "invalid" python code there immediately strikes the eye as out of the ordinary - something special happens here that you need to understand.
As far as IDEs go, I looked at how pyxl
sole it and they simply provide an extension to IDEs:
https://github.com/christoffer/pycharm-pyxl
https://github.com/yyjhao/sublime-pyxl
Out syntax is way simpler so it shouldn't be a problem to do the same.
As for non IDE tools - like pylint, mypy etc - when they do open(filename)
codec should run automatically and they should get the generated code and never see the gql
syntax.
(This poses other problems - how do we make this generated code not break the users build on pylint\flake8\whatever he's using)
WDYT?
Another problem with the gql( ... )
syntax is that if your code has import gql
said IDEs will definitely not like it...
I would like for all tools - esp lints - to run on source code (i.e., what the user actually types) rather then generated code; mypy only works on the AST, but other tools (pylint) check for code formatting.
I don't understand the issue with import gql
- it's a real name, isn't it?
As far as the user being confused with gql()
looking like a function:
- python has plenty of things that are confusing about "is this a line running code" (imports, function definitions...
- conceptually, it's pretty much similar to a function call, except when running in a debugger, which I wouldn't expect anyone to do: It's running some code and returning an rvalue; The only difference is that this format doesn't support non-constant parameters, which we can handle statically.
Also, making this section valid python would allow us to provide a good error message if the user somehow didn't load our custom encoder, rather than having the python loader error out.
If mypy sees the generated code, it might show up errors that don't make sense to the user; I'm sort-of guessing the generated code should end up being just GetFilm = from .generated_gql_stuff import GetFilm
, and all the rest goes in the new file.
@aviv-ebates isn't the whole point of strong typing the response objects is for these tools to be able to recognize them? For example:
GetFilm = gql'''
query GetFilm {
film(id: "1") { title, director }
}
'''
result = GetFilm.execute()
film = result.data.starship
I would like this code to throw some lint\mypy error that data
doesn't have a starship
property
Like we talked offline - for some things (like "exactly 1 space before =
") we want the tool to see the original code, and for others (static typing) to see the generated stuff (but we need to make sure the error messages make sense).
I'm a little surprised by open()
considering the encoding
directive, but the tool might not have the codec loaded, and it might not even be written in Python anyway.