Skip to content

Commit

Permalink
add benchmark for connection fields
Browse files Browse the repository at this point in the history
  • Loading branch information
jnak committed Jan 22, 2020
1 parent d90de4a commit 5c1c9e9
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 4 deletions.
9 changes: 6 additions & 3 deletions graphene_sqlalchemy/batching.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@


def get_batch_resolver(relationship_prop):

# Cache this across `batch_load_fn` calls
# This is so SQL string generation is cached under-the-hood via `bakery`
selectin_loader = strategies.SelectInLoader(relationship_prop, (('lazy', 'selectin'),))

class RelationshipLoader(dataloader.DataLoader):
cache = False

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

loader = strategies.SelectInLoader(relationship_prop, (('lazy', 'selectin'),))

# Should the boolean be set to False? Does it matter for our purposes?
states = [(sqlalchemy.inspect(parent), True) for parent in parents]

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

loader._load_for_path(
selectin_loader._load_for_path(
query_context,
parent_mapper._path_registry,
states,
Expand Down
221 changes: 221 additions & 0 deletions graphene_sqlalchemy/tests/test_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
from graphql.backend import GraphQLCachedBackend, GraphQLCoreBackend

import graphene
from graphene import relay

from ..fields import BatchSQLAlchemyConnectionField
from ..types import SQLAlchemyObjectType
from .models import Article, HairKind, Pet, Reporter


def get_schema():
class ReporterType(SQLAlchemyObjectType):
class Meta:
model = Reporter
interfaces = (relay.Node,)
connection_field_factory = BatchSQLAlchemyConnectionField.from_relationship

class ArticleType(SQLAlchemyObjectType):
class Meta:
model = Article
interfaces = (relay.Node,)
connection_field_factory = BatchSQLAlchemyConnectionField.from_relationship

class PetType(SQLAlchemyObjectType):
class Meta:
model = Pet
interfaces = (relay.Node,)
connection_field_factory = BatchSQLAlchemyConnectionField.from_relationship

class Query(graphene.ObjectType):
articles = graphene.Field(graphene.List(ArticleType))
reporters = graphene.Field(graphene.List(ReporterType))

def resolve_articles(self, info):
return info.context.get('session').query(Article).all()

def resolve_reporters(self, info):
return info.context.get('session').query(Reporter).all()

return graphene.Schema(query=Query)


def benchmark_query(session_factory, benchmark, query):
schema = get_schema()
cached_backend = GraphQLCachedBackend(GraphQLCoreBackend())
cached_backend.document_from_string(schema, query) # Prime cache

@benchmark
def execute_query():
result = schema.execute(
query,
context_value={"session": session_factory()},
backend=cached_backend,
)
assert not result.errors


def test_one_to_one(session_factory, benchmark):
session = session_factory()

reporter_1 = Reporter(
first_name='Reporter_1',
)
session.add(reporter_1)
reporter_2 = Reporter(
first_name='Reporter_2',
)
session.add(reporter_2)

article_1 = Article(headline='Article_1')
article_1.reporter = reporter_1
session.add(article_1)

article_2 = Article(headline='Article_2')
article_2.reporter = reporter_2
session.add(article_2)

session.commit()
session.close()

benchmark_query(session_factory, benchmark, """
query {
reporters {
firstName
favoriteArticle {
headline
}
}
}
""")


def test_many_to_one(session_factory, benchmark):
session = session_factory()

reporter_1 = Reporter(
first_name='Reporter_1',
)
session.add(reporter_1)
reporter_2 = Reporter(
first_name='Reporter_2',
)
session.add(reporter_2)

article_1 = Article(headline='Article_1')
article_1.reporter = reporter_1
session.add(article_1)

article_2 = Article(headline='Article_2')
article_2.reporter = reporter_2
session.add(article_2)

session.commit()
session.close()

benchmark_query(session_factory, benchmark, """
query {
articles {
headline
reporter {
firstName
}
}
}
""")


def test_one_to_many(session_factory, benchmark):
session = session_factory()

reporter_1 = Reporter(
first_name='Reporter_1',
)
session.add(reporter_1)
reporter_2 = Reporter(
first_name='Reporter_2',
)
session.add(reporter_2)

article_1 = Article(headline='Article_1')
article_1.reporter = reporter_1
session.add(article_1)

article_2 = Article(headline='Article_2')
article_2.reporter = reporter_1
session.add(article_2)

article_3 = Article(headline='Article_3')
article_3.reporter = reporter_2
session.add(article_3)

article_4 = Article(headline='Article_4')
article_4.reporter = reporter_2
session.add(article_4)

session.commit()
session.close()

benchmark_query(session_factory, benchmark, """
query {
reporters {
firstName
articles(first: 2) {
edges {
node {
headline
}
}
}
}
}
""")


def test_many_to_many(session_factory, benchmark):
session = session_factory()

reporter_1 = Reporter(
first_name='Reporter_1',
)
session.add(reporter_1)
reporter_2 = Reporter(
first_name='Reporter_2',
)
session.add(reporter_2)

pet_1 = Pet(name='Pet_1', pet_kind='cat', hair_kind=HairKind.LONG)
session.add(pet_1)

pet_2 = Pet(name='Pet_2', pet_kind='cat', hair_kind=HairKind.LONG)
session.add(pet_2)

reporter_1.pets.append(pet_1)
reporter_1.pets.append(pet_2)

pet_3 = Pet(name='Pet_3', pet_kind='cat', hair_kind=HairKind.LONG)
session.add(pet_3)

pet_4 = Pet(name='Pet_4', pet_kind='cat', hair_kind=HairKind.LONG)
session.add(pet_4)

reporter_2.pets.append(pet_3)
reporter_2.pets.append(pet_4)

session.commit()
session.close()

benchmark_query(session_factory, benchmark, """
query {
reporters {
firstName
pets(first: 2) {
edges {
node {
name
}
}
}
}
}
""")
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ max-line-length = 120
no_lines_before=FIRSTPARTY
known_graphene=graphene,graphql_relay,flask_graphql,graphql_server,sphinx_graphene_theme
known_first_party=graphene_sqlalchemy
known_third_party=app,database,flask,mock,models,nameko,pkg_resources,promise,pytest,schema,setuptools,singledispatch,six,sqlalchemy,sqlalchemy_utils
known_third_party=app,database,flask,graphql,mock,models,nameko,pkg_resources,promise,pytest,schema,setuptools,singledispatch,six,sqlalchemy,sqlalchemy_utils
sections=FUTURE,STDLIB,THIRDPARTY,GRAPHENE,FIRSTPARTY,LOCALFOLDER
skip_glob=examples/nameko_sqlalchemy

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"mock==2.0.0",
"pytest-cov==2.6.1",
"sqlalchemy_utils==0.33.9",
"pytest-benchmark==3.2.1",
]

setup(
Expand Down

0 comments on commit 5c1c9e9

Please sign in to comment.