Skip to content

Commit 1b15d12

Browse files
committed
Added RST Readme
1 parent b385382 commit 1b15d12

File tree

2 files changed

+270
-0
lines changed

2 files changed

+270
-0
lines changed

README.rst

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
Relay Library for GraphQL Python
2+
================================
3+
4+
This is a library to allow the easy creation of Relay-compliant servers
5+
using the `GraphQL Python <https://github.com/dittos/graphqllib>`__
6+
reference implementation of a GraphQL server.
7+
8+
Note: The code is a **exact** port of the original `graphql-relay js
9+
implementation <https://github.com/graphql/graphql-relay-js>`__ from
10+
Facebook
11+
12+
|Build Status| |Coverage Status|
13+
14+
Getting Started
15+
---------------
16+
17+
A basic understanding of GraphQL and of the GraphQL Python
18+
implementation is needed to provide context for this library.
19+
20+
An overview of GraphQL in general is available in the
21+
`README <https://github.com/dittos/graphqllib/blob/master/README.md>`__
22+
for the `Specification for
23+
GraphQL <https://github.com/dittos/graphqllib>`__.
24+
25+
This library is designed to work with the the `GraphQL
26+
Python <https://github.com/dittos/graphqllib>`__ reference
27+
implementation of a GraphQL server.
28+
29+
An overview of the functionality that a Relay-compliant GraphQL server
30+
should provide is in the `GraphQL Relay
31+
Specification <https://facebook.github.io/relay/docs/graphql-relay-specification.html>`__
32+
on the `Relay website <https://facebook.github.io/relay/>`__. That
33+
overview describes a simple set of examples that exist as
34+
`tests <tests>`__ in this repository. A good way to get started with
35+
this repository is to walk through that documentation and the
36+
corresponding tests in this library together.
37+
38+
Using Relay Library for GraphQL Python (graphqllib)
39+
---------------------------------------------------
40+
41+
Install Relay Library for GraphQL Python
42+
43+
.. code:: sh
44+
45+
pip install git+https://github.com/dittos/graphqllib.git # Last version of graphqllib
46+
pip install graphql-relay
47+
48+
When building a schema for
49+
`GraphQL <https://github.com/dittos/graphqllib>`__, the provided library
50+
functions can be used to simplify the creation of Relay patterns.
51+
52+
Connections
53+
~~~~~~~~~~~
54+
55+
Helper functions are provided for both building the GraphQL types for
56+
connections and for implementing the ``resolver`` method for fields
57+
returning those types.
58+
59+
- ``connectionArgs`` returns the arguments that fields should provide
60+
when they return a connection type.
61+
- ``connectionDefinitions`` returns a ``connectionType`` and its
62+
associated ``edgeType``, given a name and a node type.
63+
- ``connectionFromArray`` is a helper method that takes an array and
64+
the arguments from ``connectionArgs``, does pagination and filtering,
65+
and returns an object in the shape expected by a ``connectionType``'s
66+
``resolver`` function.
67+
- ``connectionFromPromisedArray`` is similar to
68+
``connectionFromArray``, but it takes a promise that resolves to an
69+
array, and returns a promise that resolves to the expected shape by
70+
``connectionType``.
71+
- ``cursorForObjectInConnection`` is a helper method that takes an
72+
array and a member object, and returns a cursor for use in the
73+
mutation payload.
74+
75+
An example usage of these methods from the `test
76+
schema <tests/starwars/schema.py>`__:
77+
78+
.. code:: python
79+
80+
shipConnection = connectionDefinitions('Ship', shipType).connectionType
81+
82+
factionType = GraphQLObjectType(
83+
name= 'Faction',
84+
description= 'A faction in the Star Wars saga',
85+
fields= lambda: {
86+
'id': globalIdField('Faction'),
87+
'name': GraphQLField(
88+
GraphQLString,
89+
description='The name of the faction.',
90+
),
91+
'ships': GraphQLField(
92+
shipConnection,
93+
description= 'The ships used by the faction.',
94+
args= connectionArgs,
95+
resolver= lambda faction, args, *_: connectionFromArray(
96+
map(getShip, faction.ships),
97+
args
98+
),
99+
)
100+
},
101+
interfaces= [nodeInterface]
102+
)
103+
104+
This shows adding a ``ships`` field to the ``Faction`` object that is a
105+
connection. It uses
106+
``connectionDefinitions({name: 'Ship', nodeType: shipType})`` to create
107+
the connection type, adds ``connectionArgs`` as arguments on this
108+
function, and then implements the resolver function by passing the array
109+
of ships and the arguments to ``connectionFromArray``.
110+
111+
Object Identification
112+
~~~~~~~~~~~~~~~~~~~~~
113+
114+
Helper functions are provided for both building the GraphQL types for
115+
nodes and for implementing global IDs around local IDs.
116+
117+
- ``nodeDefinitions`` returns the ``Node`` interface that objects can
118+
implement, and returns the ``node`` root field to include on the
119+
query type. To implement this, it takes a function to resolve an ID
120+
to an object, and to determine the type of a given object.
121+
- ``toGlobalId`` takes a type name and an ID specific to that type
122+
name, and returns a "global ID" that is unique among all types.
123+
- ``fromGlobalId`` takes the "global ID" created by ``toGlobalID``, and
124+
retuns the type name and ID used to create it.
125+
- ``globalIdField`` creates the configuration for an ``id`` field on a
126+
node.
127+
- ``pluralIdentifyingRootField`` creates a field that accepts a list of
128+
non-ID identifiers (like a username) and maps then to their
129+
corresponding objects.
130+
131+
An example usage of these methods from the `test
132+
schema <tests/starwars/schema.py>`__:
133+
134+
.. code:: python
135+
136+
def getNode(globalId, *args):
137+
resolvedGlobalId = fromGlobalId(globalId)
138+
_type, _id = resolvedGlobalId.type, resolvedGlobalId.id
139+
if _type == 'Faction':
140+
return getFaction(_id)
141+
elif _type == 'Ship':
142+
return getShip(_id)
143+
else:
144+
return None
145+
146+
def getNodeType(obj):
147+
if isinstance(obj, Faction):
148+
return factionType
149+
else:
150+
return shipType
151+
152+
_nodeDefinitions = nodeDefinitions(getNode, getNodeType)
153+
nodeField, nodeInterface = _nodeDefinitions.nodeField, _nodeDefinitions.nodeInterface
154+
155+
factionType = GraphQLObjectType(
156+
name= 'Faction',
157+
description= 'A faction in the Star Wars saga',
158+
fields= lambda: {
159+
'id': globalIdField('Faction'),
160+
},
161+
interfaces= [nodeInterface]
162+
)
163+
164+
queryType = GraphQLObjectType(
165+
name= 'Query',
166+
fields= lambda: {
167+
'node': nodeField
168+
}
169+
)
170+
171+
This uses ``nodeDefinitions`` to construct the ``Node`` interface and
172+
the ``node`` field; it uses ``fromGlobalId`` to resolve the IDs passed
173+
in in the implementation of the function mapping ID to object. It then
174+
uses the ``globalIdField`` method to create the ``id`` field on
175+
``Faction``, which also ensures implements the ``nodeInterface``.
176+
Finally, it adds the ``node`` field to the query type, using the
177+
``nodeField`` returned by ``nodeDefinitions``.
178+
179+
Mutations
180+
~~~~~~~~~
181+
182+
A helper function is provided for building mutations with single inputs
183+
and client mutation IDs.
184+
185+
- ``mutationWithClientMutationId`` takes a name, input fields, output
186+
fields, and a mutation method to map from the input fields to the
187+
output fields, performing the mutation along the way. It then creates
188+
and returns a field configuration that can be used as a top-level
189+
field on the mutation type.
190+
191+
An example usage of these methods from the `test
192+
schema <tests/starwars/schema.py>`__:
193+
194+
.. code:: python
195+
196+
class IntroduceShipMutation(object):
197+
def __init__(self, shipId, factionId, clientMutationId=None):
198+
self.shipId = shipId
199+
self.factionId = factionId
200+
self.clientMutationId = None
201+
202+
def mutateAndGetPayload(data, *_):
203+
shipName = data.get('shipName')
204+
factionId = data.get('factionId')
205+
newShip = createShip(shipName, factionId)
206+
return IntroduceShipMutation(
207+
shipId=newShip.id,
208+
factionId=factionId,
209+
)
210+
211+
shipMutation = mutationWithClientMutationId(
212+
'IntroduceShip',
213+
inputFields={
214+
'shipName': GraphQLField(
215+
GraphQLNonNull(GraphQLString)
216+
),
217+
'factionId': GraphQLField(
218+
GraphQLNonNull(GraphQLID)
219+
)
220+
},
221+
outputFields= {
222+
'ship': GraphQLField(
223+
shipType,
224+
resolver= lambda payload, *_: getShip(payload.shipId)
225+
),
226+
'faction': GraphQLField(
227+
factionType,
228+
resolver= lambda payload, *_: getFaction(payload.factionId)
229+
)
230+
},
231+
mutateAndGetPayload=mutateAndGetPayload
232+
)
233+
234+
mutationType = GraphQLObjectType(
235+
'Mutation',
236+
fields= lambda: {
237+
'introduceShip': shipMutation
238+
}
239+
)
240+
241+
This code creates a mutation named ``IntroduceShip``, which takes a
242+
faction ID and a ship name as input. It outputs the ``Faction`` and the
243+
``Ship`` in question. ``mutateAndGetPayload`` then gets an object with a
244+
property for each input field, performs the mutation by constructing the
245+
new ship, then returns an object that will be resolved by the output
246+
fields.
247+
248+
Our mutation type then creates the ``introduceShip`` field using the
249+
return value of ``mutationWithClientMutationId``.
250+
251+
Contributing
252+
------------
253+
254+
After cloning this repo, ensure dependencies are installed by running:
255+
256+
.. code:: sh
257+
258+
python setup.py install
259+
260+
After developing, the full test suite can be evaluated by running:
261+
262+
.. code:: sh
263+
264+
python setup.py test # Use --pytest-args="-v -s" for verbose mode
265+
266+
.. |Build Status| image:: https://travis-ci.org/syrusakbary/graphql-relay-py.svg?branch=master
267+
:target: https://travis-ci.org/syrusakbary/graphql-relay-py
268+
.. |Coverage Status| image:: https://coveralls.io/repos/syrusakbary/graphql-relay-py/badge.svg?branch=master&service=github
269+
:target: https://coveralls.io/github/syrusakbary/graphql-relay-py?branch=master

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def run_tests(self):
2727
version='0.1.1',
2828

2929
description='Relay implementation for Python',
30+
long_description=open('README.rst').read(),
3031

3132
url='https://github.com/syrusakbary/graphql-relay-py',
3233

0 commit comments

Comments
 (0)