Repository: getsentry/sentry-python

Issue:
No more last_event_id
### How do you use Sentry?

Sentry Saas (sentry.io)

### Version

2.1.0

### Steps to Reproduce
[https://develop.sentry.dev/sdk/features/#retrieve-last-event-id](https://develop.sentry.dev/sdk/features/#retrieve-last-event-id)
The method last_event_id does not exist anymore. I understand that's been deprecated but, how are we supposed to show the event_id to the user now?
from sentry_sdk import last_event_id

### Expected Result

Be able to get the last event id from a non controlled exception and show it to the final user.
In django for example, the recommended way was this:

```
from sentry_sdk import last_event_id

from django.shortcuts import render


def custom_handler500(request):
    context = {
        "request": request,
        "title": "Error 500",
        "last_event_id": last_event_id(),
    }
    template_name = "500.html"
    return render(request, template_name, context, status=500)
```

### Actual Result

`ImportError: cannot import name 'last_event_id' from 'sentry_sdk' (/app/.venv/lib/python3.11/site-packages/sentry_sdk/__init__.py)`



Discussion:
Hi @maltalk, thank you for reporting this feedback. We plan to re-add this functionality to the SDK, although we might rename the function
Thanks @szokeasaurusrex for your quick response.
Could you give me a date estimate?
Right now we are frozen on version 1.45 for this reason.

I'm not trying to rush anything at all. We understand that after the total refactor of the SDK it is not something that can be done overnight.
No estimate yet @maltalk – however, since we need the `last_event_id` functionality for the user feedback dialog, we will likely prioritize adding this back

Resolving patch:
diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py
--- a/sentry_sdk/__init__.py
+++ b/sentry_sdk/__init__.py
@@ -33,6 +33,7 @@
     "get_traceparent",
     "is_initialized",
     "isolation_scope",
+    "last_event_id",
     "new_scope",
     "push_scope",
     "set_context",
diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py
--- a/sentry_sdk/api.py
+++ b/sentry_sdk/api.py
@@ -59,6 +59,7 @@ def overload(x):
     "get_traceparent",
     "is_initialized",
     "isolation_scope",
+    "last_event_id",
     "new_scope",
     "push_scope",
     "set_context",
@@ -332,6 +333,16 @@ def start_transaction(
     )
 
 
+@scopemethod
+def last_event_id():
+    # type: () -> Optional[str]
+    """
+    See :py:meth:`sentry_sdk.Scope.last_event_id` documentation regarding
+    this method's limitations.
+    """
+    return Scope.last_event_id()
+
+
 def set_measurement(name, value, unit=""):
     # type: (str, float, MeasurementUnit) -> None
     transaction = Scope.get_current_scope().transaction
diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py
--- a/sentry_sdk/scope.py
+++ b/sentry_sdk/scope.py
@@ -185,6 +185,7 @@ class Scope(object):
         "_propagation_context",
         "client",
         "_type",
+        "_last_event_id",
     )
 
     def __init__(self, ty=None, client=None):
@@ -207,6 +208,9 @@ def __init__(self, ty=None, client=None):
         incoming_trace_information = self._load_trace_data_from_env()
         self.generate_propagation_context(incoming_data=incoming_trace_information)
 
+        # self._last_event_id is only applicable to isolation scopes
+        self._last_event_id = None  # type: Optional[str]
+
     def __copy__(self):
         # type: () -> Scope
         """
@@ -308,6 +312,23 @@ def get_global_scope(cls):
 
         return _global_scope
 
+    @classmethod
+    def last_event_id(cls):
+        # type: () -> Optional[str]
+        """
+        .. versionadded:: 2.2.0
+
+        Returns event ID of the event most recently captured by the isolation scope, or None if no event
+        has been captured. We do not consider events that are dropped, e.g. by a before_send hook.
+        Transactions also are not considered events in this context.
+
+        The event corresponding to the returned event ID is NOT guaranteed to actually be sent to Sentry;
+        whether the event is sent depends on the transport. The event could be sent later or not at all.
+        Even a sent event could fail to arrive in Sentry due to network issues, exhausted quotas, or
+        various other reasons.
+        """
+        return cls.get_isolation_scope()._last_event_id
+
     def _merge_scopes(self, additional_scope=None, additional_scope_kwargs=None):
         # type: (Optional[Scope], Optional[Dict[str, Any]]) -> Scope
         """
@@ -1089,7 +1110,12 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs):
         """
         scope = self._merge_scopes(scope, scope_kwargs)
 
-        return Scope.get_client().capture_event(event=event, hint=hint, scope=scope)
+        event_id = Scope.get_client().capture_event(event=event, hint=hint, scope=scope)
+
+        if event_id is not None and event.get("type") != "transaction":
+            self.get_isolation_scope()._last_event_id = event_id
+
+        return event_id
 
     def capture_message(self, message, level=None, scope=None, **scope_kwargs):
         # type: (str, Optional[LogLevelStr], Optional[Scope], Any) -> Optional[str]


Test patch:
diff --git a/tests/test_basics.py b/tests/test_basics.py
--- a/tests/test_basics.py
+++ b/tests/test_basics.py
@@ -15,6 +15,7 @@
     capture_exception,
     capture_message,
     start_transaction,
+    last_event_id,
     add_breadcrumb,
     Hub,
     Scope,
@@ -778,3 +779,24 @@ def test_classmethod_tracing(sentry_init):
         with patch_start_tracing_child() as fake_start_child:
             assert instance_or_class.class_(1) == (TracingTestClass, 1)
             assert fake_start_child.call_count == 1
+
+
+def test_last_event_id(sentry_init):
+    sentry_init(enable_tracing=True)
+
+    assert last_event_id() is None
+
+    capture_exception(Exception("test"))
+
+    assert last_event_id() is not None
+
+
+def test_last_event_id_transaction(sentry_init):
+    sentry_init(enable_tracing=True)
+
+    assert last_event_id() is None
+
+    with start_transaction(name="test"):
+        pass
+
+    assert last_event_id() is None, "Transaction should not set last_event_id"
diff --git a/tests/test_scope.py b/tests/test_scope.py
--- a/tests/test_scope.py
+++ b/tests/test_scope.py
@@ -822,3 +822,24 @@ def test_set_tags():
         "tag2": "updated",
         "tag3": "new",
     }, "Updating tags with empty dict changed tags"
+
+
+def test_last_event_id(sentry_init):
+    sentry_init(enable_tracing=True)
+
+    assert Scope.last_event_id() is None
+
+    sentry_sdk.capture_exception(Exception("test"))
+
+    assert Scope.last_event_id() is not None
+
+
+def test_last_event_id_transaction(sentry_init):
+    sentry_init(enable_tracing=True)
+
+    assert Scope.last_event_id() is None
+
+    with sentry_sdk.start_transaction(name="test"):
+        pass
+
+    assert Scope.last_event_id() is None, "Transaction should not set last_event_id"