Skip to content

Commit 6e27011

Browse files
committed
perf: Avoid lambdas
It improves performance on recent Hypothesis versions
1 parent c527deb commit 6e27011

2 files changed

Lines changed: 99 additions & 27 deletions

File tree

src/hypothesis_graphql/_strategies/primitives.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Strategies for simple types like scalars or enums."""
2-
from typing import Union
2+
from typing import Type, Union
33

44
import graphql
55
from hypothesis import strategies as st
@@ -26,33 +26,65 @@ def scalar(type_: graphql.GraphQLScalarType, nullable: bool = True) -> st.Search
2626

2727
def enum(type_: graphql.GraphQLEnumType, nullable: bool = True) -> st.SearchStrategy[graphql.EnumValueNode]:
2828
value = st.sampled_from(list(type_.values))
29-
return maybe_null(value.map(lambda v: graphql.EnumValueNode(value=v)), nullable)
29+
return maybe_null(value.map(make_enum_node), nullable)
3030

3131

3232
def int_(nullable: bool = True) -> st.SearchStrategy[graphql.IntValueNode]:
3333
value = st.integers(min_value=MIN_INT, max_value=MAX_INT)
34-
return maybe_null(value.map(lambda v: graphql.IntValueNode(value=str(v))), nullable)
34+
return maybe_null(value.map(make_int_node), nullable)
3535

3636

3737
def float_(nullable: bool = True) -> st.SearchStrategy[graphql.FloatValueNode]:
3838
value = st.floats(allow_infinity=False, allow_nan=False)
39-
return maybe_null(value.map(lambda v: graphql.FloatValueNode(value=str(v))), nullable)
39+
return maybe_null(value.map(make_float_node), nullable)
4040

4141

4242
def string(nullable: bool = True) -> st.SearchStrategy[graphql.StringValueNode]:
4343
value = st.text(alphabet=st.characters(blacklist_categories=("Cs",), max_codepoint=0xFFFF))
44-
return maybe_null(value.map(lambda v: graphql.StringValueNode(value=v)), nullable)
44+
return maybe_null(value.map(make_string_node), nullable)
4545

4646

4747
def id_(nullable: bool = True) -> st.SearchStrategy[Union[graphql.StringValueNode, graphql.IntValueNode]]:
4848
return string(nullable) | int_(nullable)
4949

5050

5151
def boolean(nullable: bool = True) -> st.SearchStrategy[graphql.BooleanValueNode]:
52-
return maybe_null(st.booleans().map(lambda v: graphql.BooleanValueNode(value=v)), nullable)
52+
return maybe_null(st.booleans().map(make_boolean_node), nullable)
5353

5454

5555
def maybe_null(strategy: st.SearchStrategy, nullable: bool) -> st.SearchStrategy:
5656
if nullable:
5757
strategy |= st.just(graphql.NullValueNode())
5858
return strategy
59+
60+
61+
# Separate functions to use in `map` and avoid costs of handling lambda
62+
# constructors are passed as locals to optimize the byte code a little
63+
64+
65+
def make_boolean_node(
66+
value: bool, BooleanValueNode: Type[graphql.BooleanValueNode] = graphql.BooleanValueNode
67+
) -> graphql.BooleanValueNode:
68+
return BooleanValueNode(value=value)
69+
70+
71+
def make_string_node(
72+
value: str, StringValueNode: Type[graphql.StringValueNode] = graphql.StringValueNode
73+
) -> graphql.StringValueNode:
74+
return StringValueNode(value=value)
75+
76+
77+
def make_float_node(
78+
value: float, FloatValueNode: Type[graphql.FloatValueNode] = graphql.FloatValueNode
79+
) -> graphql.FloatValueNode:
80+
return FloatValueNode(value=str(value))
81+
82+
83+
def make_int_node(value: int, IntValueNode: Type[graphql.IntValueNode] = graphql.IntValueNode) -> graphql.IntValueNode:
84+
return IntValueNode(value=str(value))
85+
86+
87+
def make_enum_node(
88+
value: str, EnumValueNode: Type[graphql.EnumValueNode] = graphql.EnumValueNode
89+
) -> graphql.EnumValueNode:
90+
return EnumValueNode(value=value)

src/hypothesis_graphql/_strategies/selections.py

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@ def selections(
2020
else:
2121
subset = object_type.fields
2222
# minimum 1 field, an empty query is not valid
23-
return subset_of_fields(subset).flatmap(lambda f: list_of_nodes(context, f, strategy=field_nodes))
23+
return subset_of_fields(subset).flatmap(list_of_nodes_factory(context, field_nodes))
24+
25+
26+
def list_of_nodes_factory(
27+
context: Context, strategy: Callable[[Context, str, Field], st.SearchStrategy]
28+
) -> Callable[[List[Tuple]], st.SearchStrategy[List]]:
29+
def factory(items: List[Tuple]) -> st.SearchStrategy[List]:
30+
return list_of_nodes(context, items, strategy=strategy)
31+
32+
return factory
2433

2534

2635
def unwrap_field_type(field: Field) -> graphql.GraphQLNamedType:
@@ -34,12 +43,22 @@ def unwrap_field_type(field: Field) -> graphql.GraphQLNamedType:
3443
def field_nodes(context: Context, name: str, field: graphql.GraphQLField) -> st.SearchStrategy[graphql.FieldNode]:
3544
"""Generate a single field node with optional children."""
3645
return st.tuples(list_of_arguments(context, field.args), selections_for_type(context, field)).map(
37-
lambda tup: graphql.FieldNode(
46+
field_node_factory(name)
47+
)
48+
49+
50+
FieldNodeInput = Tuple[List[graphql.ArgumentNode], Optional[SelectionNodes]]
51+
52+
53+
def field_node_factory(name: str) -> Callable[[FieldNodeInput], graphql.FieldNode]:
54+
def factory(tup: FieldNodeInput) -> graphql.FieldNode:
55+
return graphql.FieldNode(
3856
name=graphql.NameNode(value=name),
3957
arguments=tup[0],
4058
selection_set=graphql.SelectionSetNode(kind="selection_set", selections=add_selection_aliases(tup[1])),
4159
)
42-
)
60+
61+
return factory
4362

4463

4564
def add_selection_aliases(nodes: Optional[SelectionNodes]) -> Optional[SelectionNodes]:
@@ -209,14 +228,21 @@ def inline_fragment(
209228
context: Context, type_: graphql.GraphQLObjectType
210229
) -> st.SearchStrategy[graphql.InlineFragmentNode]:
211230
"""Build `InlineFragmentNode` for the given type."""
212-
return selections(context, type_).map(
213-
lambda sel: graphql.InlineFragmentNode(
231+
return selections(context, type_).map(inline_fragment_node_factory(type_))
232+
233+
234+
def inline_fragment_node_factory(
235+
type_: graphql.GraphQLObjectType,
236+
) -> Callable[[SelectionNodes], graphql.InlineFragmentNode]:
237+
def factory(nodes: SelectionNodes) -> graphql.InlineFragmentNode:
238+
return graphql.InlineFragmentNode(
214239
type_condition=graphql.NamedTypeNode(
215240
name=graphql.NameNode(value=type_.name),
216241
),
217-
selection_set=graphql.SelectionSetNode(kind="selection_set", selections=sel),
242+
selection_set=graphql.SelectionSetNode(kind="selection_set", selections=nodes),
218243
)
219-
)
244+
245+
return factory
220246

221247

222248
def list_of_arguments(
@@ -231,16 +257,17 @@ def list_of_arguments(
231257
if not isinstance(argument.type, graphql.GraphQLNonNull):
232258
continue
233259
raise TypeError("Non-nullable custom scalar types are not supported as arguments") from exc
234-
args.append(
235-
argument_strategy.map(
236-
# Use `node_name` to prevent the lambda always using the last `name` value in this loop.
237-
# See pylint W0640 (cell-var-from-loop)
238-
lambda arg, node_name=name: graphql.ArgumentNode(name=graphql.NameNode(value=node_name), value=arg)
239-
)
240-
)
260+
args.append(argument_strategy.map(argument_node_factory(name)))
241261
return fixed_lists(args)
242262

243263

264+
def argument_node_factory(name: str) -> Callable[[graphql.ValueNode], graphql.ArgumentNode]:
265+
def factory(value: graphql.ValueNode) -> graphql.ArgumentNode:
266+
return graphql.ArgumentNode(name=graphql.NameNode(value=name), value=value)
267+
268+
return factory
269+
270+
244271
def fixed_lists(args: Iterable[st.SearchStrategy[T]]) -> st.SearchStrategy[List[T]]:
245272
return st.tuples(*args).map(list)
246273

@@ -295,17 +322,25 @@ def lists(
295322
"""Generate a `graphql.ListValueNode`."""
296323
type_ = type_.of_type
297324
list_value = st.lists(value_nodes(context, type_))
298-
return primitives.maybe_null(list_value.map(lambda values: graphql.ListValueNode(values=values)), nullable)
325+
return primitives.maybe_null(list_value.map(make_list_value_node), nullable)
326+
327+
328+
def make_list_value_node(values: List[graphql.ValueNode]) -> graphql.ListValueNode:
329+
return graphql.ListValueNode(values=values)
299330

300331

301332
def objects(
302333
context: Context, type_: graphql.GraphQLInputObjectType, nullable: bool = True
303334
) -> st.SearchStrategy[graphql.ObjectValueNode]:
304335
"""Generate a `graphql.ObjectValueNode`."""
305336
fields_value = subset_of_fields(type_.fields, force_required=True).flatmap(
306-
lambda fields: list_of_nodes(context, fields, strategy=object_field_nodes)
337+
list_of_nodes_factory(context, object_field_nodes)
307338
)
308-
return primitives.maybe_null(fields_value.map(lambda fields: graphql.ObjectValueNode(fields=fields)), nullable)
339+
return primitives.maybe_null(fields_value.map(make_object_value_node), nullable)
340+
341+
342+
def make_object_value_node(fields: List[graphql.ObjectFieldNode]) -> graphql.ObjectValueNode:
343+
return graphql.ObjectValueNode(fields=fields)
309344

310345

311346
def subset_of_fields(
@@ -324,7 +359,7 @@ def subset_of_fields(
324359
else:
325360
optional[name] = field
326361
if optional:
327-
return subset_of_fields(optional).map(lambda p: p + required)
362+
return subset_of_fields(optional).map(required.__add__)
328363
return st.just(required)
329364
# pairs are unique by field name
330365
return st.lists(st.sampled_from(field_pairs), min_size=1, unique_by=lambda x: x[0])
@@ -333,9 +368,14 @@ def subset_of_fields(
333368
def object_field_nodes(
334369
context: Context, name: str, field: graphql.GraphQLInputField
335370
) -> st.SearchStrategy[graphql.ObjectFieldNode]:
336-
return value_nodes(context, field.type).map(
337-
lambda value: graphql.ObjectFieldNode(name=graphql.NameNode(value=name), value=value)
338-
)
371+
return value_nodes(context, field.type).map(object_field_node_factory(name))
372+
373+
374+
def object_field_node_factory(name: str) -> Callable[[graphql.ValueNode], graphql.ObjectFieldNode]:
375+
def factory(value: graphql.ValueNode) -> graphql.ObjectFieldNode:
376+
return graphql.ObjectFieldNode(name=graphql.NameNode(value=name), value=value)
377+
378+
return factory
339379

340380

341381
def list_of_nodes(

0 commit comments

Comments
 (0)