Skip to content

Commit

Permalink
Remove deprecated ignore argument from QueryDepthLimiter (#3093)
Browse files Browse the repository at this point in the history
  • Loading branch information
benesgarage authored Sep 14, 2023
1 parent 2b9a87b commit 37ad715
Show file tree
Hide file tree
Showing 4 changed files with 9 additions and 538 deletions.
3 changes: 3 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Release type: minor

This release removes the deprecated `ignore` argument from the `QueryDepthLimiter` extension.
132 changes: 1 addition & 131 deletions docs/extensions/query-depth-limiter.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,14 @@ schema = strawberry.Schema(
## API reference:

```python
class QueryDepthLimiter(max_depth, ignore=None, callback=None, should_ignore=None):
class QueryDepthLimiter(max_depth, callback=None, should_ignore=None):
...
```

#### `max_depth: int`

The maximum allowed depth for any operation in a GraphQL document.

#### `ignore: Optional[List[IgnoreType]]`

Stops recursive depth checking based on a field name.
Either a string or regexp to match the name, or a function that returns
a boolean.

This variable has been deprecated in favour of the `should_ignore` argument
as documented below.

#### `callback: Optional[Callable[[Dict[str, int]], None]`

Called each time validation runs. Receives a dictionary which is a
Expand Down Expand Up @@ -166,124 +157,3 @@ schema.execute(
"""
)
```

## More examples for deprecated `ignore` argument:

<details>
<summary>Ignoring fields</summary>

```python
import strawberry
from strawberry.extensions import QueryDepthLimiter

schema = strawberry.Schema(
Query,
extensions=[
QueryDepthLimiter(max_depth=2, ignore=["user"]),
],
)

# This query fails
schema.execute(
"""
query TooDeep {
book {
author {
publishedBooks {
title
}
}
}
}
"""
)

# This query succeeds because the `user` field is ignored
schema.execute(
"""
query NotTooDeep {
user {
favouriteBooks {
author {
publishedBooks {
title
}
}
}
}
}
"""
)
```

</details>

<details>
<summary>Ignoring fields with regex</summary>

```python
import re
import strawberry
from strawberry.extensions import QueryDepthLimiter

schema = strawberry.Schema(
Query,
extensions=[
QueryDepthLimiter(max_depth=2, ignore=[re.compile(r".*favourite.*")]),
],
)

# This query succeeds because an field that contains `favourite` is ignored
schema.execute(
"""
query NotTooDeep {
user {
favouriteBooks {
author {
publishedBooks {
title
}
}
}
}
}
"""
)
```

</details>

<details>
<summary>Ignoring fields with a function</summary>

```python
import strawberry
from strawberry.extensions import QueryDepthLimiter

schema = strawberry.Schema(
Query,
extensions=[
QueryDepthLimiter(
max_depth=2, ignore=[lambda field_name: field_name == "user"]
),
],
)

schema.execute(
"""
query NotTooDeep {
user {
favouriteBooks {
author {
publishedBooks {
title
}
}
}
}
}
"""
)
```

</details>
130 changes: 5 additions & 125 deletions strawberry/extensions/query_depth_limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from __future__ import annotations

import re
import warnings
from dataclasses import dataclass
from typing import (
Callable,
Expand Down Expand Up @@ -101,10 +100,6 @@ class QueryDepthLimiter(AddValidationRules):
`max_depth: int`
The maximum allowed depth for any operation in a GraphQL document.
`ignore: Optional[List[IgnoreType]] DEPRECATED`
Stops recursive depth checking based on a field name.
Either a string or regexp to match the name, or a function that returns
a boolean.
`callback: Optional[Callable[[Dict[str, int]], None]`
Called each time validation runs. Receives an Object which is a
map of the depths for each operation.
Expand All @@ -117,25 +112,15 @@ class QueryDepthLimiter(AddValidationRules):
def __init__(
self,
max_depth: int,
ignore: Optional[List[IgnoreType]] = None, # DEPRECATED
callback: Optional[Callable[[Dict[str, int]], None]] = None,
should_ignore: Optional[ShouldIgnoreType] = None,
):
if should_ignore is not None:
if not callable(should_ignore):
raise TypeError(
"The `should_ignore` argument to "
"`QueryDepthLimiter` must be a callable."
)
validator = create_validator(max_depth, should_ignore, callback)
else:
warnings.warn(
"The `ignore` argument to `QueryDepthLimiter` is deprecated. "
"Please use `should_ignore` instead.",
DeprecationWarning,
stacklevel=1,
if should_ignore is not None and not callable(should_ignore):
raise TypeError(
"The `should_ignore` argument to "
"`QueryDepthLimiter` must be a callable."
)
validator = create_validator_deprecated(max_depth, ignore, callback)
validator = create_validator(max_depth, should_ignore, callback)
super().__init__([validator])


Expand Down Expand Up @@ -313,111 +298,6 @@ def determine_depth(
raise TypeError(f"Depth crawler cannot handle: {node.kind}") # pragma: no cover


def create_validator_deprecated(
max_depth: int,
ignore: Optional[List[IgnoreType]] = None,
callback: Optional[Callable[[Dict[str, int]], None]] = None,
) -> Type[ValidationRule]:
class DepthLimitValidator(ValidationRule):
def __init__(self, validation_context: ValidationContext):
document = validation_context.document
definitions = document.definitions

fragments = get_fragments(definitions)
queries = get_queries_and_mutations(definitions)
query_depths = {}

for name in queries:
query_depths[name] = determine_depth_deprecated(
node=queries[name],
fragments=fragments,
depth_so_far=0,
max_depth=max_depth,
context=validation_context,
operation_name=name,
ignore=ignore,
)

if callable(callback):
callback(query_depths)
super().__init__(validation_context)

return DepthLimitValidator


def determine_depth_deprecated(
node: Node,
fragments: Dict[str, FragmentDefinitionNode],
depth_so_far: int,
max_depth: int,
context: ValidationContext,
operation_name: str,
ignore: Optional[List[IgnoreType]] = None,
) -> int:
if depth_so_far > max_depth:
context.report_error(
GraphQLError(
f"'{operation_name}' exceeds maximum operation depth of {max_depth}",
[node],
)
)
return depth_so_far

if isinstance(node, FieldNode):
# by default, ignore the introspection fields which begin
# with double underscores
should_ignore = is_introspection_key(node.name.value) or is_ignored(
node, ignore
)

if should_ignore or not node.selection_set:
return 0

return 1 + max(
map(
lambda selection: determine_depth_deprecated(
node=selection,
fragments=fragments,
depth_so_far=depth_so_far + 1,
max_depth=max_depth,
context=context,
operation_name=operation_name,
ignore=ignore,
),
node.selection_set.selections,
)
)
elif isinstance(node, FragmentSpreadNode):
return determine_depth_deprecated(
node=fragments[node.name.value],
fragments=fragments,
depth_so_far=depth_so_far,
max_depth=max_depth,
context=context,
operation_name=operation_name,
ignore=ignore,
)
elif isinstance(
node, (InlineFragmentNode, FragmentDefinitionNode, OperationDefinitionNode)
):
return max(
map(
lambda selection: determine_depth_deprecated(
node=selection,
fragments=fragments,
depth_so_far=depth_so_far,
max_depth=max_depth,
context=context,
operation_name=operation_name,
ignore=ignore,
),
node.selection_set.selections,
)
)
else:
raise TypeError(f"Depth crawler cannot handle: {node.kind}") # pragma: no cover


def is_ignored(node: FieldNode, ignore: Optional[List[IgnoreType]] = None) -> bool:
if ignore is None:
return False
Expand Down
Loading

0 comments on commit 37ad715

Please sign in to comment.