Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/marvelapp/graphql-relay-py
fork of graphql-python/graphql-relay-py to fix hasPreviousPage
https://github.com/marvelapp/graphql-relay-py
Last synced: 3 days ago
JSON representation
fork of graphql-python/graphql-relay-py to fix hasPreviousPage
- Host: GitHub
- URL: https://github.com/marvelapp/graphql-relay-py
- Owner: marvelapp
- License: mit
- Created: 2020-11-25T15:10:55.000Z (almost 4 years ago)
- Default Branch: master
- Last Pushed: 2020-11-25T15:47:48.000Z (almost 4 years ago)
- Last Synced: 2024-04-15T01:19:31.340Z (7 months ago)
- Language: Python
- Size: 93.8 KB
- Stars: 0
- Watchers: 7
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Relay Library for GraphQL Python
This is a library to allow the easy creation of Relay-compliant servers using
the [GraphQL Python](https://github.com/graphql-python/graphql-core) reference implementation
of a GraphQL server.*Note: The code is a __exact__ port of the original [graphql-relay js implementation](https://github.com/graphql/graphql-relay-js)
from Facebook*[![PyPI version](https://badge.fury.io/py/graphql-relay.svg)](https://badge.fury.io/py/graphql-relay)
[![Build Status](https://travis-ci.org/graphql-python/graphql-relay-py.svg?branch=master)](https://travis-ci.org/graphql-python/graphql-relay-py)
[![Coverage Status](https://coveralls.io/repos/graphql-python/graphql-relay-py/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphql-relay-py?branch=master)## Getting Started
A basic understanding of GraphQL and of the GraphQL Python implementation is needed
to provide context for this library.An overview of GraphQL in general is available in the
[README](https://github.com/graphql-python/graphql-core/blob/master/README.md) for the
[Specification for GraphQL](https://github.com/graphql-python/graphql-core).This library is designed to work with the
the [GraphQL Python](https://github.com/graphql-python/graphql-core) reference implementation
of a GraphQL server.An overview of the functionality that a Relay-compliant GraphQL server should
provide is in the [GraphQL Relay Specification](https://facebook.github.io/relay/docs/graphql-relay-specification.html)
on the [Relay website](https://facebook.github.io/relay/). That overview
describes a simple set of examples that exist as [tests](tests) in this
repository. A good way to get started with this repository is to walk through
that documentation and the corresponding tests in this library together.## Using Relay Library for GraphQL Python (graphql-core)
Install Relay Library for GraphQL Python
```sh
pip install graphql-core --pre # Last version of graphql-core
pip install graphql-relay
```When building a schema for [GraphQL](https://github.com/graphql-python/graphql-core),
the provided library functions can be used to simplify the creation of Relay
patterns.### Connections
Helper functions are provided for both building the GraphQL types
for connections and for implementing the `resolver` method for fields
returning those types.- `connection_args` returns the arguments that fields should provide when
they return a connection type.
- `connection_definitions` returns a `connection_type` and its associated
`edgeType`, given a name and a node type.
- `connection_from_list` is a helper method that takes an array and the
arguments from `connection_args`, does pagination and filtering, and returns
an object in the shape expected by a `connection_type`'s `resolver` function.
- `connection_from_promised_list` is similar to `connection_from_list`, but
it takes a promise that resolves to an array, and returns a promise that
resolves to the expected shape by `connection_type`.
- `cursor_for_object_in_connection` is a helper method that takes an array and a
member object, and returns a cursor for use in the mutation payload.An example usage of these methods from the [test schema](tests/starwars/schema.py):
```python
ship_edge, ship_connection = connection_definitions('Ship', shipType)factionType = GraphQLObjectType(
name= 'Faction',
description= 'A faction in the Star Wars saga',
fields= lambda: {
'id': global_id_field('Faction'),
'name': GraphQLField(
GraphQLString,
description='The name of the faction.',
),
'ships': GraphQLField(
shipConnection,
description= 'The ships used by the faction.',
args= connection_args,
resolver= lambda faction, args, *_: connection_from_list(
map(getShip, faction.ships),
args
),
)
},
interfaces= [node_interface]
)
```This shows adding a `ships` field to the `Faction` object that is a connection.
It uses `connection_definitions({name: 'Ship', nodeType: shipType})` to create
the connection type, adds `connection_args` as arguments on this function, and
then implements the resolver function by passing the array of ships and the
arguments to `connection_from_list`.### Object Identification
Helper functions are provided for both building the GraphQL types
for nodes and for implementing global IDs around local IDs.- `node_definitions` returns the `Node` interface that objects can implement,
and returns the `node` root field to include on the query type. To implement
this, it takes a function to resolve an ID to an object, and to determine
the type of a given object.
- `to_global_id` takes a type name and an ID specific to that type name,
and returns a "global ID" that is unique among all types.
- `from_global_id` takes the "global ID" created by `toGlobalID`, and retuns
the type name and ID used to create it.
- `global_id_field` creates the configuration for an `id` field on a node.
- `plural_identifying_root_field` creates a field that accepts a list of
non-ID identifiers (like a username) and maps then to their corresponding
objects.An example usage of these methods from the [test schema](tests/starwars/schema.py):
```python
def get_node(global_id, context, info):
resolvedGlobalId = from_global_id(global_id)
_type, _id = resolvedGlobalId.type, resolvedGlobalId.id
if _type == 'Faction':
return getFaction(_id)
elif _type == 'Ship':
return getShip(_id)
else:
return Nonedef get_node_type(obj, context, info):
if isinstance(obj, Faction):
return factionType
else:
return shipTypenode_interface, node_field = node_definitions(get_node, get_node_type)
factionType = GraphQLObjectType(
name= 'Faction',
description= 'A faction in the Star Wars saga',
fields= lambda: {
'id': global_id_field('Faction'),
},
interfaces= [node_interface]
)queryType = GraphQLObjectType(
name= 'Query',
fields= lambda: {
'node': node_field
}
)
```This uses `node_definitions` to construct the `Node` interface and the `node`
field; it uses `from_global_id` to resolve the IDs passed in in the implementation
of the function mapping ID to object. It then uses the `global_id_field` method to
create the `id` field on `Faction`, which also ensures implements the
`node_interface`. Finally, it adds the `node` field to the query type, using the
`node_field` returned by `node_definitions`.### Mutations
A helper function is provided for building mutations with
single inputs and client mutation IDs.- `mutation_with_client_mutation_id` takes a name, input fields, output fields,
and a mutation method to map from the input fields to the output fields,
performing the mutation along the way. It then creates and returns a field
configuration that can be used as a top-level field on the mutation type.An example usage of these methods from the [test schema](tests/starwars/schema.py):
```python
class IntroduceShipMutation(object):
def __init__(self, shipId, factionId, clientMutationId=None):
self.shipId = shipId
self.factionId = factionId
self.clientMutationId = Nonedef mutate_and_get_payload(data, *_):
shipName = data.get('shipName')
factionId = data.get('factionId')
newShip = createShip(shipName, factionId)
return IntroduceShipMutation(
shipId=newShip.id,
factionId=factionId,
)shipMutation = mutation_with_client_mutation_id(
'IntroduceShip',
input_fields={
'shipName': GraphQLField(
GraphQLNonNull(GraphQLString)
),
'factionId': GraphQLField(
GraphQLNonNull(GraphQLID)
)
},
output_fields= {
'ship': GraphQLField(
shipType,
resolver= lambda payload, *_: getShip(payload.shipId)
),
'faction': GraphQLField(
factionType,
resolver= lambda payload, *_: getFaction(payload.factionId)
)
},
mutate_and_get_payload=mutate_and_get_payload
)mutationType = GraphQLObjectType(
'Mutation',
fields= lambda: {
'introduceShip': shipMutation
}
)
```This code creates a mutation named `IntroduceShip`, which takes a faction
ID and a ship name as input. It outputs the `Faction` and the `Ship` in
question. `mutate_and_get_payload` then gets an object with a property for
each input field, performs the mutation by constructing the new ship, then
returns an object that will be resolved by the output fields.Our mutation type then creates the `introduceShip` field using the return
value of `mutation_with_client_mutation_id`.## Contributing
After cloning this repo, ensure dependencies are installed by running:
```sh
python setup.py install
```After developing, the full test suite can be evaluated by running:
```sh
python setup.py test # Use --pytest-args="-v -s" for verbose mode
```