Skip to content

Commit 73137ca

Browse files
authored
Merge pull request #1 from Checho3388/dev
Add support for DirectivesEstimator, include and skip directives and added shortcuts for complexity calculation
2 parents 4b01be4 + 7ee4cdd commit 73137ca

21 files changed

+947
-288
lines changed

.mutmut-cache

-56 KB
Binary file not shown.

README.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,166 @@
11
# graphql-complexity
22
Python library to compute the complexity of a GraphQL operation
3+
4+
![Unit Tests](https://github.com/Checho3388/graphql-complexity/actions/workflows/python-package.yml/badge.svg)
5+
6+
7+
## Usage
8+
The library uses the query complexity algorithm to compute the complexity of a GraphQL operation. The algorithm is
9+
based on the number of fields requested in the operation and the depth of the query.
10+
11+
```python
12+
from graphql_complexity import (get_complexity, SimpleEstimator)
13+
14+
query = """
15+
query SomeQuery {
16+
user {
17+
id
18+
name
19+
}
20+
}
21+
"""
22+
23+
complexity = get_complexity(query, estimator=SimpleEstimator(complexity=1, multiplier=1))
24+
if complexity > 10:
25+
raise Exception("Query is too complex")
26+
```
27+
28+
## Estimators
29+
In order to get the complexity of a query, an estimator needs to be defined. The main responsibility of
30+
the estimator is to give each node an integer value representing its complexity and (optionally) a
31+
multiplier that reflects the complexity in relation to the depth of the query.
32+
33+
There are two built-in estimators, plus the capability to create any new estimator by
34+
implementing the `ComplexityEstimator` interface.
35+
36+
### Estimate fields complexity based on constants for complexity and multiplier
37+
38+
This estimator assigns a **constant** complexity value to each field and multiplies
39+
it by another **constant** which is propagated along the depth of the query.
40+
41+
```python
42+
from graphql_complexity import SimpleEstimator
43+
44+
45+
estimator = SimpleEstimator(complexity=2, multiplier=1)
46+
```
47+
48+
Given the following query:
49+
```qgl
50+
query {
51+
user {
52+
name
53+
email
54+
}
55+
}
56+
```
57+
As the complexity and multiplier are constant, the complexity of the fields will be:
58+
59+
| Field | Complexity |
60+
|-------|---------------|
61+
| user | `1` |
62+
| name | `2 * (2 * 1)` |
63+
| email | `2 * (2 * 1)` |
64+
65+
And the total complexity will be `6`.
66+
67+
### Define fields complexity using schema directives
68+
69+
Assigns a complexity value to each field and multiplies it by the depth of the query.
70+
It also supports the `@complexity` directive to assign a custom complexity value to a field.
71+
72+
This approach requires to provide the schema to the estimator.
73+
74+
```python
75+
from graphql_complexity import DirectivesEstimator
76+
77+
78+
schema = """
79+
directive @complexity(
80+
value: Int!
81+
) on FIELD_DEFINITION
82+
83+
type Query {
84+
oneField: String @complexity(value: 5)
85+
otherField: String @complexity(value: 1)
86+
withoutDirective: String
87+
}
88+
"""
89+
90+
estimator = DirectivesEstimator(schema)
91+
```
92+
93+
Given the schema from above and the following query:
94+
```qgl
95+
query {
96+
oneField
97+
otherField
98+
withoutDirective
99+
}
100+
```
101+
102+
The complexity of the fields will be:
103+
104+
| Field | Complexity | Comment |
105+
|------------------|------------|---------------------------------------------------------------------------------------------------|
106+
| oneField | `5` | |
107+
| otherField | `1` | |
108+
| withoutDirective | `1` | The default complexity for fields without directive is `1`, this can be modified by parameters. |
109+
110+
And the total complexity will be `7`.
111+
112+
### Create a custom estimator
113+
This option allows to define a custom estimator to compute the complexity of a field using the `ComplexityEstimator` interface. For example:
114+
115+
```python
116+
from graphql_complexity import ComplexityEstimator
117+
118+
119+
class CustomEstimator(ComplexityEstimator):
120+
def get_field_complexity(self, node, key, parent, path, ancestors) -> int:
121+
if node.name.value == "specificField":
122+
return 100
123+
return 1
124+
125+
def get_field_multiplier(self, node, key, parent, path, ancestors) -> int:
126+
return 1
127+
```
128+
129+
130+
## Supported libraries (based on GraphQL-core)
131+
The library is compatible with the following GraphQL libraries:
132+
133+
### Strawberry
134+
135+
136+
The library is compatible with [strawberry-graphql](https://pypi.org/project/strawberry-graphql/).
137+
To use the library with strawberry-graphql, you need to install the library with the `strawberry-graphql` extra.
138+
```shell
139+
poetry install --extras strawberry-graphql
140+
```
141+
142+
To use the library with [strawberry-graphql](https://pypi.org/project/strawberry-graphql/), you need to use the `build_complexity_extension` method to build
143+
the complexity extension and add it to the schema. This method receives an estimator and returns a complexity
144+
extension that can be added to the schema.
145+
146+
```python
147+
import strawberry
148+
from graphql_complexity.extensions.strawberry_graphql import build_complexity_extension
149+
150+
@strawberry.type
151+
class Query:
152+
@strawberry.field()
153+
def hello_world(self) -> str:
154+
return "Hello world!"
155+
156+
extension = build_complexity_extension()
157+
schema = strawberry.Schema(query=Query, extensions=[extension])
158+
159+
schema.execute_sync("query { helloWorld }")
160+
```
161+
The `build_complexity_extension` method accepts an estimator as optional argument giving the possibility to use one
162+
of the built-in estimators or a custom estimator.
163+
164+
## Credits
165+
166+
Estimators idea was heavily inspired by [graphql-query-complexity](https://github.com/slicknode/graphql-query-complexity).

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
[tool.poetry]
2-
name = "graphql-complexity"
2+
name = "graphql_complexity"
33
version = "0.1.0"
44
description = "A python library that provides complexity calculation helpers for GraphQL"
55
authors = ["Checho3388 <ezequiel.grondona@gmail.com>"]
6+
packages = [
7+
{ include = "graphql_complexity", from = "src" },
8+
]
69
license = "MIT"
710
readme = "README.md"
811

src/estimators.py

Lines changed: 0 additions & 112 deletions
This file was deleted.

src/extensions.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/graphql_complexity/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from graphql_complexity.evaluator.complexity import get_complexity
2+
3+
from .estimators import (
4+
ComplexityEstimator,
5+
DirectivesEstimator,
6+
SimpleEstimator
7+
)
8+
9+
__all__ = [
10+
"get_complexity",
11+
"SimpleEstimator",
12+
"ComplexityEstimator",
13+
"DirectivesEstimator",
14+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .base import ComplexityEstimator
2+
from .directive import DirectivesEstimator
3+
from .simple import SimpleEstimator
4+
5+
__all__ = ["ComplexityEstimator", "SimpleEstimator", "DirectivesEstimator"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import abc
2+
3+
4+
class ComplexityEstimator(abc.ABC):
5+
@abc.abstractmethod
6+
def get_field_complexity(self, node, key, parent, path, ancestors) -> int:
7+
"""Return the complexity of the field."""
8+
9+
@abc.abstractmethod
10+
def get_field_multiplier(self, node, key, parent, path, ancestors) -> int:
11+
"""Return the multiplier that will be applied to the children of the given node."""

0 commit comments

Comments
 (0)