Repository: getsentry/sentry-python

Issue:
FastApiIntegration hides request handler function name
### How do you use Sentry?

Sentry Saas (sentry.io)

### Version

1.31.0

### Steps to Reproduce

Use the python sentry sdk in conjunction with FastAPI and the NewRelic python agent. 
View transactions in the NewRelic UI. 

### Expected Result

The NewRelic transaction name should show the function name handling the FastAPI route. 

### Actual Result

New Relic services representing our new fastapi services are seeing significant time spent in a transaction named `sentry_sdk.integrations.fastapi:patch_get_request_handler.<locals>._sentry_get_request_handler.<locals>._sentry_call`.

![image](https://github.com/getsentry/sentry-python/assets/66433626/22bb7222-fb35-45fa-8b19-cba6432b8752)

The source code for `fastspi:patch_get_request_handler` wraps non-async request handlers in a local function called `_sentry_call`. This local function shares the same `__qualname__` responsible for the significant transaction time. The result is every route handler has the same name and it appears in New Relic that the service has one transaction type.  

Sentry sdk integrations wrap functions all the time to create sentry scope and yet we never have this problem. It appears the issue comes from a simple oversight when writing the fastapi integration. Other integrations use `sentry_sdk._functools:wraps` to wrap a function with sentry specific behavior, yet ensure the name, qualname, etc. are retrained from the wrapped function. 


Discussion:

Resolving patch:
diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py
--- a/sentry_sdk/integrations/fastapi.py
+++ b/sentry_sdk/integrations/fastapi.py
@@ -1,6 +1,7 @@
 import asyncio
 from copy import deepcopy
 
+from sentry_sdk._functools import wraps
 from sentry_sdk._types import TYPE_CHECKING
 from sentry_sdk.hub import Hub, _should_send_default_pii
 from sentry_sdk.integrations import DidNotEnable
@@ -79,6 +80,7 @@ def _sentry_get_request_handler(*args, **kwargs):
         ):
             old_call = dependant.call
 
+            @wraps(old_call)
             def _sentry_call(*args, **kwargs):
                 # type: (*Any, **Any) -> Any
                 hub = Hub.current


Test patch:
diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py
--- a/tests/integrations/fastapi/test_fastapi.py
+++ b/tests/integrations/fastapi/test_fastapi.py
@@ -377,6 +377,28 @@ def test_transaction_name(
     )
 
 
+def test_route_endpoint_equal_dependant_call(sentry_init):
+    """
+    Tests that the route endpoint name is equal to the wrapped dependant call name.
+    """
+    sentry_init(
+        auto_enabling_integrations=False,  # Make sure that httpx integration is not added, because it adds tracing information to the starlette test clients request.
+        integrations=[
+            StarletteIntegration(),
+            FastApiIntegration(),
+        ],
+        traces_sample_rate=1.0,
+        debug=True,
+    )
+
+    app = fastapi_app_factory()
+
+    for route in app.router.routes:
+        if not hasattr(route, "dependant"):
+            continue
+        assert route.endpoint.__qualname__ == route.dependant.call.__qualname__
+
+
 @pytest.mark.parametrize(
     "request_url,transaction_style,expected_transaction_name,expected_transaction_source",
     [