Skip to content

Instantly share code, notes, and snippets.

@AstraLuma
Last active August 28, 2019 19:14
Show Gist options
  • Save AstraLuma/9309970b898e13d6195cf4fb9f5d5262 to your computer and use it in GitHub Desktop.
Save AstraLuma/9309970b898e13d6195cf4fb9f5d5262 to your computer and use it in GitHub Desktop.
Importable GraphQL

The basic idea: Define GraphQL queries as importable modules.

For example:

# queries.gql
#!starwars

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}
# app.py
from queries import HeroForEpisode

print(HeroForEpisode(ep='JEDI'))

We can also provide additional tools for static checking (compare queries/mutations to the schema) and precompilation (compile to python files for optimized deployment). We can do this with a context manager, the contextvars module, and a stack.

This should support multiple services (GitHub, Cirrus CI, etc) and multiple contexts in those services (authentication, etc). The service being called is defined in the qgl file.

Because of the vagueness of GraphQL, this depends on service-specific code (TODO Providers? Services?). These are factories and callables: The factory takes arguments and produces a callable, which takes the query and variables and actually calls the service.

I have two ideas for tying together service code and names.

entrypoints

Entrypoints are used to autodiscover names, so if no additional arguments are needed, the application can just use the queries.

Examples:

with gqlimp.with_provider('starwars', token=MYTOKEN):
   print(HeroForEpisode(ep='JEDI'))

The application never needs to touch the actual instances.

This is not compatible with having multiple implementations of the same service installed at the same time.

Explicit Instances

Implementations expose classes to users, and the application registers these.

with gqlimp.with_provider(starwars=StarWars(token=MYTOKEN)):
   print(HeroForEpisode(ep='JEDI'))

The library scans for __services__.py files for default registrations for CLI invocations:

# __services__.py
starwars = StarWars()

This provides better support for services without canonical instances, but requires the application to do more work.

Considerations for service code

The library should be async-transparent: It is 100% up to the the service implementation wether it is sync or async.

An easy way to implement a service is just a class, but this is (strictly speaking) not required:

class StarWars:
    def __init__(self, creds):
        ...

    def __call__(self, query, variables):
        ...
class StarWarsAsync:
    def __init__(self, creds):
        ...

    async def __call__(self, query, variables):
        ...

CLI tools depend on default instantiations to get the schema to compare with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment