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 564adcfb7a Deprecate PY* constants into the airflow module (#37575) 564adcfb7a is described below commit 564adcfb7a41c4ed20a63f39f132194465400245 Author: Andrey Anshin <andrey.ans...@taragol.is> AuthorDate: Wed Feb 21 23:24:07 2024 +0400 Deprecate PY* constants into the airflow module (#37575) --- airflow/__init__.py | 19 ++++---- airflow/utils/hashlib_wrapper.py | 5 +- pyproject.toml | 9 ++++ tests/core/test_airflow_module.py | 54 ++++++++++++++++++++++ tests/dag_processing/test_processor.py | 4 +- tests/decorators/test_python.py | 3 +- tests/operators/test_python.py | 2 +- tests/providers/apache/hive/hooks/test_hive.py | 11 +---- .../serialization/serializers/test_serializers.py | 4 +- 9 files changed, 85 insertions(+), 26 deletions(-) diff --git a/airflow/__init__.py b/airflow/__init__.py index 3d9ea828e8..6464f0bc6e 100644 --- a/airflow/__init__.py +++ b/airflow/__init__.py @@ -39,7 +39,7 @@ if os.environ.get("_AIRFLOW_PATCH_GEVENT"): # configuration is therefore initted early here, simply by importing it. from airflow import configuration, settings -__all__ = ["__version__", "DAG", "PY36", "PY37", "PY38", "PY39", "PY310", "PY311", "XComArg"] +__all__ = ["__version__", "DAG", "Dataset", "XComArg"] # Make `airflow` a namespace package, supporting installing # airflow.providers.* in different locations (i.e. one in site, and one in user @@ -55,13 +55,6 @@ __path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore if not os.environ.get("_AIRFLOW__AS_LIBRARY", None): settings.initialize() -PY36 = sys.version_info >= (3, 6) -PY37 = sys.version_info >= (3, 7) -PY38 = sys.version_info >= (3, 8) -PY39 = sys.version_info >= (3, 9) -PY310 = sys.version_info >= (3, 10) -PY311 = sys.version_info >= (3, 11) - # Things to lazy import in form {local_name: ('target_module', 'target_name', 'deprecated')} __lazy_imports: dict[str, tuple[str, str, bool]] = { "DAG": (".models.dag", "DAG", False), @@ -77,6 +70,16 @@ def __getattr__(name: str): # PEP-562: Lazy loaded attributes on python modules module_path, attr_name, deprecated = __lazy_imports.get(name, ("", "", False)) if not module_path: + if name.startswith("PY3") and (py_minor := name[3:]) in ("6", "7", "8", "9", "10", "11", "12"): + import warnings + + warnings.warn( + f"Python version constraint {name!r} is deprecated and will be removed in the future. " + f"Please get version info from the 'sys.version_info'.", + DeprecationWarning, + ) + return sys.version_info >= (3, int(py_minor)) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") elif deprecated: import warnings diff --git a/airflow/utils/hashlib_wrapper.py b/airflow/utils/hashlib_wrapper.py index 1390ada334..f48a093bb2 100644 --- a/airflow/utils/hashlib_wrapper.py +++ b/airflow/utils/hashlib_wrapper.py @@ -18,13 +18,12 @@ from __future__ import annotations import hashlib +import sys from typing import TYPE_CHECKING if TYPE_CHECKING: from _typeshed import ReadableBuffer -from airflow import PY39 - def md5(__string: ReadableBuffer = b"") -> hashlib._Hash: """ @@ -33,6 +32,6 @@ def md5(__string: ReadableBuffer = b"") -> hashlib._Hash: :param __string: The data to hash. Default to empty str byte. :return: The hashed value. """ - if PY39: + if sys.version_info >= (3, 9): return hashlib.md5(__string, usedforsecurity=False) # type: ignore return hashlib.md5(__string) diff --git a/pyproject.toml b/pyproject.toml index 54041c3d90..7dfeb83389 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1462,8 +1462,17 @@ combine-as-imports = true banned-module-level-imports = ["numpy", "pandas"] [tool.ruff.lint.flake8-tidy-imports.banned-api] +# Direct import from the airflow package modules and constraints "airflow.AirflowException".msg = "Use airflow.exceptions.AirflowException instead." "airflow.Dataset".msg = "Use airflow.datasets.Dataset instead." +"airflow.PY36".msg = "Use sys.version_info >= (3, 6) instead." +"airflow.PY37".msg = "Use sys.version_info >= (3, 7) instead." +"airflow.PY38".msg = "Use sys.version_info >= (3, 8) instead." +"airflow.PY39".msg = "Use sys.version_info >= (3, 9) instead." +"airflow.PY310".msg = "Use sys.version_info >= (3, 10) instead." +"airflow.PY311".msg = "Use sys.version_info >= (3, 11) instead." +"airflow.PY312".msg = "Use sys.version_info >= (3, 12) instead." +# Deprecated imports "airflow.models.baseoperator.BaseOperatorLink".msg = "Use airflow.models.baseoperatorlink.BaseOperatorLink" # Deprecated in Python 3.11, Pending Removal in Python 3.15: https://github.com/python/cpython/issues/90817 # Deprecation warning in Python 3.11 also recommends using locale.getencoding but it available in Python 3.11 diff --git a/tests/core/test_airflow_module.py b/tests/core/test_airflow_module.py new file mode 100644 index 0000000000..d448f4bc93 --- /dev/null +++ b/tests/core/test_airflow_module.py @@ -0,0 +1,54 @@ +# 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 sys + +import pytest + +import airflow +from airflow.exceptions import AirflowException + +TEST_CASES = { + "PY36": sys.version_info >= (3, 6), + "PY37": sys.version_info >= (3, 7), + "PY38": sys.version_info >= (3, 8), + "PY39": sys.version_info >= (3, 9), + "PY310": sys.version_info >= (3, 10), + "PY311": sys.version_info >= (3, 11), + "PY312": sys.version_info >= (3, 12), +} + + +@pytest.mark.parametrize("py_attr, expected", TEST_CASES.items()) +def test_lazy_load_py_versions(py_attr, expected): + with pytest.warns(DeprecationWarning, match=f"Python version constraint '{py_attr}' is deprecated"): + # If there is no warning, then most possible it imported somewhere else. + assert getattr(airflow, py_attr) is expected + + +@pytest.mark.parametrize("py_attr", ["PY35", "PY313"]) +def test_wrong_py_version(py_attr): + with pytest.raises(AttributeError, match=f"'airflow' has no attribute '{py_attr}'"): + getattr(airflow, py_attr) + + +def test_deprecated_exception(): + warning_pattern = "Import 'AirflowException' directly from the airflow module is deprecated" + with pytest.warns(DeprecationWarning, match=warning_pattern): + # If there is no warning, then most possible it imported somewhere else. + assert getattr(airflow, "AirflowException") is AirflowException diff --git a/tests/dag_processing/test_processor.py b/tests/dag_processing/test_processor.py index ac94edc510..6a3c51fd10 100644 --- a/tests/dag_processing/test_processor.py +++ b/tests/dag_processing/test_processor.py @@ -19,13 +19,14 @@ from __future__ import annotations import datetime import os +import sys from unittest import mock from unittest.mock import MagicMock, patch from zipfile import ZipFile import pytest -from airflow import PY311, settings +from airflow import settings from airflow.callbacks.callback_requests import TaskCallbackRequest from airflow.configuration import TEST_DAGS_FOLDER, conf from airflow.dag_processing.manager import DagFileProcessorAgent @@ -53,6 +54,7 @@ from tests.test_utils.mock_executor import MockExecutor pytestmark = pytest.mark.db_test DEFAULT_DATE = timezone.datetime(2016, 1, 1) +PY311 = sys.version_info >= (3, 11) # Include the words "airflow" and "dag" in the file contents, # tricking airflow into thinking these diff --git a/tests/decorators/test_python.py b/tests/decorators/test_python.py index 69b52d7724..f78d71e05f 100644 --- a/tests/decorators/test_python.py +++ b/tests/decorators/test_python.py @@ -22,7 +22,6 @@ from typing import TYPE_CHECKING, Dict, Tuple, Union import pytest -from airflow import PY38, PY311 from airflow.decorators import setup, task as task_decorator, teardown from airflow.decorators.base import DecoratedMappedOperator from airflow.exceptions import AirflowException, XComNotFound @@ -49,6 +48,8 @@ if TYPE_CHECKING: from airflow.models.dagrun import DagRun DEFAULT_DATE = timezone.datetime(2016, 1, 1) +PY38 = sys.version_info >= (3, 8) +PY311 = sys.version_info >= (3, 11) class TestAirflowTaskDecorator(BasePythonTest): diff --git a/tests/operators/test_python.py b/tests/operators/test_python.py index f1a297aab3..a4de8510a1 100644 --- a/tests/operators/test_python.py +++ b/tests/operators/test_python.py @@ -37,7 +37,6 @@ from unittest.mock import MagicMock import pytest from slugify import slugify -from airflow import PY311 from airflow.decorators import task_group from airflow.exceptions import AirflowException, DeserializingResultError, RemovedInAirflow3Warning from airflow.models.baseoperator import BaseOperator @@ -75,6 +74,7 @@ DEFAULT_DATE = timezone.datetime(2016, 1, 1) TEMPLATE_SEARCHPATH = os.path.join(AIRFLOW_MAIN_FOLDER, "tests", "config_templates") LOGGER_NAME = "airflow.task.operators" DEFAULT_PYTHON_VERSION = f"{sys.version_info[0]}.{sys.version_info[1]}" +PY311 = sys.version_info >= (3, 11) class BasePythonTest: diff --git a/tests/providers/apache/hive/hooks/test_hive.py b/tests/providers/apache/hive/hooks/test_hive.py index a137364767..027ad8c688 100644 --- a/tests/providers/apache/hive/hooks/test_hive.py +++ b/tests/providers/apache/hive/hooks/test_hive.py @@ -17,22 +17,13 @@ # under the License. from __future__ import annotations -import pytest - -from airflow import PY311 - -if PY311: - pytest.skip( - "The tests are skipped because Apache Hive provider is not supported on Python 3.11", - allow_module_level=True, - ) - import datetime import itertools from collections import namedtuple from unittest import mock import pandas as pd +import pytest from hmsclient import HMSClient from airflow.exceptions import AirflowException diff --git a/tests/serialization/serializers/test_serializers.py b/tests/serialization/serializers/test_serializers.py index cb0b03b324..1f64f90baf 100644 --- a/tests/serialization/serializers/test_serializers.py +++ b/tests/serialization/serializers/test_serializers.py @@ -18,6 +18,7 @@ from __future__ import annotations import datetime import decimal +import sys from importlib import metadata from unittest.mock import patch @@ -31,11 +32,10 @@ from packaging import version from pendulum import DateTime from pendulum.tz.timezone import FixedTimezone, Timezone -from airflow import PY39 from airflow.models.param import Param, ParamsDict from airflow.serialization.serde import DATA, deserialize, serialize -if PY39: +if sys.version_info >= (3, 9): from zoneinfo import ZoneInfo else: from backports.zoneinfo import ZoneInfo