Skip to content

Commit 8c118b8

Browse files
authored
chore(core): deprecate core.dispatch_with_results (#14915)
## Description We want to move people away from `core.dispatch_with_results`. This PR adds a linting rule to check for usage and encourage other patterns with `core.dispatch`. We added explicit allows for the existing usage and we can work to slowly migrate them away. ## Testing Added tests for the ast-grep linting rules. ## Risks <!-- Note any risks associated with this change, or "None" if no risks --> ## Additional Notes <!-- Any other information that would be helpful for reviewers -->
1 parent 6b54ca3 commit 8c118b8

File tree

21 files changed

+303
-24
lines changed

21 files changed

+303
-24
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
id: core-dispatch-with-results
2+
message: Avoid using `dispatch_with_results()` - use `core.dispatch()` instead
3+
severity: warning
4+
language: python
5+
ignores:
6+
- "ddtrace/internal/core/__init__.py"
7+
- "ddtrace/internal/core/event_hub.py"
8+
- "tests/internal/test_context_events_api.py"
9+
rule:
10+
any:
11+
# Match function calls to dispatch_with_results
12+
- pattern: core.dispatch_with_results($$$ARGS)
13+
- pattern: dispatch_with_results($$$ARGS)
14+
- pattern: event_hub.dispatch_with_results($$$ARGS)
15+
# Match imports of dispatch_with_results
16+
- pattern: from ddtrace.internal.core import dispatch_with_results
17+
- pattern: from ddtrace.internal.core import dispatch_with_results as $ALIAS
18+
- pattern: from ddtrace.internal.core import $$$IMPORTS, dispatch_with_results
19+
- pattern: from ddtrace.internal.core import $$$IMPORTS, dispatch_with_results as $ALIAS
20+
- pattern: from ddtrace.internal.core import dispatch_with_results, $$$IMPORTS
21+
- pattern: from ddtrace.internal.core import dispatch_with_results as $ALIAS, $$$IMPORTS
22+
- pattern: from ddtrace.internal.core.event_hub import dispatch_with_results
23+
- pattern: from ddtrace.internal.core.event_hub import dispatch_with_results as $ALIAS
24+
- pattern: from ddtrace.internal.core.event_hub import $$$IMPORTS, dispatch_with_results
25+
- pattern: from ddtrace.internal.core.event_hub import $$$IMPORTS, dispatch_with_results as $ALIAS
26+
- pattern: from ddtrace.internal.core.event_hub import dispatch_with_results, $$$IMPORTS
27+
- pattern: from ddtrace.internal.core.event_hub import dispatch_with_results as $ALIAS, $$$IMPORTS
28+
constraints:
29+
# Exclude the implementation files where it's defined or exported
30+
ALIAS:
31+
regex: ".*"
32+
note: |
33+
Avoid using `dispatch_with_results()` to prevent tight coupling between dispatched events
34+
and potential listeners. This function creates explicit dependencies that make the code
35+
harder to maintain and understand.
36+
37+
Prefer managing state/context via core.ExecutionContext using `core.set_item()` and
38+
`core.get_item()`:
39+
40+
Before:
41+
result = core.dispatch_with_results("my.event", (arg1, arg2)).result_key.value
42+
43+
After:
44+
core.dispatch("my.event", (arg1, arg2))
45+
result = core.get_item("result_key")
46+
47+
Alternatively, pass mutable state in the dispatch call:
48+
49+
data = {}
50+
core.dispatch("my.event", (data,))
51+
if data.get("result"):
52+
# do something with data["result"]
53+
54+
To interrupt application flow, use try/except around dispatch calls:
55+
56+
try:
57+
core.dispatch("my.event", (args,))
58+
except MyException:
59+
# handle interruption
60+
pass
61+
62+
Note: try/except will block all other listeners from running after the exception is raised.
63+
64+
This rule does not apply to:
65+
- The implementation in ddtrace/internal/core/event_hub.py
66+
- The export in ddtrace/internal/core/__init__.py
67+
- Test files and benchmarks
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
id: core-dispatch-with-results
2+
snapshots:
3+
? |
4+
# Using dispatch_with_results in a chain
5+
value = core.dispatch_with_results("my.event", (x,)).result.value
6+
print(value)
7+
: labels:
8+
- source: core.dispatch_with_results("my.event", (x,))
9+
style: primary
10+
start: 49
11+
end: 93
12+
core.dispatch_with_results("my.event", (arg1, arg2)):
13+
labels:
14+
- source: core.dispatch_with_results("my.event", (arg1, arg2))
15+
style: primary
16+
start: 0
17+
end: 52
18+
? |
19+
data = core.dispatch_with_results("my.event", (arg1, arg2))
20+
if data.result_key:
21+
process(data.result_key.value)
22+
: labels:
23+
- source: core.dispatch_with_results("my.event", (arg1, arg2))
24+
style: primary
25+
start: 7
26+
end: 59
27+
dispatch_with_results("my.event", (arg1,)):
28+
labels:
29+
- source: dispatch_with_results("my.event", (arg1,))
30+
style: primary
31+
start: 0
32+
end: 42
33+
event_hub.dispatch_with_results("my.event", ()):
34+
labels:
35+
- source: event_hub.dispatch_with_results("my.event", ())
36+
style: primary
37+
start: 0
38+
end: 47
39+
? |
40+
from ddtrace.internal import core
41+
result = core.dispatch_with_results("my.event", (arg1, arg2))
42+
: labels:
43+
- source: core.dispatch_with_results("my.event", (arg1, arg2))
44+
style: primary
45+
start: 43
46+
end: 95
47+
from ddtrace.internal.core import dispatch, dispatch_with_results:
48+
labels:
49+
- source: from ddtrace.internal.core import dispatch, dispatch_with_results
50+
style: primary
51+
start: 0
52+
end: 65
53+
from ddtrace.internal.core import dispatch, dispatch_with_results, on:
54+
labels:
55+
- source: from ddtrace.internal.core import dispatch, dispatch_with_results, on
56+
style: primary
57+
start: 0
58+
end: 69
59+
from ddtrace.internal.core import dispatch_with_results:
60+
labels:
61+
- source: from ddtrace.internal.core import dispatch_with_results
62+
style: primary
63+
start: 0
64+
end: 55
65+
? |
66+
from ddtrace.internal.core import dispatch_with_results
67+
result = dispatch_with_results("my.event", (arg1,))
68+
: labels:
69+
- source: from ddtrace.internal.core import dispatch_with_results
70+
style: primary
71+
start: 0
72+
end: 55
73+
from ddtrace.internal.core import dispatch_with_results as dwr:
74+
labels:
75+
- source: from ddtrace.internal.core import dispatch_with_results as dwr
76+
style: primary
77+
start: 0
78+
end: 62
79+
from ddtrace.internal.core import dispatch_with_results, on:
80+
labels:
81+
- source: from ddtrace.internal.core import dispatch_with_results, on
82+
style: primary
83+
start: 0
84+
end: 59
85+
from ddtrace.internal.core.event_hub import dispatch, dispatch_with_results:
86+
labels:
87+
- source: from ddtrace.internal.core.event_hub import dispatch, dispatch_with_results
88+
style: primary
89+
start: 0
90+
end: 75
91+
from ddtrace.internal.core.event_hub import dispatch_with_results:
92+
labels:
93+
- source: from ddtrace.internal.core.event_hub import dispatch_with_results
94+
style: primary
95+
start: 0
96+
end: 65
97+
? |
98+
from ddtrace.internal.core.event_hub import dispatch_with_results
99+
result = dispatch_with_results("my.event", ())
100+
: labels:
101+
- source: from ddtrace.internal.core.event_hub import dispatch_with_results
102+
style: primary
103+
start: 0
104+
end: 65
105+
from ddtrace.internal.core.event_hub import dispatch_with_results as dwr:
106+
labels:
107+
- source: from ddtrace.internal.core.event_hub import dispatch_with_results as dwr
108+
style: primary
109+
start: 0
110+
end: 72
111+
? |
112+
result = core.dispatch_with_results("my.event", (arg1, arg2)).result_key.value
113+
: labels:
114+
- source: core.dispatch_with_results("my.event", (arg1, arg2))
115+
style: primary
116+
start: 9
117+
end: 61
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
id: core-dispatch-with-results
2+
valid:
3+
# These should NOT trigger the rule (valid code)
4+
- core.dispatch("my.event", (arg1, arg2))
5+
- |
6+
from ddtrace.internal.core import dispatch
7+
dispatch("my.event", (arg1,))
8+
- |
9+
from ddtrace.internal.core import core
10+
core.dispatch("my.event", ())
11+
- |
12+
# Using dispatch with mutable state is OK
13+
data = {}
14+
core.dispatch("my.event", (data,))
15+
if data.get("result"):
16+
process(data["result"])
17+
- |
18+
# Using set_item/get_item is the preferred pattern
19+
core.dispatch("my.event", (arg1, arg2))
20+
result = core.get_item("result_key")
21+
- |
22+
# Using try/except for flow control is OK
23+
try:
24+
core.dispatch("my.event", (args,))
25+
except MyException:
26+
pass
27+
- |
28+
# Other functions with similar names are OK
29+
my_module.dispatch_with_results(args)
30+
dispatch_with_other_results(args)
31+
32+
invalid:
33+
# These should trigger the rule (warnings)
34+
# Direct function calls
35+
- core.dispatch_with_results("my.event", (arg1, arg2))
36+
- dispatch_with_results("my.event", (arg1,))
37+
- event_hub.dispatch_with_results("my.event", ())
38+
- |
39+
result = core.dispatch_with_results("my.event", (arg1, arg2)).result_key.value
40+
- |
41+
data = core.dispatch_with_results("my.event", (arg1, arg2))
42+
if data.result_key:
43+
process(data.result_key.value)
44+
- |
45+
# Using dispatch_with_results in a chain
46+
value = core.dispatch_with_results("my.event", (x,)).result.value
47+
print(value)
48+
# Import statements
49+
- from ddtrace.internal.core import dispatch_with_results
50+
- from ddtrace.internal.core import dispatch_with_results as dwr
51+
- from ddtrace.internal.core import dispatch, dispatch_with_results
52+
- from ddtrace.internal.core import dispatch_with_results, on
53+
- from ddtrace.internal.core import dispatch, dispatch_with_results, on
54+
- from ddtrace.internal.core.event_hub import dispatch_with_results
55+
- from ddtrace.internal.core.event_hub import dispatch_with_results as dwr
56+
- from ddtrace.internal.core.event_hub import dispatch, dispatch_with_results
57+
- |
58+
from ddtrace.internal.core import dispatch_with_results
59+
result = dispatch_with_results("my.event", (arg1,))
60+
- |
61+
from ddtrace.internal.core.event_hub import dispatch_with_results
62+
result = dispatch_with_results("my.event", ())
63+
- |
64+
from ddtrace.internal import core
65+
result = core.dispatch_with_results("my.event", (arg1, arg2))

benchmarks/core_api/scenario.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ def core_dispatch(loops):
3636
def core_dispatch_with_results(loops):
3737
"""Measure the cost to dispatch an event on the hub"""
3838
for _ in range(loops):
39-
core.dispatch_with_results(CUSTOM_EVENT_NAME, (5, 6, 7, 8))
39+
core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
40+
CUSTOM_EVENT_NAME, (5, 6, 7, 8)
41+
)
4042

4143
def context_with_data(loops):
4244
"""Measure the cost of creating and ending a new context"""

ddtrace/contrib/dbapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def _trace_method(self, method, name, resource, extra_tags, dbm_propagator, *arg
106106
# dispatch DBM
107107
if dbm_propagator:
108108
# this check is necessary to prevent fetch methods from trying to add dbm propagation
109-
result = core.dispatch_with_results(
109+
result = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
110110
f"{self._self_config.integration_name}.execute", (self._self_config, s, args, kwargs)
111111
).result
112112
if result:

ddtrace/contrib/dbapi_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ async def _trace_method(self, method, name, resource, extra_tags, dbm_propagator
8181
# dispatch DBM
8282
if dbm_propagator:
8383
# this check is necessary to prevent fetch methods from trying to add dbm propagation
84-
result = core.dispatch_with_results(
84+
result = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
8585
f"{self._self_config.integration_name}.execute", (self._self_config, s, args, kwargs)
8686
).result
8787
if result:

ddtrace/contrib/internal/aiomysql/patch.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ async def _trace_method(self, method, resource, extra_tags, *args, **kwargs):
9393
s.set_tags(extra_tags)
9494

9595
# dispatch DBM
96-
result = core.dispatch_with_results("aiomysql.execute", (config.aiomysql, s, args, kwargs)).result
96+
result = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
97+
"aiomysql.execute", (config.aiomysql, s, args, kwargs)
98+
).result
9799
if result:
98100
s, args, kwargs = result.value
99101

ddtrace/contrib/internal/asgi/middleware.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,9 @@ async def __call__(self, scope: Mapping[str, Any], receive: Callable, send: Call
261261
if not self.integration_config.trace_query_string:
262262
query_string = None
263263
body = None
264-
result = core.dispatch_with_results("asgi.request.parse.body", (receive, headers)).await_receive_and_body
264+
result = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
265+
"asgi.request.parse.body", (receive, headers)
266+
).await_receive_and_body
265267
if result:
266268
receive, body = await result.value
267269

@@ -421,7 +423,9 @@ async def wrapped_send(message: Mapping[str, Any]):
421423
span.finish()
422424

423425
async def wrapped_blocked_send(message: Mapping[str, Any]):
424-
result = core.dispatch_with_results("asgi.block.started", (ctx, url)).status_headers_content
426+
result = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
427+
"asgi.block.started", (ctx, url)
428+
).status_headers_content
425429
if result:
426430
status, headers, content = result.value
427431
else:

ddtrace/contrib/internal/asyncpg/patch.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ async def _traced_query(pin, method, query, args, kwargs):
142142
span.set_tags(pin.tags)
143143

144144
# dispatch DBM
145-
result = core.dispatch_with_results("asyncpg.execute", (config.asyncpg, method, span, args, kwargs)).result
145+
result = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
146+
"asyncpg.execute", (config.asyncpg, method, span, args, kwargs)
147+
).result
146148
if result:
147149
span, args, kwargs = result.value
148150

ddtrace/contrib/internal/django/patch.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def traced_authenticate(django, pin, func, instance, args, kwargs):
374374
if mode == "disabled":
375375
return result_user
376376
try:
377-
result = core.dispatch_with_results(
377+
result = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
378378
"django.auth",
379379
(result_user, mode, kwargs, pin, _DjangoUserInfoRetriever(result_user, credentials=kwargs), config_django),
380380
).user
@@ -485,7 +485,9 @@ def _(m):
485485

486486

487487
def wrap_wsgi_environ(wrapped, _instance, args, kwargs):
488-
result = core.dispatch_with_results("django.wsgi_environ", (wrapped, _instance, args, kwargs)).wrapped_result
488+
result = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results
489+
"django.wsgi_environ", (wrapped, _instance, args, kwargs)
490+
).wrapped_result
489491
# if the callback is registered and runs, return the result
490492
if result:
491493
return result.value

0 commit comments

Comments
 (0)