This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new b20d7cfcc5 Use `packaging` for parse requirements in documentation 
generation (#35718)
b20d7cfcc5 is described below

commit b20d7cfcc5607171c2dc8923b01f9f36d825cf21
Author: Andrey Anshin <[email protected]>
AuthorDate: Sat Nov 18 16:56:21 2023 +0400

    Use `packaging` for parse requirements in documentation generation (#35718)
---
 .../prepare_providers/provider_documentation.py    | 44 ++++++++++++---
 dev/breeze/tests/test_provider_documentation.py    | 64 ++++++++++++++++++----
 2 files changed, 88 insertions(+), 20 deletions(-)

diff --git 
a/dev/breeze/src/airflow_breeze/prepare_providers/provider_documentation.py 
b/dev/breeze/src/airflow_breeze/prepare_providers/provider_documentation.py
index 6842d1c4fd..5cddc80d92 100644
--- a/dev/breeze/src/airflow_breeze/prepare_providers/provider_documentation.py
+++ b/dev/breeze/src/airflow_breeze/prepare_providers/provider_documentation.py
@@ -32,6 +32,7 @@ from typing import Any, Iterable, NamedTuple
 
 import jinja2
 import semver
+from packaging.requirements import Requirement
 from rich.syntax import Syntax
 
 from airflow_breeze.global_constants import PROVIDER_DEPENDENCIES
@@ -115,6 +116,34 @@ class Change(NamedTuple):
     pr: str | None
 
 
+class PipRequirements(NamedTuple):
+    """Store details about python packages"""
+
+    package: str
+    version_required: str
+
+    @classmethod
+    def from_requirement(cls, requirement_string: str) -> PipRequirements:
+        req = Requirement(requirement_string)
+
+        package = req.name
+        if req.extras:
+            # Sort extras by name
+            package += f"[{','.join(sorted(req.extras))}]"
+
+        version_required = ""
+        if req.specifier:
+            # String representation of `packaging.specifiers.SpecifierSet` 
sorted by the operator
+            # which might not looking good, e.g. '>=5.3.0,<6,!=5.3.3,!=5.3.2' 
transform into the
+            # '!=5.3.3,!=5.3.2,<6,>=5.3.0'. Instead of that we sort by version 
and resulting string would be
+            # '>=5.3.0,!=5.3.2,!=5.3.3,<6'
+            version_required = ",".join(map(str, sorted(req.specifier, 
key=lambda spec: spec.version)))
+        if req.marker:
+            version_required += f"; {req.marker}"
+
+        return cls(package=package, version_required=version_required.strip())
+
+
 class TypeOfChange(Enum):
     DOCUMENTATION = "d"
     BUGFIX = "b"
@@ -566,15 +595,12 @@ def _convert_pip_requirements_to_table(requirements: 
Iterable[str], markdown: bo
     headers = ["PIP package", "Version required"]
     table_data = []
     for dependency in requirements:
-        found = re.match(r"(^[^<=>~!]*)([^<=>~!]?.*)$", dependency)
-        if found:
-            package = found.group(1)
-            version_required = found.group(2)
-            if version_required != "":
-                version_required = f"`{version_required}`" if markdown else 
f"``{version_required}``"
-            table_data.append((f"`{package}`" if markdown else 
f"``{package}``", version_required))
-        else:
-            table_data.append((dependency, ""))
+        req = PipRequirements.from_requirement(dependency)
+        formatted_package = f"`{req.package}`" if markdown else 
f"``{req.package}``"
+        formatted_version = ""
+        if req.version_required:
+            formatted_version = f"`{req.version_required}`" if markdown else 
f"``{req.version_required}``"
+        table_data.append((formatted_package, formatted_version))
     return tabulate(table_data, headers=headers, tablefmt="pipe" if markdown 
else "rst")
 
 
diff --git a/dev/breeze/tests/test_provider_documentation.py 
b/dev/breeze/tests/test_provider_documentation.py
index cf1a1e3024..764038769d 100644
--- a/dev/breeze/tests/test_provider_documentation.py
+++ b/dev/breeze/tests/test_provider_documentation.py
@@ -22,6 +22,7 @@ import pytest
 
 from airflow_breeze.prepare_providers.provider_documentation import (
     Change,
+    PipRequirements,
     _convert_git_changes_to_table,
     _convert_pip_requirements_to_table,
     _find_insertion_index_for_version,
@@ -236,33 +237,74 @@ def test_convert_git_changes_to_table(input: str, output: 
str, markdown: bool, c
     assert list_of_changes[2].pr == "12346"
 
 
[email protected](
+    "requirement_string, expected",
+    [
+        pytest.param("apache-airflow", ("apache-airflow", ""), 
id="no-version-specifier"),
+        pytest.param(
+            "apache-airflow <2.7,>=2.5", ("apache-airflow", ">=2.5,<2.7"), 
id="range-version-specifier"
+        ),
+        pytest.param("watchtower~=3.0.1", ("watchtower", "~=3.0.1"), 
id="compat-version-specifier"),
+        pytest.param("PyGithub!=1.58", ("PyGithub", "!=1.58"), 
id="not-equal-version-specifier"),
+        pytest.param(
+            "apache-airflow[amazon,google,microsoft.azure,docker]>2.7.0",
+            ("apache-airflow[amazon,docker,google,microsoft.azure]", ">2.7.0"),
+            id="package-with-extra",
+        ),
+        pytest.param(
+            'mysql-connector-python>=8.0.11; platform_machine != "aarch64"',
+            ("mysql-connector-python", '>=8.0.11; platform_machine != 
"aarch64"'),
+            id="version-with-platform-marker",
+        ),
+        pytest.param(
+            "backports.zoneinfo>=0.2.1;python_version<'3.9'",
+            ("backports.zoneinfo", '>=0.2.1; python_version < "3.9"'),
+            id="version-with-python-marker",
+        ),
+        pytest.param(
+            "celery>=5.3.0,<6,!=5.3.3,!=5.3.2",
+            ("celery", ">=5.3.0,!=5.3.2,!=5.3.3,<6"),
+            id="complex-version-specifier",
+        ),
+        pytest.param(
+            "apache-airflow; python_version<'3.12' or platform_machine != 
'i386'",
+            ("apache-airflow", '; python_version < "3.12" or platform_machine 
!= "i386"'),
+            id="no-version-specifier-with-complex-marker",
+        ),
+    ],
+)
+def test_parse_pip_requirements_parse(requirement_string, expected):
+    assert PipRequirements.from_requirement(requirement_string) == expected
+
+
 @pytest.mark.parametrize(
     "requirements, markdown, table",
     [
         (
-            ["apache-airflow>2.5.0"],
+            ["apache-airflow>2.5.0", "apache-airflow-providers-http"],
             False,
             """
-==================  ==================
-PIP package         Version required
-==================  ==================
-``apache-airflow``  ``>2.5.0``
-==================  ==================
+=================================  ==================
+PIP package                        Version required
+=================================  ==================
+``apache-airflow``                 ``>2.5.0``
+``apache-airflow-providers-http``
+=================================  ==================
 """,
         ),
         (
-            ["apache-airflow>2.5.0"],
+            ["apache-airflow>2.5.0", "apache-airflow-providers-http"],
             True,
             """
-| PIP package      | Version required   |
-|:-----------------|:-------------------|
-| `apache-airflow` | `>2.5.0`           |
+| PIP package                     | Version required   |
+|:--------------------------------|:-------------------|
+| `apache-airflow`                | `>2.5.0`           |
+| `apache-airflow-providers-http` |                    |
 """,
         ),
     ],
 )
 def test_convert_pip_requirements_to_table(requirements: Iterable[str], 
markdown: bool, table: str):
-    print(_convert_pip_requirements_to_table(requirements, markdown))
     assert _convert_pip_requirements_to_table(requirements, markdown).strip() 
== table.strip()
 
 

Reply via email to