Skip to content

Commit 5c1c9e9

Browse files
committed
add benchmark for connection fields
1 parent d90de4a commit 5c1c9e9

File tree

4 files changed

+229
-4
lines changed

4 files changed

+229
-4
lines changed

graphene_sqlalchemy/batching.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66

77
def get_batch_resolver(relationship_prop):
8+
9+
# Cache this across `batch_load_fn` calls
10+
# This is so SQL string generation is cached under-the-hood via `bakery`
11+
selectin_loader = strategies.SelectInLoader(relationship_prop, (('lazy', 'selectin'),))
12+
813
class RelationshipLoader(dataloader.DataLoader):
914
cache = False
1015

@@ -43,15 +48,13 @@ def batch_load_fn(self, parents): # pylint: disable=method-hidden
4348
# The behavior of `selectin` is undefined if the parent is dirty
4449
assert parent not in session.dirty
4550

46-
loader = strategies.SelectInLoader(relationship_prop, (('lazy', 'selectin'),))
47-
4851
# Should the boolean be set to False? Does it matter for our purposes?
4952
states = [(sqlalchemy.inspect(parent), True) for parent in parents]
5053

5154
# For our purposes, the query_context will only used to get the session
5255
query_context = QueryContext(session.query(parent_mapper.entity))
5356

