Repository: getsentry/sentry-python

Issue:
Collections.abc deprecation warning
The below line generates a warning regarding importing from collections.

https://github.com/getsentry/sentry-python/blob/7c4683f0cd2a32738ba5e22c4a9bc6040ea13f15/sentry_sdk/tracing.py#L27

```
  /home/karthi/freezegun-venv/lib/python3.9/site-packages/sentry_sdk/tracing.py:27: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working
    class EnvironHeaders(collections.Mapping):  # type: ignore
```

This can be fixed like similar fix done in other file

https://github.com/getsentry/sentry-python/blob/44b8949fe2ff0f6319dd586d15c3e76b239971d6/sentry_sdk/serializer.py#L30-L37


Discussion:

Resolving patch:
diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py
--- a/sentry_sdk/integrations/__init__.py
+++ b/sentry_sdk/integrations/__init__.py
@@ -82,7 +82,7 @@ def setup_integrations(integrations, with_defaults=True):
                     type(integration).setup_once()
                 except NotImplementedError:
                     if getattr(integration, "install", None) is not None:
-                        logger.warn(
+                        logger.warning(
                             "Integration %s: The install method is "
                             "deprecated. Use `setup_once`.",
                             identifier,
diff --git a/sentry_sdk/integrations/flask.py b/sentry_sdk/integrations/flask.py
--- a/sentry_sdk/integrations/flask.py
+++ b/sentry_sdk/integrations/flask.py
@@ -131,7 +131,7 @@ def cookies(self):
 
     def raw_data(self):
         # type: () -> bytes
-        return self.request.data
+        return self.request.get_data()
 
     def form(self):
         # type: () -> ImmutableMultiDict
diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py
--- a/sentry_sdk/tracing.py
+++ b/sentry_sdk/tracing.py
@@ -1,18 +1,23 @@
 import re
 import uuid
 import contextlib
-import collections
 
 from datetime import datetime
 
 from sentry_sdk.utils import capture_internal_exceptions, concat_strings
+from sentry_sdk._compat import PY2
 
+if PY2:
+    from collections import Mapping
+else:
+    from collections.abc import Mapping
 
 if False:
+    import typing
+
     from typing import Optional
     from typing import Any
     from typing import Dict
-    from typing import Mapping
     from typing import List
 
 _traceparent_header_format_re = re.compile(
@@ -24,10 +29,10 @@
 )
 
 
-class EnvironHeaders(collections.Mapping):  # type: ignore
+class EnvironHeaders(Mapping):  # type: ignore
     def __init__(
         self,
-        environ,  # type: Mapping[str, str]
+        environ,  # type: typing.Mapping[str, str]
         prefix="HTTP_",  # type: str
     ):
         # type: (...) -> None


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -0,0 +1,13 @@
+import sys
+
+import pytest
+
+# This is used in _capture_internal_warnings. We need to run this at import
+# time because that's where many deprecation warnings might get thrown.
+#
+# This lives in tests/__init__.py because apparently even tests/conftest.py
+# gets loaded too late.
+assert "sentry_sdk" not in sys.modules
+
+_warning_recorder_mgr = pytest.warns(None)
+_warning_recorder = _warning_recorder_mgr.__enter__()
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -8,6 +8,8 @@
 from sentry_sdk._compat import reraise, string_types, iteritems
 from sentry_sdk.transport import Transport
 
+from tests import _warning_recorder, _warning_recorder_mgr
+
 SEMAPHORE = "./semaphore"
 
 if not os.path.isfile(SEMAPHORE):
@@ -48,6 +50,56 @@ def _():
     return errors
 
 
+@pytest.fixture(autouse=True, scope="session")
+def _capture_internal_warnings():
+    yield
+
+    _warning_recorder_mgr.__exit__(None, None, None)
+    recorder = _warning_recorder
+
+    for warning in recorder:
+        try:
+            if isinstance(warning.message, ResourceWarning):
+                continue
+        except NameError:
+            pass
+
+        # pytest-django
+        if "getfuncargvalue" in str(warning.message):
+            continue
+
+        # Happens when re-initializing the SDK
+        if "but it was only enabled on init()" in str(warning.message):
+            continue
+
+        # sanic's usage of aiohttp for test client
+        if "verify_ssl is deprecated, use ssl=False instead" in str(warning.message):
+            continue
+
+        if "getargspec" in str(warning.message) and warning.filename.endswith(
+            ("pyramid/config/util.py", "pyramid/config/views.py")
+        ):
+            continue
+
+        if "isAlive() is deprecated" in str(
+            warning.message
+        ) and warning.filename.endswith("celery/utils/timer2.py"):
+            continue
+
+        if "collections.abc" in str(warning.message) and warning.filename.endswith(
+            ("celery/canvas.py", "werkzeug/datastructures.py", "tornado/httputil.py")
+        ):
+            continue
+
+        # Django 1.7 emits a (seemingly) false-positive warning for our test
+        # app and suggests to use a middleware that does not exist in later
+        # Django versions.
+        if "SessionAuthenticationMiddleware" in str(warning.message):
+            continue
+
+        raise AssertionError(warning)
+
+
 @pytest.fixture
 def monkeypatch_test_transport(monkeypatch, assert_semaphore_acceptance):
     def check_event(event):
diff --git a/tests/integrations/flask/test_flask.py b/tests/integrations/flask/test_flask.py
--- a/tests/integrations/flask/test_flask.py
+++ b/tests/integrations/flask/test_flask.py
@@ -195,8 +195,8 @@ def test_flask_large_json_request(sentry_init, capture_events, app):
 
     @app.route("/", methods=["POST"])
     def index():
-        assert request.json == data
-        assert request.data == json.dumps(data).encode("ascii")
+        assert request.get_json() == data
+        assert request.get_data() == json.dumps(data).encode("ascii")
         assert not request.form
         capture_message("hi")
         return "ok"
@@ -220,8 +220,8 @@ def test_flask_empty_json_request(sentry_init, capture_events, app, data):
 
     @app.route("/", methods=["POST"])
     def index():
-        assert request.json == data
-        assert request.data == json.dumps(data).encode("ascii")
+        assert request.get_json() == data
+        assert request.get_data() == json.dumps(data).encode("ascii")
         assert not request.form
         capture_message("hi")
         return "ok"
@@ -244,8 +244,8 @@ def test_flask_medium_formdata_request(sentry_init, capture_events, app):
     @app.route("/", methods=["POST"])
     def index():
         assert request.form["foo"] == data["foo"]
-        assert not request.data
-        assert not request.json
+        assert not request.get_data()
+        assert not request.get_json()
         capture_message("hi")
         return "ok"
 
@@ -272,10 +272,10 @@ def test_flask_too_large_raw_request(sentry_init, input_char, capture_events, ap
     def index():
         assert not request.form
         if isinstance(data, bytes):
-            assert request.data == data
+            assert request.get_data() == data
         else:
-            assert request.data == data.encode("ascii")
-        assert not request.json
+            assert request.get_data() == data.encode("ascii")
+        assert not request.get_json()
         capture_message("hi")
         return "ok"
 
@@ -301,7 +301,7 @@ def test_flask_files_and_form(sentry_init, capture_events, app):
     def index():
         assert list(request.form) == ["foo"]
         assert list(request.files) == ["file"]
-        assert not request.json
+        assert not request.get_json()
         capture_message("hi")
         return "ok"
 
diff --git a/tests/integrations/logging/test_logging.py b/tests/integrations/logging/test_logging.py
--- a/tests/integrations/logging/test_logging.py
+++ b/tests/integrations/logging/test_logging.py
@@ -91,7 +91,7 @@ def test_logging_level(sentry_init, capture_events):
     del events[:]
 
     logger.setLevel(logging.ERROR)
-    logger.warn("hi")
+    logger.warning("hi")
     assert not events