gql-next icon indicating copy to clipboard operation
gql-next copied to clipboard

WIP - Feature/custom codec

Open ekampf opened this issue 6 years ago • 7 comments

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

ekampf avatar Jan 02 '19 21:01 ekampf

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 avatar Jan 03 '19 08:01 aviv-ebates

@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?

ekampf avatar Jan 04 '19 18:01 ekampf

Another problem with the gql( ... ) syntax is that if your code has import gql said IDEs will definitely not like it...

ekampf avatar Jan 04 '19 18:01 ekampf

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.

aviv-ebates avatar Jan 06 '19 09:01 aviv-ebates

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 avatar Jan 06 '19 12:01 aviv-ebates

@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

ekampf avatar Jan 07 '19 23:01 ekampf

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.

aviv-ebates avatar Jan 08 '19 10:01 aviv-ebates