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