Skip to content

Commit f07436b

Browse files
author
Sairam Krishnan
committed
Enable users to load schemas from GraphQL files.
Implemented in response to graphql-python/graphql-core#137.
1 parent 9202021 commit f07436b

File tree

8 files changed

+197
-3
lines changed

8 files changed

+197
-3
lines changed

graphql/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
# Parse and operate on GraphQL language source files.
9090
from .language.base import ( # no import order
9191
Source,
92+
FileSource,
9293
get_location,
9394
# Parse
9495
parse,
@@ -223,6 +224,7 @@
223224
"BREAK",
224225
"ParallelVisitor",
225226
"Source",
227+
"FileSource",
226228
"TypeInfoVisitor",
227229
"get_location",
228230
"parse",

graphql/language/ast.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,7 @@ def __repr__(self):
10791079
"name={self.name!r}"
10801080
", arguments={self.arguments!r}"
10811081
", type={self.type!r}"
1082+
", directives={self.directives!r}"
10821083
")"
10831084
).format(self=self)
10841085

graphql/language/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from .location import get_location
33
from .parser import parse, parse_value
44
from .printer import print_ast
5-
from .source import Source
5+
from .source import FileSource, Source
66
from .visitor import BREAK, ParallelVisitor, TypeInfoVisitor, visit
77

88
__all__ = [
@@ -12,6 +12,7 @@
1212
"parse_value",
1313
"print_ast",
1414
"Source",
15+
"FileSource",
1516
"BREAK",
1617
"ParallelVisitor",
1718
"TypeInfoVisitor",

graphql/language/source.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
__all__ = ["Source"]
1+
import os
22

3+
__all__ = ["Source", "FileSource"]
34

45
class Source(object):
56
__slots__ = "body", "name"
@@ -15,3 +16,45 @@ def __eq__(self, other):
1516
and self.body == other.body
1617
and self.name == other.name
1718
)
19+
20+
class FileSource(Source):
21+
__slots__ = "body", "name"
22+
23+
def __init__(self, *args, **kwargs):
24+
"""Create a Source using the specified GraphQL files' contents."""
25+
name = kwargs.get("name", "GraphQL")
26+
27+
# From the specified list of paths, first identify all files. Then, load
28+
# their contents into a single, newline delimited string.
29+
file_contents = []
30+
file_paths = self.__get_file_paths__(args)
31+
for fp in file_paths:
32+
with open(fp) as f:
33+
file_contents.append(f.read())
34+
body = '\n'.join(file_contents)
35+
36+
super(FileSource, self).__init__(body, name)
37+
38+
def __get_file_paths__(self, paths):
39+
"""Get the paths to all files in the given list of paths. This means
40+
filtering out invalid paths and recursively walking a given directory
41+
path to gather the paths of all files that it contains."""
42+
all_file_paths = []
43+
44+
# Filter out invalid paths.
45+
valid_paths = [p for p in paths if os.path.exists(p)]
46+
47+
# Add all paths pointing to a file to all_file_paths.
48+
all_file_paths += [p for p in valid_paths if os.path.isfile(p)]
49+
50+
# For each path referring to a directory, walk that directory's structure
51+
# recursively, and add its constituent files' paths to all_file_paths.
52+
all_file_paths += [
53+
os.path.join(dir_name, file_name)
54+
for p in valid_paths
55+
if os.path.isdir(p)
56+
for dir_name, _, files_in_dir in os.walk(p)
57+
for file_name in files_in_dir
58+
]
59+
60+
return all_file_paths

graphql/language/tests/test_schema_parser.py

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from pytest import raises
22

3-
from graphql import Source, parse
3+
from graphql import FileSource, Source, parse
44
from graphql.error import GraphQLSyntaxError
55
from graphql.language import ast
66
from graphql.language.parser import Loc
@@ -567,6 +567,132 @@ def test_parses_simple_input_object():
567567
assert doc == expected
568568

569569

