Repository: DataDog/dd-trace-py

Issue:
AttributeError: type object 'Flask' has no attribute 'try_trigger_before_first_request_functions'
### Which version of dd-trace-py are you using?

```bash
ddtrace=1.3.0
```

### Which version of pip are you using?

```bash
# pip --version
pip 22.0.4 from /usr/local/lib/python3.10/site-packages/pip (python 3.10)

```

### Which version of the libraries are you using?

```bash
Flask = 2.2.0
ddtrace = 1.3.0
```

### How can we reproduce your problem?

Install the following dependencies

```bash
pip install Flask==2.2.0
pip install ddtrace==1.3.0
```

**created the following main.py**

```python
from ddtrace import patch_all
from flask import Flask

patch_all()

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"
```

run the following command:

```bash
ddtrace-run flask --app main run
```

### What is the result that you get?

```bash
Traceback (most recent call last):
  File "/usr/local/bin/flask", line 5, in <module>
    from flask.cli import main
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "/usr/local/lib/python3.10/site-packages/ddtrace/vendor/wrapt/importer.py", line 177, in _exec_module
    notify_module_loaded(module)
  File "/usr/local/lib/python3.10/site-packages/ddtrace/vendor/wrapt/decorators.py", line 470, in _synchronized
    return wrapped(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/ddtrace/vendor/wrapt/importer.py", line 136, in notify_module_loaded
    hook(module)
  File "/usr/local/lib/python3.10/site-packages/ddtrace/_monkey.py", line 143, in on_import
    imported_module.patch()
  File "/usr/local/lib/python3.10/site-packages/ddtrace/contrib/flask/patch.py", line 111, in patch
    _w("flask", "Flask.{}".format(name), simple_tracer("flask.{}".format(name)))
  File "/usr/local/lib/python3.10/site-packages/ddtrace/vendor/wrapt/wrappers.py", line 870, in wrap_function_wrapper
    return wrap_object(module, name, FunctionWrapper, (wrapper,))
  File "/usr/local/lib/python3.10/site-packages/ddtrace/vendor/wrapt/wrappers.py", line 816, in wrap_object
    (parent, attribute, original) = resolve_path(module, name)
  File "/usr/local/lib/python3.10/site-packages/ddtrace/vendor/wrapt/wrappers.py", line 808, in resolve_path
    original = lookup_attribute(parent, attribute)
  File "/usr/local/lib/python3.10/site-packages/ddtrace/vendor/wrapt/wrappers.py", line 800, in lookup_attribute
    return getattr(parent, attribute)
AttributeError: type object 'Flask' has no attribute 'try_trigger_before_first_request_functions'
```

### What is the result that you expected?

Service starts successfully without raising an attribute error when datadog is enabled.

### Current understanding of the issue

Flask removed `try_trigger_before_first_request_functions ` in https://github.com/pallets/flask/pull/4621 which was released as the `2.2.0` version of Flask.


Discussion:

Resolving patch:
diff --git a/ddtrace/contrib/flask/patch.py b/ddtrace/contrib/flask/patch.py
--- a/ddtrace/contrib/flask/patch.py
+++ b/ddtrace/contrib/flask/patch.py
@@ -102,11 +102,13 @@ def patch():
         "handle_exception",
         "handle_http_exception",
         "handle_user_exception",
-        "try_trigger_before_first_request_functions",
         "do_teardown_request",
         "do_teardown_appcontext",
         "send_static_file",
     ]
+    if flask_version < (2, 2, 0):
+        flask_app_traces.append("try_trigger_before_first_request_functions")
+
     for name in flask_app_traces:
         _w("flask", "Flask.{}".format(name), simple_tracer("flask.{}".format(name)))
 
@@ -184,7 +186,6 @@ def unpatch():
         "Flask.handle_exception",
         "Flask.handle_http_exception",
         "Flask.handle_user_exception",
-        "Flask.try_trigger_before_first_request_functions",
         "Flask.do_teardown_request",
         "Flask.do_teardown_appcontext",
         "Flask.send_static_file",
@@ -236,6 +237,10 @@ def unpatch():
         props.append("appcontext_popped.receivers_for")
         props.append("message_flashed.receivers_for")
 
+    # These were removed in 2.2.0
+    if flask_version < (2, 2, 0):
+        props.append("Flask.try_trigger_before_first_request_functions")
+
     for prop in props:
         # Handle 'flask.request_started.receivers_for'
         obj = flask


Test patch:
diff --git a/tests/contrib/flask/test_flask_snapshot.py b/tests/contrib/flask/test_flask_snapshot.py
--- a/tests/contrib/flask/test_flask_snapshot.py
+++ b/tests/contrib/flask/test_flask_snapshot.py
@@ -9,6 +9,7 @@
 import pytest
 import tenacity
 
+from ddtrace.contrib.flask.patch import flask_version
 from tests.webclient import Client
 
 
@@ -92,13 +93,17 @@ def flask_client(flask_command, flask_env, flask_port):
         proc.wait()
 
 
-@pytest.mark.snapshot(ignores=["meta.flask.version"])
+@pytest.mark.snapshot(
+    ignores=["meta.flask.version"], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}
+)
 def test_flask_200(flask_client):
     # type: (Client) -> None
     assert flask_client.get("/").status_code == 200
 
 
