From 7d14008477fff7a2f580c8cf951e456c54217a4d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 08:36:33 +0200 Subject: [PATCH 01/32] Some test showing trx in trx is a bad idea --- .../integrations/threading/test_threading.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 0d14fae352..1271e44c55 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -172,3 +172,57 @@ def target(): assert Thread.run.__qualname__ == original_run.__qualname__ assert t.run.__name__ == "run" assert t.run.__qualname__ == original_run.__qualname__ + + +def test_trx_in_trx_not_working(sentry_init, capture_events): + sentry_init( + traces_sample_rate=1.0, + integrations=[ThreadingIntegration(propagate_hub=True)], + ) + events = capture_events() + + def do_some_work(number): + with sentry_sdk.start_transaction(name=f"inner-trx-{number}") as trx_inner: + print(f"Inner Trx: {trx_inner}") + with sentry_sdk.start_span(op=f"inner-{number}-a", name=str(number)): + with sentry_sdk.start_span(op=f"inner-{number}-b", name=str(number)): + with sentry_sdk.start_span( + op=f"inner-{number}-c", name=str(number) + ): + with sentry_sdk.start_span( + op=f"inner-{number}-d", name=str(number) + ): + with sentry_sdk.start_span( + op=f"inner-{number}-e", name=str(number) + ): + with sentry_sdk.start_span( + op=f"inner-{number}-f", name=str(number) + ): + pass + + with sentry_sdk.start_transaction(name="outer-trx") as trx_outer: + print(f"Outer Trx: {trx_outer}") + with futures.ThreadPoolExecutor(max_workers=2) as executor: + for number in range(20): + with sentry_sdk.start_span( + op=f"outer1-{number}", name=str(number) + ) as span: + with sentry_sdk.start_span( + op=f"outer2-{number}", name=str(number) + ) as span: + with sentry_sdk.start_span( + op=f"outer3-{number}", name=str(number) + ) as span: + executor.submit(do_some_work, number) + + wrong_span_found_in_trx = False + for event in events: + prefix = "inner" if event["transaction"].startswith("inner") else "outer" + for span in event["spans"]: + if not span["op"].startswith(prefix): + wrong_span_found_in_trx = True + break + + assert ( + wrong_span_found_in_trx + ), "No wrong span found in transaction. It is excepted that there is one. (as we are testing a race condition, this test might fail randomly)" From 9c35e83a91ed5c9ce42a7fb4ee0a41bdaaca2291 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 09:01:19 +0200 Subject: [PATCH 02/32] test that shows we are leaking scope changes --- .../integrations/threading/test_threading.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 1271e44c55..0fd3cc3cfe 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -226,3 +226,26 @@ def do_some_work(number): assert ( wrong_span_found_in_trx ), "No wrong span found in transaction. It is excepted that there is one. (as we are testing a race condition, this test might fail randomly)" + + +def test_scope_data_not_leaked_in_threads(sentry_init): + sentry_init( + traces_sample_rate=1.0, + integrations=[ThreadingIntegration(propagate_hub=True)], + ) + + initial_iso_scope = sentry_sdk.get_isolation_scope() + + def do_some_work(number): + # change data in isolation scope in thread + sentry_sdk.set_tag("foo", "bar") + + with futures.ThreadPoolExecutor(max_workers=2) as executor: + all_futures = [] + for number in range(10): + all_futures.append(executor.submit(do_some_work, number)) + futures.wait(all_futures) + + assert ( + initial_iso_scope._tags == {} + ), "The isolation scope in the main thread should not be modified by the started threads." From 664f0e08306cfad1eca435cd995c1160b4c48f3e Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 09:03:12 +0200 Subject: [PATCH 03/32] remove flaky tests, it does more harm then it helps --- .../integrations/threading/test_threading.py | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 0fd3cc3cfe..e8c0201bb4 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -174,60 +174,6 @@ def target(): assert t.run.__qualname__ == original_run.__qualname__ -def test_trx_in_trx_not_working(sentry_init, capture_events): - sentry_init( - traces_sample_rate=1.0, - integrations=[ThreadingIntegration(propagate_hub=True)], - ) - events = capture_events() - - def do_some_work(number): - with sentry_sdk.start_transaction(name=f"inner-trx-{number}") as trx_inner: - print(f"Inner Trx: {trx_inner}") - with sentry_sdk.start_span(op=f"inner-{number}-a", name=str(number)): - with sentry_sdk.start_span(op=f"inner-{number}-b", name=str(number)): - with sentry_sdk.start_span( - op=f"inner-{number}-c", name=str(number) - ): - with sentry_sdk.start_span( - op=f"inner-{number}-d", name=str(number) - ): - with sentry_sdk.start_span( - op=f"inner-{number}-e", name=str(number) - ): - with sentry_sdk.start_span( - op=f"inner-{number}-f", name=str(number) - ): - pass - - with sentry_sdk.start_transaction(name="outer-trx") as trx_outer: - print(f"Outer Trx: {trx_outer}") - with futures.ThreadPoolExecutor(max_workers=2) as executor: - for number in range(20): - with sentry_sdk.start_span( - op=f"outer1-{number}", name=str(number) - ) as span: - with sentry_sdk.start_span( - op=f"outer2-{number}", name=str(number) - ) as span: - with sentry_sdk.start_span( - op=f"outer3-{number}", name=str(number) - ) as span: - executor.submit(do_some_work, number) - - wrong_span_found_in_trx = False - for event in events: - prefix = "inner" if event["transaction"].startswith("inner") else "outer" - for span in event["spans"]: - if not span["op"].startswith(prefix): - wrong_span_found_in_trx = True - break - - assert ( - wrong_span_found_in_trx - ), "No wrong span found in transaction. It is excepted that there is one. (as we are testing a race condition, this test might fail randomly)" - - def test_scope_data_not_leaked_in_threads(sentry_init): sentry_init( traces_sample_rate=1.0, From 0d9a2214f4386519ab6b7d854ecd71f30beb0f27 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 09:10:17 +0200 Subject: [PATCH 04/32] better readability --- tests/integrations/threading/test_threading.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index e8c0201bb4..7f832cb48c 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -176,15 +176,15 @@ def target(): def test_scope_data_not_leaked_in_threads(sentry_init): sentry_init( - traces_sample_rate=1.0, - integrations=[ThreadingIntegration(propagate_hub=True)], + integrations=[ThreadingIntegration()], ) + sentry_sdk.set_tag("initial_tag", "initial_value") initial_iso_scope = sentry_sdk.get_isolation_scope() def do_some_work(number): # change data in isolation scope in thread - sentry_sdk.set_tag("foo", "bar") + sentry_sdk.set_tag("thread_tag", "thread_value") with futures.ThreadPoolExecutor(max_workers=2) as executor: all_futures = [] @@ -192,6 +192,6 @@ def do_some_work(number): all_futures.append(executor.submit(do_some_work, number)) futures.wait(all_futures) - assert ( - initial_iso_scope._tags == {} - ), "The isolation scope in the main thread should not be modified by the started threads." + assert initial_iso_scope._tags == { + "initial_tag": "initial_value" + }, "The isolation scope in the main thread should not be modified by the started threads." From 0100bdb6e8f7f721dc4fbc20827df3f3edfdcc39 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 09:22:19 +0200 Subject: [PATCH 05/32] better readable --- .../integrations/threading/test_threading.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 7f832cb48c..f1c879ea4e 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -174,24 +174,36 @@ def target(): assert t.run.__qualname__ == original_run.__qualname__ -def test_scope_data_not_leaked_in_threads(sentry_init): +@pytest.mark.parametrize( + "propagate_scope", + (True, False), + ids=["propagate_scope=True", "propagate_scope=False"], +) +def test_scope_data_not_leaked_in_threads(sentry_init, propagate_scope): sentry_init( - integrations=[ThreadingIntegration()], + integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], ) sentry_sdk.set_tag("initial_tag", "initial_value") initial_iso_scope = sentry_sdk.get_isolation_scope() - def do_some_work(number): + def do_some_work(): + # check if we have the initial scope data propagated into the thread + if propagate_scope: + assert sentry_sdk.get_isolation_scope()._tags == { + "initial_tag": "initial_value" + } + else: + assert sentry_sdk.get_isolation_scope()._tags == {} + # change data in isolation scope in thread sentry_sdk.set_tag("thread_tag", "thread_value") - with futures.ThreadPoolExecutor(max_workers=2) as executor: - all_futures = [] - for number in range(10): - all_futures.append(executor.submit(do_some_work, number)) - futures.wait(all_futures) + t = Thread(target=do_some_work) + t.start() + t.join() + # check if the initial scope data is not modified by the started thread assert initial_iso_scope._tags == { "initial_tag": "initial_value" - }, "The isolation scope in the main thread should not be modified by the started threads." + }, "The isolation scope in the main thread should not be modified by the started thread." From bd449590ee6107dc071fa2f68d6030afb75a1b84 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 09:35:06 +0200 Subject: [PATCH 06/32] Propagate a copy of the scopes to threads --- sentry_sdk/integrations/threading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index 5de736e23b..a4d8b2026e 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -57,8 +57,8 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - isolation_scope = sentry_sdk.get_isolation_scope() - current_scope = sentry_sdk.get_current_scope() + isolation_scope = sentry_sdk.get_isolation_scope().fork() + current_scope = sentry_sdk.get_current_scope().fork() else: isolation_scope = None current_scope = None From 0e92608f230914105a284aace037eefa6b20e2fb Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 11:11:56 +0200 Subject: [PATCH 07/32] More tests --- .../integrations/threading/test_threading.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index f1c879ea4e..58cd2a2053 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -1,5 +1,6 @@ import gc from concurrent import futures +from textwrap import dedent from threading import Thread import pytest @@ -7,6 +8,7 @@ import sentry_sdk from sentry_sdk import capture_message from sentry_sdk.integrations.threading import ThreadingIntegration +from sentry_sdk.utils import get_current_thread_meta original_start = Thread.start original_run = Thread.run @@ -207,3 +209,70 @@ def do_some_work(): assert initial_iso_scope._tags == { "initial_tag": "initial_value" }, "The isolation scope in the main thread should not be modified by the started thread." + + +@pytest.mark.parametrize( + "propagate_scope", + (True, False), + ids=["propagate_scope=True", "propagate_scope=False"], +) +def test_spans_from_multiple_threads( + sentry_init, capture_events, render_span_tree, propagate_scope +): + sentry_init( + traces_sample_rate=1.0, + integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], + ) + events = capture_events() + + def do_some_work(number): + with sentry_sdk.start_span( + op=f"inner-run-{number}", + name=f"Thread: {get_current_thread_meta()[1]}", + ): + pass + + threads = [] + + with sentry_sdk.start_transaction(op="outer-trx"): + for number in range(5): + with sentry_sdk.start_span( + op=f"outer-submit-{number}", + name=f"Thread: {get_current_thread_meta()[1]}", + ): + t = Thread(target=do_some_work, args=(number,)) + t.start() + threads.append(t) + + for t in threads: + t.join() + + (event,) = events + if propagate_scope: + assert render_span_tree(event) == dedent( + """\ + - op="outer-trx": description=null + - op="outer-submit-0": description="Thread: MainThread" + - op="inner-run-0": description="Thread: Thread-1 (do_some_work)" + - op="outer-submit-1": description="Thread: MainThread" + - op="inner-run-1": description="Thread: Thread-2 (do_some_work)" + - op="outer-submit-2": description="Thread: MainThread" + - op="inner-run-2": description="Thread: Thread-3 (do_some_work)" + - op="outer-submit-3": description="Thread: MainThread" + - op="inner-run-3": description="Thread: Thread-4 (do_some_work)" + - op="outer-submit-4": description="Thread: MainThread" + - op="inner-run-4": description="Thread: Thread-5 (do_some_work)"\ +""" + ) + + elif not propagate_scope: + assert render_span_tree(event) == dedent( + """\ + - op="outer-trx": description=null + - op="outer-submit-0": description="Thread: MainThread" + - op="outer-submit-1": description="Thread: MainThread" + - op="outer-submit-2": description="Thread: MainThread" + - op="outer-submit-3": description="Thread: MainThread" + - op="outer-submit-4": description="Thread: MainThread"\ +""" + ) From fd69f64331f011ad69cbfc38961c0eff90794bb0 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 11:22:38 +0200 Subject: [PATCH 08/32] better span names --- .../integrations/threading/test_threading.py | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 58cd2a2053..4395891d62 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -8,7 +8,6 @@ import sentry_sdk from sentry_sdk import capture_message from sentry_sdk.integrations.threading import ThreadingIntegration -from sentry_sdk.utils import get_current_thread_meta original_start = Thread.start original_run = Thread.run @@ -227,8 +226,7 @@ def test_spans_from_multiple_threads( def do_some_work(number): with sentry_sdk.start_span( - op=f"inner-run-{number}", - name=f"Thread: {get_current_thread_meta()[1]}", + op=f"inner-run-{number}", name=f"Thread: child-{number}" ): pass @@ -237,8 +235,7 @@ def do_some_work(number): with sentry_sdk.start_transaction(op="outer-trx"): for number in range(5): with sentry_sdk.start_span( - op=f"outer-submit-{number}", - name=f"Thread: {get_current_thread_meta()[1]}", + op=f"outer-submit-{number}", name="Thread: main" ): t = Thread(target=do_some_work, args=(number,)) t.start() @@ -252,16 +249,16 @@ def do_some_work(number): assert render_span_tree(event) == dedent( """\ - op="outer-trx": description=null - - op="outer-submit-0": description="Thread: MainThread" - - op="inner-run-0": description="Thread: Thread-1 (do_some_work)" - - op="outer-submit-1": description="Thread: MainThread" - - op="inner-run-1": description="Thread: Thread-2 (do_some_work)" - - op="outer-submit-2": description="Thread: MainThread" - - op="inner-run-2": description="Thread: Thread-3 (do_some_work)" - - op="outer-submit-3": description="Thread: MainThread" - - op="inner-run-3": description="Thread: Thread-4 (do_some_work)" - - op="outer-submit-4": description="Thread: MainThread" - - op="inner-run-4": description="Thread: Thread-5 (do_some_work)"\ + - op="outer-submit-0": description="Thread: main" + - op="inner-run-0": description="Thread: child-0" + - op="outer-submit-1": description="Thread: main" + - op="inner-run-1": description="Thread: child-1" + - op="outer-submit-2": description="Thread: main" + - op="inner-run-2": description="Thread: child-2" + - op="outer-submit-3": description="Thread: main" + - op="inner-run-3": description="Thread: child-3" + - op="outer-submit-4": description="Thread: main" + - op="inner-run-4": description="Thread: child-4"\ """ ) @@ -269,10 +266,10 @@ def do_some_work(number): assert render_span_tree(event) == dedent( """\ - op="outer-trx": description=null - - op="outer-submit-0": description="Thread: MainThread" - - op="outer-submit-1": description="Thread: MainThread" - - op="outer-submit-2": description="Thread: MainThread" - - op="outer-submit-3": description="Thread: MainThread" - - op="outer-submit-4": description="Thread: MainThread"\ + - op="outer-submit-0": description="Thread: main" + - op="outer-submit-1": description="Thread: main" + - op="outer-submit-2": description="Thread: main" + - op="outer-submit-3": description="Thread: main" + - op="outer-submit-4": description="Thread: main"\ """ ) From e4d94a5917931b0d6911281abfce8ab8a4b6d5da Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 13:17:56 +0200 Subject: [PATCH 09/32] what is happening? --- sentry_sdk/integrations/threading.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index a4d8b2026e..b9c4ed25be 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -57,8 +57,10 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - isolation_scope = sentry_sdk.get_isolation_scope().fork() - current_scope = sentry_sdk.get_current_scope().fork() + isolation_scope = sentry_sdk.get_isolation_scope() + current_scope = sentry_sdk.get_current_scope() + # isolation_scope = sentry_sdk.get_isolation_scope().fork() + # current_scope = sentry_sdk.get_current_scope().fork() else: isolation_scope = None current_scope = None From c7af4b9171472e92f1da343a426282cbd668c617 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 11 Apr 2025 14:03:48 +0200 Subject: [PATCH 10/32] back to forking --- sentry_sdk/integrations/threading.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index b9c4ed25be..a4d8b2026e 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -57,10 +57,8 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - isolation_scope = sentry_sdk.get_isolation_scope() - current_scope = sentry_sdk.get_current_scope() - # isolation_scope = sentry_sdk.get_isolation_scope().fork() - # current_scope = sentry_sdk.get_current_scope().fork() + isolation_scope = sentry_sdk.get_isolation_scope().fork() + current_scope = sentry_sdk.get_current_scope().fork() else: isolation_scope = None current_scope = None From 678f515a9175063ff37a24c0d4a6c898ca45d2ae Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 10:13:20 +0200 Subject: [PATCH 11/32] trigger ci From eef7ff6538b5f234352d14b0cf0efff27136b607 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 10:25:56 +0200 Subject: [PATCH 12/32] trying without new tests --- .../integrations/threading/test_threading.py | 199 +++++++++--------- 1 file changed, 100 insertions(+), 99 deletions(-) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 4395891d62..38e6b3df14 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -1,6 +1,7 @@ import gc from concurrent import futures -from textwrap import dedent + +# from textwrap import dedent from threading import Thread import pytest @@ -175,101 +176,101 @@ def target(): assert t.run.__qualname__ == original_run.__qualname__ -@pytest.mark.parametrize( - "propagate_scope", - (True, False), - ids=["propagate_scope=True", "propagate_scope=False"], -) -def test_scope_data_not_leaked_in_threads(sentry_init, propagate_scope): - sentry_init( - integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], - ) - - sentry_sdk.set_tag("initial_tag", "initial_value") - initial_iso_scope = sentry_sdk.get_isolation_scope() - - def do_some_work(): - # check if we have the initial scope data propagated into the thread - if propagate_scope: - assert sentry_sdk.get_isolation_scope()._tags == { - "initial_tag": "initial_value" - } - else: - assert sentry_sdk.get_isolation_scope()._tags == {} - - # change data in isolation scope in thread - sentry_sdk.set_tag("thread_tag", "thread_value") - - t = Thread(target=do_some_work) - t.start() - t.join() - - # check if the initial scope data is not modified by the started thread - assert initial_iso_scope._tags == { - "initial_tag": "initial_value" - }, "The isolation scope in the main thread should not be modified by the started thread." - - -@pytest.mark.parametrize( - "propagate_scope", - (True, False), - ids=["propagate_scope=True", "propagate_scope=False"], -) -def test_spans_from_multiple_threads( - sentry_init, capture_events, render_span_tree, propagate_scope -): - sentry_init( - traces_sample_rate=1.0, - integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], - ) - events = capture_events() - - def do_some_work(number): - with sentry_sdk.start_span( - op=f"inner-run-{number}", name=f"Thread: child-{number}" - ): - pass - - threads = [] - - with sentry_sdk.start_transaction(op="outer-trx"): - for number in range(5): - with sentry_sdk.start_span( - op=f"outer-submit-{number}", name="Thread: main" - ): - t = Thread(target=do_some_work, args=(number,)) - t.start() - threads.append(t) - - for t in threads: - t.join() - - (event,) = events - if propagate_scope: - assert render_span_tree(event) == dedent( - """\ - - op="outer-trx": description=null - - op="outer-submit-0": description="Thread: main" - - op="inner-run-0": description="Thread: child-0" - - op="outer-submit-1": description="Thread: main" - - op="inner-run-1": description="Thread: child-1" - - op="outer-submit-2": description="Thread: main" - - op="inner-run-2": description="Thread: child-2" - - op="outer-submit-3": description="Thread: main" - - op="inner-run-3": description="Thread: child-3" - - op="outer-submit-4": description="Thread: main" - - op="inner-run-4": description="Thread: child-4"\ -""" - ) - - elif not propagate_scope: - assert render_span_tree(event) == dedent( - """\ - - op="outer-trx": description=null - - op="outer-submit-0": description="Thread: main" - - op="outer-submit-1": description="Thread: main" - - op="outer-submit-2": description="Thread: main" - - op="outer-submit-3": description="Thread: main" - - op="outer-submit-4": description="Thread: main"\ -""" - ) +# @pytest.mark.parametrize( +# "propagate_scope", +# (True, False), +# ids=["propagate_scope=True", "propagate_scope=False"], +# ) +# def test_scope_data_not_leaked_in_threads(sentry_init, propagate_scope): +# sentry_init( +# integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], +# ) + +# sentry_sdk.set_tag("initial_tag", "initial_value") +# initial_iso_scope = sentry_sdk.get_isolation_scope() + +# def do_some_work(): +# # check if we have the initial scope data propagated into the thread +# if propagate_scope: +# assert sentry_sdk.get_isolation_scope()._tags == { +# "initial_tag": "initial_value" +# } +# else: +# assert sentry_sdk.get_isolation_scope()._tags == {} + +# # change data in isolation scope in thread +# sentry_sdk.set_tag("thread_tag", "thread_value") + +# t = Thread(target=do_some_work) +# t.start() +# t.join() + +# # check if the initial scope data is not modified by the started thread +# assert initial_iso_scope._tags == { +# "initial_tag": "initial_value" +# }, "The isolation scope in the main thread should not be modified by the started thread." + + +# @pytest.mark.parametrize( +# "propagate_scope", +# (True, False), +# ids=["propagate_scope=True", "propagate_scope=False"], +# ) +# def test_spans_from_multiple_threads( +# sentry_init, capture_events, render_span_tree, propagate_scope +# ): +# sentry_init( +# traces_sample_rate=1.0, +# integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], +# ) +# events = capture_events() + +# def do_some_work(number): +# with sentry_sdk.start_span( +# op=f"inner-run-{number}", name=f"Thread: child-{number}" +# ): +# pass + +# threads = [] + +# with sentry_sdk.start_transaction(op="outer-trx"): +# for number in range(5): +# with sentry_sdk.start_span( +# op=f"outer-submit-{number}", name="Thread: main" +# ): +# t = Thread(target=do_some_work, args=(number,)) +# t.start() +# threads.append(t) + +# for t in threads: +# t.join() + +# (event,) = events +# if propagate_scope: +# assert render_span_tree(event) == dedent( +# """\ +# - op="outer-trx": description=null +# - op="outer-submit-0": description="Thread: main" +# - op="inner-run-0": description="Thread: child-0" +# - op="outer-submit-1": description="Thread: main" +# - op="inner-run-1": description="Thread: child-1" +# - op="outer-submit-2": description="Thread: main" +# - op="inner-run-2": description="Thread: child-2" +# - op="outer-submit-3": description="Thread: main" +# - op="inner-run-3": description="Thread: child-3" +# - op="outer-submit-4": description="Thread: main" +# - op="inner-run-4": description="Thread: child-4"\ +# """ +# ) + +# elif not propagate_scope: +# assert render_span_tree(event) == dedent( +# """\ +# - op="outer-trx": description=null +# - op="outer-submit-0": description="Thread: main" +# - op="outer-submit-1": description="Thread: main" +# - op="outer-submit-2": description="Thread: main" +# - op="outer-submit-3": description="Thread: main" +# - op="outer-submit-4": description="Thread: main"\ +# """ +# ) From 518dc04865c0666840da24699ec2f7e892787374 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 10:33:38 +0200 Subject: [PATCH 13/32] Revert all the changes --- sentry_sdk/integrations/threading.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index a4d8b2026e..6db6e91ea4 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -57,8 +57,10 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - isolation_scope = sentry_sdk.get_isolation_scope().fork() - current_scope = sentry_sdk.get_current_scope().fork() + # isolation_scope = sentry_sdk.get_isolation_scope().fork() + # current_scope = sentry_sdk.get_current_scope().fork() + isolation_scope = sentry_sdk.get_isolation_scope() + current_scope = sentry_sdk.get_current_scope() else: isolation_scope = None current_scope = None From 24ea43378a7aac0a3ad1d43a647685de925354c7 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 10:44:14 +0200 Subject: [PATCH 14/32] Trying somethign --- scripts/populate_tox/config.py | 1 + sentry_sdk/integrations/threading.py | 8 ++++---- tox.ini | 25 ++++++++++++++----------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index 0bacfcaa7b..d0cd355b5e 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -46,6 +46,7 @@ ], "<3.1": ["pytest-django<4.0"], ">=2.0": ["channels[daphne]"], + "py3.6": ["aiocontextvars"], }, }, "dramatiq": { diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index 6db6e91ea4..72200cd645 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -57,10 +57,10 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - # isolation_scope = sentry_sdk.get_isolation_scope().fork() - # current_scope = sentry_sdk.get_current_scope().fork() - isolation_scope = sentry_sdk.get_isolation_scope() - current_scope = sentry_sdk.get_current_scope() + isolation_scope = sentry_sdk.get_isolation_scope().fork() + current_scope = sentry_sdk.get_current_scope().fork() + # isolation_scope = sentry_sdk.get_isolation_scope() + # current_scope = sentry_sdk.get_current_scope() else: isolation_scope = None current_scope = None diff --git a/tox.ini b/tox.ini index c04691e2ac..bd0d58a6ba 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-04-08T10:33:11.499210+00:00 +# Last generated: 2025-04-14T08:42:32.681337+00:00 [tox] requires = @@ -157,7 +157,7 @@ envlist = {py3.6}-pymongo-v3.5.1 {py3.6,py3.10,py3.11}-pymongo-v3.13.0 {py3.6,py3.9,py3.10}-pymongo-v4.0.2 - {py3.9,py3.12,py3.13}-pymongo-v4.11.3 + {py3.9,py3.12,py3.13}-pymongo-v4.12.0 {py3.6}-redis_py_cluster_legacy-v1.3.6 {py3.6,py3.7}-redis_py_cluster_legacy-v2.0.0 @@ -175,11 +175,11 @@ envlist = {py3.8,py3.12,py3.13}-launchdarkly-v9.10.0 {py3.8,py3.12,py3.13}-openfeature-v0.7.5 - {py3.9,py3.12,py3.13}-openfeature-v0.8.0 + {py3.9,py3.12,py3.13}-openfeature-v0.8.1 {py3.7,py3.12,py3.13}-statsig-v0.55.3 {py3.7,py3.12,py3.13}-statsig-v0.56.0 - {py3.7,py3.12,py3.13}-statsig-v0.57.2 + {py3.7,py3.12,py3.13}-statsig-v0.57.3 {py3.8,py3.12,py3.13}-unleash-v6.0.1 {py3.8,py3.12,py3.13}-unleash-v6.1.0 @@ -202,7 +202,7 @@ envlist = {py3.8,py3.10,py3.11}-strawberry-v0.209.8 {py3.8,py3.11,py3.12}-strawberry-v0.227.7 {py3.8,py3.11,py3.12}-strawberry-v0.245.0 - {py3.9,py3.12,py3.13}-strawberry-v0.263.2 + {py3.9,py3.12,py3.13}-strawberry-v0.264.0 # ~~~ Network ~~~ @@ -210,6 +210,7 @@ envlist = {py3.7,py3.9,py3.10}-grpc-v1.44.0 {py3.7,py3.10,py3.11}-grpc-v1.58.3 {py3.9,py3.12,py3.13}-grpc-v1.71.0 + {py3.9,py3.12,py3.13}-grpc-v1.72.0rc1 # ~~~ Tasks ~~~ @@ -245,7 +246,7 @@ envlist = {py3.6,py3.9,py3.10}-starlette-v0.16.0 {py3.7,py3.10,py3.11}-starlette-v0.26.1 {py3.8,py3.11,py3.12}-starlette-v0.36.3 - {py3.9,py3.12,py3.13}-starlette-v0.46.1 + {py3.9,py3.12,py3.13}-starlette-v0.46.2 # ~~~ Web 2 ~~~ @@ -519,7 +520,7 @@ deps = pymongo-v3.5.1: pymongo==3.5.1 pymongo-v3.13.0: pymongo==3.13.0 pymongo-v4.0.2: pymongo==4.0.2 - pymongo-v4.11.3: pymongo==4.11.3 + pymongo-v4.12.0: pymongo==4.12.0 pymongo: mockupdb redis_py_cluster_legacy-v1.3.6: redis-py-cluster==1.3.6 @@ -538,11 +539,11 @@ deps = launchdarkly-v9.10.0: launchdarkly-server-sdk==9.10.0 openfeature-v0.7.5: openfeature-sdk==0.7.5 - openfeature-v0.8.0: openfeature-sdk==0.8.0 + openfeature-v0.8.1: openfeature-sdk==0.8.1 statsig-v0.55.3: statsig==0.55.3 statsig-v0.56.0: statsig==0.56.0 - statsig-v0.57.2: statsig==0.57.2 + statsig-v0.57.3: statsig==0.57.3 statsig: typing_extensions unleash-v6.0.1: UnleashClient==6.0.1 @@ -574,7 +575,7 @@ deps = strawberry-v0.209.8: strawberry-graphql[fastapi,flask]==0.209.8 strawberry-v0.227.7: strawberry-graphql[fastapi,flask]==0.227.7 strawberry-v0.245.0: strawberry-graphql[fastapi,flask]==0.245.0 - strawberry-v0.263.2: strawberry-graphql[fastapi,flask]==0.263.2 + strawberry-v0.264.0: strawberry-graphql[fastapi,flask]==0.264.0 strawberry: httpx strawberry-v0.209.8: pydantic<2.11 strawberry-v0.227.7: pydantic<2.11 @@ -586,6 +587,7 @@ deps = grpc-v1.44.0: grpcio==1.44.0 grpc-v1.58.3: grpcio==1.58.3 grpc-v1.71.0: grpcio==1.71.0 + grpc-v1.72.0rc1: grpcio==1.72.0rc1 grpc: protobuf grpc: mypy-protobuf grpc: types-protobuf @@ -644,6 +646,7 @@ deps = django-v4.2.20: channels[daphne] django-v5.0.9: channels[daphne] django-v5.2: channels[daphne] + py3.6-django: aiocontextvars flask-v1.1.4: flask==1.1.4 flask-v2.3.3: flask==2.3.3 @@ -657,7 +660,7 @@ deps = starlette-v0.16.0: starlette==0.16.0 starlette-v0.26.1: starlette==0.26.1 starlette-v0.36.3: starlette==0.36.3 - starlette-v0.46.1: starlette==0.46.1 + starlette-v0.46.2: starlette==0.46.2 starlette: pytest-asyncio starlette: python-multipart starlette: requests From 0d5b3fbc8adee1d2a34108156d4b9b73e5711540 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 10:45:03 +0200 Subject: [PATCH 15/32] Reenable tests --- .../integrations/threading/test_threading.py | 199 +++++++++--------- 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 38e6b3df14..4395891d62 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -1,7 +1,6 @@ import gc from concurrent import futures - -# from textwrap import dedent +from textwrap import dedent from threading import Thread import pytest @@ -176,101 +175,101 @@ def target(): assert t.run.__qualname__ == original_run.__qualname__ -# @pytest.mark.parametrize( -# "propagate_scope", -# (True, False), -# ids=["propagate_scope=True", "propagate_scope=False"], -# ) -# def test_scope_data_not_leaked_in_threads(sentry_init, propagate_scope): -# sentry_init( -# integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], -# ) - -# sentry_sdk.set_tag("initial_tag", "initial_value") -# initial_iso_scope = sentry_sdk.get_isolation_scope() - -# def do_some_work(): -# # check if we have the initial scope data propagated into the thread -# if propagate_scope: -# assert sentry_sdk.get_isolation_scope()._tags == { -# "initial_tag": "initial_value" -# } -# else: -# assert sentry_sdk.get_isolation_scope()._tags == {} - -# # change data in isolation scope in thread -# sentry_sdk.set_tag("thread_tag", "thread_value") - -# t = Thread(target=do_some_work) -# t.start() -# t.join() - -# # check if the initial scope data is not modified by the started thread -# assert initial_iso_scope._tags == { -# "initial_tag": "initial_value" -# }, "The isolation scope in the main thread should not be modified by the started thread." - - -# @pytest.mark.parametrize( -# "propagate_scope", -# (True, False), -# ids=["propagate_scope=True", "propagate_scope=False"], -# ) -# def test_spans_from_multiple_threads( -# sentry_init, capture_events, render_span_tree, propagate_scope -# ): -# sentry_init( -# traces_sample_rate=1.0, -# integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], -# ) -# events = capture_events() - -# def do_some_work(number): -# with sentry_sdk.start_span( -# op=f"inner-run-{number}", name=f"Thread: child-{number}" -# ): -# pass - -# threads = [] - -# with sentry_sdk.start_transaction(op="outer-trx"): -# for number in range(5): -# with sentry_sdk.start_span( -# op=f"outer-submit-{number}", name="Thread: main" -# ): -# t = Thread(target=do_some_work, args=(number,)) -# t.start() -# threads.append(t) - -# for t in threads: -# t.join() - -# (event,) = events -# if propagate_scope: -# assert render_span_tree(event) == dedent( -# """\ -# - op="outer-trx": description=null -# - op="outer-submit-0": description="Thread: main" -# - op="inner-run-0": description="Thread: child-0" -# - op="outer-submit-1": description="Thread: main" -# - op="inner-run-1": description="Thread: child-1" -# - op="outer-submit-2": description="Thread: main" -# - op="inner-run-2": description="Thread: child-2" -# - op="outer-submit-3": description="Thread: main" -# - op="inner-run-3": description="Thread: child-3" -# - op="outer-submit-4": description="Thread: main" -# - op="inner-run-4": description="Thread: child-4"\ -# """ -# ) - -# elif not propagate_scope: -# assert render_span_tree(event) == dedent( -# """\ -# - op="outer-trx": description=null -# - op="outer-submit-0": description="Thread: main" -# - op="outer-submit-1": description="Thread: main" -# - op="outer-submit-2": description="Thread: main" -# - op="outer-submit-3": description="Thread: main" -# - op="outer-submit-4": description="Thread: main"\ -# """ -# ) +@pytest.mark.parametrize( + "propagate_scope", + (True, False), + ids=["propagate_scope=True", "propagate_scope=False"], +) +def test_scope_data_not_leaked_in_threads(sentry_init, propagate_scope): + sentry_init( + integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], + ) + + sentry_sdk.set_tag("initial_tag", "initial_value") + initial_iso_scope = sentry_sdk.get_isolation_scope() + + def do_some_work(): + # check if we have the initial scope data propagated into the thread + if propagate_scope: + assert sentry_sdk.get_isolation_scope()._tags == { + "initial_tag": "initial_value" + } + else: + assert sentry_sdk.get_isolation_scope()._tags == {} + + # change data in isolation scope in thread + sentry_sdk.set_tag("thread_tag", "thread_value") + + t = Thread(target=do_some_work) + t.start() + t.join() + + # check if the initial scope data is not modified by the started thread + assert initial_iso_scope._tags == { + "initial_tag": "initial_value" + }, "The isolation scope in the main thread should not be modified by the started thread." + + +@pytest.mark.parametrize( + "propagate_scope", + (True, False), + ids=["propagate_scope=True", "propagate_scope=False"], +) +def test_spans_from_multiple_threads( + sentry_init, capture_events, render_span_tree, propagate_scope +): + sentry_init( + traces_sample_rate=1.0, + integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], + ) + events = capture_events() + + def do_some_work(number): + with sentry_sdk.start_span( + op=f"inner-run-{number}", name=f"Thread: child-{number}" + ): + pass + + threads = [] + + with sentry_sdk.start_transaction(op="outer-trx"): + for number in range(5): + with sentry_sdk.start_span( + op=f"outer-submit-{number}", name="Thread: main" + ): + t = Thread(target=do_some_work, args=(number,)) + t.start() + threads.append(t) + + for t in threads: + t.join() + + (event,) = events + if propagate_scope: + assert render_span_tree(event) == dedent( + """\ + - op="outer-trx": description=null + - op="outer-submit-0": description="Thread: main" + - op="inner-run-0": description="Thread: child-0" + - op="outer-submit-1": description="Thread: main" + - op="inner-run-1": description="Thread: child-1" + - op="outer-submit-2": description="Thread: main" + - op="inner-run-2": description="Thread: child-2" + - op="outer-submit-3": description="Thread: main" + - op="inner-run-3": description="Thread: child-3" + - op="outer-submit-4": description="Thread: main" + - op="inner-run-4": description="Thread: child-4"\ +""" + ) + + elif not propagate_scope: + assert render_span_tree(event) == dedent( + """\ + - op="outer-trx": description=null + - op="outer-submit-0": description="Thread: main" + - op="outer-submit-1": description="Thread: main" + - op="outer-submit-2": description="Thread: main" + - op="outer-submit-3": description="Thread: main" + - op="outer-submit-4": description="Thread: main"\ +""" + ) From d3585a32aff08370219dd67d26b6906bd5505a51 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 10:45:14 +0200 Subject: [PATCH 16/32] cleanup --- sentry_sdk/integrations/threading.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index 72200cd645..a4d8b2026e 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -59,8 +59,6 @@ def sentry_start(self, *a, **kw): if integration.propagate_scope: isolation_scope = sentry_sdk.get_isolation_scope().fork() current_scope = sentry_sdk.get_current_scope().fork() - # isolation_scope = sentry_sdk.get_isolation_scope() - # current_scope = sentry_sdk.get_current_scope() else: isolation_scope = None current_scope = None From 5bcba35ca6a43cc98464c46feb066f34607d0785 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 12:27:10 +0200 Subject: [PATCH 17/32] trying test forking --- tests/integrations/threading/test_threading.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 4395891d62..1cc0461d6b 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -175,6 +175,7 @@ def target(): assert t.run.__qualname__ == original_run.__qualname__ +@pytest.mark.forked @pytest.mark.parametrize( "propagate_scope", (True, False), @@ -210,6 +211,7 @@ def do_some_work(): }, "The isolation scope in the main thread should not be modified by the started thread." +@pytest.mark.forked @pytest.mark.parametrize( "propagate_scope", (True, False), From 4c2f7d93c1c125ef09516c8459eecc7e20c86b43 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 12:33:39 +0200 Subject: [PATCH 18/32] Removed some stuff again --- scripts/populate_tox/config.py | 2 +- sentry_sdk/integrations/threading.py | 2 ++ tests/integrations/threading/test_threading.py | 2 -- tox.ini | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index d0cd355b5e..cb5321d8de 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -46,7 +46,7 @@ ], "<3.1": ["pytest-django<4.0"], ">=2.0": ["channels[daphne]"], - "py3.6": ["aiocontextvars"], + # "py3.6": ["aiocontextvars"], }, }, "dramatiq": { diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index a4d8b2026e..d06fb37009 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -57,6 +57,8 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: + # isolation_scope = sentry_sdk.get_isolation_scope() + # current_scope = sentry_sdk.get_current_scope() isolation_scope = sentry_sdk.get_isolation_scope().fork() current_scope = sentry_sdk.get_current_scope().fork() else: diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 1cc0461d6b..4395891d62 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -175,7 +175,6 @@ def target(): assert t.run.__qualname__ == original_run.__qualname__ -@pytest.mark.forked @pytest.mark.parametrize( "propagate_scope", (True, False), @@ -211,7 +210,6 @@ def do_some_work(): }, "The isolation scope in the main thread should not be modified by the started thread." -@pytest.mark.forked @pytest.mark.parametrize( "propagate_scope", (True, False), diff --git a/tox.ini b/tox.ini index bd0d58a6ba..b8e6b7f01e 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-04-14T08:42:32.681337+00:00 +# Last generated: 2025-04-14T10:33:01.506897+00:00 [tox] requires = @@ -646,7 +646,6 @@ deps = django-v4.2.20: channels[daphne] django-v5.0.9: channels[daphne] django-v5.2: channels[daphne] - py3.6-django: aiocontextvars flask-v1.1.4: flask==1.1.4 flask-v2.3.3: flask==2.3.3 From 890640f1debf9765da8013f43c6609d4c1d9ebda Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 12:34:25 +0200 Subject: [PATCH 19/32] commented wrong lines --- sentry_sdk/integrations/threading.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index d06fb37009..b9c4ed25be 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -57,10 +57,10 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - # isolation_scope = sentry_sdk.get_isolation_scope() - # current_scope = sentry_sdk.get_current_scope() - isolation_scope = sentry_sdk.get_isolation_scope().fork() - current_scope = sentry_sdk.get_current_scope().fork() + isolation_scope = sentry_sdk.get_isolation_scope() + current_scope = sentry_sdk.get_current_scope() + # isolation_scope = sentry_sdk.get_isolation_scope().fork() + # current_scope = sentry_sdk.get_current_scope().fork() else: isolation_scope = None current_scope = None From 957d7bb4c17b38dccae3be4f92c1ded395c5bfab Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 12:48:34 +0200 Subject: [PATCH 20/32] does it work in django 3.0? --- sentry_sdk/integrations/__init__.py | 2 +- sentry_sdk/integrations/threading.py | 6 ++---- tox.ini | 27 +++++++++------------------ 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index 9bff264752..27cceab6f8 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -131,7 +131,7 @@ def iter_default_integrations(with_auto_enabling_integrations): "celery": (4, 4, 7), "chalice": (1, 16, 0), "clickhouse_driver": (0, 2, 0), - "django": (1, 8), + "django": (3, 0), "dramatiq": (1, 9), "falcon": (1, 4), "fastapi": (0, 79, 0), diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index b9c4ed25be..a4d8b2026e 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -57,10 +57,8 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - isolation_scope = sentry_sdk.get_isolation_scope() - current_scope = sentry_sdk.get_current_scope() - # isolation_scope = sentry_sdk.get_isolation_scope().fork() - # current_scope = sentry_sdk.get_current_scope().fork() + isolation_scope = sentry_sdk.get_isolation_scope().fork() + current_scope = sentry_sdk.get_current_scope().fork() else: isolation_scope = None current_scope = None diff --git a/tox.ini b/tox.ini index b8e6b7f01e..5c5aa2ce42 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-04-14T10:33:01.506897+00:00 +# Last generated: 2025-04-14T10:46:11.205400+00:00 [tox] requires = @@ -230,9 +230,7 @@ envlist = # ~~~ Web 1 ~~~ - {py3.6}-django-v1.11.9 - {py3.6,py3.7}-django-v1.11.29 - {py3.6,py3.8,py3.9}-django-v2.2.28 + {py3.6,py3.7,py3.8}-django-v3.0.9 {py3.6,py3.9,py3.10}-django-v3.2.25 {py3.8,py3.11,py3.12}-django-v4.2.20 {py3.10,py3.11,py3.12}-django-v5.0.9 @@ -614,9 +612,7 @@ deps = # ~~~ Web 1 ~~~ - django-v1.11.9: django==1.11.9 - django-v1.11.29: django==1.11.29 - django-v2.2.28: django==2.2.28 + django-v3.0.9: django==3.0.9 django-v3.2.25: django==3.2.25 django-v4.2.20: django==4.2.20 django-v5.0.9: django==5.0.9 @@ -625,23 +621,18 @@ deps = django: djangorestframework django: pytest-django django: Werkzeug + django-v3.0.9: pytest-asyncio django-v3.2.25: pytest-asyncio django-v4.2.20: pytest-asyncio django-v5.0.9: pytest-asyncio django-v5.2: pytest-asyncio - django-v2.2.28: six - django-v1.11.9: djangorestframework>=3.0,<4.0 - django-v1.11.9: Werkzeug<2.1.0 - django-v1.11.29: djangorestframework>=3.0,<4.0 - django-v1.11.29: Werkzeug<2.1.0 - django-v2.2.28: djangorestframework>=3.0,<4.0 - django-v2.2.28: Werkzeug<2.1.0 + django-v3.0.9: six + django-v3.0.9: djangorestframework>=3.0,<4.0 + django-v3.0.9: Werkzeug<2.1.0 django-v3.2.25: djangorestframework>=3.0,<4.0 django-v3.2.25: Werkzeug<2.1.0 - django-v1.11.9: pytest-django<4.0 - django-v1.11.29: pytest-django<4.0 - django-v2.2.28: pytest-django<4.0 - django-v2.2.28: channels[daphne] + django-v3.0.9: pytest-django<4.0 + django-v3.0.9: channels[daphne] django-v3.2.25: channels[daphne] django-v4.2.20: channels[daphne] django-v5.0.9: channels[daphne] From 07d44c910e32d074fd5322cc0a72ee2daaa446da Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 15:10:40 +0200 Subject: [PATCH 21/32] no channels in django 3.0 --- scripts/populate_tox/config.py | 2 +- tox.ini | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index cb5321d8de..359d0448c4 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -45,7 +45,7 @@ "Werkzeug<2.1.0", ], "<3.1": ["pytest-django<4.0"], - ">=2.0": ["channels[daphne]"], + ">=3.1": ["channels[daphne]"], # "py3.6": ["aiocontextvars"], }, }, diff --git a/tox.ini b/tox.ini index 5c5aa2ce42..b94959dc9f 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-04-14T10:46:11.205400+00:00 +# Last generated: 2025-04-14T13:09:46.859612+00:00 [tox] requires = @@ -632,7 +632,6 @@ deps = django-v3.2.25: djangorestframework>=3.0,<4.0 django-v3.2.25: Werkzeug<2.1.0 django-v3.0.9: pytest-django<4.0 - django-v3.0.9: channels[daphne] django-v3.2.25: channels[daphne] django-v4.2.20: channels[daphne] django-v5.0.9: channels[daphne] From 2a2cde194527e13222eafa2bbb773b216e600a41 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 15:57:39 +0200 Subject: [PATCH 22/32] trying without channels --- scripts/populate_tox/config.py | 2 +- tox.ini | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index 359d0448c4..c7c233a163 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -45,7 +45,7 @@ "Werkzeug<2.1.0", ], "<3.1": ["pytest-django<4.0"], - ">=3.1": ["channels[daphne]"], + "<3.0": ["channels[daphne]"], # "py3.6": ["aiocontextvars"], }, }, diff --git a/tox.ini b/tox.ini index b94959dc9f..b3737bfa82 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-04-14T13:09:46.859612+00:00 +# Last generated: 2025-04-14T13:55:07.190599+00:00 [tox] requires = @@ -632,10 +632,6 @@ deps = django-v3.2.25: djangorestframework>=3.0,<4.0 django-v3.2.25: Werkzeug<2.1.0 django-v3.0.9: pytest-django<4.0 - django-v3.2.25: channels[daphne] - django-v4.2.20: channels[daphne] - django-v5.0.9: channels[daphne] - django-v5.2: channels[daphne] flask-v1.1.4: flask==1.1.4 flask-v2.3.3: flask==2.3.3 From 8cd789b0d61350e15428a81f10463859d8578cbf Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 16:03:56 +0200 Subject: [PATCH 23/32] No channels for newer Django versions --- scripts/populate_tox/config.py | 3 +-- sentry_sdk/integrations/__init__.py | 2 +- tox.ini | 26 ++++++++++++++++++-------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index c7c233a163..0b2fe264b1 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -45,8 +45,7 @@ "Werkzeug<2.1.0", ], "<3.1": ["pytest-django<4.0"], - "<3.0": ["channels[daphne]"], - # "py3.6": ["aiocontextvars"], + ">=2.0,<3.0": ["channels[daphne]"], }, }, "dramatiq": { diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index 27cceab6f8..9bff264752 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -131,7 +131,7 @@ def iter_default_integrations(with_auto_enabling_integrations): "celery": (4, 4, 7), "chalice": (1, 16, 0), "clickhouse_driver": (0, 2, 0), - "django": (3, 0), + "django": (1, 8), "dramatiq": (1, 9), "falcon": (1, 4), "fastapi": (0, 79, 0), diff --git a/tox.ini b/tox.ini index b3737bfa82..63f14cb4ba 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-04-14T13:55:07.190599+00:00 +# Last generated: 2025-04-14T14:02:53.533465+00:00 [tox] requires = @@ -230,7 +230,9 @@ envlist = # ~~~ Web 1 ~~~ - {py3.6,py3.7,py3.8}-django-v3.0.9 + {py3.6}-django-v1.11.9 + {py3.6,py3.7}-django-v1.11.29 + {py3.6,py3.8,py3.9}-django-v2.2.28 {py3.6,py3.9,py3.10}-django-v3.2.25 {py3.8,py3.11,py3.12}-django-v4.2.20 {py3.10,py3.11,py3.12}-django-v5.0.9 @@ -612,7 +614,9 @@ deps = # ~~~ Web 1 ~~~ - django-v3.0.9: django==3.0.9 + django-v1.11.9: django==1.11.9 + django-v1.11.29: django==1.11.29 + django-v2.2.28: django==2.2.28 django-v3.2.25: django==3.2.25 django-v4.2.20: django==4.2.20 django-v5.0.9: django==5.0.9 @@ -621,17 +625,23 @@ deps = django: djangorestframework django: pytest-django django: Werkzeug - django-v3.0.9: pytest-asyncio django-v3.2.25: pytest-asyncio django-v4.2.20: pytest-asyncio django-v5.0.9: pytest-asyncio django-v5.2: pytest-asyncio - django-v3.0.9: six - django-v3.0.9: djangorestframework>=3.0,<4.0 - django-v3.0.9: Werkzeug<2.1.0 + django-v2.2.28: six + django-v1.11.9: djangorestframework>=3.0,<4.0 + django-v1.11.9: Werkzeug<2.1.0 + django-v1.11.29: djangorestframework>=3.0,<4.0 + django-v1.11.29: Werkzeug<2.1.0 + django-v2.2.28: djangorestframework>=3.0,<4.0 + django-v2.2.28: Werkzeug<2.1.0 django-v3.2.25: djangorestframework>=3.0,<4.0 django-v3.2.25: Werkzeug<2.1.0 - django-v3.0.9: pytest-django<4.0 + django-v1.11.9: pytest-django<4.0 + django-v1.11.29: pytest-django<4.0 + django-v2.2.28: pytest-django<4.0 + django-v2.2.28: channels[daphne] flask-v1.1.4: flask==1.1.4 flask-v2.3.3: flask==2.3.3 From 6982479882b00bd1b554a132065606985741d94e Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 14 Apr 2025 16:50:25 +0200 Subject: [PATCH 24/32] Warning --- scripts/populate_tox/config.py | 2 +- sentry_sdk/integrations/threading.py | 33 ++++++++++++++++++++++++++-- tox.ini | 6 ++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index 0b2fe264b1..0bacfcaa7b 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -45,7 +45,7 @@ "Werkzeug<2.1.0", ], "<3.1": ["pytest-django<4.0"], - ">=2.0,<3.0": ["channels[daphne]"], + ">=2.0": ["channels[daphne]"], }, }, "dramatiq": { diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index a4d8b2026e..5cb939afab 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -1,4 +1,5 @@ import sys +import warnings from functools import wraps from threading import Thread, current_thread @@ -57,8 +58,36 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - isolation_scope = sentry_sdk.get_isolation_scope().fork() - current_scope = sentry_sdk.get_current_scope().fork() + django_version = None + channels_version = None + try: + from django import VERSION as django_version # noqa: N811 + import channels + + channels_version = channels.__version__ + except ImportError: + pass + + if ( + sys.version_info <= (3, 8) + and channels_version is not None + and channels_version < "4.0.0" + and django_version is not None + and django_version >= (3, 0) + and django_version < (4, 0) + ): + warnings.warn( + "Sentry is not supported with Django channels 2.x and 3.x. when using Python 3.8 or older. " + "Please either upgrade to Django channels 4.x or later, or upgrade to Python 3.9 or later.", + DeprecationWarning, + stacklevel=2, + ) + isolation_scope = sentry_sdk.get_isolation_scope() + current_scope = sentry_sdk.get_current_scope() + + else: + isolation_scope = sentry_sdk.get_isolation_scope().fork() + current_scope = sentry_sdk.get_current_scope().fork() else: isolation_scope = None current_scope = None diff --git a/tox.ini b/tox.ini index 63f14cb4ba..ad0a7546c7 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-04-14T14:02:53.533465+00:00 +# Last generated: 2025-04-14T14:46:40.523627+00:00 [tox] requires = @@ -642,6 +642,10 @@ deps = django-v1.11.29: pytest-django<4.0 django-v2.2.28: pytest-django<4.0 django-v2.2.28: channels[daphne] + django-v3.2.25: channels[daphne] + django-v4.2.20: channels[daphne] + django-v5.0.9: channels[daphne] + django-v5.2: channels[daphne] flask-v1.1.4: flask==1.1.4 flask-v2.3.3: flask==2.3.3 From 3b85ef4c2bb433b166b5765518933bcfe0227ab5 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 15 Apr 2025 10:18:51 +0200 Subject: [PATCH 25/32] Better error message --- sentry_sdk/integrations/threading.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index 5cb939afab..b0b9cc5076 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -58,15 +58,14 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - django_version = None - channels_version = None try: from django import VERSION as django_version # noqa: N811 import channels channels_version = channels.__version__ except ImportError: - pass + django_version = None + channels_version = None if ( sys.version_info <= (3, 8) @@ -77,9 +76,10 @@ def sentry_start(self, *a, **kw): and django_version < (4, 0) ): warnings.warn( - "Sentry is not supported with Django channels 2.x and 3.x. when using Python 3.8 or older. " - "Please either upgrade to Django channels 4.x or later, or upgrade to Python 3.9 or later.", - DeprecationWarning, + "There is a known issue with Django channels 2.x and 3.x when using Python 3.8 or older. " + "(Async support is emulated using threads and some Sentry data may be leaked between those threads.) " + "Please either upgrade to Django channels 4.x or later, use Django's async features " + "available in Django 3.1 and later, or upgrade to Python 3.9 or later.", stacklevel=2, ) isolation_scope = sentry_sdk.get_isolation_scope() From e783af9c7263e600a52bff465407130f4832b1d7 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 15 Apr 2025 10:29:33 +0200 Subject: [PATCH 26/32] . --- sentry_sdk/integrations/threading.py | 4 ++-- tests/integrations/django/asgi/test_asgi.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index b0b9cc5076..89bc760ac7 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -78,8 +78,8 @@ def sentry_start(self, *a, **kw): warnings.warn( "There is a known issue with Django channels 2.x and 3.x when using Python 3.8 or older. " "(Async support is emulated using threads and some Sentry data may be leaked between those threads.) " - "Please either upgrade to Django channels 4.x or later, use Django's async features " - "available in Django 3.1 and later, or upgrade to Python 3.9 or later.", + "Please either upgrade to Django channels 4.0+, use Django's async features " + "available in Django 3.1+ instead of Django channels, or upgrade to Python 3.9+.", stacklevel=2, ) isolation_scope = sentry_sdk.get_isolation_scope() diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 063aed63ad..fee1e9d184 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -31,10 +31,17 @@ @pytest.mark.asyncio @pytest.mark.forked async def test_basic(sentry_init, capture_events, application): - sentry_init( - integrations=[DjangoIntegration()], - send_default_pii=True, - ) + if sys.version_info <= (3, 8): + with pytest.warns(DeprecationWarning): + sentry_init( + integrations=[DjangoIntegration()], + send_default_pii=True, + ) + else: + sentry_init( + integrations=[DjangoIntegration()], + send_default_pii=True, + ) events = capture_events() From 160e430d6c7ed13be40c81d8a9b69fa352c79dd1 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 15 Apr 2025 10:56:35 +0200 Subject: [PATCH 27/32] checking for warning --- tests/integrations/django/asgi/test_asgi.py | 27 ++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index fee1e9d184..f1903da1f9 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -31,23 +31,22 @@ @pytest.mark.asyncio @pytest.mark.forked async def test_basic(sentry_init, capture_events, application): - if sys.version_info <= (3, 8): - with pytest.warns(DeprecationWarning): - sentry_init( - integrations=[DjangoIntegration()], - send_default_pii=True, - ) - else: - sentry_init( - integrations=[DjangoIntegration()], - send_default_pii=True, - ) + sentry_init( + integrations=[DjangoIntegration()], + send_default_pii=True, + ) events = capture_events() - comm = HttpCommunicator(application, "GET", "/view-exc?test=query") - response = await comm.get_response() - await comm.wait() + if sys.version_info <= (3, 8): + with pytest.warns(DeprecationWarning): + comm = HttpCommunicator(application, "GET", "/view-exc?test=query") + response = await comm.get_response() + await comm.wait() + else: + comm = HttpCommunicator(application, "GET", "/view-exc?test=query") + response = await comm.get_response() + await comm.wait() assert response["status"] == 500 From 9aad0995a0e85cd1ba7c9ff640adaa6c9af3f48d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 15 Apr 2025 11:03:45 +0200 Subject: [PATCH 28/32] linting --- sentry_sdk/integrations/threading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index 89bc760ac7..f72fbc29e5 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -60,7 +60,7 @@ def sentry_start(self, *a, **kw): if integration.propagate_scope: try: from django import VERSION as django_version # noqa: N811 - import channels + import channels # type: ignore[import-not-found] channels_version = channels.__version__ except ImportError: From 0da1c2eee99702223f511c7ffb660f3493d0a693 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 15 Apr 2025 11:29:46 +0200 Subject: [PATCH 29/32] explanation --- tests/integrations/django/asgi/test_asgi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index f1903da1f9..308f7d8afc 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -39,7 +39,10 @@ async def test_basic(sentry_init, capture_events, application): events = capture_events() if sys.version_info <= (3, 8): - with pytest.warns(DeprecationWarning): + # We emit a UserWarning for channels 2.x and 3.x on Python 3.8 and older + # because the async support was not really good back then and there is a known issue. + # See the TreadingIntegration for details. + with pytest.warns(UserWarning): comm = HttpCommunicator(application, "GET", "/view-exc?test=query") response = await comm.get_response() await comm.wait() From 096579e4bf9b00578124b9dacae6069da5b4b1ea Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 15 Apr 2025 11:31:24 +0200 Subject: [PATCH 30/32] Revert tox to version in master --- tox.ini | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tox.ini b/tox.ini index ad0a7546c7..c04691e2ac 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-04-14T14:46:40.523627+00:00 +# Last generated: 2025-04-08T10:33:11.499210+00:00 [tox] requires = @@ -157,7 +157,7 @@ envlist = {py3.6}-pymongo-v3.5.1 {py3.6,py3.10,py3.11}-pymongo-v3.13.0 {py3.6,py3.9,py3.10}-pymongo-v4.0.2 - {py3.9,py3.12,py3.13}-pymongo-v4.12.0 + {py3.9,py3.12,py3.13}-pymongo-v4.11.3 {py3.6}-redis_py_cluster_legacy-v1.3.6 {py3.6,py3.7}-redis_py_cluster_legacy-v2.0.0 @@ -175,11 +175,11 @@ envlist = {py3.8,py3.12,py3.13}-launchdarkly-v9.10.0 {py3.8,py3.12,py3.13}-openfeature-v0.7.5 - {py3.9,py3.12,py3.13}-openfeature-v0.8.1 + {py3.9,py3.12,py3.13}-openfeature-v0.8.0 {py3.7,py3.12,py3.13}-statsig-v0.55.3 {py3.7,py3.12,py3.13}-statsig-v0.56.0 - {py3.7,py3.12,py3.13}-statsig-v0.57.3 + {py3.7,py3.12,py3.13}-statsig-v0.57.2 {py3.8,py3.12,py3.13}-unleash-v6.0.1 {py3.8,py3.12,py3.13}-unleash-v6.1.0 @@ -202,7 +202,7 @@ envlist = {py3.8,py3.10,py3.11}-strawberry-v0.209.8 {py3.8,py3.11,py3.12}-strawberry-v0.227.7 {py3.8,py3.11,py3.12}-strawberry-v0.245.0 - {py3.9,py3.12,py3.13}-strawberry-v0.264.0 + {py3.9,py3.12,py3.13}-strawberry-v0.263.2 # ~~~ Network ~~~ @@ -210,7 +210,6 @@ envlist = {py3.7,py3.9,py3.10}-grpc-v1.44.0 {py3.7,py3.10,py3.11}-grpc-v1.58.3 {py3.9,py3.12,py3.13}-grpc-v1.71.0 - {py3.9,py3.12,py3.13}-grpc-v1.72.0rc1 # ~~~ Tasks ~~~ @@ -246,7 +245,7 @@ envlist = {py3.6,py3.9,py3.10}-starlette-v0.16.0 {py3.7,py3.10,py3.11}-starlette-v0.26.1 {py3.8,py3.11,py3.12}-starlette-v0.36.3 - {py3.9,py3.12,py3.13}-starlette-v0.46.2 + {py3.9,py3.12,py3.13}-starlette-v0.46.1 # ~~~ Web 2 ~~~ @@ -520,7 +519,7 @@ deps = pymongo-v3.5.1: pymongo==3.5.1 pymongo-v3.13.0: pymongo==3.13.0 pymongo-v4.0.2: pymongo==4.0.2 - pymongo-v4.12.0: pymongo==4.12.0 + pymongo-v4.11.3: pymongo==4.11.3 pymongo: mockupdb redis_py_cluster_legacy-v1.3.6: redis-py-cluster==1.3.6 @@ -539,11 +538,11 @@ deps = launchdarkly-v9.10.0: launchdarkly-server-sdk==9.10.0 openfeature-v0.7.5: openfeature-sdk==0.7.5 - openfeature-v0.8.1: openfeature-sdk==0.8.1 + openfeature-v0.8.0: openfeature-sdk==0.8.0 statsig-v0.55.3: statsig==0.55.3 statsig-v0.56.0: statsig==0.56.0 - statsig-v0.57.3: statsig==0.57.3 + statsig-v0.57.2: statsig==0.57.2 statsig: typing_extensions unleash-v6.0.1: UnleashClient==6.0.1 @@ -575,7 +574,7 @@ deps = strawberry-v0.209.8: strawberry-graphql[fastapi,flask]==0.209.8 strawberry-v0.227.7: strawberry-graphql[fastapi,flask]==0.227.7 strawberry-v0.245.0: strawberry-graphql[fastapi,flask]==0.245.0 - strawberry-v0.264.0: strawberry-graphql[fastapi,flask]==0.264.0 + strawberry-v0.263.2: strawberry-graphql[fastapi,flask]==0.263.2 strawberry: httpx strawberry-v0.209.8: pydantic<2.11 strawberry-v0.227.7: pydantic<2.11 @@ -587,7 +586,6 @@ deps = grpc-v1.44.0: grpcio==1.44.0 grpc-v1.58.3: grpcio==1.58.3 grpc-v1.71.0: grpcio==1.71.0 - grpc-v1.72.0rc1: grpcio==1.72.0rc1 grpc: protobuf grpc: mypy-protobuf grpc: types-protobuf @@ -659,7 +657,7 @@ deps = starlette-v0.16.0: starlette==0.16.0 starlette-v0.26.1: starlette==0.26.1 starlette-v0.36.3: starlette==0.36.3 - starlette-v0.46.2: starlette==0.46.2 + starlette-v0.46.1: starlette==0.46.1 starlette: pytest-asyncio starlette: python-multipart starlette: requests From 815e930f49359a2fff24b0c6ed5ad27f02a89d4a Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 15 Apr 2025 12:05:44 +0200 Subject: [PATCH 31/32] Better checks and importing only once --- sentry_sdk/integrations/threading.py | 20 ++++++++++---------- tests/integrations/django/asgi/test_asgi.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index f72fbc29e5..9c99a8e896 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -50,6 +50,15 @@ def setup_once(): # type: () -> None old_start = Thread.start + try: + from django import VERSION as django_version # noqa: N811 + import channels # type: ignore[import-not-found] + + channels_version = channels.__version__ + except ImportError: + django_version = None + channels_version = None + @wraps(old_start) def sentry_start(self, *a, **kw): # type: (Thread, *Any, **Any) -> Any @@ -58,17 +67,8 @@ def sentry_start(self, *a, **kw): return old_start(self, *a, **kw) if integration.propagate_scope: - try: - from django import VERSION as django_version # noqa: N811 - import channels # type: ignore[import-not-found] - - channels_version = channels.__version__ - except ImportError: - django_version = None - channels_version = None - if ( - sys.version_info <= (3, 8) + sys.version_info < (3, 9) and channels_version is not None and channels_version < "4.0.0" and django_version is not None diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 308f7d8afc..2ce48c3486 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -38,7 +38,7 @@ async def test_basic(sentry_init, capture_events, application): events = capture_events() - if sys.version_info <= (3, 8): + if sys.version_info < (3, 9): # We emit a UserWarning for channels 2.x and 3.x on Python 3.8 and older # because the async support was not really good back then and there is a known issue. # See the TreadingIntegration for details. From 8aa5edfaf43d18414b6b722027df16150131889d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 15 Apr 2025 12:15:46 +0200 Subject: [PATCH 32/32] Better check --- tests/integrations/django/asgi/test_asgi.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 2ce48c3486..82eae30b1d 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -38,7 +38,14 @@ async def test_basic(sentry_init, capture_events, application): events = capture_events() - if sys.version_info < (3, 9): + import channels # type: ignore[import-not-found] + + if ( + sys.version_info < (3, 9) + and channels.__version__ < "4.0.0" + and django.VERSION >= (3, 0) + and django.VERSION < (4, 0) + ): # We emit a UserWarning for channels 2.x and 3.x on Python 3.8 and older # because the async support was not really good back then and there is a known issue. # See the TreadingIntegration for details.