54-
loader._load_for_path(
57+
selectin_loader._load_for_path(
5558
query_context,
5659
parent_mapper._path_registry,
5760
states,
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
from graphql.backend import GraphQLCachedBackend, GraphQLCoreBackend
2+
3+
import graphene
4+
from graphene import relay
5+
6+
from ..fields import BatchSQLAlchemyConnectionField
7+
from ..types import SQLAlchemyObjectType
8+
from .models import Article, HairKind, Pet, Reporter
9+
10+
11+
def get_schema():
12+
class ReporterType(SQLAlchemyObjectType):
13+
class Meta:
14+
model = Reporter
15+
interfaces = (relay.Node,)
16+
connection_field_factory = BatchSQLAlchemyConnectionField.from_relationship
17+
18+
class ArticleType(SQLAlchemyObjectType):
19+
class Meta:
20+
model = Article
21+
interfaces = (relay.Node,)
22+
connection_field_factory = BatchSQLAlchemyConnectionField.from_relationship
23+
24+
class PetType(SQLAlchemyObjectType):
25+
class Meta:
26+
model = Pet
27+
interfaces = (relay.Node,)
28+
connection_field_factory = BatchSQLAlchemyConnectionField.from_relationship
29+
30+
class Query(graphene.ObjectType):
31+
articles = graphene.Field(graphene.List(ArticleType))
32+
reporters = graphene.Field(graphene.List(ReporterType))
33+
34+
def resolve_articles(self, info):
35+
return info.context.get('session').query(Article).all()
36+
37+
def resolve_reporters(self, info):
38+
return info.context.get('session').query(Reporter).all()
39+
40+
return graphene.Schema(query=Query)
41+
42+
43+
def benchmark_query(session_factory, benchmark, query):
44+
schema = get_schema()
45+
cached_backend = GraphQLCachedBackend(GraphQLCoreBackend())
46+
cached_backend.document_from_string(schema, query) # Prime cache
47+
48+
@benchmark
49+
def execute_query():
50+
result = schema.execute(
51+
query,
52+
context_value={"session": session_factory()},
53+
backend=cached_backend,
54+
)
55+
assert not result.errors
56+
57+
58+
def test_one_to_one(session_factory, benchmark):
59+
session = session_factory()
60+
61+
reporter_1 = Reporter(
62+
first_name='Reporter_1',
63+
)
64+
session.add(reporter_1)
65+
reporter_2 = Reporter(
66+
first_name='Reporter_2',
67+
)
68+
session.add(reporter_2)
69+
70+
article_1 = Article(headline='Article_1')
71+
article_1.reporter = reporter_1
72+
session.add(article_1)
73+
74+
article_2 = Article(headline='Article_2')
75+
article_2.reporter = reporter_2
76+
session.add(article_2)
77+
78+
session.commit()
79+
session.close()
80+
81+
benchmark_query(session_factory, benchmark, """
82+
query {
83+
reporters {
84+
firstName
85+
favoriteArticle {
86+
headline
87+
}
88+
}
89+
}
90+
""")
91+
92+
93+
def test_many_to_one(session_factory, benchmark):
94+
session = session_factory()
95+
96+
reporter_1 = Reporter(
97+
first_name='Reporter_1',
98+
)
99+
session.add(reporter_1)
100+
reporter_2 = Reporter(
101+
first_name='Reporter_2',
102+
)
103+
session.add(reporter_2)
104+
105+
article_1 = Article(headline='Article_1')
106+
article_1.reporter = reporter_1
107+
session.add(article_1)
108+
109+
article_2 = Article(headline='Article_2')
110+
article_2.reporter = reporter_2
111+
session.add(article_2)
112+
113+
session.commit()
114+
session.close()
115+
116+
benchmark_query(session_factory, benchmark, """
117+
query {
118+
articles {
119+
headline
120+
reporter {
121+
firstName
122+
}
123+
}
124+
}
125+
""")
126+
127+
128+
def test_one_to_many(session_factory, benchmark):
129+
session = session_factory()
130+
131+
reporter_1 = Reporter(
132+
first_name='Reporter_1',
133+
)
134+
session.add(reporter_1)
135+
reporter_2 = Reporter(
136+
first_name='Reporter_2',
137+
)
138+
session.add(reporter_2)
139+
140+
article_1 = Article(headline='Article_1')
141+
article_1.reporter = reporter_1
142+
session.add(article_1)
143+
144+
article_2 = Article(headline='Article_2')
145+
article_2.reporter = reporter_1
146+
session.add(article_2)
147+
148+
article_3 = Article(headline='Article_3')
149+
article_3.reporter = reporter_2
150+
session.add(article_3)
151+
152+
article_4 = Article(headline='Article_4')
153+
article_4.reporter = reporter_2
154+
session.add(article_4)
155+
156+
session.commit()
157+
session.close()
158+
159+
benchmark_query(session_factory, benchmark, """
160+
query {
161+
reporters {
162+
firstName
163+
articles(first: 2) {
164+
edges {
165+
node {
166+
headline
167+
}
168+
}
169+
}
170+
}
171+
}
172+
""")
173+
174+
175+
def test_many_to_many(session_factory, benchmark):
176+
session = session_factory()
177+
178+
reporter_1 = Reporter(
179+
first_name='Reporter_1',
180+
)
181+
session.add(reporter_1)
182+
reporter_2 = Reporter(
183+
first_name='Reporter_2',
184+
)
185+
session.add(reporter_2)
186+
187+
pet_1 = Pet(name='Pet_1', pet_kind='cat', hair_kind=HairKind.LONG)
188+
session.add(pet_1)
189+
190+
pet_2 = Pet(name='Pet_2', pet_kind='cat', hair_kind=HairKind.LONG)
191+
session.add(pet_2)
192+
193+
reporter_1.pets.append(pet_1)
194+
reporter_1.pets.append(pet_2)
195+
196+
pet_3 = Pet(name='Pet_3', pet_kind='cat', hair_kind=HairKind.LONG)
197+
session.add(pet_3)
198+
199+
pet_4 = Pet(name='Pet_4', pet_kind='cat', hair_kind=HairKind.LONG)
200+
session.add(pet_4)
201+
202+
reporter_2.pets.append(pet_3)
203+
reporter_2.pets.append(pet_4)
204+
205+
session.commit()
206+
session.close()
207+
208+
benchmark_query(session_factory, benchmark, """
209+
query {
210+
reporters {
211+
firstName
212+
pets(first: 2) {
213+
edges {
214+
node {
215+
name
216+
}
217+
}
218+
}
219+
}
220+
}
221+
""")

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ max-line-length = 120
99
no_lines_before=FIRSTPARTY
1010
known_graphene=graphene,graphql_relay,flask_graphql,graphql_server,sphinx_graphene_theme
1111
known_first_party=graphene_sqlalchemy
12-
known_third_party=app,database,flask,mock,models,nameko,pkg_resources,promise,pytest,schema,setuptools,singledispatch,six,sqlalchemy,sqlalchemy_utils
12+
known_third_party=app,database,flask,graphql,mock,models,nameko,pkg_resources,promise,pytest,schema,setuptools,singledispatch,six,sqlalchemy,sqlalchemy_utils
1313
sections=FUTURE,STDLIB,THIRDPARTY,GRAPHENE,FIRSTPARTY,LOCALFOLDER
1414
skip_glob=examples/nameko_sqlalchemy
1515

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"mock==2.0.0",
3131
"pytest-cov==2.6.1",
3232
"sqlalchemy_utils==0.33.9",
33+
"pytest-benchmark==3.2.1",
3334
]
3435

3536
setup(

0 commit comments

Comments
 (0)