Migrate from 1.x to 2.x

Learn about migrating from sentry-python 1.x to 2.x

This guide describes the common patterns involved in migrating to version 2.x of the sentry-python SDK.

While the top-level API stays the same for the most part, there's a number of deprecations and breaking changes in 2.0. This guide captures the top changes that will affect most users and the corresponding updates they'll need to make in order to migrate. For the full list of changes, check out the detailed migration guide in the repository.

Sentry Python SDK 2.0 now only supports Python 3.6 and higher. If you're on Python 2.7 or 3.5 and lower, you'll need to stay on 1.x.

The profiles_sample_rate and profiler_mode options are not experimental anymore and can be used directly. Setting them via _experiments is deprecated and will be removed in the future.

The deprecated with_locals and request_bodies options have been removed in favor of their new counterparts: include_local_variables and max_request_body_size, respectively.

These are all potential changes you need to apply to your init:

import sentry_sdk


    # Replace with_locals
-   with_locals=False,
+   include_local_variables=False,

    # Replace request_bodies
-   request_bodies="always",
+   max_request_body_size="always",

    # Replace _experiments["profiles_sample_rate"]
    # Replace _experiments["profiler_mode"]
-   _experiments={
-       "profiles_sample_rate": 1.0,
-       "profiler_mode": "thread",
-   },
+   profiles_sample_rate=1.0,
+   profiler_mode="thread",

The signature for the experimental Sentry Metrics callback function set with before_emit_metric has changed from before_emit_callback(key, tags) to before_emit_callback(key, value, unit, tags).

The API function last_event_id() was removed. The last event ID is still returned by capture_event(), capture_exception(), and capture_message().

Hub-based APIs as well as some scope-based APIs (like configure_scope() and push_scope()) are now deprecated as a consequence of reworking the way the SDK saves and propagates data internally. Instead, SDK 2.0 introduces the concept of a global, isolation and current scope. In short:

  • The global scope holds data that doesn't change as long as your app is running (for example, the current release).
  • The isolation scope follows a single basic unit of your program, such as a single request-response cycle or one task in a task queue, and holds data pertaining to it. In most cases, the lifecycle of an isolation scope maps to the lifecycle of a transaction.
  • The current scope wraps a smaller part of the code, often something that's being tracked by a single span.

The SDK will take care of deciding what data belongs on which scope as long as you use the top-level API. For example, you can use sentry_sdk.set_tag() and let the SDK handle the data for you, instead of manually setting it with scope.set_tag().

The old APIs will continue to work in 2.0, but we recommend migrating to their new counterparts as soon as possible since they will be removed in the next major release.

Creating a new isolation scope provides a similar level of isolation to what you'd previously get by using a hub clone:

- hub = Hub(Hub.current)
- # do something
+ with isolation_scope() as scope:
+     # do something

In case of configure_scope(), you will need to modify either the isolation scope or the current scope, depending on whether the change you're making to the scope should affect the whole transaction (one request-response cycle, one execution of a task, and so on) or just a specific part of the code.

If you want your scope change to affect a smaller unit of code such as a span, use the current scope.

- with configure_scope() as scope:
-     # do something with scope
+ scope = Scope.get_current_scope()
+ # do something with scope

If you want your scope change to affect the whole transaction, use the isolation scope.

- with configure_scope() as scope:
-     # do something with scope
+ scope = Scope.get_isolation_scope()
+ # do something with scope

Additionally, you no longer have to use configure_scope to mutate a transaction. Instead, you simply get the current scope to mutate the transaction. Here is a recipe on how to change your code to make it work:

transaction = sentry_sdk.transaction(...)

# later in the code execution:

- with sentry_sdk.configure_scope() as scope:
-     scope.set_transaction_name("new-transaction-name")

+ scope = sentry_sdk.Scope.get_current_scope()
+ scope.set_transaction_name("new-transaction-name")

Scope pushing translates to forking a scope in 2.0. You either want to fork the current or the isolation scope, depending on how long the new scope should be active. If it should encompass the whole transaction, fork the isolation scope, otherwise fork the current scope.

If you only want to apply something to a smaller, localized part of the code, fork the current scope with new_scope():

- with push_scope() as scope:
-     # do something
+ with new_scope() as scope:
+     # do something

If you're wrapping a whole request-response cycle or a whole execution of a task, fork the isolation scope:

- with push_scope() as scope:
-     # do something
+ with isolation_scope() as scope:
+     # do something

Sentry Python 2.0 is now only compatible with self-hosted Sentry v20.6.0 and above, since the SDK now sends all events to the /envelope API endpoint. Self-hosted Sentry users must upgrade their self-hosted instance to a compatible version. No action is needed for SaaS users and self-hosted users already running a compatible Sentry version.

If you're using a custom transport, sentry_sdk.transport.Transport.capture_event has been deprecated. Use sentry_sdk.transport.Transport.capture_envelope instead. Additionally, custom transports should now all be implemented as sentry_sdk.transport.Transport subclasses, since transport functions are deprecated.

Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").