Skip to content

Commit bab02d5

Browse files
committed
Bring node subpackage up to date
Also start using mypy and adding type hints.
1 parent 3b0d40c commit bab02d5

File tree

14 files changed

+540
-316
lines changed

14 files changed

+540
-316
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ language: python
22

33
matrix:
44
include:
5-
- env: TOXENV=flake8
5+
- env: TOXENV=flake8,mypy
66
python: 3.7
77
dist: xenial
88
sudo: true

poetry.lock

Lines changed: 32 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pytest-cov = "^2.7"
3737
pyyaml = "^5.1"
3838
black = ">=19.3b0"
3939
flake8 = "^3.7"
40+
mypy = ">=0.711"
4041
codecov = "^2"
4142
check-manifest = ">=0.39"
4243
tox = "^3.13"

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@
3232
python_requires='>=3.6,<4',
3333
packages=find_packages('src'),
3434
package_dir={'': 'src'},
35+
# PEP-561: https://www.python.org/dev/peps/pep-0561/
36+
package_data={"graphql_relay": ["py.typed"]},
37+
include_package_data=True,
3538
zip_safe=False,
3639
)

src/graphql_relay/node/node.py

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any, Callable, NamedTuple
2+
13
from graphql_relay.utils.base64 import base64, unbase64
24

35
from graphql.type import (
@@ -6,15 +8,27 @@
68
GraphQLID,
79
GraphQLField,
810
GraphQLInterfaceType,
11+
GraphQLList,
12+
GraphQLResolveInfo,
13+
GraphQLTypeResolver
914
)
1015

1116

12-
def node_definitions(id_fetcher, type_resolver=None):
17+
class GraphQLNodeDefinitions(NamedTuple):
18+
19+
node_interface: GraphQLInterfaceType
20+
node_field: GraphQLField
21+
nodes_field: GraphQLField
22+
23+
24+
def node_definitions(
25+
id_fetcher: Callable[[str, GraphQLResolveInfo], Any],
26+
type_resolver: GraphQLTypeResolver = None) -> GraphQLNodeDefinitions:
1327
"""
1428
Given a function to map from an ID to an underlying object, and a function
1529
to map from an underlying object to the concrete GraphQLObjectType it
1630
corresponds to, constructs a `Node` interface that objects can implement,
17-
and a field config for a `node` root field.
31+
and a field object to be used as a `node` root field.
1832
1933
If the type_resolver is omitted, object resolution on the interface will be
2034
handled with the `is_type_of` method on object types, as with any GraphQL
@@ -29,6 +43,7 @@ def node_definitions(id_fetcher, type_resolver=None):
2943
description='The id of the object.')},
3044
resolve_type=type_resolver,
3145
)
46+
3247
# noinspection PyShadowingBuiltins
3348
node_field = GraphQLField(
3449
node_interface,
@@ -39,39 +54,59 @@ def node_definitions(id_fetcher, type_resolver=None):
3954
description='The ID of an object')},
4055
resolve=lambda _obj, info, id: id_fetcher(id, info)
4156
)
42-
return node_interface, node_field
4357

58+
nodes_field = GraphQLField(
59+
GraphQLNonNull(GraphQLList(node_interface)),
60+
args={
61+
'ids': GraphQLArgument(
62+
GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLID))),
63+
description='The IDs of objects')},
64+
resolve=lambda _obj, info, ids: [id_fetcher(id_, info) for id_ in ids]
65+
)
66+
67+
return GraphQLNodeDefinitions(node_interface, node_field, nodes_field)
68+
69+
70+
class ResolvedGlobalId(NamedTuple):
71+
72+
type: str
73+
id: str
4474

45-
def to_global_id(type_, id_):
75+
76+
def to_global_id(type_: str, id_: str) -> str:
4677
"""
4778
Takes a type name and an ID specific to that type name, and returns a
4879
"global ID" that is unique among all types.
4980
"""
50-
return base64(':'.join([type_, str(id_)]))
81+
return base64(f'{type_}:{id_}')
5182

5283

53-
def from_global_id(global_id):
84+
def from_global_id(global_id: str) -> ResolvedGlobalId:
5485
"""
55-
Takes the "global ID" created by toGlobalID, and retuns the type name and ID
86+
Takes the "global ID" created by to_global_id, and returns the type name and ID
5687
used to create it.
5788
"""
58-
unbased_global_id = unbase64(global_id)
59-
_type, _id = unbased_global_id.split(':', 1)
60-
return _type, _id
89+
return ResolvedGlobalId(*unbase64(global_id).split(':', 1))
6190

6291

63-
def global_id_field(type_name, id_fetcher=None):
92+
def global_id_field(
93+
type_name: str = None,
94+
id_fetcher: Callable[[Any, GraphQLResolveInfo], str] = None) -> GraphQLField:
6495
"""
6596
Creates the configuration for an id field on a node, using `to_global_id` to
66-
construct the ID from the provided typename. The type-specific ID is fetcher
97+
construct the ID from the provided typename. The type-specific ID is fetched
6798
by calling id_fetcher on the object, or if not provided, by accessing the `id`
68-
property on the object.
99+
attribute of the object, or the `id` if the object is a dict.
69100
"""
101+
102+
def resolve(obj: Any, info: GraphQLResolveInfo, **_args: Any) -> str:
103+
type_ = type_name or info.parent_type.name
104+
id_ = id_fetcher(obj, info) if id_fetcher else (
105+
obj['id'] if isinstance(obj, dict) else obj.id)
106+
return to_global_id(type_, id_)
107+
70108
return GraphQLField(
71109
GraphQLNonNull(GraphQLID),
72110
description='The ID of an object',
73-
resolve=lambda obj, info, **args: to_global_id(
74-
type_name or info.parent_type.name,
75-
id_fetcher(obj, info) if id_fetcher else obj.id
76-
)
111+
resolve=resolve
77112
)

src/graphql_relay/node/plural.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1+
from typing import Any, Callable
2+
13
from graphql.type import (
24
GraphQLArgument,
5+
GraphQLField,
6+
GraphQLInputType,
7+
GraphQLOutputType,
38
GraphQLList,
49
GraphQLNonNull,
5-
GraphQLField
6-
)
10+
GraphQLResolveInfo)
711

812

913
def plural_identifying_root_field(
10-
arg_name, input_type, output_type,
11-
resolve_single_input, description=None):
14+
arg_name: str,
15+
input_type: GraphQLInputType,
16+
output_type: GraphQLOutputType,
17+
resolve_single_input: Callable[[GraphQLResolveInfo, str], Any],
18+
description: str = None) -> GraphQLField:
19+
if isinstance(input_type, GraphQLNonNull):
20+
input_type = input_type.of_type
1221
input_args = {arg_name: GraphQLArgument(
1322
GraphQLNonNull(GraphQLList(GraphQLNonNull(input_type))))}
1423

src/graphql_relay/py.typed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Marker file for PEP 561. The graphql package uses inline types.

0 commit comments

Comments
 (0)