Epoxy is a magical tool for rapid development of GraphQL types, schemas, resolvers, mutations quickly & pragmatically.
- Minimal Boilerplate: You can create a GraphQL schema and execute it in less than 5 lines of code.
- Definition Ordering: It doesn't matter. Define your objects in any order you want. Epoxy will take care of the rest.
- Quick: Once you create your schema, epoxy doesn't get in the way. Your resolvers will be called directly by
graphql-corewith no additional indirection.
Epoxy is available on pypi under the package name graphql-epoxy, you can get it by running:
pip install graphql-epoxyDefining a GraphQL Schema using Epoxy is as simple as creating a TypeRegistry and using it to create types for you.
fromepoxyimportTypeRegistryR=TypeRegistry()
classCharacter(R.Interface):
id=R.IDname=R.Stringfriends=R.Character.ListclassHuman(R.Implements.Character):
home_planet=R.String.NonNullclassQuery(R.ObjectType):
human=R.Humanfoo=R.Foo# This is defined below! Ordering doesn't matter! defresolve_human(self, obj, args, info):
"""This will be used as the description of the field Query.human."""returnHuman(id=5, name='Bob', friends=[Human(id=6, name='Bill')]You can even have epoxy learn about your already defined Python enums.
classMoodStatus(enums.Enum):
HAPPY=1SAD=2MELANCHOLY=3R(MoodStatus)And then use it in an ObjectType:
classFoo(R.ObjectType):
mood=R.MoodStatus# or mood=R.Field(R.MoodStatus, description="Describing the mood of Foo, is sometimes pretty hard.")
defresolve_mood(self, *args):
returnMoodStatus.HAPPY.valueSchema 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:
enumMoodStatus {
HAPPY SAD MELANCHOLY
}
interfaceCharacter {
id: IDname: Stringfriends: [Character]
}
typeHumanimplementsCharacter {
id: IDname: Stringfriends: [Character]
homePlanet: String!
}
typeFoo {
mood: MoodStatus
}
typeQuery {
human: Humanfoo: Foo
}Notice that epoxy converted snake_cased fields to camelCase in the GraphQL Schema.
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) # JakeEpoxy 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.
fromdjango.dbimportmodelsfrommy_app.graphqlimportR@R.Human.CanBeclassRealHumanBean(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)Epoxy also supports defining mutations. Making a Mutation a Relay mutation is as simple as changing R.Mutation to
Relay.Mutation.
classAddFriend(R.Mutation):
classInput:
human_to_add=R.ID.NonNullclassOutput:
new_friends_list=R.Human.List@R.resolve_with_argsdefresolve(self, obj, human_to_add):
obj.add_friend(human_to_add)
returnself.Output(new_friends_list=obj.friends)
schema=R.schema(R.Query, R.Mutations)You can then execute the query:
mutationAddFriend {
addFriend(input: {humanToAdd: 6}) {
newFriendsList {
idnamehomePlanet
}
}
}classDateTime(R.Scalar):
@staticmethoddefserialize(dt):
returndt.isoformat()
@staticmethoddefparse_literal(node):
ifisinstance(node, ast.StringValue):
returndatetime.datetime.strptime(node.value, "%Y-%m-%dT%H:%M:%S.%f")
@staticmethoddefparse_value(value):
returndatetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")classSimpleInput(R.InputType):
a=R.Intb=R.Intsome_underscore=R.Stringsome_from_field=R.String(default_value='Hello World')fromenumimportEnum@RclassMyEnum(Enum):
FOO=1BAR=2BAZ=3Use the force, check out how we've defined the schema for the starwars tests, and compare them to the reference implementation's schema.
At this point, Epoxy has rudimentary relay support. Enable support for Relay by mixing in the RelayMixin using
TypeResolver.Mixin.
fromepoxy.contrib.relayimportRelayMixinfromepoxy.contrib.relay.data_source.memoryimportInMemoryDataSource# 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)Once RelayMixin has been mixed into the Registry, things can subclass Node automatically!
classPet(R.Implements[Relay.Node]):
name=R.StringConnections 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.
classQuery(R.ObjectType):
pets=Relay.Connection('Pet', R.Pet) # The duplicate 'Pet' definition is just temporary and will be removed.node=Relay.NodeFieldclassSimpleAddition(Relay.Mutation):
classInput:
a=R.Intb=R.IntclassOutput:
sum=R.Intdefexecute(self, obj, input, info):
returnself.Output(sum=input.a+input.b)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"]
fori, pet_nameinenumerate(pet_names, 1):
data_source.add(Pet(id=i, name=pet_name))result=graphql(Schema, '''{ pets(first: 5) { edges { node { id name } cursor } pageInfo { hasPreviousPage hasNextPage startCursor endCursor } } node(id: "UGV0OjU=") { id ... on Pet { name } }}''')