From ad35f73bdead3a04f67800d99a0b55116e4dea0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Wed, 17 Nov 2021 09:56:24 +0100
Subject: [PATCH 01/11] Update INTHEWILD.md

---
 INTHEWILD.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/INTHEWILD.md b/INTHEWILD.md
index 5a674333f837e..3cf5c69cabb6e 100644
--- a/INTHEWILD.md
+++ b/INTHEWILD.md
@@ -425,6 +425,7 @@ Currently, **officially** using Airflow:
 1. [Ubisoft](https://www.ubisoft.com/) [[@Walkoss](https://github.com/Walkoss)]
 1. [Udacity](https://www.udacity.com/) [[@dandikunited](https://github.com/DandikUnited), [@simon-uc](https://github.com/simon-uc)]
 1. [Umami Collective](https://umamicollective.com) [[@juanuicich](https://github.com/juanuicich)]
+1. [Unacast](https://unacast.com)[[@unacast](https://github.com/unacast)]
 1. [United Airlines](https://www.united.com/) [[@ilopezfr](https://github.com/ilopezfr)]
 1. [Upsight](https://www.upsight.com)
 1. [US Bank](https://www.usbank.com) [@BShraman]

From e21f563871305fbd0eafcadd3fcc8fc0f7532a5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Wed, 17 Nov 2021 13:03:04 +0100
Subject: [PATCH 02/11] Enable GFM like markdown via markdown-it-py

Closes: #16435
---
 airflow/www/utils.py    |  5 ++-
 setup.py                |  3 ++
 tests/www/test_utils.py | 84 ++++++++++++++++++++++++++---------------
 3 files changed, 59 insertions(+), 33 deletions(-)

diff --git a/airflow/www/utils.py b/airflow/www/utils.py
index 5d7368b367237..27c7569a0e66e 100644
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -21,7 +21,7 @@
 from typing import Any, Dict, List, Optional, Union
 from urllib.parse import urlencode
 
-import markdown
+from markdown_it import MarkdownIt
 import sqlalchemy as sqla
 from flask import Markup, Response, request, url_for
 from flask.helpers import flash
@@ -395,10 +395,11 @@ def json_render(obj, lexer):
 
 def wrapped_markdown(s, css_class='rich_doc'):
     """Convert a Markdown string to HTML."""
+    md = MarkdownIt("gfm-like")
     if s is None:
         return None
     s = textwrap.dedent(s)
-    return Markup(f'<div class="{css_class}" >' + markdown.markdown(s, extensions=['tables']) + "</div>")
+    return Markup(f'<div class="{css_class}" >' + md.render(s) + "</div>")
 
 
 def get_attr_renderer():
diff --git a/setup.py b/setup.py
index afbea8596ad16..c478252445ea8 100644
--- a/setup.py
+++ b/setup.py
@@ -531,6 +531,9 @@ def write_version(filename: str = os.path.join(*[my_dir, "airflow", "git_version
     'ipdb',
     'jira',
     'jsondiff',
+    'markdown-it-py',
+    'mdit-py-plugins',
+    'linkify-it-py',
     'mongomock',
     'moto~=2.2, >=2.2.7',
     'mypy==0.770',
diff --git a/tests/www/test_utils.py b/tests/www/test_utils.py
index 01c49e1fdcafd..d5367602bf017 100644
--- a/tests/www/test_utils.py
+++ b/tests/www/test_utils.py
@@ -87,11 +87,11 @@ def test_params_none_and_zero(self):
     def test_params_all(self):
         query = utils.get_params(tags=['tag1', 'tag2'], status='active', page=3, search='bash_')
         assert {
-            'tags': ['tag1', 'tag2'],
-            'page': ['3'],
-            'search': ['bash_'],
-            'status': ['active'],
-        } == parse_qs(query)
+                   'tags': ['tag1', 'tag2'],
+                   'page': ['3'],
+                   'search': ['bash_'],
+                   'status': ['active'],
+               } == parse_qs(query)
 
     def test_params_escape(self):
         assert 'search=%27%3E%22%2F%3E%3Cimg+src%3Dx+onerror%3Dalert%281%29%3E' == utils.get_params(
@@ -160,7 +160,7 @@ def setUp(self):
         self.attr_renderer = utils.get_attr_renderer()
 
     def test_python_callable(self):
-        def example_callable(unused_self):
+        def example_callable():
             print("example")
 
         rendered = self.attr_renderer["python_callable"](example_callable)
@@ -184,29 +184,46 @@ def test_markdown_none(self):
 class TestWrappedMarkdown(unittest.TestCase):
     def test_wrapped_markdown_with_docstring_curly_braces(self):
         rendered = wrapped_markdown("{braces}", css_class="a_class")
-        assert '<div class="a_class" ><p>{braces}</p></div>' == rendered
+        self.assertEqual(
+            '''<div class="a_class" ><p>{braces}</p>
+</div>''', rendered)
 
     def test_wrapped_markdown_with_some_markdown(self):
-        rendered = wrapped_markdown("*italic*\n**bold**\n", css_class="a_class")
-        assert (
+        rendered = wrapped_markdown("""*italic*
+        **bold**
+        """, css_class="a_class")
+        self.assertEqual(
             '''<div class="a_class" ><p><em>italic</em>
-<strong>bold</strong></p></div>'''
-            == rendered
-        )
+<strong>bold</strong></p>
+</div>''', rendered)
 
     def test_wrapped_markdown_with_table(self):
         rendered = wrapped_markdown(
-            """| Job | Duration |
-               | ----------- | ----------- |
-               | ETL | 14m |"""
+            """
+| Job | Duration |
+| ----------- | ----------- |
+| ETL | 14m |
+"""
         )
 
-        assert (
-            '<div class="rich_doc" ><table>\n<thead>\n<tr>\n<th>Job</th>\n'
-            '<th>Duration</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>ETL'
-            '</td>\n<td>14m</td>\n</tr>\n</tbody>\n'
-            '</table></div>'
-        ) == rendered
+        self.assertEqual(
+            '''<div class="rich_doc" ><table>
+<thead>
+<tr>
+<th>Job</th>
+<th>Duration</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ETL</td>
+<td>14m</td>
+</tr>
+</tbody>
+</table>
+</div>'''
+            , rendered
+        )
 
     def test_wrapped_markdown_with_indented_lines(self):
         rendered = wrapped_markdown(
@@ -217,7 +234,8 @@ def test_wrapped_markdown_with_indented_lines(self):
             """
         )
 
-        assert '<div class="rich_doc" ><h1>header</h1>\n<p>1st line\n2nd line</p></div>' == rendered
+        self.assertEqual('''<div class="rich_doc" ><h1>header</h1>\n<p>1st line\n2nd line</p>
+</div>''', rendered)
 
     def test_wrapped_markdown_with_raw_code_block(self):
         rendered = wrapped_markdown(
@@ -234,11 +252,10 @@ def test_wrapped_markdown_with_raw_code_block(self):
             """
         )
 
-        assert (
-            '<div class="rich_doc" ><h1>Markdown code block</h1>\n'
-            '<p>Inline <code>code</code> works well.</p>\n'
-            '<pre><code>Code block\ndoes not\nrespect\nnewlines\n</code></pre></div>'
-        ) == rendered
+        self.assertEqual('''<div class="rich_doc" ><h1>Markdown code block</h1>
+<p>Inline <code>code</code> works well.</p>
+<pre><code>Code block\ndoes not\nrespect\nnewlines\n</code></pre>
+</div>''', rendered)
 
     def test_wrapped_markdown_with_nested_list(self):
         rendered = wrapped_markdown(
@@ -250,7 +267,12 @@ def test_wrapped_markdown_with_nested_list(self):
             """
         )
 
-        assert (
-            '<div class="rich_doc" ><h3>Docstring with a code block</h3>\n'
-            '<ul>\n<li>And<ul>\n<li>A nested list</li>\n</ul>\n</li>\n</ul></div>'
-        ) == rendered
+        self.assertEqual('''<div class="rich_doc" ><h3>Docstring with a code block</h3>
+<ul>
+<li>And
+<ul>
+<li>A nested list</li>
+</ul>
+</li>
+</ul>
+</div>''', rendered)

From 69fa8241ebfeef8fadcb0b26c74e1e080799f5c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Wed, 17 Nov 2021 16:04:53 +0100
Subject: [PATCH 03/11] Running isort on utils.py

---
 airflow/www/utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/airflow/www/utils.py b/airflow/www/utils.py
index 27c7569a0e66e..7d74717d978a4 100644
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -21,13 +21,13 @@
 from typing import Any, Dict, List, Optional, Union
 from urllib.parse import urlencode
 
-from markdown_it import MarkdownIt
 import sqlalchemy as sqla
 from flask import Markup, Response, request, url_for
 from flask.helpers import flash
 from flask_appbuilder.forms import FieldConverter
 from flask_appbuilder.models.sqla import filters as fab_sqlafilters
 from flask_appbuilder.models.sqla.interface import SQLAInterface
+from markdown_it import MarkdownIt
 from pendulum.datetime import DateTime
 from pygments import highlight, lexers
 from pygments.formatters import HtmlFormatter

From 92c5d121323495bd9ecdeac4467251cae383fc2b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Fri, 19 Nov 2021 11:15:04 +0100
Subject: [PATCH 04/11] Run pre-commit black to format some files

---
 tests/www/test_utils.py | 85 ++++++++++++++++++++++++++++++++---------
 1 file changed, 68 insertions(+), 17 deletions(-)

diff --git a/tests/www/test_utils.py b/tests/www/test_utils.py
index d5367602bf017..517ad21bc6332 100644
--- a/tests/www/test_utils.py
+++ b/tests/www/test_utils.py
@@ -87,11 +87,11 @@ def test_params_none_and_zero(self):
     def test_params_all(self):
         query = utils.get_params(tags=['tag1', 'tag2'], status='active', page=3, search='bash_')
         assert {
-                   'tags': ['tag1', 'tag2'],
-                   'page': ['3'],
-                   'search': ['bash_'],
-                   'status': ['active'],
-               } == parse_qs(query)
+            'tags': ['tag1', 'tag2'],
+            'page': ['3'],
+            'search': ['bash_'],
+            'status': ['active'],
+        } == parse_qs(query)
 
     def test_params_escape(self):
         assert 'search=%27%3E%22%2F%3E%3Cimg+src%3Dx+onerror%3Dalert%281%29%3E' == utils.get_params(
@@ -186,16 +186,23 @@ def test_wrapped_markdown_with_docstring_curly_braces(self):
         rendered = wrapped_markdown("{braces}", css_class="a_class")
         self.assertEqual(
             '''<div class="a_class" ><p>{braces}</p>
-</div>''', rendered)
+</div>''',
+            rendered,
+        )
 
     def test_wrapped_markdown_with_some_markdown(self):
-        rendered = wrapped_markdown("""*italic*
+        rendered = wrapped_markdown(
+            """*italic*
         **bold**
-        """, css_class="a_class")
+        """,
+            css_class="a_class",
+        )
         self.assertEqual(
             '''<div class="a_class" ><p><em>italic</em>
 <strong>bold</strong></p>
-</div>''', rendered)
+</div>''',
+            rendered,
+        )
 
     def test_wrapped_markdown_with_table(self):
         rendered = wrapped_markdown(
@@ -221,8 +228,8 @@ def test_wrapped_markdown_with_table(self):
 </tr>
 </tbody>
 </table>
-</div>'''
-            , rendered
+</div>''',
+            rendered,
         )
 
     def test_wrapped_markdown_with_indented_lines(self):
@@ -234,8 +241,11 @@ def test_wrapped_markdown_with_indented_lines(self):
             """
         )
 
-        self.assertEqual('''<div class="rich_doc" ><h1>header</h1>\n<p>1st line\n2nd line</p>
-</div>''', rendered)
+        self.assertEqual(
+            '''<div class="rich_doc" ><h1>header</h1>\n<p>1st line\n2nd line</p>
+</div>''',
+            rendered,
+        )
 
     def test_wrapped_markdown_with_raw_code_block(self):
         rendered = wrapped_markdown(
@@ -252,10 +262,13 @@ def test_wrapped_markdown_with_raw_code_block(self):
             """
         )
 
-        self.assertEqual('''<div class="rich_doc" ><h1>Markdown code block</h1>
+        self.assertEqual(
+            '''<div class="rich_doc" ><h1>Markdown code block</h1>
 <p>Inline <code>code</code> works well.</p>
 <pre><code>Code block\ndoes not\nrespect\nnewlines\n</code></pre>
-</div>''', rendered)
+</div>''',
+            rendered,
+        )
 
     def test_wrapped_markdown_with_nested_list(self):
         rendered = wrapped_markdown(
@@ -267,7 +280,8 @@ def test_wrapped_markdown_with_nested_list(self):
             """
         )
 
-        self.assertEqual('''<div class="rich_doc" ><h3>Docstring with a code block</h3>
+        self.assertEqual(
+            '''<div class="rich_doc" ><h3>Docstring with a code block</h3>
 <ul>
 <li>And
 <ul>
@@ -275,4 +289,41 @@ def test_wrapped_markdown_with_nested_list(self):
 </ul>
 </li>
 </ul>
-</div>''', rendered)
+</div>''',
+            rendered,
+        )
+
+    def test_wrapped_markdown_with_collapsible_section(self):
+        rendered = wrapped_markdown(
+            """
+# A collapsible section with markdown
+<details>
+  <summary>Click to expand!</summary>
+
+  ## Heading
+  1. A numbered
+  2. list
+     * With some
+     * Sub bullets
+</details>
+            """
+        )
+
+        self.assertEqual(
+            '''<div class="rich_doc" ><h1>A collapsible section with markdown</h1>
+<details>
+  <summary>Click to expand!</summary>
+<h2>Heading</h2>
+<ol>
+<li>A numbered</li>
+<li>list
+<ul>
+<li>With some</li>
+<li>Sub bullets</li>
+</ul>
+</li>
+</ol>
+</details>
+</div>''',
+            rendered,
+        )

From 8db36ddec2cbca37f8e593d72a3c2567a92c899c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Fri, 19 Nov 2021 14:59:30 +0100
Subject: [PATCH 05/11] Switch back to plain assert

---
 tests/www/test_utils.py | 43 +++++++++++++++++++++--------------------
 1 file changed, 22 insertions(+), 21 deletions(-)

diff --git a/tests/www/test_utils.py b/tests/www/test_utils.py
index 517ad21bc6332..d607f193d9e8f 100644
--- a/tests/www/test_utils.py
+++ b/tests/www/test_utils.py
@@ -184,10 +184,10 @@ def test_markdown_none(self):
 class TestWrappedMarkdown(unittest.TestCase):
     def test_wrapped_markdown_with_docstring_curly_braces(self):
         rendered = wrapped_markdown("{braces}", css_class="a_class")
-        self.assertEqual(
+        assert (
             '''<div class="a_class" ><p>{braces}</p>
-</div>''',
-            rendered,
+</div>'''
+            == rendered
         )
 
     def test_wrapped_markdown_with_some_markdown(self):
@@ -197,11 +197,12 @@ def test_wrapped_markdown_with_some_markdown(self):
         """,
             css_class="a_class",
         )
-        self.assertEqual(
+
+        assert (
             '''<div class="a_class" ><p><em>italic</em>
 <strong>bold</strong></p>
-</div>''',
-            rendered,
+</div>'''
+            == rendered
         )
 
     def test_wrapped_markdown_with_table(self):
@@ -213,7 +214,7 @@ def test_wrapped_markdown_with_table(self):
 """
         )
 
-        self.assertEqual(
+        assert (
             '''<div class="rich_doc" ><table>
 <thead>
 <tr>
@@ -228,8 +229,8 @@ def test_wrapped_markdown_with_table(self):
 </tr>
 </tbody>
 </table>
-</div>''',
-            rendered,
+</div>'''
+            == rendered
         )
 
     def test_wrapped_markdown_with_indented_lines(self):
@@ -241,10 +242,10 @@ def test_wrapped_markdown_with_indented_lines(self):
             """
         )
 
-        self.assertEqual(
+        assert (
             '''<div class="rich_doc" ><h1>header</h1>\n<p>1st line\n2nd line</p>
-</div>''',
-            rendered,
+</div>'''
+            == rendered
         )
 
     def test_wrapped_markdown_with_raw_code_block(self):
@@ -262,12 +263,12 @@ def test_wrapped_markdown_with_raw_code_block(self):
             """
         )
 
-        self.assertEqual(
+        assert (
             '''<div class="rich_doc" ><h1>Markdown code block</h1>
 <p>Inline <code>code</code> works well.</p>
 <pre><code>Code block\ndoes not\nrespect\nnewlines\n</code></pre>
-</div>''',
-            rendered,
+</div>'''
+            == rendered
         )
 
     def test_wrapped_markdown_with_nested_list(self):
@@ -280,7 +281,7 @@ def test_wrapped_markdown_with_nested_list(self):
             """
         )
 
-        self.assertEqual(
+        assert (
             '''<div class="rich_doc" ><h3>Docstring with a code block</h3>
 <ul>
 <li>And
@@ -289,8 +290,8 @@ def test_wrapped_markdown_with_nested_list(self):
 </ul>
 </li>
 </ul>
-</div>''',
-            rendered,
+</div>'''
+            == rendered
         )
 
     def test_wrapped_markdown_with_collapsible_section(self):
@@ -309,7 +310,7 @@ def test_wrapped_markdown_with_collapsible_section(self):
             """
         )
 
-        self.assertEqual(
+        assert (
             '''<div class="rich_doc" ><h1>A collapsible section with markdown</h1>
 <details>
   <summary>Click to expand!</summary>
@@ -324,6 +325,6 @@ def test_wrapped_markdown_with_collapsible_section(self):
 </li>
 </ol>
 </details>
-</div>''',
-            rendered,
+</div>'''
+            == rendered
         )

From aebf28d7211ef963beacd18a71895152e285f55c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Thu, 10 Mar 2022 08:32:05 +0100
Subject: [PATCH 06/11] Make <summary> within <details> in Markdown collapsible

---
 airflow/www/static/css/main.css | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/airflow/www/static/css/main.css b/airflow/www/static/css/main.css
index ad13ac68f85db..b904c35e53a08 100644
--- a/airflow/www/static/css/main.css
+++ b/airflow/www/static/css/main.css
@@ -463,3 +463,7 @@ label[for="timezone-other"],
 .tooltip.d3-tip {
   z-index: 1070;
 }
+
+details summary {
+  display: list-item;
+}

From 913007f5209313166a7f21fb48bcfc280b677632 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Mon, 14 Mar 2022 10:57:51 +0100
Subject: [PATCH 07/11] Update airflow/www/utils.py

Following @uranusjr 's suggestion

Co-authored-by: Tzu-ping Chung <uranusjr@gmail.com>
---
 airflow/www/utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/airflow/www/utils.py b/airflow/www/utils.py
index db2715b79ad8c..ffa4c1fa3a515 100644
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -483,7 +483,7 @@ def wrapped_markdown(s, css_class='rich_doc'):
     if s is None:
         return None
     s = textwrap.dedent(s)
-    return Markup(f'<div class="{css_class}" >' + md.render(s) + "</div>")
+    return Markup(f'<div class="{css_class}" >{md.render(s)}</div>')
 
 
 def get_attr_renderer():

From 35e0df1df70e0f43e03845a484657e1a91d8cdc0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Mon, 14 Mar 2022 11:00:26 +0100
Subject: [PATCH 08/11] Reintroduce unused_self in example_callable

---
 tests/www/test_utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/www/test_utils.py b/tests/www/test_utils.py
index d607f193d9e8f..8ac06f75de010 100644
--- a/tests/www/test_utils.py
+++ b/tests/www/test_utils.py
@@ -160,7 +160,7 @@ def setUp(self):
         self.attr_renderer = utils.get_attr_renderer()
 
     def test_python_callable(self):
-        def example_callable():
+        def example_callable(unused_self):
             print("example")
 
         rendered = self.attr_renderer["python_callable"](example_callable)

From 6f289dd07f8a0f2f75f602bdf38121dd16aaaee0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Mon, 14 Mar 2022 12:43:25 +0100
Subject: [PATCH 09/11] Sort imports in utils.py

---
 airflow/www/utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/airflow/www/utils.py b/airflow/www/utils.py
index ffa4c1fa3a515..b094ebd90296e 100644
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -29,8 +29,8 @@
 from flask_appbuilder.models.sqla import filters as fab_sqlafilters
 from flask_appbuilder.models.sqla.filters import get_field_setup_query, set_value_to_type
 from flask_appbuilder.models.sqla.interface import SQLAInterface
-from markdown_it import MarkdownIt
 from flask_babel import lazy_gettext
+from markdown_it import MarkdownIt
 from markupsafe import Markup
 from pendulum.datetime import DateTime
 from pygments import highlight, lexers

From 7dc729c8ef1714202df82f06a6b7dbecd700e41d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Tue, 19 Apr 2022 09:15:27 +0200
Subject: [PATCH 10/11] Update setup.py

Co-authored-by: Tzu-ping Chung <uranusjr@gmail.com>
---
 setup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index fa67fbfe812fe..da306c275641d 100644
--- a/setup.py
+++ b/setup.py
@@ -616,9 +616,9 @@ def write_version(filename: str = os.path.join(*[my_dir, "airflow", "git_version
     'ipdb',
     'jira',
     'jsondiff',
+    'linkify-it-py',
     'markdown-it-py',
     'mdit-py-plugins',
-    'linkify-it-py',
     'mongomock',
     'moto>=3.0.7',
     'parameterized',

From 536c530602008b0a9da15baead3abc67e4ca0bd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?= <torbjorn.vatn@unacast.com>
Date: Fri, 17 Jun 2022 13:38:18 +0200
Subject: [PATCH 11/11] Move the mardown deps to setup.cfg

---
 setup.cfg | 3 +++
 setup.py  | 3 ---
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/setup.cfg b/setup.cfg
index bd7310499674b..8d996337ebfb2 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -135,6 +135,7 @@ install_requires =
     # we pin to the same upper-bound as connexion.
     jsonschema>=3.2.0, <5.0
     lazy-object-proxy
+    linkify-it-py>=2.0.0
     lockfile>=0.12.2
     markdown>=3.0
     # Markupsafe 2.1.0 breaks with error: import name 'soft_unicode' from 'markupsafe'.
@@ -142,8 +143,10 @@ install_requires =
     # https://github.com/pallets/markupsafe/issues/284
     # or when we will be able to upgrade JINJA to newer version (currently limited due to Flask and
     # Flask Application Builder)
+    markdown-it-py>=2.1.0
     markupsafe>=1.1.1,<2.1.0
     marshmallow-oneofschema>=2.0.1
+    mdit-py-plugins>=0.3.0
     packaging>=14.0
     pathspec~=0.9.0
     pendulum>=2.0
diff --git a/setup.py b/setup.py
index 8cab11401bd1d..a07368d127732 100644
--- a/setup.py
+++ b/setup.py
@@ -632,9 +632,6 @@ def write_version(filename: str = os.path.join(*[my_dir, "airflow", "git_version
     'ipdb',
     'jira',
     'jsondiff',
-    'linkify-it-py',
-    'markdown-it-py',
-    'mdit-py-plugins',
     'mongomock',
     'moto[cloudformation, glue]>=3.1.12',
     'parameterized',
