Repository: getsentry/sentry-python

Issue:
TypeError when using sentry-sdk>=0.11.0 in Django
When I used sentry-sdk==0.10.1, everything was ok. But when I upgraded sentry-sdk to 0.11.0 and higher, there's a TypeError.

```
Traceback (most recent call last):
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/apscheduler/executors/base_py3.py", line 29, in run_coroutine_job
    retval = await job.func(*job.args, **job.kwargs)
  File "/var/www/pixiu.40huo.cn/pixiu/backend/scheduler.py", line 96, in refresh_task
    req = await fetch_resource_list(loop=loop)
  File "/var/www/pixiu.40huo.cn/pixiu/backend/scheduler.py", line 27, in fetch_resource_list
    req = await loop.run_in_executor(executor, send_req, "get", reverse(viewname="resource-list"))
  File "/usr/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/var/www/pixiu.40huo.cn/pixiu/utils/http_req.py", line 37, in send_req
    return client.get(url=abs_url, headers=headers)
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/requests/sessions.py", line 546, in get
    return self.request('GET', url, **kwargs)
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/rest_framework/test.py", line 115, in request
    return super().request(method, url, *args, **kwargs)
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/requests/sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/requests/sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/rest_framework/test.py", line 93, in send
    wsgi_response = self.app(environ, start_response)
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/sentry_sdk/integrations/django/__init__.py", line 101, in sentry_patched_wsgi_handler
    return SentryWsgiMiddleware(lambda *a, **kw: old_app(self, *a, **kw))(
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/sentry_sdk/integrations/wsgi.py", line 106, in __call__
    reraise(*_capture_exception(hub))
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/sentry_sdk/_compat.py", line 54, in reraise
    raise value
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/sentry_sdk/integrations/wsgi.py", line 101, in __call__
    rv = self.app(
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/sentry_sdk/integrations/django/__init__.py", line 101, in <lambda>
    return SentryWsgiMiddleware(lambda *a, **kw: old_app(self, *a, **kw))(
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/django/core/handlers/wsgi.py", line 142, in __call__
    start_response(status, response_headers)
  File "/var/www/pixiu.40huo.cn/pixiu/venv/lib/python3.8/site-packages/sentry_sdk/integrations/wsgi.py", line 121, in _sentry_start_response
    return old_start_response(status, response_headers, exc_info)
TypeError: start_response() takes 2 positional arguments but 3 were given
```
The related code https://github.com/40huo/pixiu/blob/develop/backend/scheduler.py .

Plus, I use gunicorn[gevent] starting wsgi.


Discussion:
Hi @40huo, sorry to hear that you're experiencing this issue. Could you create a minimal reproducible example that shows the issue? I do not speak chinese, so I do not know how to run your app.
Create an test project.
```shell
# Create project
django-admin startproject sentry-test

# Create APP
python manage.py startapp sentry_a
```
Modify settings of sentry and `installed_apps`.
Write a simple view.
```python
from django.shortcuts import HttpResponse


# Create your views here.
def index(request):
    return HttpResponse("hello")
```
Write a backend job using Django-REST-Framework test client.
```python
from rest_framework.test import RequestsClient


def run():
    c = RequestsClient()
    resp = c.get(url='http://testserver/')
    print(resp.text)
```
Create Django custom command.
```python
from django.core.management.base import BaseCommand, CommandError

import backend


class Command(BaseCommand):
    help = 'Run backend spider.'

    def handle(self, *args, **options):
        try:
            backend.run()
        except Exception as e:
            raise CommandError(f'Unhandled error occurred when running backend spider {e}')
```
And run the backend job.
```shell
python manage.py aaa                                                                                                   ⎈ minikube
CommandError: Unhandled error occurred when running backend spider start_response() takes 2 positional arguments but 3 were given
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
```
@40huo could you give me a repo I can clone to repro this? I don't know what I would write in `installed_apps` to reproduce this. The Sentry SDK does not require you to modify that
> @40huo could you give me a repo I can clone to repro this? I don't know what I would write in `installed_apps` to reproduce this. The Sentry SDK does not require you to modify that

Sorry for misunderstanding, I meant modify the django app name in `installed_apps`. Now you could use this repo https://github.com/40huo/sentry_test.git to reproduce. You may need to modify the sentry DSN in `settings.py`.

Resolving patch:
diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py
--- a/sentry_sdk/integrations/wsgi.py
+++ b/sentry_sdk/integrations/wsgi.py
@@ -21,13 +21,19 @@
     from typing import Tuple
     from typing import Optional
     from typing import TypeVar
+    from typing import Protocol
 
     from sentry_sdk.utils import ExcInfo
     from sentry_sdk._types import EventProcessor
 
-    T = TypeVar("T")
-    U = TypeVar("U")
-    E = TypeVar("E")
+    WsgiResponseIter = TypeVar("WsgiResponseIter")
+    WsgiResponseHeaders = TypeVar("WsgiResponseHeaders")
+    WsgiExcInfo = TypeVar("WsgiExcInfo")
+
+    class StartResponse(Protocol):
+        def __call__(self, status, response_headers, exc_info=None):
+            # type: (str, WsgiResponseHeaders, Optional[WsgiExcInfo]) -> WsgiResponseIter
+            pass
 
 
 _wsgi_middleware_applied = ContextVar("sentry_wsgi_middleware_applied")
@@ -125,14 +131,24 @@ def __call__(self, environ, start_response):
 
 
 def _sentry_start_response(
-    old_start_response, span, status, response_headers, exc_info=None
+    old_start_response,  # type: StartResponse
+    span,  # type: Span
+    status,  # type: str
+    response_headers,  # type: WsgiResponseHeaders
+    exc_info=None,  # type: Optional[WsgiExcInfo]
 ):
-    # type: (Callable[[str, U, Optional[E]], T], Span, str, U, Optional[E]) -> T
+    # type: (...) -> WsgiResponseIter
     with capture_internal_exceptions():
         status_int = int(status.split(" ", 1)[0])
         span.set_http_status(status_int)
 
-    return old_start_response(status, response_headers, exc_info)
+    if exc_info is None:
+        # The Django Rest Framework WSGI test client, and likely other
+        # (incorrect) implementations, cannot deal with the exc_info argument
+        # if one is present. Avoid providing a third argument if not necessary.
+        return old_start_response(status, response_headers)
+    else:
+        return old_start_response(status, response_headers, exc_info)
 
 
 def _get_environ(environ):


Test patch: