Skip to content

Commit 85e354c

Browse files
committed
Merge branch 'master' into feature/date-improvements
# Conflicts: # graphene/types/datetime.py
2 parents b4255e5 + d46d8e8 commit 85e354c

33 files changed

+547
-92
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ htmlcov/
4242
.coverage
4343
.coverage.*
4444
.cache
45+
.pytest_cache
4546
nosetests.xml
4647
coverage.xml
4748
*,cover

UPGRADE-v2.0.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ developer has to write to use them.
2121

2222

2323
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
24-
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/2.0/graphene/tests/issues/test_425.py).
24+
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py).
2525
2626
## Deprecations
2727

docs/execution/middleware.rst

+31-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Middleware
33

44
You can use ``middleware`` to affect the evaluation of fields in your schema.
55

6-
A middleware is any object that responds to ``resolve(*args, next_middleware)``.
6+
A middleware is any object or function that responds to ``resolve(next_middleware, *args)``.
77

88
Inside that method, it should either:
99

@@ -18,10 +18,8 @@ Middlewares ``resolve`` is invoked with several arguments:
1818

1919
- ``next`` represents the execution chain. Call ``next`` to continue evalution.
2020
- ``root`` is the root value object passed throughout the query.
21-
- ``args`` is the hash of arguments passed to the field.
22-
- ``context`` is the context object passed throughout the query.
2321
- ``info`` is the resolver info.
24-
22+
- ``args`` is the dict of arguments passed to the field.
2523

2624
Example
2725
-------
@@ -42,3 +40,32 @@ And then execute it with:
4240
.. code:: python
4341
4442
result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])
43+
44+
45+
Functional example
46+
------------------
47+
48+
Middleware can also be defined as a function. Here we define a middleware that
49+
logs the time it takes to resolve each field
50+
51+
.. code:: python
52+
53+
from time import time as timer
54+
55+
def timing_middleware(next, root, info, **args):
56+
start = timer()
57+
return_value = next(root, info, **args)
58+
duration = timer() - start
59+
logger.debug("{parent_type}.{field_name}: {duration} ms".format(
60+
parent_type=root._meta.name if root and hasattr(root, '_meta') else '',
61+
field_name=info.field_name,
62+
duration=round(duration * 1000, 2)
63+
))
64+
return return_value
65+
66+
67+
And then execute it with:
68+
69+
.. code:: python
70+
71+
result = schema.execute('THE QUERY', middleware=[timing_middleware])

docs/relay/index.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ Useful links
2121
- `Relay Cursor Connection Specification`_
2222
- `Relay input Object Mutation`_
2323

24-
.. _Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html
24+
.. _Relay: https://facebook.github.io/relay/docs/en/graphql-server-specification.html
2525
.. _Relay specification: https://facebook.github.io/relay/graphql/objectidentification.htm#sec-Node-root-field
26-
.. _Getting started with Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html
26+
.. _Getting started with Relay: https://facebook.github.io/relay/docs/en/quick-start-guide.html
2727
.. _Relay Global Identification Specification: https://facebook.github.io/relay/graphql/objectidentification.htm
2828
.. _Relay Cursor Connection Specification: https://facebook.github.io/relay/graphql/connections.htm
2929
.. _Relay input Object Mutation: https://facebook.github.io/relay/graphql/mutations.htm

docs/relay/nodes.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ Example of a custom node:
5555
return '{}:{}'.format(type, id)
5656
5757
@staticmethod
58-
def get_node_from_global_id(info global_id, only_type=None):
58+
def get_node_from_global_id(info, global_id, only_type=None):
5959
type, id = global_id.split(':')
60-
if only_node:
60+
if only_type:
6161
# We assure that the node type that we want to retrieve
6262
# is the same that was indicated in the field type
63-
assert type == only_node._meta.name, 'Received not compatible node.'
63+
assert type == only_type._meta.name, 'Received not compatible node.'
6464
6565
if type == 'User':
6666
return get_user(id)
@@ -75,10 +75,10 @@ Accessing node types
7575
--------------------
7676

