blob: 8da25c042c036ca6602f9b5698735acde19b738a [file] [log] [blame]
# 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.
from __future__ import annotations
import ast
import glob
import itertools
import mmap
import pathlib
import pytest
from tests_common.test_utils.paths import (
AIRFLOW_CORE_SOURCES_PATH,
AIRFLOW_PROVIDERS_ROOT_PATH,
AIRFLOW_ROOT_PATH,
)
class TestProjectStructure:
def test_reference_to_providers_from_core(self):
for filename in AIRFLOW_CORE_SOURCES_PATH.glob("example_dags/**/*.py"):
self.assert_file_not_contains(filename, "providers")
def test_deprecated_packages(self):
for filename in AIRFLOW_CORE_SOURCES_PATH.glob("airflow/contrib/**/*.py"):
if filename.name == "__init__.py":
self.assert_file_contains(filename, "This package is deprecated.")
else:
self.assert_file_contains(filename, "This module is deprecated.")
def assert_file_not_contains(self, filename: pathlib.Path, pattern: str):
with open(filename, "rb", 0) as file, mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as content:
if content.find(bytes(pattern, "utf-8")) != -1:
pytest.fail(f"File {filename} not contains pattern - {pattern}")
def assert_file_contains(self, filename: pathlib.Path, pattern: str):
with open(filename, "rb", 0) as file, mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as content:
if content.find(bytes(pattern, "utf-8")) == -1:
pytest.fail(f"File {filename} contains illegal pattern - {pattern}")
def test_providers_modules_should_have_tests(self):
"""
Assert every module in /providers/ has a corresponding test_ file in providers/providers.
"""
# The test below had a but for quite a while and we missed a lot of modules to have tess
# We should make sure that one goes to 0
# TODO(potiuk) - check if that test actually tests something
OVERLOOKED_TESTS = [
"providers/amazon/tests/unit/amazon/aws/auth_manager/datamodels/test_login.py",
"providers/amazon/tests/unit/amazon/aws/auth_manager/security_manager/test_aws_security_manager_override.py",
"providers/amazon/tests/unit/amazon/aws/executors/batch/test_batch_executor_config.py",
"providers/amazon/tests/unit/amazon/aws/executors/batch/test_boto_schema.py",
"providers/amazon/tests/unit/amazon/aws/executors/ecs/test_ecs_executor_config.py",
"providers/amazon/tests/unit/amazon/aws/executors/ecs/test_utils.py",
"providers/amazon/tests/unit/amazon/aws/executors/aws_lambda/test_utils.py",
"providers/amazon/tests/unit/amazon/aws/executors/aws_lambda/docker/test_app.py",
"providers/amazon/tests/unit/amazon/aws/executors/utils/test_base_config_keys.py",
"providers/amazon/tests/unit/amazon/aws/operators/test_emr.py",
"providers/amazon/tests/unit/amazon/aws/operators/test_sagemaker.py",
"providers/amazon/tests/unit/amazon/aws/sensors/test_emr.py",
"providers/amazon/tests/unit/amazon/aws/sensors/test_sagemaker.py",
"providers/amazon/tests/unit/amazon/aws/test_exceptions.py",
"providers/amazon/tests/unit/amazon/aws/triggers/test_sagemaker_unified_studio.py",
"providers/amazon/tests/unit/amazon/aws/triggers/test_step_function.py",
"providers/amazon/tests/unit/amazon/aws/utils/test_rds.py",
"providers/amazon/tests/unit/amazon/aws/utils/test_sagemaker.py",
"providers/amazon/tests/unit/amazon/aws/waiters/test_base_waiter.py",
"providers/apache/hdfs/tests/unit/apache/hdfs/hooks/test_hdfs.py",
"providers/apache/hdfs/tests/unit/apache/hdfs/sensors/test_hdfs.py",
"providers/apache/hive/tests/unit/apache/hive/plugins/test_hive.py",
"providers/celery/tests/unit/celery/executors/test_celery_executor_utils.py",
"providers/celery/tests/unit/celery/executors/test_default_celery.py",
"providers/cloudant/tests/unit/cloudant/test_cloudant_fake.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/executors/test_kubernetes_executor_types.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/executors/test_kubernetes_executor_utils.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/operators/test_kubernetes_pod.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/test_exceptions.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/test_k8s_model.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/test_kube_client.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/test_kube_config.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/test_python_kubernetes_script.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/test_secret.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/triggers/test_kubernetes_pod.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/utils/test_delete_from.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/utils/test_k8s_hashlib_wrapper.py",
"providers/cncf/kubernetes/tests/unit/cncf/kubernetes/utils/test_xcom_sidecar.py",
"providers/common/compat/tests/unit/common/compat/lineage/test_entities.py",
"providers/common/compat/tests/unit/common/compat/standard/test_operators.py",
"providers/common/compat/tests/unit/common/compat/standard/test_triggers.py",
"providers/common/compat/tests/unit/common/compat/standard/test_utils.py",
"providers/common/messaging/tests/unit/common/messaging/providers/test_base_provider.py",
"providers/common/messaging/tests/unit/common/messaging/providers/test_sqs.py",
"providers/edge3/tests/unit/edge3/models/test_edge_job.py",
"providers/edge3/tests/unit/edge3/models/test_edge_logs.py",
"providers/edge3/tests/unit/edge3/models/test_edge_worker.py",
"providers/edge3/tests/unit/edge3/worker_api/routes/test__v2_compat.py",
"providers/edge3/tests/unit/edge3/worker_api/routes/test__v2_routes.py",
"providers/edge3/tests/unit/edge3/worker_api/test_app.py",
"providers/edge3/tests/unit/edge3/worker_api/test_auth.py",
"providers/edge3/tests/unit/edge3/worker_api/test_datamodels.py",
"providers/edge3/tests/unit/edge3/worker_api/test_datamodels_ui.py",
"providers/fab/tests/unit/fab/auth_manager/api_fastapi/datamodels/test_login.py",
"providers/fab/tests/unit/fab/migrations/test_env.py",
"providers/fab/tests/unit/fab/www/api_connexion/test_exceptions.py",
"providers/fab/tests/unit/fab/www/api_connexion/test_parameters.py",
"providers/fab/tests/unit/fab/www/api_connexion/test_security.py",
"providers/fab/tests/unit/fab/www/api_connexion/test_types.py",
"providers/fab/tests/unit/fab/www/extensions/test_init_appbuilder.py",
"providers/fab/tests/unit/fab/www/extensions/test_init_jinja_globals.py",
"providers/fab/tests/unit/fab/www/extensions/test_init_manifest_files.py",
"providers/fab/tests/unit/fab/www/extensions/test_init_security.py",
"providers/fab/tests/unit/fab/www/extensions/test_init_session.py",
"providers/fab/tests/unit/fab/www/extensions/test_init_views.py",
"providers/fab/tests/unit/fab/www/extensions/test_init_wsgi_middlewares.py",
"providers/fab/tests/unit/fab/www/security/test_permissions.py",
"providers/fab/tests/unit/fab/www/test_airflow_flask_app.py",
"providers/fab/tests/unit/fab/www/test_app.py",
"providers/fab/tests/unit/fab/www/test_constants.py",
"providers/fab/tests/unit/fab/www/test_security_appless.py",
"providers/fab/tests/unit/fab/www/test_security_manager.py",
"providers/fab/tests/unit/fab/www/test_session.py",
"providers/fab/tests/unit/fab/www/test_views.py",
"providers/google/tests/unit/google/cloud/fs/test_gcs.py",
"providers/google/tests/unit/google/cloud/links/test_automl.py",
"providers/google/tests/unit/google/cloud/links/test_base.py",
"providers/google/tests/unit/google/cloud/links/test_bigquery.py",
"providers/google/tests/unit/google/cloud/links/test_bigquery_dts.py",
"providers/google/tests/unit/google/cloud/links/test_bigtable.py",
"providers/google/tests/unit/google/cloud/links/test_cloud_build.py",
"providers/google/tests/unit/google/cloud/links/test_cloud_functions.py",
"providers/google/tests/unit/google/cloud/links/test_cloud_memorystore.py",
"providers/google/tests/unit/google/cloud/links/test_cloud_sql.py",
"providers/google/tests/unit/google/cloud/links/test_cloud_storage_transfer.py",
"providers/google/tests/unit/google/cloud/links/test_cloud_tasks.py",
"providers/google/tests/unit/google/cloud/links/test_compute.py",
"providers/google/tests/unit/google/cloud/links/test_data_loss_prevention.py",
"providers/google/tests/unit/google/cloud/links/test_datacatalog.py",
"providers/google/tests/unit/google/cloud/links/test_dataflow.py",
"providers/google/tests/unit/google/cloud/links/test_dataform.py",
"providers/google/tests/unit/google/cloud/links/test_datafusion.py",
"providers/google/tests/unit/google/cloud/links/test_dataprep.py",
"providers/google/tests/unit/google/cloud/links/test_dataproc.py",
"providers/google/tests/unit/google/cloud/links/test_datastore.py",
"providers/google/tests/unit/google/cloud/links/test_kubernetes_engine.py",
"providers/google/tests/unit/google/cloud/links/test_mlengine.py",
"providers/google/tests/unit/google/cloud/links/test_pubsub.py",
"providers/google/tests/unit/google/cloud/links/test_spanner.py",
"providers/google/tests/unit/google/cloud/links/test_stackdriver.py",
"providers/google/tests/unit/google/cloud/links/test_workflows.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_auto_ml.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_batch_prediction_job.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_custom_job.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_dataset.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_endpoint_service.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_hyperparameter_tuning_job.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_model_service.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_pipeline_job.py",
"providers/google/tests/unit/google/cloud/operators/vertex_ai/test_ray.py",
"providers/google/tests/unit/google/cloud/sensors/vertex_ai/test_feature_store.py",
"providers/google/tests/unit/google/cloud/transfers/test_bigquery_to_sql.py",
"providers/google/tests/unit/google/cloud/transfers/test_presto_to_gcs.py",
"providers/google/tests/unit/google/cloud/utils/test_bigquery.py",
"providers/google/tests/unit/google/cloud/utils/test_bigquery_get_data.py",
"providers/google/tests/unit/google/common/hooks/test_operation_helpers.py",
"providers/google/tests/unit/google/test_go_module_utils.py",
"providers/http/tests/unit/http/test_exceptions.py",
"providers/keycloak/tests/unit/keycloak/auth_manager/datamodels/test_token.py",
"providers/microsoft/azure/tests/unit/microsoft/azure/operators/test_adls.py",
"providers/snowflake/tests/unit/snowflake/triggers/test_snowflake_trigger.py",
"providers/standard/tests/unit/standard/operators/test_branch.py",
"providers/standard/tests/unit/standard/operators/test_empty.py",
"providers/standard/tests/unit/standard/operators/test_latest_only.py",
"providers/standard/tests/unit/standard/sensors/test_external_task.py",
"providers/sftp/tests/unit/sftp/test_exceptions.py",
]
modules_files: list[pathlib.Path] = list(
AIRFLOW_PROVIDERS_ROOT_PATH.glob("**/src/airflow/providers/**/*.py")
)
# Exclude .build files
modules_files = (f for f in modules_files if ".build" not in f.parts)
# Exclude .git files
modules_files = (f for f in modules_files if ".git" not in f.parts)
# Exclude .venv files
modules_files = (f for f in modules_files if ".venv" not in f.parts)
# Exclude node_modules
modules_files = (f for f in modules_files if "node_modules" not in f.parts)
# Exclude __init__.py
modules_files = filter(lambda f: f.name != "__init__.py", modules_files)
# Exclude example_dags
modules_files = (f for f in modules_files if "example_dags" not in f.parts)
# Exclude _vendor
modules_files = (f for f in modules_files if "_vendor" not in f.parts)
# Exclude versions file
modules_files = (f for f in modules_files if "versions" not in f.parts)
# Exclude get_provider_info files
modules_files = (f for f in modules_files if "get_provider_info.py" not in f.parts)
# Make path relative
modules_files = list(f.relative_to(AIRFLOW_ROOT_PATH) for f in modules_files)
current_test_files = list(AIRFLOW_PROVIDERS_ROOT_PATH.rglob("**/tests/**/*.py"))
# Make path relative
current_test_files = list(f.relative_to(AIRFLOW_ROOT_PATH) for f in current_test_files)
# Exclude __init__.py
current_test_files = set(f for f in current_test_files if not f.name == "__init__.py")
# Exclude node_modules
current_test_files = set(f for f in current_test_files if "node_modules" not in f.parts)
# Exclude version_compat.py
modules_files = filter(lambda f: f.name != "version_compat.py", modules_files)
modules_files_set = set(modules_files)
expected_test_files = set(
[
pathlib.Path(
f.with_name("test_" + f.name)
.as_posix()
.replace("/src/airflow/providers/", "/tests/unit/")
)
for f in modules_files_set
]
)
expected_test_files = set(expected_test_files) - set(
[pathlib.Path(test_file) for test_file in OVERLOOKED_TESTS]
)
missing_tests_files = [
file.as_posix()
for file in sorted(expected_test_files - expected_test_files.intersection(current_test_files))
]
assert missing_tests_files == [], "Detect missing tests in providers module - please add tests"
added_test_files = current_test_files.intersection(OVERLOOKED_TESTS)
assert set() == added_test_files, (
"Detect added tests in providers module - please remove the tests "
"from OVERLOOKED_TESTS list above"
)
def get_imports_from_file(filepath: str):
with open(filepath) as py_file:
content = py_file.read()
doc_node = ast.parse(content, filepath)
import_names: set[str] = set()
for current_node in ast.walk(doc_node):
if not isinstance(current_node, (ast.Import, ast.ImportFrom)):
continue
for alias in current_node.names:
name = alias.name
fullname = f"{current_node.module}.{name}" if isinstance(current_node, ast.ImportFrom) else name
import_names.add(fullname)
return import_names
def filepath_to_module(path: pathlib.Path, src_folder: pathlib.Path):
path = path.relative_to(src_folder)
return path.as_posix().replace("/", ".")[: -(len(".py"))]
def print_sorted(container: set, indent: str = " ") -> None:
sorted_container = sorted(container)
print(f"{indent}" + f"\n{indent}".join(sorted_container))
class ProjectStructureTest:
PROVIDER = "blank"
CLASS_DIRS = {"operators", "sensors", "transfers"}
CLASS_SUFFIXES = ["Operator", "Sensor"]
def new_class_paths(self):
for resource_type in self.CLASS_DIRS:
python_files = AIRFLOW_PROVIDERS_ROOT_PATH.glob(
f"{self.PROVIDER}/**/{resource_type}/**/*.py",
)
# Make path relative
resource_files = filter(lambda f: f.name != "__init__.py", python_files)
yield from resource_files
def list_of_classes(self):
classes = {}
for file in self.new_class_paths():
operators_paths = self.get_classes_from_file(file, AIRFLOW_PROVIDERS_ROOT_PATH)
classes.update(operators_paths)
return classes
def get_classes_from_file(self, filepath: pathlib.Path, src_folder: pathlib.Path):
with open(filepath) as py_file:
content = py_file.read()
doc_node = ast.parse(content, filepath)
module = filepath_to_module(filepath, src_folder)
results: dict = {}
for current_node in ast.walk(doc_node):
if isinstance(current_node, ast.ClassDef) and current_node.name.endswith(
tuple(self.CLASS_SUFFIXES)
):
if "unit" in module:
continue
if "integration" in module:
continue
if "system" in module:
continue
module_path = module[module.find("airflow.providers") :]
results[f"{module_path}.{current_node.name}"] = current_node
print(f"{results}")
return results
class ExampleCoverageTest(ProjectStructureTest):
"""Checks that every operator is covered by example"""
# Those operators are deprecated, so we do not need examples for them
DEPRECATED_CLASSES: set = set()
# Those operators should not have examples as they are never used standalone (they are abstract)
BASE_CLASSES: set = set()
# Please add the examples to those operators at the earliest convenience :)
MISSING_EXAMPLES_FOR_CLASSES: set = set()
def example_paths(self):
"""Override this method if your example dags are located elsewhere"""
yield from glob.glob(
f"{AIRFLOW_ROOT_PATH}/providers/{self.PROVIDER}/tests/system/{self.PROVIDER}/**/example_*.py",
recursive=True,
)
yield from glob.glob(
f"{AIRFLOW_ROOT_PATH}/providers/{self.PROVIDER}/src/airflow/providers/{self.PROVIDER}/**/example_*.py",
recursive=True,
)
def test_missing_examples(self):
"""
Assert that all operators defined under operators, sensors and transfers directories
are used in any of the example dags
"""
classes = self.list_of_classes()
assert len(classes) != 0, "Failed to retrieve operators, override class_paths if needed"
classes = set(classes.keys())
for example in self.example_paths():
classes -= get_imports_from_file(example)
covered_but_omitted = self.MISSING_EXAMPLES_FOR_CLASSES - classes
classes -= self.MISSING_EXAMPLES_FOR_CLASSES
classes -= self.DEPRECATED_CLASSES
classes -= self.BASE_CLASSES
classes = set(class_name for class_name in classes if not class_name.startswith("Test"))
if set() != classes:
print("Classes with missing examples:")
print_sorted(classes)
pytest.fail(
"Not all classes are covered with example dags. Update self.MISSING_EXAMPLES_FOR_CLASSES "
"if you want to skip this error"
)
if set() != covered_but_omitted:
print("Covered classes that are listed as missing:")
print_sorted(covered_but_omitted)
pytest.fail("Operator listed in missing examples but is used in example dag")
class AssetsCoverageTest(ProjectStructureTest):
"""Checks that every operator have operator_extra_links attribute"""
# These operators should not have assets
ASSETS_NOT_REQUIRED: set = set()
# Please add assets to following classes
MISSING_ASSETS_FOR_CLASSES: set = set()
def test_missing_assets(self):
classes = self.list_of_classes()
assets, no_assets = set(), set()
for name, operator in classes.items():
for attr in operator.body:
if (
isinstance(attr, ast.Assign)
and attr.targets
and getattr(attr.targets[0], "id", "") == "operator_extra_links"
):
assets.add(name)
break
else:
no_assets.add(name)
asset_should_be_missing = self.ASSETS_NOT_REQUIRED - no_assets
no_assets -= self.ASSETS_NOT_REQUIRED
no_assets -= self.MISSING_ASSETS_FOR_CLASSES
if set() != no_assets:
print("Classes with missing assets:")
print_sorted(no_assets)
pytest.fail("Some classes are missing assets")
if set() != asset_should_be_missing:
print("Classes that should not have assets:")
print_sorted(asset_should_be_missing)
pytest.fail("Class should not have assets")
class TestGoogleProviderProjectStructure(ExampleCoverageTest, AssetsCoverageTest):
PROVIDER = "google"
CLASS_DIRS = ProjectStructureTest.CLASS_DIRS | {"operators/vertex_ai"}
DEPRECATED_CLASSES = {
"airflow.providers.google.cloud.operators.cloud_storage_transfer_service"
".CloudDataTransferServiceS3ToGCSOperator",
"airflow.providers.google.cloud.operators.cloud_storage_transfer_service"
".CloudDataTransferServiceGCSToGCSOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLTablesListColumnSpecsOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLTablesListTableSpecsOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLTablesUpdateDatasetOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLDeployModelOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLTrainModelOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLPredictOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLCreateDatasetOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLImportDataOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLGetModelOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLDeleteModelOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLListDatasetOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLDeleteDatasetOperator",
"airflow.providers.google.cloud.operators.vertex_ai.auto_ml.CreateAutoMLVideoTrainingJobOperator",
"airflow.providers.google.cloud.operators.bigquery.BigQueryCreateEmptyTableOperator",
"airflow.providers.google.cloud.operators.bigquery.BigQueryCreateExternalTableOperator",
"airflow.providers.google.cloud.operators.datapipeline.CreateDataPipelineOperator",
"airflow.providers.google.cloud.operators.datapipeline.RunDataPipelineOperator",
"airflow.providers.google.cloud.operators.mlengine.MLEngineCreateModelOperator",
"airflow.providers.google.cloud.operators.vertex_ai.generative_model.TextGenerationModelPredictOperator",
"airflow.providers.google.marketing_platform.operators.GoogleDisplayVideo360CreateQueryOperator",
"airflow.providers.google.marketing_platform.operators.GoogleDisplayVideo360DeleteReportOperator",
"airflow.providers.google.marketing_platform.operators.GoogleDisplayVideo360RunQueryOperator",
"airflow.providers.google.marketing_platform.operators.GoogleDisplayVideo360DownloadReportV2Operator",
"airflow.providers.google.marketing_platform.operators.GoogleDisplayVideo360UploadLineItemsOperator",
"airflow.providers.google.marketing_platform.operators.GoogleDisplayVideo360DownloadLineItemsOperator",
"airflow.providers.google.marketing_platform.sensors.GoogleDisplayVideo360RunQuerySensor",
"airflow.providers.google.cloud.hooks.datacatalog.CloudDataCatalogHook",
"airflow.providers.google.cloud.links.datacatalog.DataCatalogEntryGroupLink",
"airflow.providers.google.cloud.links.datacatalog.DataCatalogEntryLink",
"airflow.providers.google.cloud.links.datacatalog.DataCatalogTagTemplateLink",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogCreateEntryOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogCreateEntryGroupOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogCreateTagOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogCreateTagTemplateOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogCreateTagTemplateFieldOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteEntryGroupOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteTagOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteTagTemplateOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteTagTemplateFieldOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogGetEntryOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogGetEntryGroupOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogGetTagTemplateOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogListTagsOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogLookupEntryOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogRenameTagTemplateFieldOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogSearchCatalogOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogUpdateEntryOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogUpdateTagOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogUpdateTagTemplateOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogCreateEntryOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogUpdateTagTemplateFieldOperator",
"airflow.providers.google.cloud.operators.vertex_ai.generative_model.GenerateFromCachedContentOperator",
"airflow.providers.google.cloud.operators.vertex_ai.generative_model.CreateCachedContentOperator",
"airflow.providers.google.cloud.operators.vertex_ai.generative_model.CountTokensOperator",
"airflow.providers.google.cloud.operators.vertex_ai.generative_model.SupervisedFineTuningTrainOperator",
"airflow.providers.google.cloud.operators.vertex_ai.generative_model.GenerativeModelGenerateContentOperator",
"airflow.providers.google.cloud.operators.vertex_ai.generative_model.TextEmbeddingModelGetEmbeddingsOperator",
}
BASE_CLASSES = {
"airflow.providers.google.cloud.operators.alloy_db.AlloyDBBaseOperator",
"airflow.providers.google.cloud.operators.alloy_db.AlloyDBWriteBaseOperator",
"airflow.providers.google.cloud.operators.compute.ComputeEngineBaseOperator",
"airflow.providers.google.cloud.transfers.bigquery_to_sql.BigQueryToSqlBaseOperator",
"airflow.providers.google.cloud.operators.cloud_sql.CloudSQLBaseOperator",
"airflow.providers.google.cloud.operators.dataproc.DataprocJobBaseOperator",
"airflow.providers.google.cloud.operators.dataproc._DataprocStartStopClusterBaseOperator",
"airflow.providers.google.cloud.operators.dataplex.DataplexCatalogBaseOperator",
"airflow.providers.google.cloud.operators.managed_kafka.ManagedKafkaBaseOperator",
"airflow.providers.google.cloud.operators.vertex_ai.custom_job.CustomTrainingJobBaseOperator",
"airflow.providers.google.cloud.operators.vertex_ai.ray.RayBaseOperator",
"airflow.providers.google.cloud.operators.cloud_base.GoogleCloudBaseOperator",
"airflow.providers.google.marketing_platform.operators.search_ads._GoogleSearchAdsBaseOperator",
}
MISSING_EXAMPLES_FOR_CLASSES = {
"airflow.providers.google.cloud.operators.dlp.CloudDLPRedactImageOperator",
"airflow.providers.google.cloud.transfers.cassandra_to_gcs.CassandraToGCSOperator",
"airflow.providers.google.cloud.transfers.adls_to_gcs.ADLSToGCSOperator",
"airflow.providers.google.cloud.transfers.sql_to_gcs.BaseSQLToGCSOperator",
"airflow.providers.google.cloud.operators.vertex_ai.endpoint_service.GetEndpointOperator",
"airflow.providers.google.cloud.operators.vertex_ai.auto_ml.AutoMLTrainingJobBaseOperator",
"airflow.providers.google.cloud.operators.vertex_ai.endpoint_service.UpdateEndpointOperator",
"airflow.providers.google.cloud.operators.vertex_ai.batch_prediction_job.GetBatchPredictionJobOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteEntryOperator",
"airflow.providers.google.cloud.operators.vertex_ai.generative_model.DeleteExperimentRunOperator",
}
ASSETS_NOT_REQUIRED = {
"airflow.providers.google.cloud.operators.automl.AutoMLDeleteDatasetOperator",
"airflow.providers.google.cloud.operators.automl.AutoMLDeleteModelOperator",
"airflow.providers.google.cloud.operators.bigquery.BigQueryCheckOperator",
"airflow.providers.google.cloud.operators.bigquery.BigQueryDeleteDatasetOperator",
"airflow.providers.google.cloud.operators.bigquery.BigQueryDeleteTableOperator",
"airflow.providers.google.cloud.operators.bigquery.BigQueryIntervalCheckOperator",
"airflow.providers.google.cloud.operators.bigquery.BigQueryValueCheckOperator",
"airflow.providers.google.cloud.operators.bigquery_dts.BigQueryDeleteDataTransferConfigOperator",
"airflow.providers.google.cloud.operators.bigtable.BigtableDeleteInstanceOperator",
"airflow.providers.google.cloud.operators.bigtable.BigtableDeleteTableOperator",
"airflow.providers.google.cloud.operators.cloud_build.CloudBuildDeleteBuildTriggerOperator",
"airflow.providers.google.cloud.operators.cloud_memorystore.CloudMemorystoreDeleteInstanceOperator",
"airflow.providers.google.cloud.operators.cloud_memorystore."
"CloudMemorystoreMemcachedDeleteInstanceOperator",
"airflow.providers.google.cloud.operators.cloud_sql.CloudSQLBaseOperator",
"airflow.providers.google.cloud.operators.cloud_sql.CloudSQLDeleteInstanceDatabaseOperator",
"airflow.providers.google.cloud.operators.cloud_sql.CloudSQLDeleteInstanceOperator",
"airflow.providers.google.cloud.operators.cloud_storage_transfer_service."
"CloudDataTransferServiceDeleteJobOperator",
"airflow.providers.google.cloud.operators.cloud_storage_transfer_service."
"CloudDataTransferServiceGetOperationOperator",
"airflow.providers.google.cloud.operators.cloud_storage_transfer_service."
"CloudDataTransferServiceListOperationsOperator",
"airflow.providers.google.cloud.operators.cloud_storage_transfer_service."
"CloudDataTransferServicePauseOperationOperator",
"airflow.providers.google.cloud.operators.cloud_storage_transfer_service."
"CloudDataTransferServiceResumeOperationOperator",
"airflow.providers.google.cloud.operators.compute.ComputeEngineBaseOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteEntryGroupOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteEntryOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteTagOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteTagTemplateFieldOperator",
"airflow.providers.google.cloud.operators.datacatalog.CloudDataCatalogDeleteTagTemplateOperator",
"airflow.providers.google.cloud.operators.datafusion.CloudDataFusionDeleteInstanceOperator",
"airflow.providers.google.cloud.operators.datafusion.CloudDataFusionDeletePipelineOperator",
"airflow.providers.google.cloud.operators.dataproc.DataprocDeleteBatchOperator",
"airflow.providers.google.cloud.operators.dataproc.DataprocDeleteClusterOperator",
"airflow.providers.google.cloud.operators.dataproc_metastore.DataprocMetastoreDeleteBackupOperator",
"airflow.providers.google.cloud.operators.dataproc_metastore.DataprocMetastoreDeleteServiceOperator",
"airflow.providers.google.cloud.operators.datastore.CloudDatastoreBeginTransactionOperator",
"airflow.providers.google.cloud.operators.datastore.CloudDatastoreDeleteOperationOperator",
"airflow.providers.google.cloud.operators.datastore.CloudDatastoreGetOperationOperator",
"airflow.providers.google.cloud.operators.datastore.CloudDatastoreRollbackOperator",
"airflow.providers.google.cloud.operators.datastore.CloudDatastoreRunQueryOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPDeidentifyContentOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPDeleteDLPJobOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPDeleteDeidentifyTemplateOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPDeleteInspectTemplateOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPDeleteJobTriggerOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPDeleteStoredInfoTypeOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPInspectContentOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPRedactImageOperator",
"airflow.providers.google.cloud.operators.dlp.CloudDLPReidentifyContentOperator",
"airflow.providers.google.cloud.operators.functions.CloudFunctionDeleteFunctionOperator",
"airflow.providers.google.cloud.operators.gcs.GCSDeleteBucketOperator",
"airflow.providers.google.cloud.operators.gcs.GCSDeleteObjectsOperator",
"airflow.providers.google.cloud.operators.kubernetes_engine.GKEDeleteClusterOperator",
"airflow.providers.google.cloud.operators.pubsub.PubSubDeleteSubscriptionOperator",
"airflow.providers.google.cloud.operators.pubsub.PubSubDeleteTopicOperator",
"airflow.providers.google.cloud.operators.spanner.SpannerDeleteDatabaseInstanceOperator",
"airflow.providers.google.cloud.operators.spanner.SpannerDeleteInstanceOperator",
"airflow.providers.google.cloud.operators.stackdriver.StackdriverDeleteAlertOperator",
"airflow.providers.google.cloud.operators.stackdriver.StackdriverDeleteNotificationChannelOperator",
"airflow.providers.google.cloud.operators.tasks.CloudTasksQueueDeleteOperator",
"airflow.providers.google.cloud.operators.tasks.CloudTasksTaskDeleteOperator",
"airflow.providers.google.cloud.operators.translate.CloudTranslateTextOperator",
"airflow.providers.google.cloud.operators.translate_speech.CloudTranslateSpeechOperator",
"airflow.providers.google.cloud.operators.vision.CloudVisionDeleteProductOperator",
"airflow.providers.google.cloud.operators.vision.CloudVisionDeleteProductSetOperator",
"airflow.providers.google.cloud.operators.vision.CloudVisionDeleteReferenceImageOperator",
"airflow.providers.google.cloud.operators.workflows.WorkflowsDeleteWorkflowOperator",
"airflow.providers.google.marketing_platform.sensors.campaign_manager."
"GoogleCampaignManagerReportSensor",
"airflow.providers.google.marketing_platform.sensors.display_video."
"GoogleDisplayVideo360GetSDFDownloadOperationSensor",
"airflow.providers.google.marketing_platform.sensors.display_video.GoogleDisplayVideo360ReportSensor",
}
@pytest.mark.xfail(reason="We did not reach full coverage yet")
def test_missing_assets(self):
super().test_missing_assets()
class TestAmazonProviderProjectStructure(ExampleCoverageTest):
PROVIDER = "amazon"
CLASS_DIRS = ProjectStructureTest.CLASS_DIRS
BASE_CLASSES = {
"airflow.providers.amazon.aws.operators.base_aws.AwsBaseOperator",
"airflow.providers.amazon.aws.operators.rds.RdsBaseOperator",
"airflow.providers.amazon.aws.operators.sagemaker.SageMakerBaseOperator",
"airflow.providers.amazon.aws.sensors.base_aws.AwsBaseSensor",
"airflow.providers.amazon.aws.sensors.bedrock.BedrockBaseSensor",
"airflow.providers.amazon.aws.sensors.dms.DmsTaskBaseSensor",
"airflow.providers.amazon.aws.sensors.emr.EmrBaseSensor",
"airflow.providers.amazon.aws.sensors.rds.RdsBaseSensor",
"airflow.providers.amazon.aws.sensors.sagemaker.SageMakerBaseSensor",
"airflow.providers.amazon.aws.operators.appflow.AppflowBaseOperator",
"airflow.providers.amazon.aws.operators.ecs.EcsBaseOperator",
"airflow.providers.amazon.aws.sensors.ecs.EcsBaseSensor",
"airflow.providers.amazon.aws.sensors.eks.EksBaseSensor",
"airflow.providers.amazon.aws.transfers.base.AwsToAwsBaseOperator",
"airflow.providers.amazon.aws.operators.comprehend.ComprehendBaseOperator",
"airflow.providers.amazon.aws.sensors.comprehend.ComprehendBaseSensor",
"airflow.providers.amazon.aws.sensors.kinesis_analytics.KinesisAnalyticsV2BaseSensor",
}
MISSING_EXAMPLES_FOR_CLASSES = {
# S3 Exasol transfer difficult to test, see: https://github.com/apache/airflow/issues/22632
"airflow.providers.amazon.aws.transfers.exasol_to_s3.ExasolToS3Operator",
# These operations take a lot of time, there are commented out in the system tests for this reason
"airflow.providers.amazon.aws.operators.dms.DmsStartReplicationOperator",
"airflow.providers.amazon.aws.operators.dms.DmsStopReplicationOperator",
# These modules are used in the SageMakerNotebookOperator and therefore don't have their own examples
"airflow.providers.amazon.aws.sensors.sagemaker_unified_studio.SageMakerNotebookSensor",
}
DEPRECATED_CLASSES = {
"airflow.providers.amazon.aws.operators.lambda_function.AwsLambdaInvokeFunctionOperator",
}
class TestElasticsearchProviderProjectStructure(ExampleCoverageTest):
PROVIDER = "elasticsearch"
CLASS_DIRS = {"hooks"}
CLASS_SUFFIXES = ["Hook"]
class TestCncfProviderProjectStructure(ExampleCoverageTest):
PROVIDER = "cncf/kubernetes"
CLASS_DIRS = ProjectStructureTest.CLASS_DIRS
BASE_CLASSES = {"airflow.providers.cncf.kubernetes.operators.resource.KubernetesResourceBaseOperator"}
class TestSlackProviderProjectStructure(ExampleCoverageTest):
PROVIDER = "slack"
CLASS_DIRS = ProjectStructureTest.CLASS_DIRS
BASE_CLASSES = {
"airflow.providers.slack.transfers.base_sql_to_slack.BaseSqlToSlackOperator",
"airflow.providers.slack.operators.slack.SlackAPIOperator",
}
MISSING_EXAMPLES_FOR_CLASSES = set()
DEPRECATED_CLASSES = {
"airflow.providers.slack.notifications.slack_notifier.py.",
"airflow.providers.slack.transfers.sql_to_slack.SqlToSlackOperator",
}
class TestDockerProviderProjectStructure(ExampleCoverageTest):
PROVIDER = "docker"
class TestOperatorsHooks:
def test_no_illegal_suffixes(self):
illegal_suffixes = ["_operator.py", "_hook.py", "_sensor.py"]
files = itertools.chain.from_iterable(
glob.glob(f"{AIRFLOW_ROOT_PATH}/{part}/providers/**/{resource_type}/*.py", recursive=True)
for resource_type in ["operators", "hooks", "sensors", "example_dags"]
for part in ["airflow", "tests"]
)
invalid_files = [f for f in files if f.endswith(tuple(illegal_suffixes))]
assert invalid_files == []