blob: a384c958de2b1a5e228603d7a7a757f018cb7129 [file] [log] [blame]
#!/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)