7777
If we want to retrieve node instances from a ``global_id`` (scalar that identifies an instance by it's type name and id),
78-
we can simply do ``Node.get_node_from_global_id(global_id, context, info)``.
78+
we can simply do ``Node.get_node_from_global_id(info, global_id)``.
7979

8080
In the case we want to restrict the instance retrieval to a specific type, we can do:
81-
``Node.get_node_from_global_id(global_id, context, info, only_type=Ship)``. This will raise an error
81+
``Node.get_node_from_global_id(info, global_id, only_type=Ship)``. This will raise an error
8282
if the ``global_id`` doesn't correspond to a Ship type.
8383

8484

docs/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Required library
22
Sphinx==1.5.3
33
# Docs template
4-
https://github.com/graphql-python/graphene-python.org/archive/docs.zip
4+
http://graphene-python.org/sphinx_graphene_theme.zip

docs/types/enums.rst

+9-2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ the ``Enum.from_enum`` function.
5454
5555
graphene.Enum.from_enum(AlreadyExistingPyEnum)
5656
57+
``Enum.from_enum`` supports a ``description`` and ``deprecation_reason`` lambdas as input so
58+
you can add description etc. to your enum without changing the original:
59+
60+
.. code:: python
61+
62+
graphene.Enum.from_enum(AlreadyExistingPyEnum, description=lambda value: return 'foo' if value == AlreadyExistingPyEnum.Foo else 'bar')
63+
5764
5865
Notes
5966
-----
@@ -65,7 +72,7 @@ member getters.
6572
In the Python ``Enum`` implementation you can access a member by initing the Enum.
6673

6774
.. code:: python
68-
75+
6976
from enum import Enum
7077
class Color(Enum):
7178
RED = 1
@@ -78,7 +85,7 @@ In the Python ``Enum`` implementation you can access a member by initing the Enu
7885
However, in Graphene ``Enum`` you need to call get to have the same effect:
7986

8087
.. code:: python
81-
88+
8289
from graphene import Enum
8390
class Color(Enum):
8491
RED = 1

docs/types/list-and-nonnull.rst

+21
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,24 @@ Lists work in a similar way: We can use a type modifier to mark a type as a
4848
``List``, which indicates that this field will return a list of that type.
4949
It works the same for arguments, where the validation step will expect a list
5050
for that value.
51+
52+
NonNull Lists
53+
-------------
54+
55+
By default items in a list will be considered nullable. To define a list without
56+
any nullable items the type needs to be marked as ``NonNull``. For example:
57+
58+
.. code:: python
59+
60+
import graphene
61+
62+
class Character(graphene.ObjectType):
63+
appears_in = graphene.List(graphene.NonNull(graphene.String))
64+
65+
The above results in the type definition:
66+
67+
.. code::
68+
69+
type Character {
70+
appearsIn: [String!]
71+
}

docs/types/scalars.rst

+72-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,83 @@
11
Scalars
22
=======
33

4+
All Scalar types accept the following arguments. All are optional:
5+
6+
``name``: *string*
7+
8+
Override the name of the Field.
9+
10+
``description``: *string*
11+
12+
A description of the type to show in the GraphiQL browser.
13+
14+
``required``: *boolean*
15+
16+
If ``True``, the server will enforce a value for this field. See `NonNull <./list-and-nonnull.html#nonnull>`_. Default is ``False``.
17+
18+
``deprecation_reason``: *string*
19+
20+
Provide a deprecation reason for the Field.
21+
22+
``default_value``: *any*
23+
24+
Provide a default value for the Field.
25+
26+
27+
28+
Base scalars
29+
------------
30+
431
Graphene defines the following base Scalar Types:
532

6-
- ``graphene.String``
7-
- ``graphene.Int``
8-
- ``graphene.Float``
9-
- ``graphene.Boolean``
10-
- ``graphene.ID``
33+
``graphene.String``
34+
35+
Represents textual data, represented as UTF-8
36+
character sequences. The String type is most often used by GraphQL to
37+
represent free-form human-readable text.
38+
39+
``graphene.Int``
40+
41+
Represents non-fractional signed whole numeric
42+
values. Int can represent values between `-(2^53 - 1)` and `2^53 - 1` since
43+
represented in JSON as double-precision floating point numbers specified
44+
by `IEEE 754 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
45+
46+
``graphene.Float``
47+
48+
Represents signed double-precision fractional
49+
values as specified by
50+
`IEEE 754 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
51+
52+
``graphene.Boolean``
53+
54+
Represents `true` or `false`.
55+
56+
``graphene.ID``
57+
58+
Represents a unique identifier, often used to
59+
refetch an object or as key for a cache. The ID type appears in a JSON
60+
response as a String; however, it is not intended to be human-readable.
61+
When expected as an input type, any string (such as `"4"`) or integer
62+
(such as `4`) input value will be accepted as an ID.
1163

1264
Graphene also provides custom scalars for Dates, Times, and JSON:
1365

14-
- ``graphene.types.datetime.DateTime``
15-
- ``graphene.types.datetime.Time``
16-
- ``graphene.types.json.JSONString``
66+
``graphene.types.datetime.Date``
67+
68+
Represents a Date value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
69+
70+
``graphene.types.datetime.DateTime``
71+
72+
Represents a DateTime value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
73+
74+
``graphene.types.datetime.Time``
75+
76+
Represents a Time value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
77+
78+
``graphene.types.json.JSONString``
79+
80+
Represents a JSON string.
1781

1882

1983
Custom scalars

docs/types/unions.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ The basics:
1212
Quick example
1313
-------------
1414

15-
This example model defines a ``Character`` interface with a name. ``Human``
16-
and ``Droid`` are two implementations of that interface.
15+
This example model defines several ObjectTypes with their own fields.
16+
``SearchResult`` is the implementation of ``Union`` of this object types.
1717

1818
.. code:: python
1919

graphene/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from .utils.module_loading import lazy_import
3535

3636

37-
VERSION = (2, 0, 0, 'final', 0)
37+
VERSION = (2, 0, 1, 'final', 0)
3838

3939
__version__ = get_version(VERSION)
4040

graphene/relay/connection.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class EdgeBase(object):
7373
edge = type(edge_name, edge_bases, {})
7474
cls.Edge = edge
7575

76-
_meta.name = name
76+
options['name'] = name
7777
_meta.node = node
7878
_meta.fields = OrderedDict([
7979
('page_info', Field(PageInfo, name='pageInfo', required=True)),
@@ -99,16 +99,19 @@ def __init__(self, type, *args, **kwargs):
9999
def type(self):
100100
type = super(IterableConnectionField, self).type
101101
connection_type = type
102-
if is_node(type):
102+
if isinstance(type, NonNull):
103+
connection_type = type.of_type
104+
105+
if is_node(connection_type):
103106
raise Exception(
104107
"ConnectionField's now need a explicit ConnectionType for Nodes.\n"
105-
"Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#node-connections"
108+
"Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections"
106109
)
107110

108111
assert issubclass(connection_type, Connection), (
109112
'{} type have to be a subclass of Connection. Received "{}".'
110113
).format(self.__class__.__name__, connection_type)
111-
return connection_type
114+
return type
112115

113116
@classmethod
114117
def resolve_connection(cls, connection_type, args, resolved):
@@ -133,6 +136,9 @@ def resolve_connection(cls, connection_type, args, resolved):
133136
def connection_resolver(cls, resolver, connection_type, root, info, **args):
134137
resolved = resolver(root, info, **args)
135138

139+
if isinstance(connection_type, NonNull):
140+
connection_type = connection_type.of_type
141+
136142
on_resolve = partial(cls.resolve_connection, connection_type, args)
137143
if is_thenable(resolved):
138144
return Promise.resolve(resolved).then(on_resolve)

graphene/relay/tests/test_connection.py

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from ...types import Argument, Field, Int, List, NonNull, ObjectType, String
3+
from ...types import Argument, Field, Int, List, NonNull, ObjectType, String, Schema
44
from ..connection import Connection, ConnectionField, PageInfo
55
from ..node import Node
66

@@ -52,6 +52,21 @@ class Meta:
5252
assert list(fields.keys()) == ['page_info', 'edges', 'extra']
5353

5454

55+
def test_connection_name():
56+
custom_name = "MyObjectCustomNameConnection"
57+
58+
class BaseConnection(object):
59+
extra = String()
60+
61+
class MyObjectConnection(BaseConnection, Connection):
62+
63+
class Meta:
64+
node = MyObject
65+
name = custom_name
66+
67+
assert MyObjectConnection._meta.name == custom_name
68+
69+
5570
def test_edge():
5671
class MyObjectConnection(Connection):
5772

@@ -122,9 +137,10 @@ def test_connectionfield_node_deprecated():
122137
field = ConnectionField(MyObject)
123138
with pytest.raises(Exception) as exc_info:
124139
field.type
125-
140+
126141
assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str(exc_info.value)
127142

143+
128144
def test_connectionfield_custom_args():
129145
class MyObjectConnection(Connection):
130146

@@ -139,3 +155,23 @@ class Meta:
139155
'last': Argument(Int),
140156
'extra': Argument(String),
141157
}
158+
159+
160+
def test_connectionfield_required():
161+
class MyObjectConnection(Connection):
162+
163+
class Meta:
164+
node = MyObject
165+
166+
class Query(ObjectType):
167+
test_connection = ConnectionField(MyObjectConnection, required=True)
168+
169+
def resolve_test_connection(root, info, **args):
170+
return []
171+
172+
schema = Schema(query=Query)
173+
executed = schema.execute(
174+
'{ testConnection { edges { cursor } } }'
175+
)
176+
assert not executed.errors
177+
assert executed.data == {'testConnection': {'edges': []}}

0 commit comments

Comments
 (0)