1
+ import time
1
2
from typing import List
2
3
3
4
import pytest
7
8
import strawberry_sqlalchemy_mapper
8
9
from asgiref .sync import async_to_sync
9
10
from pytest_codspeed .plugin import BenchmarkFixture
11
+ from sqlalchemy import orm
10
12
from strawberry .types import Info
11
13
12
14
13
- @pytest .mark .benchmark
14
- def test_load_many_relationships (
15
- benchmark : BenchmarkFixture , engine , base , sessionmaker
16
- ):
15
+ @pytest .fixture
16
+ def populated_tables (engine , base , sessionmaker ):
17
17
class A (base ):
18
18
__tablename__ = "a"
19
19
id = sa .Column (sa .Integer , autoincrement = True , primary_key = True )
@@ -48,24 +48,7 @@ class Parent(base):
48
48
d = sa .orm .relationship ("D" , backref = "parents" )
49
49
e = sa .orm .relationship ("E" , backref = "parents" )
50
50
51
- mapper = strawberry_sqlalchemy_mapper .StrawberrySQLAlchemyMapper ()
52
-
53
- @mapper .type (Parent )
54
- class StrawberryParent :
55
- pass
56
-
57
- @strawberry .type
58
- class Query :
59
- @strawberry .field
60
- @staticmethod
61
- async def parents (info : Info ) -> List [StrawberryParent ]:
62
- return info .context ["session" ].scalars (sa .select (Parent )).all ()
63
-
64
- mapper .finalize ()
65
51
base .metadata .create_all (engine )
66
-
67
- schema = strawberry .Schema (Query )
68
-
69
52
with sessionmaker () as session :
70
53
for _ in range (1000 ):
71
54
session .add (A ())
@@ -85,6 +68,43 @@ async def parents(info: Info) -> List[StrawberryParent]:
85
68
session .add (parent )
86
69
session .commit ()
87
70
71
+ return A , B , C , D , E , Parent
72
+
73
+
74
+ @pytest .mark .benchmark
75
+ def test_load_many_relationships (
76
+ benchmark : BenchmarkFixture , populated_tables , sessionmaker , mocker
77
+ ):
78
+ A , B , C , D , E , Parent = populated_tables
79
+
80
+ mapper = strawberry_sqlalchemy_mapper .StrawberrySQLAlchemyMapper ()
81
+
82
+ @mapper .type (Parent )
83
+ class StrawberryParent :
84
+ pass
85
+
86
+ @strawberry .type
87
+ class Query :
88
+ @strawberry .field
89
+ @staticmethod
90
+ async def parents (info : Info ) -> List [StrawberryParent ]:
91
+ return info .context ["session" ].scalars (sa .select (Parent )).all ()
92
+
93
+ mapper .finalize ()
94
+
95
+ schema = strawberry .Schema (Query )
96
+
97
+ # Now that we've seeded the database, let's add some delay to simulate network lag
98
+ # to the database.
99
+ old_execute_internal = orm .Session ._execute_internal
100
+ mocker .patch .object (orm .Session , "_execute_internal" , autospec = True )
101
+
102
+ def sleep_then_execute (self , * args , ** kwargs ):
103
+ time .sleep (0.01 )
104
+ return old_execute_internal (self , * args , ** kwargs )
105
+
106
+ orm .Session ._execute_internal .side_effect = sleep_then_execute
107
+
88
108
async def execute ():
89
109
with sessionmaker () as session :
90
110
# Notice how we use a sync session but call Strawberry's async execute.
@@ -113,3 +133,67 @@ async def execute():
113
133
assert len (result .data ["parents" ]) == 10
114
134
115
135
benchmark (async_to_sync (execute ))
136
+
137
+
138
+ @pytest .mark .benchmark
139
+ def test_load_many_relationships_async (
140
+ benchmark : BenchmarkFixture , populated_tables , async_sessionmaker , mocker
141
+ ):
142
+ A , B , C , D , E , Parent = populated_tables
143
+
144
+ mapper = strawberry_sqlalchemy_mapper .StrawberrySQLAlchemyMapper ()
145
+
146
+ @mapper .type (Parent )
147
+ class StrawberryParent :
148
+ pass
149
+
150
+ @strawberry .type
151
+ class Query :
152
+ @strawberry .field
153
+ @staticmethod
154
+ async def parents (info : Info ) -> List [StrawberryParent ]:
155
+ async with info .context ["async_sessionmaker" ]() as session :
156
+ return (await session .scalars (sa .select (Parent ))).all ()
157
+
158
+ mapper .finalize ()
159
+
160
+ schema = strawberry .Schema (Query )
161
+
162
+ # Now that we've seeded the database, let's add some delay to simulate network lag
163
+ # to the database.
164
+ old_execute_internal = orm .Session ._execute_internal
165
+ mocker .patch .object (orm .Session , "_execute_internal" , autospec = True )
166
+
167
+ def sleep_then_execute (self , * args , ** kwargs ):
168
+ time .sleep (0.01 )
169
+ return old_execute_internal (self , * args , ** kwargs )
170
+
171
+ orm .Session ._execute_internal .side_effect = sleep_then_execute
172
+
173
+ async def execute ():
174
+ # Notice how we use a sync session but call Strawberry's async execute.
175
+ # This is not an ideal combination, but it's certainly a common one that
176
+ # we need to support efficiently.
177
+ result = await schema .execute (
178
+ """
179
+ query {
180
+ parents {
181
+ a { id },
182
+ b { id },
183
+ c { id },
184
+ d { id },
185
+ e { id },
186
+ }
187
+ }
188
+ """ ,
189
+ context_value = {
190
+ "async_sessionmaker" : async_sessionmaker ,
191
+ "sqlalchemy_loader" : strawberry_sqlalchemy_mapper .StrawberrySQLAlchemyLoader (
192
+ async_bind_factory = async_sessionmaker
193
+ ),
194
+ },
195
+ )
196
+ assert not result .errors
197
+ assert len (result .data ["parents" ]) == 10
198
+
199
+ benchmark (async_to_sync (execute ))
0 commit comments