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

mobuchowski 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 b341b5927e docs: Add doc page with providers deprecations (#37075)
b341b5927e is described below

commit b341b5927e6a7271a85558d3da766b07165ee22a
Author: Kacper Muda <[email protected]>
AuthorDate: Wed Feb 14 15:19:54 2024 +0100

    docs: Add doc page with providers deprecations (#37075)
---
 airflow/providers/amazon/aws/hooks/base_aws.py     |   4 +-
 .../providers/amazon/aws/hooks/redshift_cluster.py |   2 +-
 docs/README.rst                                    |  13 +++
 .../core-extensions/deprecations.rst               |  31 ++++++
 docs/exts/docs_build/errors.py                     |   5 +-
 docs/exts/docs_build/spelling_checks.py            |  13 ++-
 docs/exts/operators_and_hooks_ref.py               | 115 +++++++++++++++++++++
 docs/exts/templates/deprecations.rst.jinja2        |  37 +++++++
 8 files changed, 212 insertions(+), 8 deletions(-)

diff --git a/airflow/providers/amazon/aws/hooks/base_aws.py 
b/airflow/providers/amazon/aws/hooks/base_aws.py
index 5f1e075cc3..ff2f33dbe8 100644
--- a/airflow/providers/amazon/aws/hooks/base_aws.py
+++ b/airflow/providers/amazon/aws/hooks/base_aws.py
@@ -1022,7 +1022,7 @@ except ImportError:
 
 @deprecated(
     reason=(
-        "airflow.providers.amazon.aws.hook.base_aws.BaseAsyncSessionFactory "
+        "`airflow.providers.amazon.aws.hook.base_aws.BaseAsyncSessionFactory` "
         "has been deprecated and will be removed in future"
     ),
     category=AirflowProviderDeprecationWarning,
@@ -1116,7 +1116,7 @@ class BaseAsyncSessionFactory(BaseSessionFactory):
 
 @deprecated(
     reason=(
-        "airflow.providers.amazon.aws.hook.base_aws.AwsBaseAsyncHook "
+        "`airflow.providers.amazon.aws.hook.base_aws.AwsBaseAsyncHook` "
         "has been deprecated and will be removed in future"
     ),
     category=AirflowProviderDeprecationWarning,
diff --git a/airflow/providers/amazon/aws/hooks/redshift_cluster.py 
b/airflow/providers/amazon/aws/hooks/redshift_cluster.py
index 286299661c..6168278a59 100644
--- a/airflow/providers/amazon/aws/hooks/redshift_cluster.py
+++ b/airflow/providers/amazon/aws/hooks/redshift_cluster.py
@@ -197,7 +197,7 @@ class RedshiftHook(AwsBaseHook):
 
 @deprecated(
     reason=(
-        "airflow.providers.amazon.aws.hook.base_aws.RedshiftAsyncHook "
+        "`airflow.providers.amazon.aws.hook.base_aws.RedshiftAsyncHook` "
         "has been deprecated and will be removed in future"
     ),
     category=AirflowProviderDeprecationWarning,
diff --git a/docs/README.rst b/docs/README.rst
index d5cddec486..9cb436c0f2 100644
--- a/docs/README.rst
+++ b/docs/README.rst
@@ -258,6 +258,19 @@ warning to report the wrong line in the file for your 
missing white space. If yo
 and the line number it reports is wrong, this is a known error. You can find 
the missing blank space by searching for the syntax you used to make your
 list, code block, or other whitespace-sensitive markup element.
 
+
+spelling error with class or method name
+****************************************
+When a spelling error occurs that has a class/function/method name as 
incorrectly spelled,
+instead of whitelisting it in docs/spelling_wordlist.txt you should make sure 
that
+this name is quoted with backticks "`" - this should exclude it from 
spellchecking process.
+
+In this example error, You should change the line with error so that the whole 
path is inside backticks "`".
+.. code-block:: text
+
+    Incorrect Spelling: 'BaseAsyncSessionFactory'
+    Line with Error: ' 
airflow.providers.amazon.aws.hook.base_aws.BaseAsyncSessionFactory has been 
deprecated'
+
 Support
 =======
 
diff --git a/docs/apache-airflow-providers/core-extensions/deprecations.rst 
b/docs/apache-airflow-providers/core-extensions/deprecations.rst
new file mode 100644
index 0000000000..177ea77ab9
--- /dev/null
+++ b/docs/apache-airflow-providers/core-extensions/deprecations.rst
@@ -0,0 +1,31 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+ ..   http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+
+Deprecations
+==========================================
+
+This is a summary of deprecated objects in latest versions of Apache Airflow 
Providers Packages.
+
+.. note::
+   This is an experimental page that may not contain all deprecated entities.
+   At the moment we only show deprecations for classes, functions, methods, 
properties etc.
+   Support for an argument deprecation or an argument value deprecation will 
be added in the future.
+
+.. airflow-deprecations::
+   :tags: None
+   :header-separator: "
diff --git a/docs/exts/docs_build/errors.py b/docs/exts/docs_build/errors.py
index 0abfc8fe57..79a403924b 100644
--- a/docs/exts/docs_build/errors.py
+++ b/docs/exts/docs_build/errors.py
@@ -77,8 +77,9 @@ def display_errors_summary(build_errors: dict[str, 
list[DocBuildError]]) -> None
                 console.print(
                     f"File path: {os.path.relpath(error.file_path, 
start=DOCS_DIR)} ({error.line_no})"
                 )
-                console.print()
-                console.print(prepare_code_snippet(error.file_path, 
error.line_no))
+                if os.path.isfile(error.file_path):
+                    console.print()
+                    console.print(prepare_code_snippet(error.file_path, 
error.line_no))
             elif error.file_path:
                 console.print(f"File path: {error.file_path}")
     console.print()
diff --git a/docs/exts/docs_build/spelling_checks.py 
b/docs/exts/docs_build/spelling_checks.py
index 2e3f7d409f..a1d56b7746 100644
--- a/docs/exts/docs_build/spelling_checks.py
+++ b/docs/exts/docs_build/spelling_checks.py
@@ -154,7 +154,9 @@ def display_spelling_error_summary(spelling_errors: 
dict[str, list[SpellingError
 
     console.print("=" * 100)
     console.print()
-    msg = """
+    msg = """[green]
+If there are spelling errors related to class or function name, make sure
+those names are quoted with backticks '`' - this should exclude it from 
spellcheck process.
 If there are spelling errors in the summary above, and the spelling is
 correct, add the spelling to docs/spelling_wordlist.txt or use the
 spelling directive.
@@ -177,11 +179,16 @@ def _display_error(error: SpellingError):
     if error.file_path:
         console.print(f"File path: {os.path.relpath(error.file_path, 
start=DOCS_DIR)}")
         if error.spelling:
-            console.print(f"Incorrect Spelling: '{error.spelling}'")
+            console.print(f"[red]Incorrect Spelling: '{error.spelling}'")
         if error.suggestion:
             console.print(f"Suggested Spelling: '{error.suggestion}'")
         if error.context_line:
             console.print(f"Line with Error: '{error.context_line}'")
-        if error.file_path and not error.file_path.endswith("<unknown>") and 
error.line_no:
+        if (
+            error.file_path
+            and not error.file_path.endswith("<unknown>")
+            and error.line_no
+            and os.path.isfile(error.file_path)
+        ):
             console.print(f"Line Number: {error.line_no}")
             console.print(prepare_code_snippet(error.file_path, error.line_no))
diff --git a/docs/exts/operators_and_hooks_ref.py 
b/docs/exts/operators_and_hooks_ref.py
index 272c8224a1..413cda59f4 100644
--- a/docs/exts/operators_and_hooks_ref.py
+++ b/docs/exts/operators_and_hooks_ref.py
@@ -221,6 +221,111 @@ def _render_deferrable_operator_content(*, 
header_separator: str):
     )
 
 
+def _get_decorator_details(decorator):
+    def get_full_name(node):
+        if isinstance(node, ast.Attribute):
+            return f"{get_full_name(node.value)}.{node.attr}"
+        elif isinstance(node, ast.Name):
+            return node.id
+        else:
+            return ast.dump(node)
+
+    def eval_node(node):
+        try:
+            return ast.literal_eval(node)
+        except ValueError:
+            return ast.dump(node)
+
+    if isinstance(decorator, ast.Call):
+        name = get_full_name(decorator.func)
+        args = [eval_node(arg) for arg in decorator.args]
+        kwargs = {kw.arg: eval_node(kw.value) for kw in decorator.keywords if 
kw.arg != "category"}
+        return name, args, kwargs
+    elif isinstance(decorator, ast.Name):
+        return decorator.id, [], {}
+    elif isinstance(decorator, ast.Attribute):
+        return decorator.attr, [], {}
+    else:
+        return decorator, [], {}
+
+
+def _iter_module_for_deprecations(ast_node, file_path, class_name=None) -> 
list[dict[str, Any]]:
+    deprecations = []
+    decorators_of_deprecation = {"deprecated"}
+
+    def analyze_decorators(node, _file_path, object_type, _class_name=None):
+        for decorator in node.decorator_list:
+            if str(_class_name).startswith("_") or 
str(node.name).startswith("_"):
+                continue
+            decorator_name, decorator_args, decorator_kwargs = 
_get_decorator_details(decorator)
+
+            instructions = decorator_kwargs.get("reason", "No instructions 
were provided.")
+            if len(decorator_args) == 1 and isinstance(decorator_args[0], str) 
and not instructions:
+                instructions = decorator_args[0]
+
+            if decorator_name in (
+                "staticmethod",
+                "classmethod",
+                "property",
+                "abstractmethod",
+                "cached_property",
+            ):
+                object_type = decorator_name
+
+            if decorator_name in decorators_of_deprecation:
+                object_name = f"{_class_name}.{node.name}" if _class_name else 
node.name
+                object_path = os.path.join(_file_path, 
object_name).replace("/", ".").lstrip(".")
+                deprecations.append(
+                    {
+                        "object_path": object_path,
+                        "object_name": object_name,
+                        "object_type": object_type,
+                        "instructions": instructions,
+                    }
+                )
+
+    for child in ast.iter_child_nodes(ast_node):
+        if isinstance(child, ast.ClassDef):
+            analyze_decorators(child, file_path, object_type="class")
+            deprecations.extend(_iter_module_for_deprecations(child, 
file_path, class_name=child.name))
+        elif isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
+            analyze_decorators(
+                child, file_path, _class_name=class_name, object_type="method" 
if class_name else "function"
+            )
+        else:
+            deprecations.extend(_iter_module_for_deprecations(child, 
file_path, class_name=class_name))
+
+    return deprecations
+
+
+def _render_deprecations_content(*, header_separator: str):
+    providers = []
+    for provider_yaml_path in get_provider_yaml_paths():
+        provider_parent_path = Path(provider_yaml_path).parent
+        provider_info: dict[str, Any] = {"name": "", "deprecations": []}
+        for root, _, file_names in os.walk(provider_parent_path):
+            file_names = [f for f in file_names if f.endswith(".py") and f != 
"__init__.py"]
+            for file_name in file_names:
+                file_path = f"{os.path.relpath(root)}/{file_name}"
+                with open(file_path) as file:
+                    ast_obj = ast.parse(file.read())
+                
provider_info["deprecations"].extend(_iter_module_for_deprecations(ast_obj, 
file_path[:-3]))
+
+        if provider_info["deprecations"]:
+            provider_info["deprecations"] = sorted(
+                provider_info["deprecations"], key=lambda p: p["object_path"]
+            )
+            provider_yaml_content = 
yaml.safe_load(Path(provider_yaml_path).read_text())
+            provider_info["name"] = provider_yaml_content["package-name"]
+            providers.append(provider_info)
+
+    return _render_template(
+        "deprecations.rst.jinja2",
+        providers=sorted(providers, key=lambda p: p["name"]),
+        header_separator=header_separator,
+    )
+
+
 class BaseJinjaReferenceDirective(Directive):
     """The base directive for OperatorsHooksReferenceDirective and 
TransfersReferenceDirective"""
 
@@ -399,6 +504,15 @@ class 
DeferrableOperatorDirective(BaseJinjaReferenceDirective):
         )
 
 
+class DeprecationsDirective(BaseJinjaReferenceDirective):
+    """Generate list of deprecated entities"""
+
+    def render_content(self, *, tags: set[str] | None, header_separator: str = 
DEFAULT_HEADER_SEPARATOR):
+        return _render_deprecations_content(
+            header_separator=header_separator,
+        )
+
+
 def setup(app):
     """Setup plugin"""
     app.add_directive("operators-hooks-ref", OperatorsHooksReferenceDirective)
@@ -412,6 +526,7 @@ def setup(app):
     app.add_directive("airflow-notifications", NotificationsDirective)
     app.add_directive("airflow-executors", ExecutorsDirective)
     app.add_directive("airflow-deferrable-operators", 
DeferrableOperatorDirective)
+    app.add_directive("airflow-deprecations", DeprecationsDirective)
 
     return {"parallel_read_safe": True, "parallel_write_safe": True}
 
diff --git a/docs/exts/templates/deprecations.rst.jinja2 
b/docs/exts/templates/deprecations.rst.jinja2
new file mode 100644
index 0000000000..f134a58ce9
--- /dev/null
+++ b/docs/exts/templates/deprecations.rst.jinja2
@@ -0,0 +1,37 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+{%- for provider in providers %}
+{{ provider["name"] }}
+{{ header_separator * (provider['name']|length) }}
+
+{% for deprecation in provider['deprecations'] %}
+
+{% set prefix = ':attr:' %}
+{% if deprecation["object_type"] == 'class' %}
+  {% set prefix = ':class:' %}
+{% elif deprecation["object_type"] == 'function' %}
+  {% set prefix = ':func:' %}
+{% elif 'method' in deprecation["object_type"] %}
+  {% set prefix = ':meth:' %}
+{% endif %}
+
+- {{ prefix }}`{{ deprecation["object_name"] }} <{{ deprecation["object_path"] 
}}>` - {{ deprecation["instructions"] }}
+{% endfor %}
+
+{% endfor %}

Reply via email to