Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add benchmark for connection fields #259

Merged
merged 8 commits into from
Jan 24, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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