-
Notifications
You must be signed in to change notification settings - Fork 3
Releases: GraphQL-python-archive/graphql-epoxy
graphql-epoxy v0.3 release
Add support for subscriptions and bump graphql-core to v0.4.9
You can now define a subscription just like you would a Query:
R = TypeRegistry() class Query(R.ObjectType): a = R.Int class Subscription(R.ObjectType): subscribe_to_foo = R.Boolean(args={'id': R.Int}) def resolve_subscribe_to_foo(self, obj, args, info): return args.get('id') == 1 Schema = R.Schema(R.Query, subscription=R.Subscription)
graphql-epoxy remains un-opinionated as to how you actually want to implement your subscription logic.
Assets 3
graphql-epoxy v0.2 release
This is the first stable release of graphql-epoxy support for defining your GraphQL schemas in a simple, pythonic way.
I'm going to copy paste the readme, as it reflects the feature-set in this release!
Usage
Defining a GraphQL Schema using Epoxy is as simple as creating a TypeRegistry and using it to create types for you.
from epoxy import TypeRegistry R = TypeRegistry() class Character(R.Interface): id = R.ID name = R.String friends = R.Character.List class Human(R.Implements.Character): home_planet = R.String.NonNull class Query(R.ObjectType): human = R.Human foo = R.Foo # This is defined below! Ordering doesn't matter! def resolve_human(self, obj, args, info): """This will be used as the description of the field Query.human.""" return Human(5, 'Bob', [Human(6, 'Bill')]
You can even have epoxy learn about your already defined Python enums.
class MoodStatus(enums.Enum): HAPPY = 1 SAD = 2 MELANCHOLY = 3 R(MoodStatus)
And then use it in an ObjectType:
class Foo(R.ObjectType): mood = R.MoodStatus # or mood = R.Field(R.MoodStatus, description="Describing the mood of Foo, is sometimes pretty hard.") def resolve_mood(self, *args): return MoodStatus.HAPPY.value
Schema is a GraphQLSchema object. You can now use it with graphql:
schema = R.schema(R.Query) result = graphql(schema, ''' { human { id name homePlanet friends { name homePlanet } } } ''')
The schema is now defined as:
enum MoodStatus { HAPPY SAD MELANCHOLY } interface Character { id: ID name: String friends: [Character] } type Human implements Character { id: ID name: String friends: [Character] homePlanet: String! } type Foo { mood: MoodStatus } type Query { human: Human foo: Foo }
Notice that epoxy converted snake_cased fields to camelCase in the GraphQL Schema.
ObjectTypes become containers
You can bring your own objects, (like a Django or SQLAlchemy model), or you can use the class you just created:
me = Human(id=2, name='Jake', home_planet='Earth', friends=[Human(id=3, name='Syrus', home_planet='Earth')]) print(me) # <Human id=2, name='Jake', home_planet='Earth', friends=[<Human id=3, name='Syrus', home_planet='Earth', friends=[]>]]> print(me.name) # Jake
Epoxy will automatically resolve the runtime types of your objects if class that you created from R.ObjectType, but
if you want to bring your own Human (i.e. a model.Model from Django), just tell Epoxy about it! And if you don't want
to, you can just override the is_type_of function inside Human to something more to your liking.
my_app/models.py
from django.db import models from my_app.graphql import R @R.Human.CanBe class RealHumanBean(models.Model): """ And a real hero. """ name = models.CharField(name=Name) # Or if you don't want to use the decorator: R.Human.CanBe(Human)
Mutations
Epoxy also supports defining mutations. Making a Mutation a Relay mutation is as simple as changing R.Mutation to
Relay.Mutation.
class AddFriend(R.Mutation): class Input: human_to_add = R.ID.NonNull class Output: new_friends_list = R.Human.List @R.resolve_with_args def resolve(self, obj, human_to_add): obj.add_friend(human_to_add) return self.Output(new_friends_list=obj.friends) schema = R.schema(R.Query, R.Mutations)
You can then execute the query:
mutation AddFriend { addFriend(input: {humanToAdd: 6}) { newFriendsList { id name homePlanet } } }
Defining custom scalar types:
class DateTime(R.Scalar): @staticmethod def serialize(dt): return dt.isoformat() @staticmethod def parse_literal(node): if isinstance(node, ast.StringValue): return datetime.datetime.strptime(node.value, "%Y-%m-%dT%H:%M:%S.%f") @staticmethod def parse_value(value): return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
Defining input types:
class SimpleInput(R.InputType): a = R.Int b = R.Int some_underscore = R.String some_from_field = R.String(default_value='Hello World')
Defining an Enum (using enum module)
from enum import Enum @R class MyEnum(Enum): FOO = 1 BAR = 2 BAZ = 3
Starwars?!
Use the force, check out how we've defined the
schema
for the starwars tests, and compare them to the reference implementation's
schema.
Relay Support
At this point, Epoxy has rudimentary relay support. Enable support for Relay by mixing in the RelayMixin using
TypeResolver.Mixin.
from epoxy.contrib.relay import RelayMixin from epoxy.contrib.relay.data_source.memory import InMemoryDataSource # Epoxy provides an "in memory" data source, that implements `epoxy.contrib.relay.data_source.BaseDataSource`, # which can be used to easily create a mock data source. In practice, you'd implement your own data source. data_source = InMemoryDataSource() R = TypeRegistry() Relay = R.Mixin(RelayMixin, data_source)
Node Definition
Once RelayMixin has been mixed into the Registry, things can subclass Node automatically!
class Pet(R.Implements[Relay.Node]): name = R.String
Connection Definition & NodeField
Connections can be defined upon any object type. Here we'll make a Query root node that provides a connection
to a list of pets & a node field to resolve an indivudal node.
class Query(R.ObjectType): pets = Relay.Connection('Pet', R.Pet) # The duplicate 'Pet' definition is just temporary and will be removed. node = Relay.NodeField
Mutations
class SimpleAddition(Relay.Mutation): class Input: a = R.Int b = R.Int class Output: sum = R.Int def execute(self, obj, input, info): return self.Output(sum=input.a + input.b)
Adding some data!
Let's add some pets to the data_source and query them!
# Schema has to be defined so that all thunks are resolved before we can use `Pet` as a container. Schema = R.Schema(R.Query) pet_names = ["Max", "Buddy", "Charlie", "Jack", "Cooper", "Rocky"] for i, pet_name in enumerate(pet_names, 1): data_source.add(Pet(id=i, name=pet_name))
Running Relay Connection Query:
result = graphql(Schema, ''' { pets(first: 5) { edges { node { id name } cursor } pageInfo { hasPreviousPage hasNextPage startCursor endCursor } } node(id: "UGV0OjU=") { id ... on Pet { name } } } ''')
Assets 3
graphql-epoxy v0.2-alpha-0 release
Since Relay was specified for the 0.2 milestone, we are going to just have a 0.2 release.
This release just fixes #4.
Assets 3
graphql-epoxy v0.1-alpha-1 release
π Initial Relay Support
Relay Support (WIP)
- Node
- Connection & Edges
- Mutations
At this point, Epoxy has rudimentary relay support. Enable support for Relay by mixing in the RelayMixin using
TypeResolver.Mixin.
from epoxy.contrib.relay import RelayMixin from epoxy.contrib.relay.data_source.memory import InMemoryDataSource # Epoxy provides an "in memory" data source, that implements `epoxy.contrib.relay.data_source.BaseDataSource`, # which can be used to easily create a mock data source. In practice, you'd implement your own data source. data_source = InMemoryDataSource() R = TypeRegistry() Relay = R.Mixin(RelayMixin, data_source)
Node Definition
Once RelayMixin has been mixed into the Registry, things can subclass Node automatically!
class Pet(R.Implements[Relay.Node]): name = R.String
Connection Definition & NodeField
Connections can be defined upon any object type. Here we'll make a Query root node that provides a connection
to a list of pets & a node field to resolve an indivudal node.
class Query(R.ObjectType): pets = Relay.Connection('Pet', R.Pet) # The duplicate 'Pet' definition is just temporary and will be removed. node = Relay.NodeField
Adding some data!
Let's add some pets to the data_source and query them!
# Schema has to be defined so that all thunks are resolved before we can use `Pet` as a container. Schema = R.Schema(R.Query) pet_names = ["Max", "Buddy", "Charlie", "Jack", "Cooper", "Rocky"] for i, pet_name in enumerate(pet_names, 1): data_source.add(Pet(id=i, name=pet_name))
Running Relay Connection Query:
result = graphql(Schema, ''' { pets(first: 5) { edges { node { id name } cursor } pageInfo { hasPreviousPage hasNextPage startCursor endCursor } } node(id: "UGV0OjU=") { id ... on Pet { name } } } ''')
Assets 3
graphql-epoxy v0.1-alpha-0 release
This marks the first release of graphql-epoxy. The APIs are subject to change, everything is subject to change. But I figured I should get it packaged so that I can start using it.
Things Done So Far
TypeRegistry
Define a type registry using:
from epoxy import TypeRegistry R = TypeRegistry()
Object Type Definition
Using the above defined TypeRegistry we can now define our first type:
class Human(R.ObjectType) id = R.ID.NonNull name = R.String age = R.Int interests = R.String.List friends = R.Human.List
Interface Definition
We can even define interfaces:
class Pet(R.Interface): name = R.String class Dog(R.Implements.Pet): bark = R.String class Cat(R.Implements.Pet): meow = R.String
Or even a type that implements multiple interfaces:
class Bean(R.Interface): real = R.Boolean hero = R.Boolean class Character(R.Interface): id = R.ID name = R.String friends_with = R.Character.List lives_remaining = R.Field(R.Int) class Human(R.Implements[R.Character, R.Bean]): home_planet = R.String
Union Definition
A union can be implemented just as easily:
class PetUnion(R.Union[R.Dog, R.Cat]): pass
Runtime Type Detection
The is_type_of methods are automatically created for Objects. If you're bringing your own object, you simply need to tell the TypeRegistry of it, using R.<type>.CanBe(klass). Remember, this does not need to be defined after you define the respective ObjectType.
@R.Dog.CanBe class MyDog(object): def __init__(self, name, bark): self.name = name self.bark = bark
Object Types as Containers
Additionally, you can use the ObjectTypes you just created as containers.
pets=[ Dog(name='Clifford', bark='Really big bark, because it\'s a really big dog.'), Cat(name='Garfield', meow='Lasagna') ])
GraphQL Object Interoperability
Epoxy works great with regular GraphQL object types as well. You can just pass them where-ever you'd expect an R.<type>, with the exception being that if you are defining a field within an ObjectType or Interface, you must wrap it in a call to R.Field.
BuiltInType = GraphQLObjectType( name='BuiltInType', fields={ 'someString': GraphQLField(GraphQLString) } ) BuiltInTypeTuple = namedtuple('BuiltInTypeData', 'someString') R = TypeRegistry() class Query(R.ObjectType): built_in_type = R.Field(BuiltInType) def resolve_built_in_type(self, obj, args, info): return BuiltInTypeTuple('Hello World. I am a string.')
For more examples, check out the tests folder.