From 0dedf5493af592d8f44b1a5b802f19e5de223be7 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Feb 2026 11:42:36 +0100 Subject: [PATCH 1/9] docs: New integration guide --- CONTRIBUTING.md | 117 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 753b169214..355264ea59 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -108,28 +108,111 @@ tox -p auto -o -e -- ## Adding a New Integration -1. Write the integration. - - Instrument all application instances by default. Prefer global signals/patches. - - Don't make the user pass anything to your integration for anything to work. Aim for zero configuration. - - Everybody monkeypatches. That means you don't need to feel bad about it. - - Make sure your changes don't break end user contracts. The SDK should never alter the expected behavior of the underlying library or framework from the user's perspective and it shouldn't have any side effects. - - Be defensive. Don't assume the code you're patching will stay the same forever, especially if it's an internal function. Allow for future variability whenever it makes sense. - - Avoid registering a new client or the like. The user drives the client, and the client owns integrations. - - Allow the user to turn off the integration by changing the client. Check `sentry_sdk.get_client().get_integration(MyIntegration)` from within your signal handlers to see if your integration is still active before you do anything impactful (such as sending an event). +### SDK Contract -2. Write tests. - - Consider the minimum versions supported, and document in `_MIN_VERSIONS` in `integrations/__init__.py`. - - Create a new folder in `tests/integrations/`, with an `__init__` file that skips the entire suite if the package is not installed. - - Add the test suite to the script generating our test matrix. See [`scripts/populate_tox/README.md`](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/README.md#add-a-new-test-suite). +The SDK runs as part of users' applications. Users do not expect: -3. Update package metadata. - - We use `extras_require` in `setup.py` to communicate minimum version requirements for integrations. People can use this in combination with tools like Poetry or Pipenv to detect conflicts between our supported versions and their used versions programmatically. +- their application to crash ([#4690](https://github.com/getsentry/sentry-python/issues/4690), [#4718](https://github.com/getsentry/sentry-python/issues/4718), [#4776](https://github.com/getsentry/sentry-python/issues/4776), [#4925](https://github.com/getsentry/sentry-python/issues/4925), [#4951](https://github.com/getsentry/sentry-python/issues/4951), [#4975](https://github.com/getsentry/sentry-python/issues/4975), [#5067](https://github.com/getsentry/sentry-python/issues/5067), [#5071](https://github.com/getsentry/sentry-python/issues/5071), [#5129](https://github.com/getsentry/sentry-python/issues/5129), [#5134](https://github.com/getsentry/sentry-python/issues/5134), [#5298](https://github.com/getsentry/sentry-python/issues/5298), [#5350](https://github.com/getsentry/sentry-python/issues/5350)). +- the SDK to mutate session cookies ([#4882](https://github.com/getsentry/sentry-python/issues/4882)). +- the SDK to swallow their exceptions ([#4853](https://github.com/getsentry/sentry-python/issues/4853)). +- their HTTP response streams to be consumed ([#4764](https://github.com/getsentry/sentry-python/issues/4764), [#4827](https://github.com/getsentry/sentry-python/issues/4827)). +- leaked file descriptors to eat their memory ([#5422](https://github.com/getsentry/sentry-python/issues/5422)). +- SDK-initiated database requests ([#5274](https://github.com/getsentry/sentry-python/issues/5274), [#5414](https://github.com/getsentry/sentry-python/issues/5414)). +- uWSGI performance degradation due to SDK patches ([#5107](https://github.com/getsentry/sentry-python/issues/5107)). +- change the signature of functions or coroutines ([#5072](https://github.com/getsentry/sentry-python/issues/5072)). - Do not set upper bounds on version requirements as people are often faster in adopting new versions of a web framework than we are in adding them to the test matrix or our package metadata. +So this means you should write the ugly code in the library to work around this? +Well, there's another consequence of running on thousands of applications. Maintenance burden is higher than for application code, because all code paths of the SDK are hit across the enormous variety of applications the SDK finds itself in. The diversity includes different CPython versions, permutations of package versions, and operating systems. +And once something you write is out there, you cannot remove it from the SDK without good reason. +This means that the SDK is not a playground for the inappropriate use of AI-assisted coding. -4. Write the [docs](https://github.com/getsentry/sentry-docs). Follow the structure of [existing integration docs](https://docs.sentry.io/platforms/python/integrations/). And, please **make sure to add your integration to the table in `python/integrations/index.md`** (people often forget this step 🙂). +Oh, and did I mention that bugs in old SDK versions can still come to haunt you? Even if you have patched them in the meantime. + +What's the concrete advice when writing a new integration? + +### Requirements + +1. Are you supporting a product feature? Then ensure that product expectations are clearly documented in https://develop.sentry.dev/sdk/telemetry/traces/modules/. + +2. Confirm that all attributes are defined in https://github.com/getsentry/sentry-conventions. + +3. Ensure that the **semantics** of the attribute are clear. If the attribute is not uniquely defined, do not add it. +- For instance, do not attach a request model to an agent invocation span. On the other hand, a default request model can be well-defined. + +### Code + +0. Document why you're patching or hooking into something. + - Even if it's just to say that you're creating and managing a certain type of span in the patch, that's valuable. + - It should be clear what span is created and exited where and which patches apply which attributes on a given span. + +1. Do you even need to add this attribute on this span? + - Be intentional with supporting product features. Only adding what's necessary, or **be very sure that your addition provides value**. + - Why? Decisions about what data lives on what types of spans are hard to undo, and limits future design space. + +2. Avoid setting arbitrary objects. + - In line with the point above, prefer using an include-list of valuable entries when setting a dictionary attribute. + - Otherwise, tests will break again and again ([#5454](https://github.com/getsentry/sentry-python/pull/5454), [#5471](https://github.com/getsentry/sentry-python/pull/5471)). + +3. Instrument all application instances by default. Prefer global signals/patches. + - Patching instances is just harder. Your patches may unexpectedly not apply to some instances, or unexpectedly be applied multiple times [!5195](https://github.com/getsentry/sentry-python/pull/5195). + +4. Don't make the user pass anything to your integration for anything to work. Aim for zero configuration. + - Users tend to only consult the documentation when something goes wrong. So the default values for integration options must lead to the best out-of-the-box experience for the majority of users. + +5. Re-use code, but only when it makes sense. + - Think about future evolution of the library and your integration. + - If you're patching two internal methods that are similar but will diverge with time, don't force a common patch. + - If the shared SDK logic will diverge for two patches, just don't force them through a common path in the first place. + - If your shared code path has a bunch of conditionals today or will have a ton of conditionals in the future, that's the sign to not stick to DRY. + +6. Be explicit + - You're developing against a library, and that library uses specific types. + - If you use `hasattr()` or `getattr()` to gate logic, you must verify the code path for all types that have this attribute. And Python has duck-typing, so good luck. + - If you use `type().__name__` to gate logic, you must verify the behavior for all types with a given name. And Python has duck-typing, so good luck. + - So just use `isinstance()` to save us a headache. + +7. Heuristics will bite you later. + - If something you write is best-effort, make sure there are no alternatives ([#4980](https://github.com/getsentry/sentry-python/issues/4980)). + +8. Obsess about the unhappy path. + - Users are interested in seeing what went wrong when something doesn't work. If the code in the `catch` block is garbage, that's a problem. + - Let exceptions bubble-up as far as possible when reporting unhandled exceptions. + - Preserve the user's original exception. Python chains exceptions when code in a `catch` block throws, so if a `catch` block in the SDK throws, the SDK exception takes the foreground ([#5188](https://github.com/getsentry/sentry-python/issues/5188)). + - Please don't report exceptions that are caught further up in the library's call chain as unhandled ([#5232](https://github.com/getsentry/sentry-python/issues/5232), [#5473](https://github.com/getsentry/sentry-python/issues/5473)). + +9. Make sure your changes don't break end user contracts. The SDK should never alter the expected behavior of the underlying library or framework from the user's perspective and it shouldn't have any side effects. + +10. Be defensive, but don't add dead code. + - Don't assume the code you're patching will stay the same forever, especially if it's an internal function. Allow for future variability whenever it makes sense. + - Dead code adds cognitive overhead when reasoning about code. + - Insidiously, dead code paths can trigger when libraries evolve. Since they tend to not be tested, they are buggy ([#5277](https://github.com/getsentry/sentry-python/issues/5277)). + +11. Write tests, but don't write mocks. + - You'd be surprised how many tests assert the wrong thing ([#5404](https://github.com/getsentry/sentry-python/pull/5404), [#5403](https://github.com/getsentry/sentry-python/pull/5403)). + - Others packaging the SDK seem to run our test suite, so don't write racy or other environment-dependent tests ([#4878](https://github.com/getsentry/sentry-python/issues/4878)). + - Actually look at the console output ([#4723](https://github.com/getsentry/sentry-python/issues/4723)). + - Don't test unreachable states, or your tests will be removed ([!5412](https://github.com/getsentry/sentry-python/pull/5412)). + - Don't call private SDK stuff directly, just use the patched library in a way that triggers the patch ([#5437](https://github.com/getsentry/sentry-python/pull/5437)). + - Don't write tests that are always skipped, that's just silly ([!5338](https://github.com/getsentry/sentry-python/pull/5338)). + - Mocks are _very expensive_ to maintain, particularly when testing patches for fast-moving libraries ([#5126](https://github.com/getsentry/sentry-python/pull/5126)). + - Consider the minimum versions supported, and document in `_MIN_VERSIONS` in `integrations/__init__.py`. + - Create a new folder in `tests/integrations/`, with an `__init__` file that skips the entire suite if the package is not installed. + - Add the test suite to the script generating our test matrix. See [`scripts/populate_tox/README.md`](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/README.md#add-a-new-test-suite). + +12. Be careful patching decorators + - Does the library's decorator apply to sync or async functions ([#5415](https://github.com/getsentry/sentry-python/pull/5415))? + - Some decorators can be applied to classes and functions, and both with and without arguments. Make sure you handle all applicable cases ([#5225](https://github.com/getsentry/sentry-python/issues/5225)). + +13. Avoid registering a new client or the like. The user drives the client, and the client owns integrations. + +14. Allow the user to turn off the integration by changing the client. Check `sentry_sdk.get_client().get_integration(MyIntegration)` from within your signal handlers to see if your integration is still active before you do anything impactful (such as sending an event). + +### Document + +1. Write the [docs](https://github.com/getsentry/sentry-docs). Follow the structure of [existing integration docs](https://docs.sentry.io/platforms/python/integrations/). And, please **make sure to add your integration to the table in `python/integrations/index.md`** (people often forget this step 🙂). + +2. Merge docs after new version has been released. The docs are built and deployed after each merge, so your changes should go live in a few minutes. -5. Merge docs after new version has been released. The docs are built and deployed after each merge, so your changes should go live in a few minutes. ## Releasing a New Version From 237d87d77c2148f01f1b6643949cf8908dd48a08 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Feb 2026 11:46:57 +0100 Subject: [PATCH 2/9] . --- CONTRIBUTING.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 355264ea59..b4a70e9e01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,7 +112,7 @@ tox -p auto -o -e -- The SDK runs as part of users' applications. Users do not expect: -- their application to crash ([#4690](https://github.com/getsentry/sentry-python/issues/4690), [#4718](https://github.com/getsentry/sentry-python/issues/4718), [#4776](https://github.com/getsentry/sentry-python/issues/4776), [#4925](https://github.com/getsentry/sentry-python/issues/4925), [#4951](https://github.com/getsentry/sentry-python/issues/4951), [#4975](https://github.com/getsentry/sentry-python/issues/4975), [#5067](https://github.com/getsentry/sentry-python/issues/5067), [#5071](https://github.com/getsentry/sentry-python/issues/5071), [#5129](https://github.com/getsentry/sentry-python/issues/5129), [#5134](https://github.com/getsentry/sentry-python/issues/5134), [#5298](https://github.com/getsentry/sentry-python/issues/5298), [#5350](https://github.com/getsentry/sentry-python/issues/5350)). +- their application to crash ([#4690](https://github.com/getsentry/sentry-python/issues/4690), [#4718](https://github.com/getsentry/sentry-python/issues/4718), [#4776](https://github.com/getsentry/sentry-python/issues/4776), [#4925](https://github.com/getsentry/sentry-python/issues/4925), [#4951](https://github.com/getsentry/sentry-python/issues/4951), [#4975](https://github.com/getsentry/sentry-python/issues/4975), [#5067](https://github.com/getsentry/sentry-python/issues/5067), [#5071](https://github.com/getsentry/sentry-python/issues/5071), [#5129](https://github.com/getsentry/sentry-python/issues/5129), [#5134](https://github.com/getsentry/sentry-python/issues/5134), [#5277](https://github.com/getsentry/sentry-python/issues/5277), [#5298](https://github.com/getsentry/sentry-python/issues/5298), [#5350](https://github.com/getsentry/sentry-python/issues/5350)). - the SDK to mutate session cookies ([#4882](https://github.com/getsentry/sentry-python/issues/4882)). - the SDK to swallow their exceptions ([#4853](https://github.com/getsentry/sentry-python/issues/4853)). - their HTTP response streams to be consumed ([#4764](https://github.com/getsentry/sentry-python/issues/4764), [#4827](https://github.com/getsentry/sentry-python/issues/4827)). @@ -146,12 +146,10 @@ What's the concrete advice when writing a new integration? - It should be clear what span is created and exited where and which patches apply which attributes on a given span. 1. Do you even need to add this attribute on this span? - - Be intentional with supporting product features. Only adding what's necessary, or **be very sure that your addition provides value**. - - Why? Decisions about what data lives on what types of spans are hard to undo, and limits future design space. + - Be intentional with supporting product features. Only adding what's necessary, or **be very sure that your addition provides value**. Decisions about what data lives on what types of spans are hard to undo, and limits future design space. 2. Avoid setting arbitrary objects. - - In line with the point above, prefer using an include-list of valuable entries when setting a dictionary attribute. - - Otherwise, tests will break again and again ([#5454](https://github.com/getsentry/sentry-python/pull/5454), [#5471](https://github.com/getsentry/sentry-python/pull/5471)). + - In line with the point above, prefer using an include-list of valuable entries when setting a dictionary attribute. Otherwise, tests will break again and again ([#5454](https://github.com/getsentry/sentry-python/pull/5454), [#5471](https://github.com/getsentry/sentry-python/pull/5471)). 3. Instrument all application instances by default. Prefer global signals/patches. - Patching instances is just harder. Your patches may unexpectedly not apply to some instances, or unexpectedly be applied multiple times [!5195](https://github.com/getsentry/sentry-python/pull/5195). @@ -184,8 +182,7 @@ What's the concrete advice when writing a new integration? 10. Be defensive, but don't add dead code. - Don't assume the code you're patching will stay the same forever, especially if it's an internal function. Allow for future variability whenever it makes sense. - - Dead code adds cognitive overhead when reasoning about code. - - Insidiously, dead code paths can trigger when libraries evolve. Since they tend to not be tested, they are buggy ([#5277](https://github.com/getsentry/sentry-python/issues/5277)). + - Dead code adds cognitive overhead when reasoning about code, so don't account for impossible scenarios. 11. Write tests, but don't write mocks. - You'd be surprised how many tests assert the wrong thing ([#5404](https://github.com/getsentry/sentry-python/pull/5404), [#5403](https://github.com/getsentry/sentry-python/pull/5403)). From 578371bec4f9691f4f95a024c5fbdd4ee12c256a Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Feb 2026 11:47:47 +0100 Subject: [PATCH 3/9] . --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4a70e9e01..68e620e259 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -152,7 +152,7 @@ What's the concrete advice when writing a new integration? - In line with the point above, prefer using an include-list of valuable entries when setting a dictionary attribute. Otherwise, tests will break again and again ([#5454](https://github.com/getsentry/sentry-python/pull/5454), [#5471](https://github.com/getsentry/sentry-python/pull/5471)). 3. Instrument all application instances by default. Prefer global signals/patches. - - Patching instances is just harder. Your patches may unexpectedly not apply to some instances, or unexpectedly be applied multiple times [!5195](https://github.com/getsentry/sentry-python/pull/5195). + - Patching instances is just harder. Your patches may unexpectedly not apply to some instances, or unexpectedly be applied multiple times ([!5195](https://github.com/getsentry/sentry-python/pull/5195)). 4. Don't make the user pass anything to your integration for anything to work. Aim for zero configuration. - Users tend to only consult the documentation when something goes wrong. So the default values for integration options must lead to the best out-of-the-box experience for the majority of users. From aa72f975d0665f1eca97418bebfcd40b2a660e11 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 19 Feb 2026 12:00:12 +0100 Subject: [PATCH 4/9] remove snarky comments --- CONTRIBUTING.md | 44 +++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68e620e259..9dd5b6bf29 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -110,23 +110,11 @@ tox -p auto -o -e -- ### SDK Contract -The SDK runs as part of users' applications. Users do not expect: - -- their application to crash ([#4690](https://github.com/getsentry/sentry-python/issues/4690), [#4718](https://github.com/getsentry/sentry-python/issues/4718), [#4776](https://github.com/getsentry/sentry-python/issues/4776), [#4925](https://github.com/getsentry/sentry-python/issues/4925), [#4951](https://github.com/getsentry/sentry-python/issues/4951), [#4975](https://github.com/getsentry/sentry-python/issues/4975), [#5067](https://github.com/getsentry/sentry-python/issues/5067), [#5071](https://github.com/getsentry/sentry-python/issues/5071), [#5129](https://github.com/getsentry/sentry-python/issues/5129), [#5134](https://github.com/getsentry/sentry-python/issues/5134), [#5277](https://github.com/getsentry/sentry-python/issues/5277), [#5298](https://github.com/getsentry/sentry-python/issues/5298), [#5350](https://github.com/getsentry/sentry-python/issues/5350)). -- the SDK to mutate session cookies ([#4882](https://github.com/getsentry/sentry-python/issues/4882)). -- the SDK to swallow their exceptions ([#4853](https://github.com/getsentry/sentry-python/issues/4853)). -- their HTTP response streams to be consumed ([#4764](https://github.com/getsentry/sentry-python/issues/4764), [#4827](https://github.com/getsentry/sentry-python/issues/4827)). -- leaked file descriptors to eat their memory ([#5422](https://github.com/getsentry/sentry-python/issues/5422)). -- SDK-initiated database requests ([#5274](https://github.com/getsentry/sentry-python/issues/5274), [#5414](https://github.com/getsentry/sentry-python/issues/5414)). -- uWSGI performance degradation due to SDK patches ([#5107](https://github.com/getsentry/sentry-python/issues/5107)). -- change the signature of functions or coroutines ([#5072](https://github.com/getsentry/sentry-python/issues/5072)). +The SDK runs as part of users' applications. Users do not expect their application to crash, the SDK to mutate object references, the SDK to swallow their exceptions, leaked file descriptors to eat their memory, SDK-initiated database requests, or the SDK to alter the signature of functions or coroutines. So this means you should write the ugly code in the library to work around this? Well, there's another consequence of running on thousands of applications. Maintenance burden is higher than for application code, because all code paths of the SDK are hit across the enormous variety of applications the SDK finds itself in. The diversity includes different CPython versions, permutations of package versions, and operating systems. And once something you write is out there, you cannot remove it from the SDK without good reason. -This means that the SDK is not a playground for the inappropriate use of AI-assisted coding. - -Oh, and did I mention that bugs in old SDK versions can still come to haunt you? Even if you have patched them in the meantime. What's the concrete advice when writing a new integration? @@ -149,10 +137,10 @@ What's the concrete advice when writing a new integration? - Be intentional with supporting product features. Only adding what's necessary, or **be very sure that your addition provides value**. Decisions about what data lives on what types of spans are hard to undo, and limits future design space. 2. Avoid setting arbitrary objects. - - In line with the point above, prefer using an include-list of valuable entries when setting a dictionary attribute. Otherwise, tests will break again and again ([#5454](https://github.com/getsentry/sentry-python/pull/5454), [#5471](https://github.com/getsentry/sentry-python/pull/5471)). + - In line with the point above, prefer using an include-list of valuable entries when setting a dictionary attribute. Otherwise, tests will break again and again. 3. Instrument all application instances by default. Prefer global signals/patches. - - Patching instances is just harder. Your patches may unexpectedly not apply to some instances, or unexpectedly be applied multiple times ([!5195](https://github.com/getsentry/sentry-python/pull/5195)). + - Patching instances is just harder. Your patches may unexpectedly not apply to some instances, or unexpectedly be applied multiple times. 4. Don't make the user pass anything to your integration for anything to work. Aim for zero configuration. - Users tend to only consult the documentation when something goes wrong. So the default values for integration options must lead to the best out-of-the-box experience for the majority of users. @@ -165,18 +153,18 @@ What's the concrete advice when writing a new integration? 6. Be explicit - You're developing against a library, and that library uses specific types. - - If you use `hasattr()` or `getattr()` to gate logic, you must verify the code path for all types that have this attribute. And Python has duck-typing, so good luck. - - If you use `type().__name__` to gate logic, you must verify the behavior for all types with a given name. And Python has duck-typing, so good luck. - - So just use `isinstance()` to save us a headache. + - If you use `hasattr()` or `getattr()` to gate logic, you must verify the code path for all types that have this attribute (and Python has duck typing). + - If you use `type().__name__` to gate logic, you must verify the behavior for all types with a given name (and Python has duck typing). + - So just use `isinstance()`. 7. Heuristics will bite you later. - - If something you write is best-effort, make sure there are no alternatives ([#4980](https://github.com/getsentry/sentry-python/issues/4980)). + - If something you write is best-effort, make sure there are no alternatives. 8. Obsess about the unhappy path. - Users are interested in seeing what went wrong when something doesn't work. If the code in the `catch` block is garbage, that's a problem. - Let exceptions bubble-up as far as possible when reporting unhandled exceptions. - Preserve the user's original exception. Python chains exceptions when code in a `catch` block throws, so if a `catch` block in the SDK throws, the SDK exception takes the foreground ([#5188](https://github.com/getsentry/sentry-python/issues/5188)). - - Please don't report exceptions that are caught further up in the library's call chain as unhandled ([#5232](https://github.com/getsentry/sentry-python/issues/5232), [#5473](https://github.com/getsentry/sentry-python/issues/5473)). + - Please don't report exceptions that are caught further up in the library's call chain as unhandled. 9. Make sure your changes don't break end user contracts. The SDK should never alter the expected behavior of the underlying library or framework from the user's perspective and it shouldn't have any side effects. @@ -185,20 +173,18 @@ What's the concrete advice when writing a new integration? - Dead code adds cognitive overhead when reasoning about code, so don't account for impossible scenarios. 11. Write tests, but don't write mocks. - - You'd be surprised how many tests assert the wrong thing ([#5404](https://github.com/getsentry/sentry-python/pull/5404), [#5403](https://github.com/getsentry/sentry-python/pull/5403)). - - Others packaging the SDK seem to run our test suite, so don't write racy or other environment-dependent tests ([#4878](https://github.com/getsentry/sentry-python/issues/4878)). - - Actually look at the console output ([#4723](https://github.com/getsentry/sentry-python/issues/4723)). - - Don't test unreachable states, or your tests will be removed ([!5412](https://github.com/getsentry/sentry-python/pull/5412)). - - Don't call private SDK stuff directly, just use the patched library in a way that triggers the patch ([#5437](https://github.com/getsentry/sentry-python/pull/5437)). - - Don't write tests that are always skipped, that's just silly ([!5338](https://github.com/getsentry/sentry-python/pull/5338)). - - Mocks are _very expensive_ to maintain, particularly when testing patches for fast-moving libraries ([#5126](https://github.com/getsentry/sentry-python/pull/5126)). + - You'd be surprised how many tests assert the wrong thing. + - Others packaging the SDK seem to run our test suite, so don't write racy or other environment-dependent tests. + - Don't test unreachable states, or your tests will be removed. + - Don't call private SDK stuff directly, just use the patched library in a way that triggers the patch. + - Mocks are _very expensive_ to maintain, particularly when testing patches for fast-moving libraries. - Consider the minimum versions supported, and document in `_MIN_VERSIONS` in `integrations/__init__.py`. - Create a new folder in `tests/integrations/`, with an `__init__` file that skips the entire suite if the package is not installed. - Add the test suite to the script generating our test matrix. See [`scripts/populate_tox/README.md`](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/README.md#add-a-new-test-suite). 12. Be careful patching decorators - - Does the library's decorator apply to sync or async functions ([#5415](https://github.com/getsentry/sentry-python/pull/5415))? - - Some decorators can be applied to classes and functions, and both with and without arguments. Make sure you handle all applicable cases ([#5225](https://github.com/getsentry/sentry-python/issues/5225)). + - Does the library's decorator apply to sync or async functions? + - Some decorators can be applied to classes and functions, and both with and without arguments. Make sure you handle all applicable cases. 13. Avoid registering a new client or the like. The user drives the client, and the client owns integrations. From 55cf5ec64b6e1df86e75a7ddd8c55eafa066f527 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 19 Feb 2026 12:01:02 +0100 Subject: [PATCH 5/9] typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9dd5b6bf29..157fbdb959 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -134,7 +134,7 @@ What's the concrete advice when writing a new integration? - It should be clear what span is created and exited where and which patches apply which attributes on a given span. 1. Do you even need to add this attribute on this span? - - Be intentional with supporting product features. Only adding what's necessary, or **be very sure that your addition provides value**. Decisions about what data lives on what types of spans are hard to undo, and limits future design space. + - Be intentional with supporting product features. Only add what's necessary, or **be very sure that your addition provides value**. Decisions about what data lives on what types of spans are hard to undo, and limits future design space. 2. Avoid setting arbitrary objects. - In line with the point above, prefer using an include-list of valuable entries when setting a dictionary attribute. Otherwise, tests will break again and again. From bc41a1db7651a1338fd827015068bb9ecfb0452d Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 19 Feb 2026 12:12:10 +0100 Subject: [PATCH 6/9] . --- CONTRIBUTING.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 157fbdb959..61f1a2e4f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -173,9 +173,6 @@ What's the concrete advice when writing a new integration? - Dead code adds cognitive overhead when reasoning about code, so don't account for impossible scenarios. 11. Write tests, but don't write mocks. - - You'd be surprised how many tests assert the wrong thing. - - Others packaging the SDK seem to run our test suite, so don't write racy or other environment-dependent tests. - - Don't test unreachable states, or your tests will be removed. - Don't call private SDK stuff directly, just use the patched library in a way that triggers the patch. - Mocks are _very expensive_ to maintain, particularly when testing patches for fast-moving libraries. - Consider the minimum versions supported, and document in `_MIN_VERSIONS` in `integrations/__init__.py`. From ccfeaadfcebdc8fbbcecfc3cee9a867a2716510f Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 19 Feb 2026 12:17:52 +0100 Subject: [PATCH 7/9] reference breaking changes doc --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61f1a2e4f4..b74a1abf83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,7 +114,7 @@ The SDK runs as part of users' applications. Users do not expect their applicati So this means you should write the ugly code in the library to work around this? Well, there's another consequence of running on thousands of applications. Maintenance burden is higher than for application code, because all code paths of the SDK are hit across the enormous variety of applications the SDK finds itself in. The diversity includes different CPython versions, permutations of package versions, and operating systems. -And once something you write is out there, you cannot remove it from the SDK without good reason. +And once something you write is out there, you cannot change or remove it from the SDK without good reason (https://develop.sentry.dev/sdk/processes/breaking_changes/#introducing-breaking-changes). What's the concrete advice when writing a new integration? From 70baeb04ae9c4809efc086778ff28505e01be4a3 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 19 Feb 2026 12:20:05 +0100 Subject: [PATCH 8/9] specify telemetry types for attributes --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b74a1abf83..9fefb5e968 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -122,7 +122,7 @@ What's the concrete advice when writing a new integration? 1. Are you supporting a product feature? Then ensure that product expectations are clearly documented in https://develop.sentry.dev/sdk/telemetry/traces/modules/. -2. Confirm that all attributes are defined in https://github.com/getsentry/sentry-conventions. +2. Confirm that all span, breadcrumb, log or metric attributes are defined in https://github.com/getsentry/sentry-conventions. 3. Ensure that the **semantics** of the attribute are clear. If the attribute is not uniquely defined, do not add it. - For instance, do not attach a request model to an agent invocation span. On the other hand, a default request model can be well-defined. From 31cc6210f3933b2b5a4cfb5308a383352681e4ad Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 19 Feb 2026 13:34:25 +0100 Subject: [PATCH 9/9] review feedback --- CONTRIBUTING.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9fefb5e968..cf35461b0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,7 +120,7 @@ What's the concrete advice when writing a new integration? ### Requirements -1. Are you supporting a product feature? Then ensure that product expectations are clearly documented in https://develop.sentry.dev/sdk/telemetry/traces/modules/. +1. Are you supporting a product feature? Then ensure that product expectations are clearly documented in the [develop docs](https://develop.sentry.dev/sdk/). Requirements for a given Insight Module must be available under https://develop.sentry.dev/sdk/telemetry/traces/modules/. 2. Confirm that all span, breadcrumb, log or metric attributes are defined in https://github.com/getsentry/sentry-conventions. @@ -151,28 +151,30 @@ What's the concrete advice when writing a new integration? - If the shared SDK logic will diverge for two patches, just don't force them through a common path in the first place. - If your shared code path has a bunch of conditionals today or will have a ton of conditionals in the future, that's the sign to not stick to DRY. -6. Be explicit +6. Be explicit. - You're developing against a library, and that library uses specific types. - If you use `hasattr()` or `getattr()` to gate logic, you must verify the code path for all types that have this attribute (and Python has duck typing). - If you use `type().__name__` to gate logic, you must verify the behavior for all types with a given name (and Python has duck typing). - So just use `isinstance()`. 7. Heuristics will bite you later. - - If something you write is best-effort, make sure there are no alternatives. + - If something you write is best-effort, make sure there are no better alternatives. 8. Obsess about the unhappy path. - - Users are interested in seeing what went wrong when something doesn't work. If the code in the `catch` block is garbage, that's a problem. + - Users are interested in seeing what went wrong when something doesn't work. The code in the `except` block should not be an afterthought. - Let exceptions bubble-up as far as possible when reporting unhandled exceptions. - - Preserve the user's original exception. Python chains exceptions when code in a `catch` block throws, so if a `catch` block in the SDK throws, the SDK exception takes the foreground ([#5188](https://github.com/getsentry/sentry-python/issues/5188)). + - Preserve the user's original exception. Python chains exceptions when code in a `except` block throws, so if a `except` block in the SDK throws, the SDK exception takes the foreground ([#5188](https://github.com/getsentry/sentry-python/issues/5188)). - Please don't report exceptions that are caught further up in the library's call chain as unhandled. 9. Make sure your changes don't break end user contracts. The SDK should never alter the expected behavior of the underlying library or framework from the user's perspective and it shouldn't have any side effects. + - For example, it shouldn't open new connections or evaluate lazy queries early. 10. Be defensive, but don't add dead code. - Don't assume the code you're patching will stay the same forever, especially if it's an internal function. Allow for future variability whenever it makes sense. - Dead code adds cognitive overhead when reasoning about code, so don't account for impossible scenarios. 11. Write tests, but don't write mocks. + - Aim for end-to-end tests, not unit tests. - Don't call private SDK stuff directly, just use the patched library in a way that triggers the patch. - Mocks are _very expensive_ to maintain, particularly when testing patches for fast-moving libraries. - Consider the minimum versions supported, and document in `_MIN_VERSIONS` in `integrations/__init__.py`. @@ -181,11 +183,11 @@ What's the concrete advice when writing a new integration? 12. Be careful patching decorators - Does the library's decorator apply to sync or async functions? - - Some decorators can be applied to classes and functions, and both with and without arguments. Make sure you handle all applicable cases. + - Some decorators can be applied to classes and functions, and both with and without arguments. Make sure you handle and test all applicable cases. 13. Avoid registering a new client or the like. The user drives the client, and the client owns integrations. -14. Allow the user to turn off the integration by changing the client. Check `sentry_sdk.get_client().get_integration(MyIntegration)` from within your signal handlers to see if your integration is still active before you do anything impactful (such as sending an event). +14. Allow the user to turn off the integration by changing the client. Check `sentry_sdk.get_client().get_integration(MyIntegration)` from within your signal handlers or patches to see if your integration is still active before you do anything impactful (such as sending an event). If it's not active, the patch my be no-op. ### Document