A Python GraphQL library that makes use of type hinting and concurrency support with the new async/await syntax.
Project description
TypeGQL
A Python GraphQL library that makes use of type hinting and concurrency support with the new async/await syntax. With the help of type hints and dataclass it’s easy to build a GraphQL schema using nothing but Python objects and primitives
Consider the following:
from graphql import (
GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString)
schema = GraphQLSchema(
query=GraphQLObjectType(
name='RootQueryType',
fields={
'hello': GraphQLField(
GraphQLString,
resolve=lambda obj, info: 'world')
}))
Versus:
from dataclasses import dataclass
from typegql import Schema
@dataclass(init=False)
class RootQuery:
hello: str
def resolve_hello(info):
return 'world
schema = Schema(query=RootQuery)
Clearly the second one looks more “Pythonic” and it’s easier to maintain for complex structures
Installation
pip install typegql
Usage
The following demonstrates how to use typegql for implementing a GraphQL API for a library of books. The example can be found in typegql/core/examples and you can run it with Sanic by executing python <path_to_example>/server.py
Define your query
from dataclasses import dataclass
from typing import List
from typegql.core.graph import Connection
from typegql.examples.library.types import Author, Category
from typegql.examples.library.types import Book
from typegql.examples.library import db
@dataclass(init=False, repr=False)
class Query:
books: List[Book]
authors: List[Author]
categories: List[Category]
books_connection: Connection[Book]
async def resolve_authors(self, info, **kwargs):
return db.get('authors')
async def resolve_books(self, info, **kwargs):
return db.get('books')
async def resolve_categories(self, info, **kwargs):
return db.get('categories')
async def resolve_books_connection(self, info, **kwargs):
data = db.get('books')
return {
'edges': [{
'node': node
} for node in data]}
Define your types
from dataclasses import dataclass, field
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import List
from examples.library import db
class Gender(Enum):
MALE = 'male'
FEMALE = 'female'
@dataclass
class GeoLocation:
latitude: Decimal
longitude: Decimal
@dataclass
class Author:
"""Person that is usually a writer"""
id: ID = field(metadata={'readonly': True})
name: str
gender: Optional[Gender] = None
geo: Optional[GeoLocation] = None
books: Optional[List[Book]] = None
@dataclass
class Category:
id: ID = field(metadata={'readonly': True})
name: str
@dataclass
class Book:
"""A book... for reading :|"""
id: ID = field(metadata={'readonly': True})
author_id: ID
title: str
author: Optional[Author] = field(default=None, metadata={'description': 'The author of this book'})
categories: Optional[List[Category]] = None
published: Optional[datetime] = None
tags: Optional[List[str]] = None
def __post_init__(self):
self.published = datetime.strptime(self.published, '%Y-%m-%d %H:%M:%S')
async def resolve_author(self, info):
data = filter(lambda x: x['id'] == self.author_id, db.get('authors'))
data = next(data)
author = Author(**data)
author.gender = Gender(author.gender)
if 'geo' in data:
author.geo = GeoLocation(**data.get('geo'))
return author
async def resolve_categories(self, selections, name=None):
data = filter(lambda x: x['id'] in self.categories, db.get('categories'))
for d in data: # showcasing async generator
yield Category(**d)
def resolve_tags(self, selections):
return ['testing', 'purpose']
Run your query
from typegql.core.schema import Schema
from examples.library.query import Query
schema = Schema(Query)
query = '''
query BooksConnection {
books_connection {
edges {
node {
id
title
published
author {
id
name
}
}
}
}
}
'''
async def run():
result = await schema.run(query)
Client
TypeGQL supports DSL client for working with a GraphQL API. The client automatically converts snake to camelcase. set camelcase=False if this is not desired
pip install typegql[client]
For example:
from typegql.client import Client
async with Client(url, camelcase=True) as client:
await client.introspection()
dsl = client.dsl
query = dsl.Query.books_connection.select(dsl.BooksConnection.total_count)
doc = dsl.query(query)
status, result = await client.execute(doc)
Change Log
3.0.1 [2019-11-26]
BREAKING: Removed Graph as a baseclass
now makes use of dataclasses.dataclass and dataclasess.fields for building the Schema
bug fixing and improvements
2.0.9 [2019-10-29]
changed the name of an input object from ObjectMuation to ObjectInput
2.0.8 [2019-10-15]
- allows forward reference between graph types (ie: Book has an author and an Author has books).
this only works with python 3.7(using from __future__ import annotations, or python 3.8
2.0.6 [2019-06-24]
updates uvloop dependency
2.0.5 [2019-04-24]
fixed a bug when sending introspection schema
2.0.4 [2019-04-24]
updates assert for introspection add message with status and result
adds support for enum objects in resolve_field_velue_or_error
2.0.3 [2019-02-08]
changes Connection, Edge, Node and PageInfo to interfaces IConnection, IEdge, etc.
implements default Connection and PageInfo objects
removes has_next, has_previous from PageInfo
2.0.1 [2019-01-19]
all properties that don’t have a Field instance assigned to them will be ignored by the Schema
updates docs & example to reflect 2.0 changes
fixed a bug when using a List argument in mutations
1.0.7 [2018-12-09]
bug fixing
adds support for camelcase in Client
1.0.1 [2018-11-19]
adds support for client DSL
Initial
added graphql-core-next as a baseline for all GraphQL operations
TODO
testing
travis
more testing
please help with testing :|
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.