-@pytest.mark.snapshot(ignores=["meta.flask.version"])
+@pytest.mark.snapshot(
+    ignores=["meta.flask.version"], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}
+)
 def test_flask_stream(flask_client):
     # type: (Client) -> None
     assert flask_client.get("/stream").status_code == 200
diff --git a/tests/contrib/flask/test_hooks.py b/tests/contrib/flask/test_hooks.py
--- a/tests/contrib/flask/test_hooks.py
+++ b/tests/contrib/flask/test_hooks.py
@@ -1,11 +1,15 @@
 from flask import Blueprint
 
+from ddtrace.contrib.flask.patch import flask_version
 from ddtrace.ext import http
 from tests.utils import assert_span_http_status_code
 
 from . import BaseFlaskTestCase
 
 
+REMOVED_SPANS_2_2_0 = 1 if flask_version >= (2, 2, 0) else 0
+
+
 class FlaskHookTestCase(BaseFlaskTestCase):
     def setUp(self):
         super(FlaskHookTestCase, self).setUp()
@@ -35,7 +39,7 @@ def before_request():
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         # DEV: This will raise an exception if this span doesn't exist
         self.find_span_by_name(spans, "flask.dispatch_request")
@@ -68,7 +72,7 @@ def before_request():
         self.assertEqual(req.data, b"Not Allowed")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 7)
+        self.assertEqual(len(spans), 7 - REMOVED_SPANS_2_2_0)
 
         dispatch = self.find_span_by_name(spans, "flask.dispatch_request", required=False)
         self.assertIsNone(dispatch)
@@ -112,7 +116,7 @@ def before_first_request():
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.before_first_request")
         parent = self.find_span_parent(spans, span)
@@ -124,7 +128,10 @@ def before_first_request():
         self.assertEqual(span.get_tags(), dict())
 
         # Assert correct parent span
-        self.assertEqual(parent.name, "flask.try_trigger_before_first_request_functions")
+        if flask_version >= (2, 2, 0):
+            self.assertEqual(parent.name, "flask.request")
+        else:
+            self.assertEqual(parent.name, "flask.try_trigger_before_first_request_functions")
 
         # Make a second request to ensure a span isn't created
         req = self.client.get("/")
