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

WIP - Feature/custom codec #16

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open

WIP - Feature/custom codec #16

wants to merge 40 commits into from

Conversation

ekampf
Copy link
Collaborator

@ekampf ekampf commented Jan 2, 2019

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

@aviv-ebates
Copy link

aviv-ebates commented Jan 3, 2019

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''').

@ekampf
Copy link
Collaborator Author

ekampf commented Jan 4, 2019

@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
Copy link
Collaborator Author

ekampf commented Jan 4, 2019

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

@aviv-ebates
Copy link

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
Copy link

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.

codecs.register(search_function)

_USAGE = """\
Wraps a python command to allow it to recognize pyxl-coded files with

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pyxl-coded files...



def search_function(encoding):
if encoding != 'gql':

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe have a more-unique name for the codec?

pyproject.toml Outdated
@@ -5,6 +5,7 @@ description = "Python GraphQL Client Library"
authors = ["Eran Kampf <[email protected]>"]
license = "MIT"
readme = "README.md"
include= ["gql.pth"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing space before =.

@ekampf
Copy link
Collaborator Author

ekampf commented Jan 7, 2019

@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

@aviv-ebates
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants