Skip to content

Commit 3d83b39

Browse files
fix(ray): Keep variadic kwargs last in signatures (#5244)
Ensure Python's function parameter order is respected when modifying Ray task signatures by injecting keyword only parameters before variadic keyword parameters. Allows the SDK to be used with Ray tasks that accept **kwargs.
1 parent f5c51fc commit 3d83b39

File tree

2 files changed

+49
-27
lines changed

2 files changed

+49
-27
lines changed

sentry_sdk/integrations/ray.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ def _check_sentry_initialized() -> None:
3636
)
3737

3838

39+
def _insert_sentry_tracing_in_signature(func: "Callable[..., Any]") -> None:
40+
# Patching new_func signature to add the _sentry_tracing parameter to it
41+
# Ray later inspects the signature and finds the unexpected parameter otherwise
42+
signature = inspect.signature(func)
43+
params = list(signature.parameters.values())
44+
sentry_tracing_param = inspect.Parameter(
45+
"_sentry_tracing",
46+
kind=inspect.Parameter.KEYWORD_ONLY,
47+
default=None,
48+
)
49+
50+
# Keyword only arguments are penultimate if function has variadic keyword arguments
51+
if params and params[-1].kind is inspect.Parameter.VAR_KEYWORD:
52+
params.insert(-1, sentry_tracing_param)
53+
else:
54+
params.append(sentry_tracing_param)
55+
56+
func.__signature__ = signature.replace(parameters=params) # type: ignore[attr-defined]
57+
58+
3959
def _patch_ray_remote() -> None:
4060
old_remote = ray.remote
4161

@@ -86,18 +106,7 @@ def new_func(
86106

87107
return result
88108

89-
# Patching new_func signature to add the _sentry_tracing parameter to it
90-
# Ray later inspects the signature and finds the unexpected parameter otherwise
91-
signature = inspect.signature(new_func)
92-
params = list(signature.parameters.values())
93-
params.append(
94-
inspect.Parameter(
95-
"_sentry_tracing",
96-
kind=inspect.Parameter.KEYWORD_ONLY,
97-
default=None,
98-
)
99-
)
100-
new_func.__signature__ = signature.replace(parameters=params) # type: ignore[attr-defined]
109+
_insert_sentry_tracing_in_signature(new_func)
101110

102111
if f:
103112
rv = old_remote(new_func)

tests/integrations/ray/test_ray.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,29 @@ def read_error_from_log(job_id, ray_temp_dir):
7474
return error
7575

7676

77+
def example_task():
78+
with sentry_sdk.start_span(op="task", name="example task step"):
79+
...
80+
81+
return sentry_sdk.get_client().transport.envelopes
82+
83+
84+
# RayIntegration must leave variadic keyword arguments at the end
85+
def example_task_with_kwargs(**kwargs):
86+
with sentry_sdk.start_span(op="task", name="example task step"):
87+
...
88+
89+
return sentry_sdk.get_client().transport.envelopes
90+
91+
7792
@pytest.mark.parametrize(
7893
"task_options", [{}, {"num_cpus": 0, "memory": 1024 * 1024 * 10}]
7994
)
80-
def test_tracing_in_ray_tasks(task_options):
95+
@pytest.mark.parametrize(
96+
"task",
97+
[example_task, example_task_with_kwargs],
98+
)
99+
def test_tracing_in_ray_tasks(task_options, task):
81100
setup_sentry()
82101

83102
ray.init(
@@ -87,21 +106,18 @@ def test_tracing_in_ray_tasks(task_options):
87106
}
88107
)
89108

90-
def example_task():
91-
with sentry_sdk.start_span(op="task", name="example task step"):
92-
...
93-
94-
return sentry_sdk.get_client().transport.envelopes
95-
96109
# Setup ray task, calling decorator directly instead of @,
97110
# to accommodate for test parametrization
98111
if task_options:
99-
example_task = ray.remote(**task_options)(example_task)
112+
example_task = ray.remote(**task_options)(task)
100113
else:
101-
example_task = ray.remote(example_task)
114+
example_task = ray.remote(task)
102115

103116
# Function name shouldn't be overwritten by Sentry wrapper
104-
assert example_task._function_name == "tests.integrations.ray.test_ray.example_task"
117+
assert (
118+
example_task._function_name
119+
== f"tests.integrations.ray.test_ray.{task.__name__}"
120+
)
105121

106122
with sentry_sdk.start_transaction(op="task", name="ray test transaction"):
107123
worker_envelopes = ray.get(example_task.remote())
@@ -115,17 +131,14 @@ def example_task():
115131
worker_transaction = worker_envelope.get_transaction_event()
116132
assert (
117133
worker_transaction["transaction"]
118-
== "tests.integrations.ray.test_ray.test_tracing_in_ray_tasks.<locals>.example_task"
134+
== f"tests.integrations.ray.test_ray.{task.__name__}"
119135
)
120136
assert worker_transaction["transaction_info"] == {"source": "task"}
121137

122138
(span,) = client_transaction["spans"]
123139
assert span["op"] == "queue.submit.ray"
124140
assert span["origin"] == "auto.queue.ray"
125-
assert (
126-
span["description"]
127-
== "tests.integrations.ray.test_ray.test_tracing_in_ray_tasks.<locals>.example_task"
128-
)
141+
assert span["description"] == f"tests.integrations.ray.test_ray.{task.__name__}"
129142
assert span["parent_span_id"] == client_transaction["contexts"]["trace"]["span_id"]
130143
assert span["trace_id"] == client_transaction["contexts"]["trace"]["trace_id"]
131144

0 commit comments

Comments
 (0)