@@ -132,7 +139,7 @@ def before_first_request():
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 8)
+        self.assertEqual(len(spans), 8 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.before_first_request", required=False)
         self.assertIsNone(span)
@@ -152,7 +159,7 @@ def after_request(response):
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.after_request")
         parent = self.find_span_parent(spans, span)
@@ -182,7 +189,7 @@ def after_request(response):
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         root = self.find_span_by_name(spans, "flask.request")
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.after_request")
@@ -215,7 +222,7 @@ def teardown_request(request):
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.teardown_request")
         parent = self.find_span_parent(spans, span)
@@ -244,7 +251,7 @@ def teardown_appcontext(appctx):
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.teardown_appcontext")
         parent = self.find_span_parent(spans, span)
@@ -274,7 +281,7 @@ def bp_before_request():
         self.assertEqual(req.data, b"Hello Blueprint")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.bp_before_request")
         parent = self.find_span_parent(spans, span)
@@ -304,7 +311,7 @@ def bp_before_app_request():
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.bp_before_app_request")
         parent = self.find_span_parent(spans, span)
@@ -334,7 +341,7 @@ def bp_before_app_first_request():
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.bp_before_app_first_request")
         parent = self.find_span_parent(spans, span)
@@ -346,7 +353,10 @@ def bp_before_app_first_request():
         self.assertEqual(span.get_tags(), dict())
 
         # Assert correct parent span
-        self.assertEqual(parent.name, "flask.try_trigger_before_first_request_functions")
+        if flask_version >= (2, 2, 0):
+            self.assertEqual(parent.name, "flask.request")
+        else:
+            self.assertEqual(parent.name, "flask.try_trigger_before_first_request_functions")
 
         # Make a second request to ensure a span isn't created
         req = self.client.get("/")
@@ -354,7 +364,7 @@ def bp_before_app_first_request():
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 8)
+        self.assertEqual(len(spans), 8 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(
             spans,
@@ -379,7 +389,7 @@ def bp_after_request(response):
         self.assertEqual(req.data, b"Hello Blueprint")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.bp_after_request")
         parent = self.find_span_parent(spans, span)
@@ -409,7 +419,7 @@ def bp_after_app_request(response):
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.bp_after_app_request")
         parent = self.find_span_parent(spans, span)
@@ -439,7 +449,7 @@ def bp_teardown_request(request):
         self.assertEqual(req.data, b"Hello Blueprint")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.bp_teardown_request")
         parent = self.find_span_parent(spans, span)
@@ -469,7 +479,7 @@ def bp_teardown_app_request(request):
         self.assertEqual(req.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         span = self.find_span_by_name(spans, "tests.contrib.flask.test_hooks.bp_teardown_app_request")
         parent = self.find_span_parent(spans, span)
diff --git a/tests/contrib/flask/test_request.py b/tests/contrib/flask/test_request.py
--- a/tests/contrib/flask/test_request.py
+++ b/tests/contrib/flask/test_request.py
@@ -18,6 +18,9 @@
 from . import BaseFlaskTestCase
 
 
+REMOVED_SPANS_2_2_0 = 1 if flask_version >= (2, 2, 0) else 0
+
+
 base_exception_name = "builtins.Exception"
 if PY2:
     base_exception_name = "exceptions.Exception"
@@ -39,20 +42,31 @@ def index():
         self.assertEqual(res.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 8)
+        self.assertEqual(len(spans), 8 - REMOVED_SPANS_2_2_0)
 
         # Assert the order of the spans created
-        self.assertListEqual(
-            [
+        expected_spans = [
+            "flask.request",
+            "flask.try_trigger_before_first_request_functions",
+            "flask.preprocess_request",
+            "flask.dispatch_request",
+            "tests.contrib.flask.test_request.index",
+            "flask.process_response",
+            "flask.do_teardown_request",
+            "flask.do_teardown_appcontext",
+        ]
+        if flask_version >= (2, 2, 0):
+            expected_spans = [
                 "flask.request",
-                "flask.try_trigger_before_first_request_functions",
                 "flask.preprocess_request",
                 "flask.dispatch_request",
                 "tests.contrib.flask.test_request.index",
                 "flask.process_response",
                 "flask.do_teardown_request",
                 "flask.do_teardown_appcontext",
-            ],
+            ]
+        self.assertListEqual(
+            expected_spans,
             [s.name for s in spans],
         )
 
@@ -79,7 +93,7 @@ def index():
         assert http.QUERY_STRING not in req_span.get_tags()
 
         # Handler span
-        handler_span = spans[4]
+        handler_span = spans[4 - REMOVED_SPANS_2_2_0]
         self.assertEqual(handler_span.service, "flask")
         self.assertEqual(handler_span.name, "tests.contrib.flask.test_request.index")
         self.assertEqual(handler_span.resource, "/")
@@ -117,7 +131,7 @@ def route_params(first, second, third, fourth):
         }
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 8)
+        self.assertEqual(len(spans), 8 - REMOVED_SPANS_2_2_0)
 
         root = spans[0]
         assert root.name == "flask.request"
@@ -348,20 +362,31 @@ def index():
         self.assertEqual(res.data, b"Hello Flask")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 8)
+        self.assertEqual(len(spans), 8 - REMOVED_SPANS_2_2_0)
 
         # Assert the order of the spans created
-        self.assertListEqual(
-            [
+        expected_spans = [
+            "flask.request",
+            "flask.try_trigger_before_first_request_functions",
+            "flask.preprocess_request",
+            "flask.dispatch_request",
+            "tests.contrib.flask.test_request.index",
+            "flask.process_response",
+            "flask.do_teardown_request",
+            "flask.do_teardown_appcontext",
+        ]
+        if flask_version >= (2, 2, 0):
+            expected_spans = [
                 "flask.request",
-                "flask.try_trigger_before_first_request_functions",
                 "flask.preprocess_request",
                 "flask.dispatch_request",
                 "tests.contrib.flask.test_request.index",
                 "flask.process_response",
                 "flask.do_teardown_request",
                 "flask.do_teardown_appcontext",
-            ],
+            ]
+        self.assertListEqual(
+            expected_spans,
             [s.name for s in spans],
         )
 
@@ -391,7 +416,7 @@ def index():
         assert_span_http_status_code(req_span, 200)
 
         # Handler span
-        handler_span = spans[4]
+        handler_span = spans[4 - REMOVED_SPANS_2_2_0]
         self.assertEqual(handler_span.service, "flask")
         self.assertEqual(handler_span.name, "tests.contrib.flask.test_request.index")
         # Note: contains no query string
@@ -414,20 +439,31 @@ def unicode():
         self.assertEqual(res.data, b"\xc3\xbc\xc5\x8b\xc3\xaf\xc4\x89\xc3\xb3\xc4\x91\xc4\x93")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 8)
+        self.assertEqual(len(spans), 8 - REMOVED_SPANS_2_2_0)
 
         # Assert the order of the spans created
-        self.assertListEqual(
-            [
+        expected_spans = [
+            "flask.request",
+            "flask.try_trigger_before_first_request_functions",
+            "flask.preprocess_request",
+            "flask.dispatch_request",
+            "tests.contrib.flask.test_request.unicode",
+            "flask.process_response",
+            "flask.do_teardown_request",
+            "flask.do_teardown_appcontext",
+        ]
+        if flask_version >= (2, 2, 0):
+            expected_spans = [
                 "flask.request",
-                "flask.try_trigger_before_first_request_functions",
                 "flask.preprocess_request",
                 "flask.dispatch_request",
                 "tests.contrib.flask.test_request.unicode",
                 "flask.process_response",
                 "flask.do_teardown_request",
                 "flask.do_teardown_appcontext",
-            ],
+            ]
+        self.assertListEqual(
+            expected_spans,
             [s.name for s in spans],
         )
 
@@ -452,7 +488,7 @@ def unicode():
         assert_span_http_status_code(req_span, 200)
 
         # Handler span
-        handler_span = spans[4]
+        handler_span = spans[4 - REMOVED_SPANS_2_2_0]
         self.assertEqual(handler_span.service, "flask")
         self.assertEqual(handler_span.name, "tests.contrib.flask.test_request.unicode")
         self.assertEqual(handler_span.resource, u"/üŋïĉóđē")
@@ -468,13 +504,23 @@ def test_request_404(self):
         self.assertEqual(res.status_code, 404)
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         # Assert the order of the spans created
-        self.assertListEqual(
-            [
+        expected_spans = [
+            "flask.request",
+            "flask.try_trigger_before_first_request_functions",
+            "flask.preprocess_request",
+            "flask.dispatch_request",
+            "flask.handle_user_exception",
+            "flask.handle_http_exception",
+            "flask.process_response",
+            "flask.do_teardown_request",
+            "flask.do_teardown_appcontext",
+        ]
+        if flask_version >= (2, 2, 0):
+            expected_spans = [
                 "flask.request",
-                "flask.try_trigger_before_first_request_functions",
                 "flask.preprocess_request",
                 "flask.dispatch_request",
                 "flask.handle_user_exception",
@@ -482,7 +528,9 @@ def test_request_404(self):
                 "flask.process_response",
                 "flask.do_teardown_request",
                 "flask.do_teardown_appcontext",
-            ],
+            ]
+        self.assertListEqual(
+            expected_spans,
             [s.name for s in spans],
         )
 
@@ -506,7 +554,7 @@ def test_request_404(self):
         assert_span_http_status_code(req_span, 404)
 
         # Dispatch span
-        dispatch_span = spans[3]
+        dispatch_span = spans[3 - REMOVED_SPANS_2_2_0]
         self.assertEqual(dispatch_span.service, "flask")
         self.assertEqual(dispatch_span.name, "flask.dispatch_request")
         self.assertEqual(dispatch_span.resource, "flask.dispatch_request")
@@ -530,13 +578,24 @@ def not_found():
         self.assertEqual(res.status_code, 404)
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 10)
+        self.assertEqual(len(spans), 10 - REMOVED_SPANS_2_2_0)
 
         # Assert the order of the spans created
-        self.assertListEqual(
-            [
+        expected_spans = [
+            "flask.request",
+            "flask.try_trigger_before_first_request_functions",
+            "flask.preprocess_request",
+            "flask.dispatch_request",
+            "tests.contrib.flask.test_request.not_found",
+            "flask.handle_user_exception",
+            "flask.handle_http_exception",
+            "flask.process_response",
+            "flask.do_teardown_request",
+            "flask.do_teardown_appcontext",
+        ]
+        if flask_version >= (2, 2, 0):
+            expected_spans = [
                 "flask.request",
-                "flask.try_trigger_before_first_request_functions",
                 "flask.preprocess_request",
                 "flask.dispatch_request",
                 "tests.contrib.flask.test_request.not_found",
@@ -545,7 +604,9 @@ def not_found():
                 "flask.process_response",
                 "flask.do_teardown_request",
                 "flask.do_teardown_appcontext",
-            ],
+            ]
+        self.assertListEqual(
+            expected_spans,
             [s.name for s in spans],
         )
 
@@ -572,7 +633,7 @@ def not_found():
         self.assertEqual(req_span.get_tag("flask.url_rule"), "/not-found")
 
         # Dispatch span
-        dispatch_span = spans[3]
+        dispatch_span = spans[3 - REMOVED_SPANS_2_2_0]
         self.assertEqual(dispatch_span.service, "flask")
         self.assertEqual(dispatch_span.name, "flask.dispatch_request")
         self.assertEqual(dispatch_span.resource, "flask.dispatch_request")
@@ -582,7 +643,7 @@ def not_found():
         self.assertIsNone(dispatch_span.get_tag("error.type"))
 
         # Handler span
-        handler_span = spans[4]
+        handler_span = spans[4 - REMOVED_SPANS_2_2_0]
         self.assertEqual(handler_span.service, "flask")
         self.assertEqual(handler_span.name, "tests.contrib.flask.test_request.not_found")
         self.assertEqual(handler_span.resource, "/not-found")
@@ -607,7 +668,7 @@ def fivehundred():
 
         spans = self.get_spans()
 
-        span_names = [
+        expected_spans = [
             "flask.request",
             "flask.try_trigger_before_first_request_functions",
             "flask.preprocess_request",
@@ -616,17 +677,26 @@ def fivehundred():
             "flask.handle_user_exception",
             "flask.handle_exception",
         ]
+        if flask_version >= (2, 2, 0):
+            expected_spans = [
+                "flask.request",
+                "flask.preprocess_request",
+                "flask.dispatch_request",
+                "tests.contrib.flask.test_request.fivehundred",
+                "flask.handle_user_exception",
+                "flask.handle_exception",
+            ]
 
         if not (flask.__version__.startswith("0.") or flask.__version__.startswith("1.0")):
-            span_names.append("flask.process_response")
+            expected_spans.append("flask.process_response")
 
-        span_names += [
+        expected_spans += [
             "flask.do_teardown_request",
             "flask.do_teardown_appcontext",
         ]
 
         # Assert the order of the spans created
-        assert span_names == [s.name for s in spans]
+        assert expected_spans == [s.name for s in spans]
 
         # Assert span services
         for span in spans:
@@ -650,7 +720,7 @@ def fivehundred():
         self.assertEqual(req_span.get_tag("flask.url_rule"), "/500")
 
         # Dispatch span
-        dispatch_span = spans[3]
+        dispatch_span = spans[3 - REMOVED_SPANS_2_2_0]
         self.assertEqual(dispatch_span.service, "flask")
         self.assertEqual(dispatch_span.name, "flask.dispatch_request")
         self.assertEqual(dispatch_span.resource, "flask.dispatch_request")
@@ -660,7 +730,7 @@ def fivehundred():
         self.assertEqual(dispatch_span.get_tag("error.type"), base_exception_name)
 
         # Handler span
-        handler_span = spans[4]
+        handler_span = spans[4 - REMOVED_SPANS_2_2_0]
         self.assertEqual(handler_span.service, "flask")
         self.assertEqual(handler_span.name, "tests.contrib.flask.test_request.fivehundred")
         self.assertEqual(handler_span.resource, "/500")
@@ -670,7 +740,7 @@ def fivehundred():
         self.assertEqual(handler_span.get_tag("error.type"), base_exception_name)
 
         # User exception span
-        user_ex_span = spans[5]
+        user_ex_span = spans[5 - REMOVED_SPANS_2_2_0]
         self.assertEqual(user_ex_span.service, "flask")
         self.assertEqual(user_ex_span.name, "flask.handle_user_exception")
         self.assertEqual(user_ex_span.resource, "flask.handle_user_exception")
@@ -694,13 +764,24 @@ def fivehundredone():
         self.assertEqual(res.status_code, 501)
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 10)
+        self.assertEqual(len(spans), 10 - REMOVED_SPANS_2_2_0)
 
         # Assert the order of the spans created
-        self.assertListEqual(
-            [
+        expected_spans = [
+            "flask.request",
+            "flask.try_trigger_before_first_request_functions",
+            "flask.preprocess_request",
+            "flask.dispatch_request",
+            "tests.contrib.flask.test_request.fivehundredone",
+            "flask.handle_user_exception",
+            "flask.handle_http_exception",
+            "flask.process_response",
+            "flask.do_teardown_request",
+            "flask.do_teardown_appcontext",
+        ]
+        if flask_version >= (2, 2, 0):
+            expected_spans = [
                 "flask.request",
-                "flask.try_trigger_before_first_request_functions",
                 "flask.preprocess_request",
                 "flask.dispatch_request",
                 "tests.contrib.flask.test_request.fivehundredone",
@@ -709,7 +790,9 @@ def fivehundredone():
                 "flask.process_response",
                 "flask.do_teardown_request",
                 "flask.do_teardown_appcontext",
-            ],
+            ]
+        self.assertListEqual(
+            expected_spans,
             [s.name for s in spans],
         )
 
@@ -735,7 +818,7 @@ def fivehundredone():
         self.assertEqual(req_span.get_tag("flask.url_rule"), "/501")
 
         # Dispatch span
-        dispatch_span = spans[3]
+        dispatch_span = spans[3 - REMOVED_SPANS_2_2_0]
         self.assertEqual(dispatch_span.service, "flask")
         self.assertEqual(dispatch_span.name, "flask.dispatch_request")
         self.assertEqual(dispatch_span.resource, "flask.dispatch_request")
@@ -745,7 +828,7 @@ def fivehundredone():
         self.assertEqual(dispatch_span.get_tag("error.type"), "werkzeug.exceptions.NotImplemented")
 
         # Handler span
-        handler_span = spans[4]
+        handler_span = spans[4 - REMOVED_SPANS_2_2_0]
         self.assertEqual(handler_span.service, "flask")
         self.assertEqual(handler_span.name, "tests.contrib.flask.test_request.fivehundredone")
         self.assertEqual(handler_span.resource, "/501")
@@ -755,7 +838,7 @@ def fivehundredone():
         self.assertEqual(handler_span.get_tag("error.type"), "werkzeug.exceptions.NotImplemented")
 
         # User exception span
-        user_ex_span = spans[5]
+        user_ex_span = spans[5 - REMOVED_SPANS_2_2_0]
         self.assertEqual(user_ex_span.service, "flask")
         self.assertEqual(user_ex_span.name, "flask.handle_user_exception")
         self.assertEqual(user_ex_span.resource, "flask.handle_user_exception")
@@ -783,13 +866,25 @@ def fivehundred():
         spans = self.get_spans()
 
         if flask_version >= (0, 12):
-            self.assertEqual(len(spans), 11)
+            self.assertEqual(len(spans), 11 - REMOVED_SPANS_2_2_0)
 
             # Assert the order of the spans created
-            self.assertListEqual(
-                [
+            expected_spans = [
+                "flask.request",
+                "flask.try_trigger_before_first_request_functions",
+                "flask.preprocess_request",
+                "flask.dispatch_request",
+                "tests.contrib.flask.test_request.fivehundred",
+                "flask.handle_user_exception",
+                "flask.handle_exception",
+                "tests.contrib.flask.test_request.error_handler",
+                "flask.process_response",
+                "flask.do_teardown_request",
+                "flask.do_teardown_appcontext",
+            ]
+            if flask_version >= (2, 2, 0):
+                expected_spans = [
                     "flask.request",
-                    "flask.try_trigger_before_first_request_functions",
                     "flask.preprocess_request",
                     "flask.dispatch_request",
                     "tests.contrib.flask.test_request.fivehundred",
@@ -799,11 +894,13 @@ def fivehundred():
                     "flask.process_response",
                     "flask.do_teardown_request",
                     "flask.do_teardown_appcontext",
-                ],
+                ]
+            self.assertListEqual(
+                expected_spans,
                 [s.name for s in spans],
             )
         else:
-            self.assertEqual(len(spans), 10)
+            self.assertEqual(len(spans), 10 - REMOVED_SPANS_2_2_0)
             # Assert the order of the spans created
             self.assertListEqual(
                 [
@@ -843,7 +940,7 @@ def fivehundred():
         self.assertEqual(req_span.get_tag("flask.url_rule"), "/500")
 
         # Dispatch span
-        dispatch_span = spans[3]
+        dispatch_span = spans[3 - REMOVED_SPANS_2_2_0]
         self.assertEqual(dispatch_span.service, "flask")
         self.assertEqual(dispatch_span.name, "flask.dispatch_request")
         self.assertEqual(dispatch_span.resource, "flask.dispatch_request")
@@ -853,7 +950,7 @@ def fivehundred():
         self.assertEqual(dispatch_span.get_tag("error.type"), base_exception_name)
 
         # Handler span
-        handler_span = spans[4]
+        handler_span = spans[4 - REMOVED_SPANS_2_2_0]
         self.assertEqual(handler_span.service, "flask")
         self.assertEqual(handler_span.name, "tests.contrib.flask.test_request.fivehundred")
         self.assertEqual(handler_span.resource, "/500")
@@ -863,7 +960,7 @@ def fivehundred():
         self.assertEqual(handler_span.get_tag("error.type"), base_exception_name)
 
         # User exception span
-        user_ex_span = spans[5]
+        user_ex_span = spans[5 - REMOVED_SPANS_2_2_0]
         self.assertEqual(user_ex_span.service, "flask")
         self.assertEqual(user_ex_span.name, "flask.handle_user_exception")
         self.assertEqual(user_ex_span.resource, "flask.handle_user_exception")
diff --git a/tests/contrib/flask/test_static.py b/tests/contrib/flask/test_static.py
--- a/tests/contrib/flask/test_static.py
+++ b/tests/contrib/flask/test_static.py
@@ -1,9 +1,13 @@
+from ddtrace.contrib.flask.patch import flask_version
 from ddtrace.ext import http
 from tests.utils import assert_span_http_status_code
 
 from . import BaseFlaskTestCase
 
 
+REMOVED_SPANS_2_2_0 = 1 if flask_version >= (2, 2, 0) else 0
+
+
 class FlaskStaticFileTestCase(BaseFlaskTestCase):
     def test_serve_static_file(self):
         """
@@ -16,7 +20,7 @@ def test_serve_static_file(self):
         self.assertEqual(res.data, b"Hello Flask\n")
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 9)
+        self.assertEqual(len(spans), 9 - REMOVED_SPANS_2_2_0)
 
         req_span = self.find_span_by_name(spans, "flask.request")
         handler_span = self.find_span_by_name(spans, "static")
@@ -57,7 +61,7 @@ def test_serve_static_file_404(self):
         self.assertEqual(res.status_code, 404)
 
         spans = self.get_spans()
-        self.assertEqual(len(spans), 11)
+        self.assertEqual(len(spans), 11 - REMOVED_SPANS_2_2_0)
 
         req_span = self.find_span_by_name(spans, "flask.request")
         handler_span = self.find_span_by_name(spans, "static")
diff --git a/tests/contrib/flask/test_template.py b/tests/contrib/flask/test_template.py
--- a/tests/contrib/flask/test_template.py
+++ b/tests/contrib/flask/test_template.py
@@ -2,6 +2,7 @@
 
 from ddtrace import Pin
 from ddtrace.contrib.flask import unpatch
+from ddtrace.contrib.flask.patch import flask_version
 
 from . import BaseFlaskTestCase
 
@@ -49,9 +50,10 @@ def test_render_template(self):
 
         self.assertIsNone(spans[0].service)
         self.assertEqual(spans[0].name, "flask.render_template")
-        self.assertEqual(spans[0].resource, "test.html")
+        resource = "tests.contrib.flask" if flask_version >= (2, 2, 0) else "test.html"
+        self.assertEqual(spans[0].resource, resource)  # FIXME: should always be 'test.html'?
         self.assertEqual(set(spans[0].get_tags().keys()), set(["flask.template_name", "runtime-id", "_dd.p.dm"]))
-        self.assertEqual(spans[0].get_tag("flask.template_name"), "test.html")
+        self.assertEqual(spans[0].get_tag("flask.template_name"), resource)  # FIXME: should always be 'test.html'?
         self.assertEqual(spans[1].name, "flask.do_teardown_request")
         self.assertEqual(spans[2].name, "flask.do_teardown_appcontext")
 
@@ -89,9 +91,10 @@ def test_render_template_string(self):
 
         self.assertIsNone(spans[0].service)
         self.assertEqual(spans[0].name, "flask.render_template_string")
-        self.assertEqual(spans[0].resource, "<memory>")
+        resource = "tests.contrib.flask" if flask_version >= (2, 2, 0) else "<memory>"
+        self.assertEqual(spans[0].resource, resource)  # FIXME: should always be '<memory>'?
         self.assertEqual(set(spans[0].get_tags().keys()), set(["flask.template_name", "runtime-id", "_dd.p.dm"]))
-        self.assertEqual(spans[0].get_tag("flask.template_name"), "<memory>")
+        self.assertEqual(spans[0].get_tag("flask.template_name"), resource)  # FIXME: should always be '<memory>'?
         self.assertEqual(spans[1].name, "flask.do_teardown_request")
         self.assertEqual(spans[2].name, "flask.do_teardown_appcontext")
 
diff --git a/tests/contrib/flask_autopatch/test_flask_autopatch.py b/tests/contrib/flask_autopatch/test_flask_autopatch.py
--- a/tests/contrib/flask_autopatch/test_flask_autopatch.py
+++ b/tests/contrib/flask_autopatch/test_flask_autopatch.py
@@ -2,6 +2,7 @@
 import flask
 
 from ddtrace import Pin
+from ddtrace.contrib.flask.patch import flask_version
 from ddtrace.ext import http
 from ddtrace.vendor import wrapt
 from tests.utils import TracerTestCase
@@ -9,6 +10,9 @@
 from tests.utils import assert_span_http_status_code
 
 
+REMOVED_SPANS_2_2_0 = 1 if flask_version >= (2, 2, 0) else 0
+
+
 class FlaskAutopatchTestCase(TracerTestCase):
     def setUp(self):
         super(FlaskAutopatchTestCase, self).setUp()
@@ -51,19 +55,31 @@ def index():
         self.assertEqual(res.data, b"Hello Flask")
 
         spans = self.pop_spans()
-        self.assertEqual(len(spans), 8)
-
-        self.assertListEqual(
-            [
+        self.assertEqual(len(spans), 8 - REMOVED_SPANS_2_2_0)
+
+        expected_spans = [
+            "flask.request",
+            "flask.try_trigger_before_first_request_functions",
+            "flask.preprocess_request",
+            "flask.dispatch_request",
+            "tests.contrib.flask_autopatch.test_flask_autopatch.index",
+            "flask.process_response",
+            "flask.do_teardown_request",
+            "flask.do_teardown_appcontext",
+        ]
+        if flask_version >= (2, 2, 0):
+            expected_spans = [
                 "flask.request",
-                "flask.try_trigger_before_first_request_functions",
                 "flask.preprocess_request",
                 "flask.dispatch_request",
                 "tests.contrib.flask_autopatch.test_flask_autopatch.index",
                 "flask.process_response",
                 "flask.do_teardown_request",
                 "flask.do_teardown_appcontext",
-            ],
+            ]
+
+        self.assertListEqual(
+            expected_spans,
             [s.name for s in spans],
         )
 
@@ -94,6 +110,13 @@ def index():
         # Handler span
         handler_span = spans[4]
         self.assertEqual(handler_span.service, "test-flask")
-        self.assertEqual(handler_span.name, "tests.contrib.flask_autopatch.test_flask_autopatch.index")
-        self.assertEqual(handler_span.resource, "/")
+
+        expected_span_name = (
+            "flask.process_response"
+            if flask_version >= (2, 2, 0)
+            else "tests.contrib.flask_autopatch.test_flask_autopatch.index"
+        )
+        self.assertEqual(handler_span.name, expected_span_name)
+        expected_span_resource = "flask.process_response" if flask_version >= (2, 2, 0) else "/"
+        self.assertEqual(handler_span.resource, expected_span_resource)
         self.assertEqual(req_span.error, 0)
diff --git a/tests/snapshots/tests.contrib.flask.test_flask_snapshot.test_flask_200_220.json b/tests/snapshots/tests.contrib.flask.test_flask_snapshot.test_flask_200_220.json
new file mode 100644
--- /dev/null
+++ b/tests/snapshots/tests.contrib.flask.test_flask_snapshot.test_flask_200_220.json
@@ -0,0 +1,90 @@
+[[
+  {
+    "name": "flask.request",
+    "service": "flask",
+    "resource": "GET /",
+    "trace_id": 0,
+    "span_id": 1,
+    "parent_id": 0,
+    "type": "web",
+    "meta": {
+      "_dd.p.dm": "-0",
+      "flask.endpoint": "index",
+      "flask.url_rule": "/",
+      "flask.version": "2.1.3",
+      "http.method": "GET",
+      "http.status_code": "200",
+      "http.url": "http://0.0.0.0:8000/",
+      "runtime-id": "bc8b73d966f1487aaf0e492b54e349ba"
+    },
+    "metrics": {
+      "_dd.agent_psr": 1.0,
+      "_dd.measured": 1,
+      "_dd.top_level": 1,
+      "_dd.tracer_kr": 1.0,
+      "_sampling_priority_v1": 1,
+      "system.pid": 69678
+    },
+    "duration": 365000,
+    "start": 1658267703064729000
+  },
+     {
+       "name": "flask.preprocess_request",
+       "service": "flask",
+       "resource": "flask.preprocess_request",
+       "trace_id": 0,
+       "span_id": 2,
+       "parent_id": 1,
+       "duration": 9000,
+       "start": 1658267703064952000
+     },
+     {
+       "name": "flask.dispatch_request",
+       "service": "flask",
+       "resource": "flask.dispatch_request",
+       "trace_id": 0,
+       "span_id": 3,
+       "parent_id": 1,
+       "duration": 23000,
+       "start": 1658267703064973000
+     },
+        {
+          "name": "tests.contrib.flask.app.index",
+          "service": "flask",
+          "resource": "/",
+          "trace_id": 0,
+          "span_id": 7,
+          "parent_id": 3,
+          "duration": 3000,
+          "start": 1658267703064990000
+        },
+     {
+       "name": "flask.process_response",
+       "service": "flask",
+       "resource": "flask.process_response",
+       "trace_id": 0,
+       "span_id": 4,
+       "parent_id": 1,
+       "duration": 8000,
+       "start": 1658267703065020000
+     },
+     {
+       "name": "flask.do_teardown_request",
+       "service": "flask",
+       "resource": "flask.do_teardown_request",
+       "trace_id": 0,
+       "span_id": 5,
+       "parent_id": 1,
+       "duration": 6000,
+       "start": 1658267703065065000
+     },
+     {
+       "name": "flask.do_teardown_appcontext",
+       "service": "flask",
+       "resource": "flask.do_teardown_appcontext",
+       "trace_id": 0,
+       "span_id": 6,
+       "parent_id": 1,
+       "duration": 4000,
+       "start": 1658267703065084000
+     }]]
diff --git a/tests/snapshots/tests.contrib.flask.test_flask_snapshot.test_flask_stream_220.json b/tests/snapshots/tests.contrib.flask.test_flask_snapshot.test_flask_stream_220.json
new file mode 100644
--- /dev/null
+++ b/tests/snapshots/tests.contrib.flask.test_flask_snapshot.test_flask_stream_220.json
@@ -0,0 +1,90 @@
+[[
+  {
+    "name": "flask.request",
+    "service": "flask",
+    "resource": "GET /stream",
+    "trace_id": 0,
+    "span_id": 1,
+    "parent_id": 0,
+    "type": "web",
+    "meta": {
+      "_dd.p.dm": "-0",
+      "flask.endpoint": "hello",
+      "flask.url_rule": "/stream",
+      "flask.version": "2.1.3",
+      "http.method": "GET",
+      "http.status_code": "200",
+      "http.url": "http://0.0.0.0:8000/stream",
+      "runtime-id": "c6a8a096ffa146b590298e5b0960757d"
+    },
+    "metrics": {
+      "_dd.agent_psr": 1.0,
+      "_dd.measured": 1,
+      "_dd.top_level": 1,
+      "_dd.tracer_kr": 1.0,
+      "_sampling_priority_v1": 1,
+      "system.pid": 72327
+    },
+    "duration": 326000,
+    "start": 1658268142335582000
+  },
+     {
+       "name": "flask.preprocess_request",
+       "service": "flask",
+       "resource": "flask.preprocess_request",
+       "trace_id": 0,
+       "span_id": 2,
+       "parent_id": 1,
+       "duration": 9000,
+       "start": 1658268142335780000
+     },
+     {
+       "name": "flask.dispatch_request",
+       "service": "flask",
+       "resource": "flask.dispatch_request",
+       "trace_id": 0,
+       "span_id": 3,
+       "parent_id": 1,
+       "duration": 31000,
+       "start": 1658268142335800000
+     },
+        {
+          "name": "tests.contrib.flask.app.hello",
+          "service": "flask",
+          "resource": "/stream",
+          "trace_id": 0,
+          "span_id": 7,
+          "parent_id": 3,
+          "duration": 14000,
+          "start": 1658268142335814000
+        },
+     {
+       "name": "flask.process_response",
+       "service": "flask",
+       "resource": "flask.process_response",
+       "trace_id": 0,
+       "span_id": 4,
+       "parent_id": 1,
+       "duration": 6000,
+       "start": 1658268142335842000
+     },
+     {
+       "name": "flask.do_teardown_request",
+       "service": "flask",
+       "resource": "flask.do_teardown_request",
+       "trace_id": 0,
+       "span_id": 5,
+       "parent_id": 1,
+       "duration": 6000,
+       "start": 1658268142335881000
+     },
+     {
+       "name": "flask.do_teardown_appcontext",
+       "service": "flask",
+       "resource": "flask.do_teardown_appcontext",
+       "trace_id": 0,
+       "span_id": 6,
+       "parent_id": 1,
+       "duration": 3000,
+       "start": 1658268142335899000
+     }]]