forked from getsentry/sentry-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgql.py
More file actions
173 lines (138 loc) · 4.93 KB
/
Copy pathgql.py
File metadata and controls
173 lines (138 loc) · 4.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import sentry_sdk
from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import (
ensure_integration_enabled,
event_from_exception,
parse_version,
)
try:
import gql # type: ignore[import-not-found]
from gql.transport import ( # type: ignore[import-not-found]
AsyncTransport,
Transport,
)
from gql.transport.exceptions import ( # type: ignore[import-not-found]
TransportQueryError,
)
from graphql import (
DocumentNode,
VariableDefinitionNode,
get_operation_ast,
print_ast,
)
try:
# gql 4.0+
from gql import GraphQLRequest
except ImportError:
GraphQLRequest = None
except ImportError:
raise DidNotEnable("gql is not installed")
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any, Dict, Tuple, Union
from sentry_sdk._types import Event, EventProcessor
EventDataType = Dict[str, Union[str, Tuple[VariableDefinitionNode, ...]]]
class GQLIntegration(Integration):
identifier = "gql"
@staticmethod
def setup_once() -> None:
gql_version = parse_version(gql.__version__)
_check_minimum_version(GQLIntegration, gql_version)
_patch_execute()
def _data_from_document(document: "DocumentNode") -> "EventDataType":
try:
operation_ast = get_operation_ast(document)
data: "EventDataType" = {"query": print_ast(document)}
if operation_ast is not None:
data["variables"] = operation_ast.variable_definitions
if operation_ast.name is not None:
data["operationName"] = operation_ast.name.value
return data
except (AttributeError, TypeError):
return dict()
def _transport_method(transport: "Union[Transport, AsyncTransport]") -> str:
"""
The RequestsHTTPTransport allows defining the HTTP method; all
other transports use POST.
"""
try:
return transport.method
except AttributeError:
return "POST"
def _request_info_from_transport(
transport: "Union[Transport, AsyncTransport, None]",
) -> "Dict[str, str]":
if transport is None:
return {}
request_info = {
"method": _transport_method(transport),
}
try:
request_info["url"] = transport.url
except AttributeError:
pass
return request_info
def _patch_execute() -> None:
real_execute = gql.Client.execute
# Maintain signature for backwards compatibility.
# gql.Client.execute() accepts a positional-only "request"
# parameter with version 4.0.0.
@ensure_integration_enabled(GQLIntegration, real_execute)
def sentry_patched_execute(
self: "gql.Client",
document: "DocumentNode",
*args: "Any",
**kwargs: "Any",
) -> "Any":
scope = sentry_sdk.get_isolation_scope()
# document is a gql.GraphQLRequest with gql v4.0.0.
scope.add_event_processor(_make_gql_event_processor(self, document))
try:
# document is a gql.GraphQLRequest with gql v4.0.0.
return real_execute(self, document, *args, **kwargs)
except TransportQueryError as e:
event, hint = event_from_exception(
e,
client_options=sentry_sdk.get_client().options,
mechanism={"type": "gql", "handled": False},
)
sentry_sdk.capture_event(event, hint)
raise e
gql.Client.execute = sentry_patched_execute
def _make_gql_event_processor(
client: "gql.Client", document_or_request: "Union[DocumentNode, gql.GraphQLRequest]"
) -> "EventProcessor":
def processor(event: "Event", hint: "dict[str, Any]") -> "Event":
try:
errors = hint["exc_info"][1].errors
except (AttributeError, KeyError):
errors = None
request = event.setdefault("request", {})
request.update(
{
"api_target": "graphql",
**_request_info_from_transport(client.transport),
}
)
if should_send_default_pii():
if GraphQLRequest is not None and isinstance(
document_or_request, GraphQLRequest
):
# In v4.0.0, gql moved to using GraphQLRequest instead of
# DocumentNode in execute
# https://github.com/graphql-python/gql/pull/556
document = document_or_request.document
else:
document = document_or_request
request["data"] = _data_from_document(document)
contexts = event.setdefault("contexts", {})
response = contexts.setdefault("response", {})
response.update(
{
"data": {"errors": errors},
"type": response,
}
)
return event
return processor