| #!/usr/bin/env python |
| # |
| # 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. |
| """ |
| Checks if all the libraries in setup.py are listed in installation.rst file |
| """ |
| from __future__ import annotations |
| |
| import os |
| import re |
| import sys |
| from os.path import dirname |
| |
| from rich import print |
| from rich.console import Console |
| from rich.table import Table |
| |
| AIRFLOW_SOURCES_DIR = os.path.join(dirname(__file__), os.pardir, os.pardir, os.pardir) |
| SETUP_PY_FILE = "setup.py" |
| DOCS_FILE = os.path.join("docs", "apache-airflow", "extra-packages-ref.rst") |
| PY_IDENTIFIER = r"[a-zA-Z_][a-zA-Z0-9_\.]*" |
| |
| sys.path.insert(0, AIRFLOW_SOURCES_DIR) |
| |
| from setup import ( # noqa # isort:skip |
| add_all_provider_packages, |
| EXTRAS_DEPRECATED_ALIASES, |
| EXTRAS_DEPENDENCIES, |
| PREINSTALLED_PROVIDERS, |
| EXTRAS_DEPRECATED_ALIASES_IGNORED_FROM_REF_DOCS, |
| ) |
| |
| |
| def get_file_content(*path_elements: str) -> str: |
| file_path = os.path.join(AIRFLOW_SOURCES_DIR, *path_elements) |
| with open(file_path) as file_to_read: |
| return file_to_read.read() |
| |
| |
| def get_extras_from_setup() -> set[str]: |
| """Returns a set of regular (non-deprecated) extras from setup.""" |
| return ( |
| set(EXTRAS_DEPENDENCIES.keys()) |
| - set(EXTRAS_DEPRECATED_ALIASES.keys()) |
| - set(EXTRAS_DEPRECATED_ALIASES_IGNORED_FROM_REF_DOCS) |
| ) |
| |
| |
| def get_extras_from_docs() -> set[str]: |
| """ |
| Returns a list of extras from airflow.docs. |
| """ |
| docs_content = get_file_content(DOCS_FILE) |
| extras_section_regex = re.compile( |
| rf"\|[^|]+\|.*pip install .apache-airflow\[({PY_IDENTIFIER})][^|]+\|[^|]+\|", |
| re.MULTILINE, |
| ) |
| doc_extra_set: set[str] = set() |
| for doc_extra in extras_section_regex.findall(docs_content): |
| doc_extra_set.add(doc_extra) |
| return doc_extra_set |
| |
| |
| def get_preinstalled_providers_from_docs() -> list[str]: |
| """ |
| Returns list of pre-installed providers from the doc. |
| """ |
| docs_content = get_file_content(DOCS_FILE) |
| preinstalled_section_regex = re.compile( |
| rf"\|\s*({PY_IDENTIFIER})\s*\|[^|]+pip install[^|]+\|[^|]+\|\s+\*\s+\|$", |
| re.MULTILINE, |
| ) |
| return preinstalled_section_regex.findall(docs_content) |
| |
| |
| def get_deprecated_extras_from_docs() -> dict[str, str]: |
| """ |
| Returns dict of deprecated extras from airflow.docs (alias -> target extra) |
| """ |
| deprecated_extras = {} |
| docs_content = get_file_content(DOCS_FILE) |
| |
| deprecated_extras_section_regex = re.compile( |
| r"\| Deprecated extra \| Extra to be used instead \|\n(.*)\n", re.DOTALL |
| ) |
| deprecated_extras_content = deprecated_extras_section_regex.findall(docs_content)[0] |
| |
| deprecated_extras_regexp = re.compile(r"\|\s(\S+)\s+\|\s(\S*)\s+\|$", re.MULTILINE) |
| for extras in deprecated_extras_regexp.findall(deprecated_extras_content): |
| deprecated_extras[extras[0]] = extras[1] |
| return deprecated_extras |
| |
| |
| def check_extras(console: Console) -> bool: |
| """ |
| Checks if non-deprecated extras match setup vs. doc. |
| :param console: print table there in case of errors |
| :return: True if all ok, False otherwise |
| """ |
| extras_table = Table() |
| extras_table.add_column("NAME", justify="right", style="cyan") |
| extras_table.add_column("SETUP", justify="center", style="magenta") |
| extras_table.add_column("DOCS", justify="center", style="yellow") |
| non_deprecated_setup_extras = get_extras_from_setup() |
| non_deprecated_docs_extras = get_extras_from_docs() |
| for extra in non_deprecated_setup_extras: |
| if extra not in non_deprecated_docs_extras: |
| extras_table.add_row(extra, "V", "") |
| for extra in non_deprecated_docs_extras: |
| if extra not in non_deprecated_setup_extras: |
| extras_table.add_row(extra, "", "V") |
| if extras_table.row_count != 0: |
| print( |
| f"""\ |
| [red bold]ERROR!![/red bold] |
| |
| The "[bold]CORE_EXTRAS_DEPENDENCIES[/bold]" |
| sections in the setup file: [bold yellow]{SETUP_PY_FILE}[/bold yellow] |
| should be synchronized with the "Extra Packages Reference" |
| in the documentation file: [bold yellow]{DOCS_FILE}[/bold yellow]. |
| |
| Below is the list of extras that: |
| |
| * are used but are not documented, |
| * are documented but not used, |
| |
| [bold]Please synchronize setup/documentation files![/bold] |
| |
| """ |
| ) |
| console.print(extras_table) |
| return False |
| return True |
| |
| |
| def check_deprecated_extras(console: Console) -> bool: |
| """ |
| Checks if deprecated extras match setup vs. doc. |
| :param console: print table there in case of errors |
| :return: True if all ok, False otherwise |
| """ |
| deprecated_setup_extras = EXTRAS_DEPRECATED_ALIASES |
| deprecated_docs_extras = get_deprecated_extras_from_docs() |
| |
| deprecated_extras_table = Table() |
| deprecated_extras_table.add_column("DEPRECATED_IN_SETUP", justify="right", style="cyan") |
| deprecated_extras_table.add_column("TARGET_IN_SETUP", justify="center", style="magenta") |
| deprecated_extras_table.add_column("DEPRECATED_IN_DOCS", justify="right", style="cyan") |
| deprecated_extras_table.add_column("TARGET_IN_DOCS", justify="center", style="magenta") |
| |
| for extra in deprecated_setup_extras.keys(): |
| if extra not in deprecated_docs_extras: |
| deprecated_extras_table.add_row(extra, deprecated_setup_extras[extra], "", "") |
| elif deprecated_docs_extras[extra] != deprecated_setup_extras[extra]: |
| deprecated_extras_table.add_row( |
| extra, deprecated_setup_extras[extra], extra, deprecated_docs_extras[extra] |
| ) |
| |
| for extra in deprecated_docs_extras.keys(): |
| if extra not in deprecated_setup_extras: |
| deprecated_extras_table.add_row("", "", extra, deprecated_docs_extras[extra]) |
| |
| if deprecated_extras_table.row_count != 0: |
| print( |
| f"""\ |
| [red bold]ERROR!![/red bold] |
| |
| The "[bold]EXTRAS_DEPRECATED_ALIASES[/bold]" section in the setup file:\ |
| [bold yellow]{SETUP_PY_FILE}[/bold yellow] |
| should be synchronized with the "Extra Packages Reference" |
| in the documentation file: [bold yellow]{DOCS_FILE}[/bold yellow]. |
| |
| Below is the list of deprecated extras that: |
| |
| * are used but are not documented, |
| * are documented but not used, |
| * or have different target extra specified in the documentation or setup. |
| |
| [bold]Please synchronize setup/documentation files![/bold] |
| |
| """ |
| ) |
| console.print(deprecated_extras_table) |
| return False |
| return True |
| |
| |
| def check_preinstalled_extras(console: Console) -> bool: |
| """ |
| Checks if preinstalled extras match setup vs. doc. |
| :param console: print table there in case of errors |
| :return: True if all ok, False otherwise |
| """ |
| preinstalled_providers_from_docs = get_preinstalled_providers_from_docs() |
| preinstalled_providers_from_setup = PREINSTALLED_PROVIDERS |
| |
| preinstalled_providers_table = Table() |
| preinstalled_providers_table.add_column("PREINSTALLED_IN_SETUP", justify="right", style="cyan") |
| preinstalled_providers_table.add_column("PREINSTALLED_IN_DOCS", justify="center", style="magenta") |
| |
| for provider in preinstalled_providers_from_setup: |
| if provider not in preinstalled_providers_from_docs: |
| preinstalled_providers_table.add_row(provider, "") |
| |
| for provider in preinstalled_providers_from_docs: |
| if provider not in preinstalled_providers_from_setup: |
| preinstalled_providers_table.add_row("", provider) |
| |
| if preinstalled_providers_table.row_count != 0: |
| print( |
| f"""\ |
| [red bold]ERROR!![/red bold] |
| |
| The "[bold]PREINSTALLED_PROVIDERS[/bold]" section in the setup file:\ |
| [bold yellow]{SETUP_PY_FILE}[/bold yellow] |
| should be synchronized with the "Extra Packages Reference" |
| in the documentation file: [bold yellow]{DOCS_FILE}[/bold yellow]. |
| |
| Below is the list of preinstalled providers that: |
| * are used but are not documented, |
| * or are documented but not used. |
| |
| [bold]Please synchronize setup/documentation files![/bold] |
| |
| """ |
| ) |
| console.print(preinstalled_providers_table) |
| return False |
| return True |
| |
| |
| if __name__ == "__main__": |
| status: list[bool] = [] |
| # force adding all provider package dependencies, to check providers status |
| add_all_provider_packages() |
| main_console = Console() |
| status.append(check_extras(main_console)) |
| status.append(check_deprecated_extras(main_console)) |
| status.append(check_preinstalled_extras(main_console)) |
| |
| if all(status): |
| print("All extras are synchronized: [green]OK[/]") |
| sys.exit(0) |
| sys.exit(1) |