570+
def test_parses_schema_files():
571+
doc = parse(FileSource("tests/graphql_schemas"))
572+
expected = ast.Document(
573+
definitions=[
574+
ast.ObjectTypeDefinition(
575+
name=ast.Name(value="Query"),
576+
interfaces=[],
577+
fields=[
578+
ast.FieldDefinition(
579+
name=ast.Name(value="person"),
580+
arguments=[
581+
ast.InputValueDefinition(
582+
name=ast.Name(value="name"),
583+
type=ast.NonNullType(
584+
type=ast.NamedType(name=ast.Name(value="ID"))
585+
),
586+
default_value=None,
587+
directives=[]
588+
)
589+
],
590+
type=ast.NamedType(name=ast.Name(value="Person")),
591+
directives=[]
592+
),
593+
ast.FieldDefinition(
594+
name=ast.Name(value="skill"),
595+
arguments=[
596+
ast.InputValueDefinition(
597+
name=ast.Name(value="name"),
598+
type=ast.NonNullType(
599+
type=ast.NamedType(name=ast.Name(value="ID"))
600+
),
601+
default_value=None,
602+
directives=[]
603+
)
604+
],
605+
type=ast.NamedType(name=ast.Name(value="Skill")),
606+
directives=[]
607+
)
608+
],
609+
directives=[]
610+
),
611+
ast.SchemaDefinition(
612+
operation_types=[
613+
ast.OperationTypeDefinition(
614+
operation="query",
615+
type=ast.NamedType(name=ast.Name(value="Query"))
616+
)
617+
],
618+
directives=[]
619+
),
620+
ast.ObjectTypeDefinition(
621+
name=ast.Name(value="Person"),
622+
interfaces=[],
623+
fields=[
624+
ast.FieldDefinition(
625+
name=ast.Name(value="name"),
626+
arguments=[],
627+
type=ast.NonNullType(
628+
type=ast.NamedType(name=ast.Name(value="ID"))
629+
),
630+
directives=[]
631+
),
632+
ast.FieldDefinition(
633+
name=ast.Name(value="age"),
634+
arguments=[],
635+
type=ast.NamedType(name=ast.Name(value="Int")),
636+
directives=[]
637+
)
638+
],
639+
directives=[]
640+
),
641+
ast.ObjectTypeDefinition(
642+
name=ast.Name(value="Skill"),
643+
interfaces=[],
644+
fields=[
645+
ast.FieldDefinition(
646+
name=ast.Name(value="name"),
647+
arguments=[],
648+
type=ast.NonNullType(
649+
type=ast.NamedType(name=ast.Name(value="ID"))
650+
),
651+
directives=[]
652+
),
653+
ast.FieldDefinition(
654+
name=ast.Name(value="level"),
655+
arguments=[],
656+
type=ast.NamedType(name=ast.Name(value="Int")),
657+
directives=[]
658+
),
659+
ast.FieldDefinition(
660+
name=ast.Name(value="possessors"),
661+
arguments=[],
662+
type=ast.ListType(
663+
type=ast.NonNullType(
664+
type=ast.NamedType(name=ast.Name(value="Person"))
665+
)
666+
),
667+
directives=[]
668+
)
669+
],
670+
directives=[]
671+
),
672+
ast.TypeExtensionDefinition(
673+
definition=ast.ObjectTypeDefinition(
674+
name=ast.Name(value="Person"),
675+
interfaces=[],
676+
fields=[
677+
ast.FieldDefinition(
678+
name=ast.Name(value="skills"),
679+
arguments=[],
680+
type=ast.ListType(
681+
type=ast.NonNullType(
682+
type=ast.NamedType(name=ast.Name(value="Skill"))
683+
)
684+
),
685+
directives=[]
686+
)
687+
],
688+
directives=[]
689+
)
690+
)
691+
]
692+
)
693+
assert doc == expected
694+
695+
570696
def test_parsing_simple_input_object_with_args_should_fail():
571697
# type: () -> None
572698
body = """
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type Person {
2+
name: ID!
3+
age: Int
4+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type Skill {
2+
name: ID!
3+
level: Int
4+
possessors: [Person!]
5+
}
6+
7+
extend type Person {
8+
skills: [Skill!]
9+
}

tests/graphql_schemas/schema.graphql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type Query {
2+
person(name: ID!): Person
3+
skill(name: ID!): Skill
4+
}
5+
6+
schema {
7+
query: Query
8+
}

0 commit comments

Comments
 (0)