This is an automated email from the ASF dual-hosted git repository. potiuk pushed a commit to branch v2-8-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 18cbdf6364a7a8c19d85c031f06d8c4059e44ff2 Author: Andrey Anshin <[email protected]> AuthorDate: Fri Jan 12 14:24:12 2024 +0400 Add support of Pendulum 3 (#36281) * Add support of Pendulum 3 * Add backcompat to pendulum 2 * Update airflow/serialization/serialized_objects.py Co-authored-by: Tzu-ping Chung <[email protected]> * Add newsfragments --------- Co-authored-by: Tzu-ping Chung <[email protected]> (cherry picked from commit 2ffa6e4c4c9dc129daa54491d5af8f535cd0d479) --- .github/workflows/ci.yml | 92 ++++++++++++- Dockerfile.ci | 13 ++ airflow/models/dag.py | 6 +- .../cncf/kubernetes/pod_launcher_deprecated.py | 6 +- airflow/serialization/serialized_objects.py | 9 +- airflow/serialization/serializers/datetime.py | 14 +- airflow/serialization/serializers/timezone.py | 7 +- airflow/settings.py | 11 +- airflow/timetables/_cron.py | 9 +- airflow/timetables/trigger.py | 18 ++- airflow/utils/sqlalchemy.py | 5 +- airflow/utils/timezone.py | 50 +++++-- .../src/airflow_breeze/commands/common_options.py | 6 + .../airflow_breeze/commands/developer_commands.py | 4 + .../commands/developer_commands_config.py | 1 + .../airflow_breeze/commands/testing_commands.py | 6 + .../commands/testing_commands_config.py | 3 + .../src/airflow_breeze/params/shell_params.py | 2 + images/breeze/output_shell.svg | 44 +++--- images/breeze/output_shell.txt | 2 +- images/breeze/output_testing_db-tests.svg | 26 ++-- images/breeze/output_testing_db-tests.txt | 2 +- images/breeze/output_testing_non-db-tests.svg | 26 ++-- images/breeze/output_testing_non-db-tests.txt | 2 +- images/breeze/output_testing_tests.svg | 26 ++-- images/breeze/output_testing_tests.txt | 2 +- kubernetes_tests/test_kubernetes_pod_operator.py | 5 +- newsfragments/36281.significant.rst | 4 + pyproject.toml | 4 +- scripts/ci/docker-compose/devcontainer.env | 1 + scripts/docker/entrypoint_ci.sh | 14 ++ tests/api_connexion/endpoints/test_dag_endpoint.py | 12 +- tests/api_connexion/schemas/test_dag_schema.py | 5 +- tests/cli/commands/test_dag_command.py | 13 +- tests/models/test_dag.py | 9 +- tests/providers/openlineage/plugins/test_utils.py | 6 +- tests/sensors/test_time_sensor.py | 19 ++- .../serialization/serializers/test_serializers.py | 152 +++++++++++++++++++++ tests/serialization/test_serialized_objects.py | 4 +- tests/triggers/test_temporal.py | 6 +- tests/utils/test_timezone.py | 47 ++++++- 41 files changed, 540 insertions(+), 153 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ab4213153..922efd9854 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1169,11 +1169,61 @@ jobs: breeze testing db-tests --parallel-test-types "${{needs.build-info.outputs.parallel-test-types-list-as-string}}" - name: > - Post Tests success: ${{needs.build-info.outputs.default-python-version}}:Boto" + Post Tests success: ${{needs.build-info.outputs.default-python-version}}:MinSQLAlchemy" uses: ./.github/actions/post_tests_success if: success() - name: > - Post Tests failure: ${{needs.build-info.outputs.default-python-version}}:Boto" + Post Tests failure: ${{needs.build-info.outputs.default-python-version}}:MinSQLAlchemy" + uses: ./.github/actions/post_tests_failure + if: failure() + + tests-postgres-pendulum-2: + timeout-minutes: 130 + name: > + DB:Postgres${{needs.build-info.outputs.default-postgres-version}}, + Pendulum2,Py${{needs.build-info.outputs.default-python-version}}: + ${{needs.build-info.outputs.parallel-test-types-list-as-string}} + runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}} + needs: [build-info, wait-for-ci-images] + env: + RUNS_ON: "${{needs.build-info.outputs.runs-on}}" + PARALLEL_TEST_TYPES: "${{needs.build-info.outputs.parallel-test-types-list-as-string}}" + PR_LABELS: "${{needs.build-info.outputs.pull-request-labels}}" + FULL_TESTS_NEEDED: "${{needs.build-info.outputs.full-tests-needed}}" + DEBUG_RESOURCES: "${{needs.build-info.outputs.debug-resources}}" + BACKEND: "postgres" + ENABLE_COVERAGE: "${{needs.build-info.outputs.run-coverage}}" + PYTHON_MAJOR_MINOR_VERSION: "${{needs.build-info.outputs.default-python-version}}" + PYTHON_VERSION: "${needs.build-info.outputs.default-python-version}}" + POSTGRES_VERSION: "${{needs.build-info.outputs.default-postgres-version}}" + BACKEND_VERSION: "${{needs.build-info.outputs.default-postgres-version}}" + DOWNGRADE_PENDULUM: "true" + JOB_ID: > + postgres-pendulum-2-${{needs.build-info.outputs.default-python-version}}- + ${{needs.build-info.outputs.default-postgres-version}} + if: needs.build-info.outputs.run-tests == 'true' + steps: + - name: Cleanup repo + shell: bash + run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*" + - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: > + Prepare breeze & CI image: ${{needs.build-info.outputs.default-python-version}}:${{env.IMAGE_TAG}} + uses: ./.github/actions/prepare_breeze_and_image + - name: > + Tests: ${{matrix.python-version}}:${{needs.build-info.outputs.parallel-test-types-list-as-string}} + run: > + breeze testing db-tests + --parallel-test-types "${{needs.build-info.outputs.parallel-test-types-list-as-string}}" + - name: > + Post Tests success: ${{needs.build-info.outputs.default-python-version}}:Pendulum2" + uses: ./.github/actions/post_tests_success + if: success() + - name: > + Post Tests failure: ${{needs.build-info.outputs.default-python-version}}:Pendulum2" uses: ./.github/actions/post_tests_failure if: failure() @@ -1616,6 +1666,44 @@ jobs: uses: ./.github/actions/post_tests_failure if: failure() + tests-no-db-pendulum-2: + timeout-minutes: 60 + name: > + Non-DB: Pendulum2, Py${{needs.build-info.outputs.default-python-version}}: + ${{needs.build-info.outputs.parallel-test-types-list-as-string}} + runs-on: ${{fromJSON(needs.build-info.outputs.runs-on)}} + needs: [build-info, wait-for-ci-images] + env: + RUNS_ON: "${{needs.build-info.outputs.runs-on}}" + PR_LABELS: "${{needs.build-info.outputs.pull-request-labels}}" + PYTHON_MAJOR_MINOR_VERSION: "${{needs.build-info.outputs.default-python-version}}" + DEBUG_RESOURCES: "${{needs.build-info.outputs.debug-resources}}" + JOB_ID: "quarantined-${{needs.build-info.outputs.default-python-version}}" + ENABLE_COVERAGE: "${{needs.build-info.outputs.run-coverage}}" + DOWNGRADE_PENDULUM: "true" + if: needs.build-info.outputs.run-tests == 'true' + steps: + - name: Cleanup repo + shell: bash + run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*" + - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: > + Prepare breeze & CI image: ${{needs.build-info.outputs.default-python-version}}:${{env.IMAGE_TAG}} + uses: ./.github/actions/prepare_breeze_and_image + - name: "Tests: ${{matrix.python-version}}:Non-DB-Pendulum2" + run: > + breeze testing non-db-tests + --parallel-test-types "${{needs.build-info.outputs.parallel-test-types-list-as-string}}" + - name: "Post Tests success: Non-DB-Pendulum2" + uses: ./.github/actions/post_tests_success + if: success() + - name: "Post Tests failure: Non-DB-Pendulum2" + uses: ./.github/actions/post_tests_failure + if: failure() + summarize-warnings: timeout-minutes: 15 name: "Summarize warnings" diff --git a/Dockerfile.ci b/Dockerfile.ci index 0f10757de5..5487e32af0 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -908,6 +908,18 @@ function check_download_sqlalchemy() { pip check } +function check_download_pendulum() { + if [[ ${DOWNGRADE_PENDULUM=} != "true" ]]; then + return + fi + min_pendulum_version=$(grep "\"pendulum>=" pyproject.toml | sed "s/.*>=\([0-9\.]*\).*/\1/" | xargs) + echo + echo "${COLOR_BLUE}Downgrading pendulum to minimum supported version: ${min_pendulum_version}${COLOR_RESET}" + echo + pip install --root-user-action ignore "pendulum==${min_pendulum_version}" + pip check +} + function check_run_tests() { if [[ ${RUN_TESTS=} != "true" ]]; then return @@ -937,6 +949,7 @@ determine_airflow_to_use environment_initialization check_boto_upgrade check_download_sqlalchemy +check_download_pendulum check_run_tests "${@}" exec /bin/bash "${@}" diff --git a/airflow/models/dag.py b/airflow/models/dag.py index d0f46feed2..c0abadf339 100644 --- a/airflow/models/dag.py +++ b/airflow/models/dag.py @@ -138,7 +138,7 @@ from airflow.utils.types import NOTSET, ArgNotSet, DagRunType, EdgeInfoType if TYPE_CHECKING: from types import ModuleType - from pendulum.tz.timezone import Timezone + from pendulum.tz.timezone import FixedTimezone, Timezone from sqlalchemy.orm.query import Query from sqlalchemy.orm.session import Session @@ -213,7 +213,7 @@ def _get_model_data_interval( return DataInterval(start, end) -def create_timetable(interval: ScheduleIntervalArg, timezone: Timezone) -> Timetable: +def create_timetable(interval: ScheduleIntervalArg, timezone: Timezone | FixedTimezone) -> Timetable: """Create a Timetable instance from a ``schedule_interval`` argument.""" if interval is NOTSET: return DeltaDataIntervalTimetable(DEFAULT_SCHEDULE_INTERVAL) @@ -529,7 +529,7 @@ class DAG(LoggingMixin): tzinfo = None if date.tzinfo else settings.TIMEZONE tz = pendulum.instance(date, tz=tzinfo).timezone - self.timezone: Timezone = tz or settings.TIMEZONE + self.timezone: Timezone | FixedTimezone = tz or settings.TIMEZONE # Apply the timezone we settled on to end_date if it wasn't supplied if "end_date" in self.default_args and self.default_args["end_date"]: diff --git a/airflow/providers/cncf/kubernetes/pod_launcher_deprecated.py b/airflow/providers/cncf/kubernetes/pod_launcher_deprecated.py index 18799ed920..6c5f038b0a 100644 --- a/airflow/providers/cncf/kubernetes/pod_launcher_deprecated.py +++ b/airflow/providers/cncf/kubernetes/pod_launcher_deprecated.py @@ -21,7 +21,7 @@ import json import math import time import warnings -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import pendulum import tenacity @@ -148,13 +148,13 @@ class PodLauncher(LoggingMixin): """ if get_logs: read_logs_since_sec = None - last_log_time = None + last_log_time: pendulum.DateTime | None = None while True: logs = self.read_pod_logs(pod, timestamps=True, since_seconds=read_logs_since_sec) for line in logs: timestamp, message = self.parse_log_line(line.decode("utf-8")) if timestamp: - last_log_time = pendulum.parse(timestamp) + last_log_time = cast(pendulum.DateTime, pendulum.parse(timestamp)) self.log.info(message) time.sleep(1) diff --git a/airflow/serialization/serialized_objects.py b/airflow/serialization/serialized_objects.py index 48aa595933..87ee4d4a73 100644 --- a/airflow/serialization/serialized_objects.py +++ b/airflow/serialization/serialized_objects.py @@ -65,6 +65,7 @@ from airflow.utils.docs import get_docs_url from airflow.utils.module_loading import import_string, qualname from airflow.utils.operator_resources import Resources from airflow.utils.task_group import MappedTaskGroup, TaskGroup +from airflow.utils.timezone import parse_timezone from airflow.utils.types import NOTSET, ArgNotSet if TYPE_CHECKING: @@ -144,7 +145,7 @@ def decode_relativedelta(var: dict[str, Any]) -> relativedelta.relativedelta: return relativedelta.relativedelta(**var) -def encode_timezone(var: Timezone) -> str | int: +def encode_timezone(var: Timezone | FixedTimezone) -> str | int: """ Encode a Pendulum Timezone for serialization. @@ -167,9 +168,9 @@ def encode_timezone(var: Timezone) -> str | int: ) -def decode_timezone(var: str | int) -> Timezone: +def decode_timezone(var: str | int) -> Timezone | FixedTimezone: """Decode a previously serialized Pendulum Timezone.""" - return pendulum.tz.timezone(var) + return parse_timezone(var) def _get_registered_timetable(importable_string: str) -> type[Timetable] | None: @@ -607,7 +608,7 @@ class BaseSerialization: raise TypeError(f"Invalid type {type_!s} in deserialization.") _deserialize_datetime = pendulum.from_timestamp - _deserialize_timezone = pendulum.tz.timezone + _deserialize_timezone = parse_timezone @classmethod def _deserialize_timedelta(cls, seconds: int) -> datetime.timedelta: diff --git a/airflow/serialization/serializers/datetime.py b/airflow/serialization/serializers/datetime.py index d32dd8897b..69058b8c02 100644 --- a/airflow/serialization/serializers/datetime.py +++ b/airflow/serialization/serializers/datetime.py @@ -24,6 +24,7 @@ from airflow.serialization.serializers.timezone import ( serialize as serialize_timezone, ) from airflow.utils.module_loading import qualname +from airflow.utils.timezone import parse_timezone if TYPE_CHECKING: import datetime @@ -62,23 +63,22 @@ def deserialize(classname: str, version: int, data: dict | str) -> datetime.date import datetime from pendulum import DateTime - from pendulum.tz import fixed_timezone, timezone tz: datetime.tzinfo | None = None if isinstance(data, dict) and TIMEZONE in data: if version == 1: # try to deserialize unsupported timezones timezone_mapping = { - "EDT": fixed_timezone(-4 * 3600), - "CDT": fixed_timezone(-5 * 3600), - "MDT": fixed_timezone(-6 * 3600), - "PDT": fixed_timezone(-7 * 3600), - "CEST": timezone("CET"), + "EDT": parse_timezone(-4 * 3600), + "CDT": parse_timezone(-5 * 3600), + "MDT": parse_timezone(-6 * 3600), + "PDT": parse_timezone(-7 * 3600), + "CEST": parse_timezone("CET"), } if data[TIMEZONE] in timezone_mapping: tz = timezone_mapping[data[TIMEZONE]] else: - tz = timezone(data[TIMEZONE]) + tz = parse_timezone(data[TIMEZONE]) else: tz = ( deserialize_timezone(data[TIMEZONE][1], data[TIMEZONE][2], data[TIMEZONE][0]) diff --git a/airflow/serialization/serializers/timezone.py b/airflow/serialization/serializers/timezone.py index 23901b9d44..0f580adef8 100644 --- a/airflow/serialization/serializers/timezone.py +++ b/airflow/serialization/serializers/timezone.py @@ -74,7 +74,7 @@ def serialize(o: object) -> tuple[U, str, int, bool]: def deserialize(classname: str, version: int, data: object) -> Any: - from pendulum.tz import fixed_timezone, timezone + from airflow.utils.timezone import parse_timezone if not isinstance(data, (str, int)): raise TypeError(f"{data} is not of type int or str but of {type(data)}") @@ -82,9 +82,6 @@ def deserialize(classname: str, version: int, data: object) -> Any: if version > __version__: raise TypeError(f"serialized {version} of {classname} > {__version__}") - if isinstance(data, int): - return fixed_timezone(data) - if "zoneinfo.ZoneInfo" in classname: try: from zoneinfo import ZoneInfo @@ -93,7 +90,7 @@ def deserialize(classname: str, version: int, data: object) -> Any: return ZoneInfo(data) - return timezone(data) + return parse_timezone(data) # ported from pendulum.tz.timezone._get_tzinfo_name diff --git a/airflow/settings.py b/airflow/settings.py index 1a38a59ed3..53c5cc6aa4 100644 --- a/airflow/settings.py +++ b/airflow/settings.py @@ -26,7 +26,6 @@ import sys import warnings from typing import TYPE_CHECKING, Any, Callable -import pendulum import pluggy import sqlalchemy from sqlalchemy import create_engine, exc, text @@ -40,6 +39,7 @@ from airflow.executors import executor_constants from airflow.logging_config import configure_logging from airflow.utils.orm_event_handlers import setup_event_handlers from airflow.utils.state import State +from airflow.utils.timezone import local_timezone, parse_timezone, utc if TYPE_CHECKING: from sqlalchemy.engine import Engine @@ -50,13 +50,12 @@ if TYPE_CHECKING: log = logging.getLogger(__name__) try: - tz = conf.get_mandatory_value("core", "default_timezone") - if tz == "system": - TIMEZONE = pendulum.tz.local_timezone() + if (tz := conf.get_mandatory_value("core", "default_timezone")) != "system": + TIMEZONE = parse_timezone(tz) else: - TIMEZONE = pendulum.tz.timezone(tz) + TIMEZONE = local_timezone() except Exception: - TIMEZONE = pendulum.tz.timezone("UTC") + TIMEZONE = utc log.info("Configured default timezone %s", TIMEZONE) diff --git a/airflow/timetables/_cron.py b/airflow/timetables/_cron.py index b0e6e256ee..fa2fb1266f 100644 --- a/airflow/timetables/_cron.py +++ b/airflow/timetables/_cron.py @@ -19,17 +19,16 @@ from __future__ import annotations import datetime from typing import TYPE_CHECKING, Any -import pendulum from cron_descriptor import CasingTypeEnum, ExpressionDescriptor, FormatException, MissingFieldException from croniter import CroniterBadCronError, CroniterBadDateError, croniter from airflow.exceptions import AirflowTimetableInvalid from airflow.utils.dates import cron_presets -from airflow.utils.timezone import convert_to_utc, make_aware, make_naive +from airflow.utils.timezone import convert_to_utc, make_aware, make_naive, parse_timezone if TYPE_CHECKING: from pendulum import DateTime - from pendulum.tz.timezone import Timezone + from pendulum.tz.timezone import FixedTimezone, Timezone def _covers_every_hour(cron: croniter) -> bool: @@ -63,11 +62,11 @@ def _covers_every_hour(cron: croniter) -> bool: class CronMixin: """Mixin to provide interface to work with croniter.""" - def __init__(self, cron: str, timezone: str | Timezone) -> None: + def __init__(self, cron: str, timezone: str | Timezone | FixedTimezone) -> None: self._expression = cron_presets.get(cron, cron) if isinstance(timezone, str): - timezone = pendulum.tz.timezone(timezone) + timezone = parse_timezone(timezone) self._timezone = timezone try: diff --git a/airflow/timetables/trigger.py b/airflow/timetables/trigger.py index 95d2923803..2a0df645da 100644 --- a/airflow/timetables/trigger.py +++ b/airflow/timetables/trigger.py @@ -26,7 +26,7 @@ from airflow.timetables.base import DagRunInfo, DataInterval, Timetable if TYPE_CHECKING: from dateutil.relativedelta import relativedelta - from pendulum.tz.timezone import Timezone + from pendulum.tz.timezone import FixedTimezone, Timezone from airflow.timetables.base import TimeRestriction @@ -48,7 +48,7 @@ class CronTriggerTimetable(CronMixin, Timetable): self, cron: str, *, - timezone: str | Timezone, + timezone: str | Timezone | FixedTimezone, interval: datetime.timedelta | relativedelta = datetime.timedelta(), ) -> None: super().__init__(cron, timezone) @@ -77,7 +77,12 @@ class CronTriggerTimetable(CronMixin, Timetable): return {"expression": self._expression, "timezone": timezone, "interval": interval} def infer_manual_data_interval(self, *, run_after: DateTime) -> DataInterval: - return DataInterval(run_after - self._interval, run_after) + return DataInterval( + # pendulum.Datetime ± timedelta should return pendulum.Datetime + # however mypy decide that output would be datetime.datetime + run_after - self._interval, # type: ignore[arg-type] + run_after, + ) def next_dagrun_info( self, @@ -101,4 +106,9 @@ class CronTriggerTimetable(CronMixin, Timetable): next_start_time = max(start_time_candidates) if restriction.latest is not None and restriction.latest < next_start_time: return None - return DagRunInfo.interval(next_start_time - self._interval, next_start_time) + return DagRunInfo.interval( + # pendulum.Datetime ± timedelta should return pendulum.Datetime + # however mypy decide that output would be datetime.datetime + next_start_time - self._interval, # type: ignore[arg-type] + next_start_time, + ) diff --git a/airflow/utils/sqlalchemy.py b/airflow/utils/sqlalchemy.py index a042d4e902..fb241f482f 100644 --- a/airflow/utils/sqlalchemy.py +++ b/airflow/utils/sqlalchemy.py @@ -24,7 +24,6 @@ import json import logging from typing import TYPE_CHECKING, Any, Generator, Iterable, overload -import pendulum from dateutil import relativedelta from sqlalchemy import TIMESTAMP, PickleType, and_, event, false, nullsfirst, or_, true, tuple_ from sqlalchemy.dialects import mssql, mysql @@ -34,7 +33,7 @@ from sqlalchemy.types import JSON, Text, TypeDecorator, UnicodeText from airflow import settings from airflow.configuration import conf from airflow.serialization.enums import Encoding -from airflow.utils.timezone import make_naive +from airflow.utils.timezone import make_naive, utc if TYPE_CHECKING: from kubernetes.client.models.v1_pod import V1Pod @@ -46,8 +45,6 @@ if TYPE_CHECKING: log = logging.getLogger(__name__) -utc = pendulum.tz.timezone("UTC") - class UtcDateTime(TypeDecorator): """ diff --git a/airflow/utils/timezone.py b/airflow/utils/timezone.py index 12c75bef59..8ac9a49e0e 100644 --- a/airflow/utils/timezone.py +++ b/airflow/utils/timezone.py @@ -18,14 +18,20 @@ from __future__ import annotations import datetime as dt -from typing import overload +from typing import TYPE_CHECKING, overload import pendulum from dateutil.relativedelta import relativedelta from pendulum.datetime import DateTime -# UTC time zone as a tzinfo instance. -utc = pendulum.tz.timezone("UTC") +if TYPE_CHECKING: + from pendulum.tz.timezone import FixedTimezone, Timezone + +_PENDULUM3 = pendulum.__version__.startswith("3") +# UTC Timezone as a tzinfo instance. Actual value depends on pendulum version: +# - Timezone("UTC") in pendulum 3 +# - FixedTimezone(0, "UTC") in pendulum 2 +utc = pendulum.UTC def is_localized(value): @@ -135,12 +141,10 @@ def make_aware(value: dt.datetime | None, timezone: dt.tzinfo | None = None) -> # Check that we won't overwrite the timezone of an aware datetime. if is_localized(value): raise ValueError(f"make_aware expects a naive datetime, got {value}") - if hasattr(value, "fold"): - # In case of python 3.6 we want to do the same that pendulum does for python3.5 - # i.e in case we move clock back we want to schedule the run at the time of the second - # instance of the same clock time rather than the first one. - # Fold parameter has no impact in other cases so we can safely set it to 1 here - value = value.replace(fold=1) + # In case we move clock back we want to schedule the run at the time of the second + # instance of the same clock time rather than the first one. + # Fold parameter has no impact in other cases, so we can safely set it to 1 here + value = value.replace(fold=1) localized = getattr(timezone, "localize", None) if localized is not None: # This method is available for pytz time zones @@ -273,3 +277,31 @@ def td_format(td_object: None | dt.timedelta | float | int) -> str | None: if not joined: return "<1s" return joined + + +def parse_timezone(name: str | int) -> FixedTimezone | Timezone: + """ + Parse timezone and return one of the pendulum Timezone. + + Provide the same interface as ``pendulum.timezone(name)`` + + :param name: Either IANA timezone or offset to UTC in seconds. + + :meta private: + """ + if _PENDULUM3: + # This only presented in pendulum 3 and code do not reached into the pendulum 2 + return pendulum.timezone(name) # type: ignore[operator] + # In pendulum 2 this refers to the function, in pendulum 3 refers to the module + return pendulum.tz.timezone(name) # type: ignore[operator] + + +def local_timezone() -> FixedTimezone | Timezone: + """ + Return local timezone. + + Provide the same interface as ``pendulum.tz.local_timezone()`` + + :meta private: + """ + return pendulum.tz.local_timezone() diff --git a/dev/breeze/src/airflow_breeze/commands/common_options.py b/dev/breeze/src/airflow_breeze/commands/common_options.py index 12d0ee77b8..a280db4e2c 100644 --- a/dev/breeze/src/airflow_breeze/commands/common_options.py +++ b/dev/breeze/src/airflow_breeze/commands/common_options.py @@ -151,6 +151,12 @@ option_downgrade_sqlalchemy = click.option( is_flag=True, envvar="DOWNGRADE_SQLALCHEMY", ) +option_downgrade_pendulum = click.option( + "--downgrade-pendulum", + help="Downgrade Pendulum to minimum supported version.", + is_flag=True, + envvar="DOWNGRADE_PENDULUM", +) option_dry_run = click.option( "-D", "--dry-run", diff --git a/dev/breeze/src/airflow_breeze/commands/developer_commands.py b/dev/breeze/src/airflow_breeze/commands/developer_commands.py index ede6dfd933..27c43fc11b 100644 --- a/dev/breeze/src/airflow_breeze/commands/developer_commands.py +++ b/dev/breeze/src/airflow_breeze/commands/developer_commands.py @@ -39,6 +39,7 @@ from airflow_breeze.commands.common_options import ( option_database_isolation, option_db_reset, option_docker_host, + option_downgrade_pendulum, option_downgrade_sqlalchemy, option_dry_run, option_forward_credentials, @@ -248,6 +249,7 @@ option_warn_image_upgrade_needed = click.option( @option_db_reset @option_docker_host @option_downgrade_sqlalchemy +@option_downgrade_pendulum @option_dry_run @option_executor_shell @option_force_build @@ -294,6 +296,7 @@ def shell( database_isolation: bool, db_reset: bool, downgrade_sqlalchemy: bool, + downgrade_pendulum: bool, docker_host: str | None, executor: str, extra_args: tuple, @@ -354,6 +357,7 @@ def shell( database_isolation=database_isolation, db_reset=db_reset, downgrade_sqlalchemy=downgrade_sqlalchemy, + downgrade_pendulum=downgrade_pendulum, docker_host=docker_host, executor=executor, extra_args=extra_args if not max_time else ["exit"], diff --git a/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py b/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py index 88b734f513..911ed9ebb5 100644 --- a/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py +++ b/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py @@ -159,6 +159,7 @@ DEVELOPER_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = { "options": [ "--upgrade-boto", "--downgrade-sqlalchemy", + "--downgrade-pendulum", ], }, { diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py b/dev/breeze/src/airflow_breeze/commands/testing_commands.py index c4aff8797e..f826d9bdff 100644 --- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py +++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py @@ -29,6 +29,7 @@ from airflow_breeze.commands.common_options import ( option_backend, option_db_reset, option_debug_resources, + option_downgrade_pendulum, option_downgrade_sqlalchemy, option_dry_run, option_forward_credentials, @@ -471,6 +472,7 @@ option_remove_arm_packages = click.option( @option_excluded_parallel_test_types @option_upgrade_boto @option_downgrade_sqlalchemy +@option_downgrade_pendulum @option_collect_only @option_remove_arm_packages @option_skip_docker_compose_down @@ -513,6 +515,7 @@ def command_for_tests(**kwargs): @option_excluded_parallel_test_types @option_upgrade_boto @option_downgrade_sqlalchemy +@option_downgrade_pendulum @option_collect_only @option_remove_arm_packages @option_skip_docker_compose_down @@ -548,6 +551,7 @@ def command_for_db_tests(**kwargs): @option_collect_only @option_debug_resources @option_downgrade_sqlalchemy +@option_downgrade_pendulum @option_dry_run @option_enable_coverage @option_excluded_parallel_test_types @@ -589,6 +593,7 @@ def _run_test_command( db_reset: bool, debug_resources: bool, downgrade_sqlalchemy: bool, + downgrade_pendulum: bool, enable_coverage: bool, excluded_parallel_test_types: str, extra_pytest_args: tuple, @@ -632,6 +637,7 @@ def _run_test_command( backend=backend, collect_only=collect_only, downgrade_sqlalchemy=downgrade_sqlalchemy, + downgrade_pendulum=downgrade_pendulum, enable_coverage=enable_coverage, forward_credentials=forward_credentials, forward_ports=False, diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py index 404e0cabe0..370cdad91f 100644 --- a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py +++ b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py @@ -79,6 +79,7 @@ TESTING_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = { "--mount-sources", "--upgrade-boto", "--downgrade-sqlalchemy", + "--downgrade-pendulum", "--remove-arm-packages", "--skip-docker-compose-down", ], @@ -126,6 +127,7 @@ TESTING_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = { "--mount-sources", "--upgrade-boto", "--downgrade-sqlalchemy", + "--downgrade-pendulum", "--remove-arm-packages", "--skip-docker-compose-down", ], @@ -177,6 +179,7 @@ TESTING_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = { "--mount-sources", "--upgrade-boto", "--downgrade-sqlalchemy", + "--downgrade-pendulum", "--remove-arm-packages", "--skip-docker-compose-down", ], diff --git a/dev/breeze/src/airflow_breeze/params/shell_params.py b/dev/breeze/src/airflow_breeze/params/shell_params.py index 85fbecd6ca..fbc02b7922 100644 --- a/dev/breeze/src/airflow_breeze/params/shell_params.py +++ b/dev/breeze/src/airflow_breeze/params/shell_params.py @@ -148,6 +148,7 @@ class ShellParams: dev_mode: bool = False docker_host: str | None = os.environ.get("DOCKER_HOST") downgrade_sqlalchemy: bool = False + downgrade_pendulum: bool = False dry_run: bool = False enable_coverage: bool = False executor: str = START_AIRFLOW_DEFAULT_ALLOWED_EXECUTOR @@ -516,6 +517,7 @@ class ShellParams: _set_var(_env, "DEV_MODE", self.dev_mode) _set_var(_env, "DOCKER_IS_ROOTLESS", self.rootless_docker) _set_var(_env, "DOWNGRADE_SQLALCHEMY", self.downgrade_sqlalchemy) + _set_var(_env, "DOWNGRADE_PENDULUM", self.downgrade_pendulum) _set_var(_env, "ENABLED_SYSTEMS", None, "") _set_var(_env, "FLOWER_HOST_PORT", None, FLOWER_HOST_PORT) _set_var(_env, "GITHUB_ACTIONS", self.github_actions) diff --git a/images/breeze/output_shell.svg b/images/breeze/output_shell.svg index 8d9d7350d5..84151382c7 100644 --- a/images/breeze/output_shell.svg +++ b/images/breeze/output_shell.svg @@ -1,4 +1,4 @@ -<svg class="rich-terminal" viewBox="0 0 1482 3026.7999999999997" xmlns="http://www.w3.org/2000/svg"> +<svg class="rich-terminal" viewBox="0 0 1482 3051.2" xmlns="http://www.w3.org/2000/svg"> <!-- Generated with Rich https://www.textualize.io --> <style> @@ -43,7 +43,7 @@ <defs> <clipPath id="breeze-shell-clip-terminal"> - <rect x="0" y="0" width="1463.0" height="2975.7999999999997" /> + <rect x="0" y="0" width="1463.0" height="3000.2" /> </clipPath> <clipPath id="breeze-shell-line-0"> <rect x="0" y="1.5" width="1464" height="24.65"/> @@ -408,9 +408,12 @@ <clipPath id="breeze-shell-line-120"> <rect x="0" y="2929.5" width="1464" height="24.65"/> </clipPath> +<clipPath id="breeze-shell-line-121"> + <rect x="0" y="2953.9" width="1464" height="24.65"/> + </clipPath> </defs> - <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="3024.8" rx="8"/><text class="breeze-shell-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: shell</text> + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="3049.2" rx="8"/><text class="breeze-shell-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: shell</text> <g transform="translate(26,22)"> <circle cx="0" cy="0" r="7" fill="#ff5f57"/> <circle cx="22" cy="0" r="7" fill="#febc2e"/> @@ -525,23 +528,24 @@ </text><text class="breeze-shell-r5" x="0" y="2508.8" textLength="24.4" clip-path="url(#breeze-shell-line-102)">╭─</text><text class="breeze-shell-r5" x="24.4" y="2508.8" textLength="500.2" clip-path="url(#breeze-shell-line-102)"> Upgrading/downgrading selected packages </text><text class="breeze-shell-r5" x="524.6" y="2508.8" textLength="915" clip-path="url(#breeze-shell-line-102)">───────────────────────────────────────────────────────────────────────────</text><tex [...] </text><text class="breeze-shell-r5" x="0" y="2533.2" textLength="12.2" clip-path="url(#breeze-shell-line-103)">│</text><text class="breeze-shell-r4" x="24.4" y="2533.2" textLength="12.2" clip-path="url(#breeze-shell-line-103)">-</text><text class="breeze-shell-r4" x="36.6" y="2533.2" textLength="97.6" clip-path="url(#breeze-shell-line-103)">-upgrade</text><text class="breeze-shell-r4" x="134.2" y="2533.2" textLength="61" clip-path="url(#breeze-shell-line-103)">-boto</text><text class="b [...] </text><text class="breeze-shell-r5" x="0" y="2557.6" textLength="12.2" clip-path="url(#breeze-shell-line-104)">│</text><text class="breeze-shell-r4" x="24.4" y="2557.6" textLength="12.2" clip-path="url(#breeze-shell-line-104)">-</text><text class="breeze-shell-r4" x="36.6" y="2557.6" textLength="122" clip-path="url(#breeze-shell-line-104)">-downgrade</text><text class="breeze-shell-r4" x="158.6" y="2557.6" textLength="134.2" clip-path="url(#breeze-shell-line-104)">-sqlalchemy</text><tex [...] -</text><text class="breeze-shell-r5" x="0" y="2582" textLength="1464" clip-path="url(#breeze-shell-line-105)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-shell-r1" x="1464" y="2582" textLength="12.2" clip-path="url(#breeze-shell-line-105)"> -</text><text class="breeze-shell-r5" x="0" y="2606.4" textLength="24.4" clip-path="url(#breeze-shell-line-106)">╭─</text><text class="breeze-shell-r5" x="24.4" y="2606.4" textLength="183" clip-path="url(#breeze-shell-line-106)"> DB test flags </text><text class="breeze-shell-r5" x="207.4" y="2606.4" textLength="1232.2" clip-path="url(#breeze-shell-line-106)">─────────────────────────────────────────────────────────────────────────────────────────────────────</text><te [...] -</text><text class="breeze-shell-r5" x="0" y="2630.8" textLength="12.2" clip-path="url(#breeze-shell-line-107)">│</text><text class="breeze-shell-r4" x="24.4" y="2630.8" textLength="12.2" clip-path="url(#breeze-shell-line-107)">-</text><text class="breeze-shell-r4" x="36.6" y="2630.8" textLength="48.8" clip-path="url(#breeze-shell-line-107)">-run</text><text class="breeze-shell-r4" x="85.4" y="2630.8" textLength="170.8" clip-path="url(#breeze-shell-line-107)">-db-tests-only</text><text c [...] -</text><text class="breeze-shell-r5" x="0" y="2655.2" textLength="12.2" clip-path="url(#breeze-shell-line-108)">│</text><text class="breeze-shell-r4" x="24.4" y="2655.2" textLength="12.2" clip-path="url(#breeze-shell-line-108)">-</text><text class="breeze-shell-r4" x="36.6" y="2655.2" textLength="61" clip-path="url(#breeze-shell-line-108)">-skip</text><text class="breeze-shell-r4" x="97.6" y="2655.2" textLength="109.8" clip-path="url(#breeze-shell-line-108)">-db-tests</text><text class=" [...] -</text><text class="breeze-shell-r5" x="0" y="2679.6" textLength="1464" clip-path="url(#breeze-shell-line-109)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-shell-r1" x="1464" y="2679.6" textLength="12.2" clip-path="url(#breeze-shell-line-109)"> -</text><text class="breeze-shell-r5" x="0" y="2704" textLength="24.4" clip-path="url(#breeze-shell-line-110)">╭─</text><text class="breeze-shell-r5" x="24.4" y="2704" textLength="183" clip-path="url(#breeze-shell-line-110)"> Other options </text><text class="breeze-shell-r5" x="207.4" y="2704" textLength="1232.2" clip-path="url(#breeze-shell-line-110)">─────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="b [...] -</text><text class="breeze-shell-r5" x="0" y="2728.4" textLength="12.2" clip-path="url(#breeze-shell-line-111)">│</text><text class="breeze-shell-r4" x="24.4" y="2728.4" textLength="12.2" clip-path="url(#breeze-shell-line-111)">-</text><text class="breeze-shell-r4" x="36.6" y="2728.4" textLength="97.6" clip-path="url(#breeze-shell-line-111)">-forward</text><text class="breeze-shell-r4" x="134.2" y="2728.4" textLength="146.4" clip-path="url(#breeze-shell-line-111)">-credentials</text><tex [...] -</text><text class="breeze-shell-r5" x="0" y="2752.8" textLength="12.2" clip-path="url(#breeze-shell-line-112)">│</text><text class="breeze-shell-r4" x="24.4" y="2752.8" textLength="12.2" clip-path="url(#breeze-shell-line-112)">-</text><text class="breeze-shell-r4" x="36.6" y="2752.8" textLength="48.8" clip-path="url(#breeze-shell-line-112)">-max</text><text class="breeze-shell-r4" x="85.4" y="2752.8" textLength="61" clip-path="url(#breeze-shell-line-112)">-time</text><text class="breeze [...] -</text><text class="breeze-shell-r5" x="0" y="2777.2" textLength="12.2" clip-path="url(#breeze-shell-line-113)">│</text><text class="breeze-shell-r7" x="353.8" y="2777.2" textLength="1049.2" clip-path="url(#breeze-shell-line-113)">(INTEGER RANGE)                                        & [...] -</text><text class="breeze-shell-r5" x="0" y="2801.6" textLength="12.2" clip-path="url(#breeze-shell-line-114)">│</text><text class="breeze-shell-r4" x="24.4" y="2801.6" textLength="12.2" clip-path="url(#breeze-shell-line-114)">-</text><text class="breeze-shell-r4" x="36.6" y="2801.6" textLength="97.6" clip-path="url(#breeze-shell-line-114)">-verbose</text><text class="breeze-shell-r4" x="134.2" y="2801.6" textLength="109.8" clip-path="url(#breeze-shell-line-114)">-commands</text><text c [...] -</text><text class="breeze-shell-r5" x="0" y="2826" textLength="1464" clip-path="url(#breeze-shell-line-115)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-shell-r1" x="1464" y="2826" textLength="12.2" clip-path="url(#breeze-shell-line-115)"> -</text><text class="breeze-shell-r5" x="0" y="2850.4" textLength="24.4" clip-path="url(#breeze-shell-line-116)">╭─</text><text class="breeze-shell-r5" x="24.4" y="2850.4" textLength="195.2" clip-path="url(#breeze-shell-line-116)"> Common options </text><text class="breeze-shell-r5" x="219.6" y="2850.4" textLength="1220" clip-path="url(#breeze-shell-line-116)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text cl [...] -</text><text class="breeze-shell-r5" x="0" y="2874.8" textLength="12.2" clip-path="url(#breeze-shell-line-117)">│</text><text class="breeze-shell-r4" x="24.4" y="2874.8" textLength="12.2" clip-path="url(#breeze-shell-line-117)">-</text><text class="breeze-shell-r4" x="36.6" y="2874.8" textLength="85.4" clip-path="url(#breeze-shell-line-117)">-answer</text><text class="breeze-shell-r6" x="158.6" y="2874.8" textLength="24.4" clip-path="url(#breeze-shell-line-117)">-a</text><text class="bre [...] -</text><text class="breeze-shell-r5" x="0" y="2899.2" textLength="12.2" clip-path="url(#breeze-shell-line-118)">│</text><text class="breeze-shell-r4" x="24.4" y="2899.2" textLength="12.2" clip-path="url(#breeze-shell-line-118)">-</text><text class="breeze-shell-r4" x="36.6" y="2899.2" textLength="48.8" clip-path="url(#breeze-shell-line-118)">-dry</text><text class="breeze-shell-r4" x="85.4" y="2899.2" textLength="48.8" clip-path="url(#breeze-shell-line-118)">-run</text><text class="breez [...] -</text><text class="breeze-shell-r5" x="0" y="2923.6" textLength="12.2" clip-path="url(#breeze-shell-line-119)">│</text><text class="breeze-shell-r4" x="24.4" y="2923.6" textLength="12.2" clip-path="url(#breeze-shell-line-119)">-</text><text class="breeze-shell-r4" x="36.6" y="2923.6" textLength="97.6" clip-path="url(#breeze-shell-line-119)">-verbose</text><text class="breeze-shell-r6" x="158.6" y="2923.6" textLength="24.4" clip-path="url(#breeze-shell-line-119)">-v</text><text class="br [...] -</text><text class="breeze-shell-r5" x="0" y="2948" textLength="12.2" clip-path="url(#breeze-shell-line-120)">│</text><text class="breeze-shell-r4" x="24.4" y="2948" textLength="12.2" clip-path="url(#breeze-shell-line-120)">-</text><text class="breeze-shell-r4" x="36.6" y="2948" textLength="61" clip-path="url(#breeze-shell-line-120)">-help</text><text class="breeze-shell-r6" x="158.6" y="2948" textLength="24.4" clip-path="url(#breeze-shell-line-120)">-h</text><text class="breeze-shell-r1 [...] -</text><text class="breeze-shell-r5" x="0" y="2972.4" textLength="1464" clip-path="url(#breeze-shell-line-121)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-shell-r1" x="1464" y="2972.4" textLength="12.2" clip-path="url(#breeze-shell-line-121)"> +</text><text class="breeze-shell-r5" x="0" y="2582" textLength="12.2" clip-path="url(#breeze-shell-line-105)">│</text><text class="breeze-shell-r4" x="24.4" y="2582" textLength="12.2" clip-path="url(#breeze-shell-line-105)">-</text><text class="breeze-shell-r4" x="36.6" y="2582" textLength="122" clip-path="url(#breeze-shell-line-105)">-downgrade</text><text class="breeze-shell-r4" x="158.6" y="2582" textLength="109.8" clip-path="url(#breeze-shell-line-105)">-pendulum</text><text class="b [...] +</text><text class="breeze-shell-r5" x="0" y="2606.4" textLength="1464" clip-path="url(#breeze-shell-line-106)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-shell-r1" x="1464" y="2606.4" textLength="12.2" clip-path="url(#breeze-shell-line-106)"> +</text><text class="breeze-shell-r5" x="0" y="2630.8" textLength="24.4" clip-path="url(#breeze-shell-line-107)">╭─</text><text class="breeze-shell-r5" x="24.4" y="2630.8" textLength="183" clip-path="url(#breeze-shell-line-107)"> DB test flags </text><text class="breeze-shell-r5" x="207.4" y="2630.8" textLength="1232.2" clip-path="url(#breeze-shell-line-107)">─────────────────────────────────────────────────────────────────────────────────────────────────────</text><te [...] +</text><text class="breeze-shell-r5" x="0" y="2655.2" textLength="12.2" clip-path="url(#breeze-shell-line-108)">│</text><text class="breeze-shell-r4" x="24.4" y="2655.2" textLength="12.2" clip-path="url(#breeze-shell-line-108)">-</text><text class="breeze-shell-r4" x="36.6" y="2655.2" textLength="48.8" clip-path="url(#breeze-shell-line-108)">-run</text><text class="breeze-shell-r4" x="85.4" y="2655.2" textLength="170.8" clip-path="url(#breeze-shell-line-108)">-db-tests-only</text><text c [...] +</text><text class="breeze-shell-r5" x="0" y="2679.6" textLength="12.2" clip-path="url(#breeze-shell-line-109)">│</text><text class="breeze-shell-r4" x="24.4" y="2679.6" textLength="12.2" clip-path="url(#breeze-shell-line-109)">-</text><text class="breeze-shell-r4" x="36.6" y="2679.6" textLength="61" clip-path="url(#breeze-shell-line-109)">-skip</text><text class="breeze-shell-r4" x="97.6" y="2679.6" textLength="109.8" clip-path="url(#breeze-shell-line-109)">-db-tests</text><text class=" [...] +</text><text class="breeze-shell-r5" x="0" y="2704" textLength="1464" clip-path="url(#breeze-shell-line-110)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-shell-r1" x="1464" y="2704" textLength="12.2" clip-path="url(#breeze-shell-line-110)"> +</text><text class="breeze-shell-r5" x="0" y="2728.4" textLength="24.4" clip-path="url(#breeze-shell-line-111)">╭─</text><text class="breeze-shell-r5" x="24.4" y="2728.4" textLength="183" clip-path="url(#breeze-shell-line-111)"> Other options </text><text class="breeze-shell-r5" x="207.4" y="2728.4" textLength="1232.2" clip-path="url(#breeze-shell-line-111)">─────────────────────────────────────────────────────────────────────────────────────────────────────</text><text cl [...] +</text><text class="breeze-shell-r5" x="0" y="2752.8" textLength="12.2" clip-path="url(#breeze-shell-line-112)">│</text><text class="breeze-shell-r4" x="24.4" y="2752.8" textLength="12.2" clip-path="url(#breeze-shell-line-112)">-</text><text class="breeze-shell-r4" x="36.6" y="2752.8" textLength="97.6" clip-path="url(#breeze-shell-line-112)">-forward</text><text class="breeze-shell-r4" x="134.2" y="2752.8" textLength="146.4" clip-path="url(#breeze-shell-line-112)">-credentials</text><tex [...] +</text><text class="breeze-shell-r5" x="0" y="2777.2" textLength="12.2" clip-path="url(#breeze-shell-line-113)">│</text><text class="breeze-shell-r4" x="24.4" y="2777.2" textLength="12.2" clip-path="url(#breeze-shell-line-113)">-</text><text class="breeze-shell-r4" x="36.6" y="2777.2" textLength="48.8" clip-path="url(#breeze-shell-line-113)">-max</text><text class="breeze-shell-r4" x="85.4" y="2777.2" textLength="61" clip-path="url(#breeze-shell-line-113)">-time</text><text class="breeze [...] +</text><text class="breeze-shell-r5" x="0" y="2801.6" textLength="12.2" clip-path="url(#breeze-shell-line-114)">│</text><text class="breeze-shell-r7" x="353.8" y="2801.6" textLength="1049.2" clip-path="url(#breeze-shell-line-114)">(INTEGER RANGE)                                        & [...] +</text><text class="breeze-shell-r5" x="0" y="2826" textLength="12.2" clip-path="url(#breeze-shell-line-115)">│</text><text class="breeze-shell-r4" x="24.4" y="2826" textLength="12.2" clip-path="url(#breeze-shell-line-115)">-</text><text class="breeze-shell-r4" x="36.6" y="2826" textLength="97.6" clip-path="url(#breeze-shell-line-115)">-verbose</text><text class="breeze-shell-r4" x="134.2" y="2826" textLength="109.8" clip-path="url(#breeze-shell-line-115)">-commands</text><text class="br [...] +</text><text class="breeze-shell-r5" x="0" y="2850.4" textLength="1464" clip-path="url(#breeze-shell-line-116)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-shell-r1" x="1464" y="2850.4" textLength="12.2" clip-path="url(#breeze-shell-line-116)"> +</text><text class="breeze-shell-r5" x="0" y="2874.8" textLength="24.4" clip-path="url(#breeze-shell-line-117)">╭─</text><text class="breeze-shell-r5" x="24.4" y="2874.8" textLength="195.2" clip-path="url(#breeze-shell-line-117)"> Common options </text><text class="breeze-shell-r5" x="219.6" y="2874.8" textLength="1220" clip-path="url(#breeze-shell-line-117)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text cl [...] +</text><text class="breeze-shell-r5" x="0" y="2899.2" textLength="12.2" clip-path="url(#breeze-shell-line-118)">│</text><text class="breeze-shell-r4" x="24.4" y="2899.2" textLength="12.2" clip-path="url(#breeze-shell-line-118)">-</text><text class="breeze-shell-r4" x="36.6" y="2899.2" textLength="85.4" clip-path="url(#breeze-shell-line-118)">-answer</text><text class="breeze-shell-r6" x="158.6" y="2899.2" textLength="24.4" clip-path="url(#breeze-shell-line-118)">-a</text><text class="bre [...] +</text><text class="breeze-shell-r5" x="0" y="2923.6" textLength="12.2" clip-path="url(#breeze-shell-line-119)">│</text><text class="breeze-shell-r4" x="24.4" y="2923.6" textLength="12.2" clip-path="url(#breeze-shell-line-119)">-</text><text class="breeze-shell-r4" x="36.6" y="2923.6" textLength="48.8" clip-path="url(#breeze-shell-line-119)">-dry</text><text class="breeze-shell-r4" x="85.4" y="2923.6" textLength="48.8" clip-path="url(#breeze-shell-line-119)">-run</text><text class="breez [...] +</text><text class="breeze-shell-r5" x="0" y="2948" textLength="12.2" clip-path="url(#breeze-shell-line-120)">│</text><text class="breeze-shell-r4" x="24.4" y="2948" textLength="12.2" clip-path="url(#breeze-shell-line-120)">-</text><text class="breeze-shell-r4" x="36.6" y="2948" textLength="97.6" clip-path="url(#breeze-shell-line-120)">-verbose</text><text class="breeze-shell-r6" x="158.6" y="2948" textLength="24.4" clip-path="url(#breeze-shell-line-120)">-v</text><text class="breeze-she [...] +</text><text class="breeze-shell-r5" x="0" y="2972.4" textLength="12.2" clip-path="url(#breeze-shell-line-121)">│</text><text class="breeze-shell-r4" x="24.4" y="2972.4" textLength="12.2" clip-path="url(#breeze-shell-line-121)">-</text><text class="breeze-shell-r4" x="36.6" y="2972.4" textLength="61" clip-path="url(#breeze-shell-line-121)">-help</text><text class="breeze-shell-r6" x="158.6" y="2972.4" textLength="24.4" clip-path="url(#breeze-shell-line-121)">-h</text><text class="breeze- [...] +</text><text class="breeze-shell-r5" x="0" y="2996.8" textLength="1464" clip-path="url(#breeze-shell-line-122)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-shell-r1" x="1464" y="2996.8" textLength="12.2" clip-path="url(#breeze-shell-line-122)"> </text> </g> </g> diff --git a/images/breeze/output_shell.txt b/images/breeze/output_shell.txt index e71507684a..0702a3041e 100644 --- a/images/breeze/output_shell.txt +++ b/images/breeze/output_shell.txt @@ -1 +1 @@ -0ceaf6dd335e09cc9ecb42cedd18c064 +35d2198ba2086d6f11da105483505fe1 diff --git a/images/breeze/output_testing_db-tests.svg b/images/breeze/output_testing_db-tests.svg index a4c191f601..9a8c28fceb 100644 --- a/images/breeze/output_testing_db-tests.svg +++ b/images/breeze/output_testing_db-tests.svg @@ -1,4 +1,4 @@ -<svg class="rich-terminal" viewBox="0 0 1482 1831.1999999999998" xmlns="http://www.w3.org/2000/svg"> +<svg class="rich-terminal" viewBox="0 0 1482 1855.6" xmlns="http://www.w3.org/2000/svg"> <!-- Generated with Rich https://www.textualize.io --> <style> @@ -43,7 +43,7 @@ <defs> <clipPath id="breeze-testing-db-tests-clip-terminal"> - <rect x="0" y="0" width="1463.0" height="1780.1999999999998" /> + <rect x="0" y="0" width="1463.0" height="1804.6" /> </clipPath> <clipPath id="breeze-testing-db-tests-line-0"> <rect x="0" y="1.5" width="1464" height="24.65"/> @@ -261,9 +261,12 @@ <clipPath id="breeze-testing-db-tests-line-71"> <rect x="0" y="1733.9" width="1464" height="24.65"/> </clipPath> +<clipPath id="breeze-testing-db-tests-line-72"> + <rect x="0" y="1758.3" width="1464" height="24.65"/> + </clipPath> </defs> - <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1829.2" rx="8"/><text class="breeze-testing-db-tests-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: testing db-tests</text> + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1853.6" rx="8"/><text class="breeze-testing-db-tests-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: testing db-tests</text> <g transform="translate(26,22)"> <circle cx="0" cy="0" r="7" fill="#ff5f57"/> <circle cx="22" cy="0" r="7" fill="#febc2e"/> @@ -338,14 +341,15 @@ </text><text class="breeze-testing-db-tests-r5" x="0" y="1532.8" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-62)">│</text><text class="breeze-testing-db-tests-r5" x="414.8" y="1532.8" textLength="1024.8" clip-path="url(#breeze-testing-db-tests-line-62)">[default: selected]                                 [...] </text><text class="breeze-testing-db-tests-r5" x="0" y="1557.2" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-63)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1557.2" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-63)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1557.2" textLength="97.6" clip-path="url(#breeze-testing-db-tests-line-63)">-upgrade</text><text class="breeze-testing-db-tests-r4" x="134.2" y="1557.2" textLeng [...] </text><text class="breeze-testing-db-tests-r5" x="0" y="1581.6" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-64)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1581.6" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-64)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1581.6" textLength="122" clip-path="url(#breeze-testing-db-tests-line-64)">-downgrade</text><text class="breeze-testing-db-tests-r4" x="158.6" y="1581.6" textLen [...] -</text><text class="breeze-testing-db-tests-r5" x="0" y="1606" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-65)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1606" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-65)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1606" textLength="85.4" clip-path="url(#breeze-testing-db-tests-line-65)">-remove</text><text class="breeze-testing-db-tests-r4" x="122" y="1606" textLength="158.6" [...] -</text><text class="breeze-testing-db-tests-r5" x="0" y="1630.4" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-66)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1630.4" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-66)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1630.4" textLength="61" clip-path="url(#breeze-testing-db-tests-line-66)">-skip</text><text class="breeze-testing-db-tests-r4" x="97.6" y="1630.4" textLength="24 [...] -</text><text class="breeze-testing-db-tests-r5" x="0" y="1654.8" textLength="1464" clip-path="url(#breeze-testing-db-tests-line-67)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-db-tests-r1" x="1464" y="1654.8" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-67)"> -</text><text class="breeze-testing-db-tests-r5" x="0" y="1679.2" textLength="24.4" clip-path="url(#breeze-testing-db-tests-line-68)">╭─</text><text class="breeze-testing-db-tests-r5" x="24.4" y="1679.2" textLength="195.2" clip-path="url(#breeze-testing-db-tests-line-68)"> Common options </text><text class="breeze-testing-db-tests-r5" x="219.6" y="1679.2" textLength="1220" clip-path="url(#breeze-testing-db-tests-line-68)">──────────────────────────────────────────────────── [...] -</text><text class="breeze-testing-db-tests-r5" x="0" y="1703.6" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-69)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1703.6" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-69)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1703.6" textLength="97.6" clip-path="url(#breeze-testing-db-tests-line-69)">-verbose</text><text class="breeze-testing-db-tests-r7" x="158.6" y="1703.6" textLeng [...] -</text><text class="breeze-testing-db-tests-r5" x="0" y="1728" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-70)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1728" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-70)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1728" textLength="48.8" clip-path="url(#breeze-testing-db-tests-line-70)">-dry</text><text class="breeze-testing-db-tests-r4" x="85.4" y="1728" textLength="48.8" cli [...] -</text><text class="breeze-testing-db-tests-r5" x="0" y="1752.4" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-71)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1752.4" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-71)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1752.4" textLength="61" clip-path="url(#breeze-testing-db-tests-line-71)">-help</text><text class="breeze-testing-db-tests-r7" x="158.6" y="1752.4" textLength="2 [...] -</text><text class="breeze-testing-db-tests-r5" x="0" y="1776.8" textLength="1464" clip-path="url(#breeze-testing-db-tests-line-72)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-db-tests-r1" x="1464" y="1776.8" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-72)"> +</text><text class="breeze-testing-db-tests-r5" x="0" y="1606" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-65)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1606" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-65)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1606" textLength="122" clip-path="url(#breeze-testing-db-tests-line-65)">-downgrade</text><text class="breeze-testing-db-tests-r4" x="158.6" y="1606" textLength="109 [...] +</text><text class="breeze-testing-db-tests-r5" x="0" y="1630.4" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-66)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1630.4" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-66)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1630.4" textLength="85.4" clip-path="url(#breeze-testing-db-tests-line-66)">-remove</text><text class="breeze-testing-db-tests-r4" x="122" y="1630.4" textLength= [...] +</text><text class="breeze-testing-db-tests-r5" x="0" y="1654.8" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-67)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1654.8" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-67)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1654.8" textLength="61" clip-path="url(#breeze-testing-db-tests-line-67)">-skip</text><text class="breeze-testing-db-tests-r4" x="97.6" y="1654.8" textLength="24 [...] +</text><text class="breeze-testing-db-tests-r5" x="0" y="1679.2" textLength="1464" clip-path="url(#breeze-testing-db-tests-line-68)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-db-tests-r1" x="1464" y="1679.2" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-68)"> +</text><text class="breeze-testing-db-tests-r5" x="0" y="1703.6" textLength="24.4" clip-path="url(#breeze-testing-db-tests-line-69)">╭─</text><text class="breeze-testing-db-tests-r5" x="24.4" y="1703.6" textLength="195.2" clip-path="url(#breeze-testing-db-tests-line-69)"> Common options </text><text class="breeze-testing-db-tests-r5" x="219.6" y="1703.6" textLength="1220" clip-path="url(#breeze-testing-db-tests-line-69)">──────────────────────────────────────────────────── [...] +</text><text class="breeze-testing-db-tests-r5" x="0" y="1728" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-70)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1728" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-70)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1728" textLength="97.6" clip-path="url(#breeze-testing-db-tests-line-70)">-verbose</text><text class="breeze-testing-db-tests-r7" x="158.6" y="1728" textLength="24.4 [...] +</text><text class="breeze-testing-db-tests-r5" x="0" y="1752.4" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-71)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1752.4" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-71)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1752.4" textLength="48.8" clip-path="url(#breeze-testing-db-tests-line-71)">-dry</text><text class="breeze-testing-db-tests-r4" x="85.4" y="1752.4" textLength="4 [...] +</text><text class="breeze-testing-db-tests-r5" x="0" y="1776.8" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-72)">│</text><text class="breeze-testing-db-tests-r4" x="24.4" y="1776.8" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-72)">-</text><text class="breeze-testing-db-tests-r4" x="36.6" y="1776.8" textLength="61" clip-path="url(#breeze-testing-db-tests-line-72)">-help</text><text class="breeze-testing-db-tests-r7" x="158.6" y="1776.8" textLength="2 [...] +</text><text class="breeze-testing-db-tests-r5" x="0" y="1801.2" textLength="1464" clip-path="url(#breeze-testing-db-tests-line-73)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-db-tests-r1" x="1464" y="1801.2" textLength="12.2" clip-path="url(#breeze-testing-db-tests-line-73)"> </text> </g> </g> diff --git a/images/breeze/output_testing_db-tests.txt b/images/breeze/output_testing_db-tests.txt index 24c7f336cf..092397ae05 100644 --- a/images/breeze/output_testing_db-tests.txt +++ b/images/breeze/output_testing_db-tests.txt @@ -1 +1 @@ -825d30b396fe55ffd0862c0d85441f36 +bbff9b2f8394bf13cb4916c15c0ba967 diff --git a/images/breeze/output_testing_non-db-tests.svg b/images/breeze/output_testing_non-db-tests.svg index e7686cd36c..3d64e0989c 100644 --- a/images/breeze/output_testing_non-db-tests.svg +++ b/images/breeze/output_testing_non-db-tests.svg @@ -1,4 +1,4 @@ -<svg class="rich-terminal" viewBox="0 0 1482 1636.0" xmlns="http://www.w3.org/2000/svg"> +<svg class="rich-terminal" viewBox="0 0 1482 1660.3999999999999" xmlns="http://www.w3.org/2000/svg"> <!-- Generated with Rich https://www.textualize.io --> <style> @@ -43,7 +43,7 @@ <defs> <clipPath id="breeze-testing-non-db-tests-clip-terminal"> - <rect x="0" y="0" width="1463.0" height="1585.0" /> + <rect x="0" y="0" width="1463.0" height="1609.3999999999999" /> </clipPath> <clipPath id="breeze-testing-non-db-tests-line-0"> <rect x="0" y="1.5" width="1464" height="24.65"/> @@ -237,9 +237,12 @@ <clipPath id="breeze-testing-non-db-tests-line-63"> <rect x="0" y="1538.7" width="1464" height="24.65"/> </clipPath> +<clipPath id="breeze-testing-non-db-tests-line-64"> + <rect x="0" y="1563.1" width="1464" height="24.65"/> + </clipPath> </defs> - <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1634" rx="8"/><text class="breeze-testing-non-db-tests-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: testing non-db-tests</text> + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1658.4" rx="8"/><text class="breeze-testing-non-db-tests-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: testing non-db-tests</text> <g transform="translate(26,22)"> <circle cx="0" cy="0" r="7" fill="#ff5f57"/> <circle cx="22" cy="0" r="7" fill="#febc2e"/> @@ -306,14 +309,15 @@ </text><text class="breeze-testing-non-db-tests-r5" x="0" y="1337.6" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-54)">│</text><text class="breeze-testing-non-db-tests-r5" x="414.8" y="1337.6" textLength="1024.8" clip-path="url(#breeze-testing-non-db-tests-line-54)">[default: selected]                               [...] </text><text class="breeze-testing-non-db-tests-r5" x="0" y="1362" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-55)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1362" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-55)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1362" textLength="97.6" clip-path="url(#breeze-testing-non-db-tests-line-55)">-upgrade</text><text class="breeze-testing-non-db-tests-r4" x="134. [...] </text><text class="breeze-testing-non-db-tests-r5" x="0" y="1386.4" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-56)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1386.4" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-56)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1386.4" textLength="122" clip-path="url(#breeze-testing-non-db-tests-line-56)">-downgrade</text><text class="breeze-testing-non-db-tests-r4" [...] -</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1410.8" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-57)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1410.8" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-57)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1410.8" textLength="85.4" clip-path="url(#breeze-testing-non-db-tests-line-57)">-remove</text><text class="breeze-testing-non-db-tests-r4" x= [...] -</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1435.2" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-58)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1435.2" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-58)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1435.2" textLength="61" clip-path="url(#breeze-testing-non-db-tests-line-58)">-skip</text><text class="breeze-testing-non-db-tests-r4" x="97. [...] -</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1459.6" textLength="1464" clip-path="url(#breeze-testing-non-db-tests-line-59)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-non-db-tests-r1" x="1464" y="1459.6" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-59)"> -</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1484" textLength="24.4" clip-path="url(#breeze-testing-non-db-tests-line-60)">╭─</text><text class="breeze-testing-non-db-tests-r5" x="24.4" y="1484" textLength="195.2" clip-path="url(#breeze-testing-non-db-tests-line-60)"> Common options </text><text class="breeze-testing-non-db-tests-r5" x="219.6" y="1484" textLength="1220" clip-path="url(#breeze-testing-non-db-tests-line-60)">────────────────────────────────── [...] -</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1508.4" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-61)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1508.4" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-61)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1508.4" textLength="48.8" clip-path="url(#breeze-testing-non-db-tests-line-61)">-dry</text><text class="breeze-testing-non-db-tests-r4" x="85 [...] -</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1532.8" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-62)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1532.8" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-62)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1532.8" textLength="97.6" clip-path="url(#breeze-testing-non-db-tests-line-62)">-verbose</text><text class="breeze-testing-non-db-tests-r7" x [...] -</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1557.2" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-63)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1557.2" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-63)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1557.2" textLength="61" clip-path="url(#breeze-testing-non-db-tests-line-63)">-help</text><text class="breeze-testing-non-db-tests-r7" x="158 [...] -</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1581.6" textLength="1464" clip-path="url(#breeze-testing-non-db-tests-line-64)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-non-db-tests-r1" x="1464" y="1581.6" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-64)"> +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1410.8" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-57)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1410.8" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-57)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1410.8" textLength="122" clip-path="url(#breeze-testing-non-db-tests-line-57)">-downgrade</text><text class="breeze-testing-non-db-tests-r4" [...] +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1435.2" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-58)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1435.2" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-58)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1435.2" textLength="85.4" clip-path="url(#breeze-testing-non-db-tests-line-58)">-remove</text><text class="breeze-testing-non-db-tests-r4" x= [...] +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1459.6" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-59)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1459.6" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-59)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1459.6" textLength="61" clip-path="url(#breeze-testing-non-db-tests-line-59)">-skip</text><text class="breeze-testing-non-db-tests-r4" x="97. [...] +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1484" textLength="1464" clip-path="url(#breeze-testing-non-db-tests-line-60)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-non-db-tests-r1" x="1464" y="1484" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-60)"> +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1508.4" textLength="24.4" clip-path="url(#breeze-testing-non-db-tests-line-61)">╭─</text><text class="breeze-testing-non-db-tests-r5" x="24.4" y="1508.4" textLength="195.2" clip-path="url(#breeze-testing-non-db-tests-line-61)"> Common options </text><text class="breeze-testing-non-db-tests-r5" x="219.6" y="1508.4" textLength="1220" clip-path="url(#breeze-testing-non-db-tests-line-61)">──────────────────────────── [...] +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1532.8" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-62)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1532.8" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-62)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1532.8" textLength="48.8" clip-path="url(#breeze-testing-non-db-tests-line-62)">-dry</text><text class="breeze-testing-non-db-tests-r4" x="85 [...] +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1557.2" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-63)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1557.2" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-63)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1557.2" textLength="97.6" clip-path="url(#breeze-testing-non-db-tests-line-63)">-verbose</text><text class="breeze-testing-non-db-tests-r7" x [...] +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1581.6" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-64)">│</text><text class="breeze-testing-non-db-tests-r4" x="24.4" y="1581.6" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-64)">-</text><text class="breeze-testing-non-db-tests-r4" x="36.6" y="1581.6" textLength="61" clip-path="url(#breeze-testing-non-db-tests-line-64)">-help</text><text class="breeze-testing-non-db-tests-r7" x="158 [...] +</text><text class="breeze-testing-non-db-tests-r5" x="0" y="1606" textLength="1464" clip-path="url(#breeze-testing-non-db-tests-line-65)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-non-db-tests-r1" x="1464" y="1606" textLength="12.2" clip-path="url(#breeze-testing-non-db-tests-line-65)"> </text> </g> </g> diff --git a/images/breeze/output_testing_non-db-tests.txt b/images/breeze/output_testing_non-db-tests.txt index 6bc1946e1a..7d9ef31069 100644 --- a/images/breeze/output_testing_non-db-tests.txt +++ b/images/breeze/output_testing_non-db-tests.txt @@ -1 +1 @@ -70b970c5c754371d9d5d80234d130615 +8aa7803c0bbff622175c01d94d22290b diff --git a/images/breeze/output_testing_tests.svg b/images/breeze/output_testing_tests.svg index 9e06eefda1..ed7c062406 100644 --- a/images/breeze/output_testing_tests.svg +++ b/images/breeze/output_testing_tests.svg @@ -1,4 +1,4 @@ -<svg class="rich-terminal" viewBox="0 0 1482 2246.0" xmlns="http://www.w3.org/2000/svg"> +<svg class="rich-terminal" viewBox="0 0 1482 2270.4" xmlns="http://www.w3.org/2000/svg"> <!-- Generated with Rich https://www.textualize.io --> <style> @@ -43,7 +43,7 @@ <defs> <clipPath id="breeze-testing-tests-clip-terminal"> - <rect x="0" y="0" width="1463.0" height="2195.0" /> + <rect x="0" y="0" width="1463.0" height="2219.4" /> </clipPath> <clipPath id="breeze-testing-tests-line-0"> <rect x="0" y="1.5" width="1464" height="24.65"/> @@ -312,9 +312,12 @@ <clipPath id="breeze-testing-tests-line-88"> <rect x="0" y="2148.7" width="1464" height="24.65"/> </clipPath> +<clipPath id="breeze-testing-tests-line-89"> + <rect x="0" y="2173.1" width="1464" height="24.65"/> + </clipPath> </defs> - <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="2244" rx="8"/><text class="breeze-testing-tests-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: testing tests</text> + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="2268.4" rx="8"/><text class="breeze-testing-tests-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: testing tests</text> <g transform="translate(26,22)"> <circle cx="0" cy="0" r="7" fill="#ff5f57"/> <circle cx="22" cy="0" r="7" fill="#febc2e"/> @@ -406,14 +409,15 @@ </text><text class="breeze-testing-tests-r5" x="0" y="1947.6" textLength="12.2" clip-path="url(#breeze-testing-tests-line-79)">│</text><text class="breeze-testing-tests-r5" x="414.8" y="1947.6" textLength="1024.8" clip-path="url(#breeze-testing-tests-line-79)">[default: selected]                                   [...] </text><text class="breeze-testing-tests-r5" x="0" y="1972" textLength="12.2" clip-path="url(#breeze-testing-tests-line-80)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="1972" textLength="12.2" clip-path="url(#breeze-testing-tests-line-80)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="1972" textLength="97.6" clip-path="url(#breeze-testing-tests-line-80)">-upgrade</text><text class="breeze-testing-tests-r4" x="134.2" y="1972" textLength="61" clip-path="url(#breez [...] </text><text class="breeze-testing-tests-r5" x="0" y="1996.4" textLength="12.2" clip-path="url(#breeze-testing-tests-line-81)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="1996.4" textLength="12.2" clip-path="url(#breeze-testing-tests-line-81)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="1996.4" textLength="122" clip-path="url(#breeze-testing-tests-line-81)">-downgrade</text><text class="breeze-testing-tests-r4" x="158.6" y="1996.4" textLength="134.2" clip-path [...] -</text><text class="breeze-testing-tests-r5" x="0" y="2020.8" textLength="12.2" clip-path="url(#breeze-testing-tests-line-82)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2020.8" textLength="12.2" clip-path="url(#breeze-testing-tests-line-82)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2020.8" textLength="85.4" clip-path="url(#breeze-testing-tests-line-82)">-remove</text><text class="breeze-testing-tests-r4" x="122" y="2020.8" textLength="158.6" clip-path="ur [...] -</text><text class="breeze-testing-tests-r5" x="0" y="2045.2" textLength="12.2" clip-path="url(#breeze-testing-tests-line-83)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2045.2" textLength="12.2" clip-path="url(#breeze-testing-tests-line-83)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2045.2" textLength="61" clip-path="url(#breeze-testing-tests-line-83)">-skip</text><text class="breeze-testing-tests-r4" x="97.6" y="2045.2" textLength="244" clip-path="url(#br [...] -</text><text class="breeze-testing-tests-r5" x="0" y="2069.6" textLength="1464" clip-path="url(#breeze-testing-tests-line-84)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-tests-r1" x="1464" y="2069.6" textLength="12.2" clip-path="url(#breeze-testing-tests-line-84)"> -</text><text class="breeze-testing-tests-r5" x="0" y="2094" textLength="24.4" clip-path="url(#breeze-testing-tests-line-85)">╭─</text><text class="breeze-testing-tests-r5" x="24.4" y="2094" textLength="195.2" clip-path="url(#breeze-testing-tests-line-85)"> Common options </text><text class="breeze-testing-tests-r5" x="219.6" y="2094" textLength="1220" clip-path="url(#breeze-testing-tests-line-85)">──────────────────────────────────────────────────────────────────────────── [...] -</text><text class="breeze-testing-tests-r5" x="0" y="2118.4" textLength="12.2" clip-path="url(#breeze-testing-tests-line-86)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2118.4" textLength="12.2" clip-path="url(#breeze-testing-tests-line-86)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2118.4" textLength="97.6" clip-path="url(#breeze-testing-tests-line-86)">-verbose</text><text class="breeze-testing-tests-r6" x="158.6" y="2118.4" textLength="24.4" clip-path=" [...] -</text><text class="breeze-testing-tests-r5" x="0" y="2142.8" textLength="12.2" clip-path="url(#breeze-testing-tests-line-87)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2142.8" textLength="12.2" clip-path="url(#breeze-testing-tests-line-87)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2142.8" textLength="48.8" clip-path="url(#breeze-testing-tests-line-87)">-dry</text><text class="breeze-testing-tests-r4" x="85.4" y="2142.8" textLength="48.8" clip-path="url(# [...] -</text><text class="breeze-testing-tests-r5" x="0" y="2167.2" textLength="12.2" clip-path="url(#breeze-testing-tests-line-88)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2167.2" textLength="12.2" clip-path="url(#breeze-testing-tests-line-88)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2167.2" textLength="61" clip-path="url(#breeze-testing-tests-line-88)">-help</text><text class="breeze-testing-tests-r6" x="158.6" y="2167.2" textLength="24.4" clip-path="url(# [...] -</text><text class="breeze-testing-tests-r5" x="0" y="2191.6" textLength="1464" clip-path="url(#breeze-testing-tests-line-89)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-tests-r1" x="1464" y="2191.6" textLength="12.2" clip-path="url(#breeze-testing-tests-line-89)"> +</text><text class="breeze-testing-tests-r5" x="0" y="2020.8" textLength="12.2" clip-path="url(#breeze-testing-tests-line-82)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2020.8" textLength="12.2" clip-path="url(#breeze-testing-tests-line-82)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2020.8" textLength="122" clip-path="url(#breeze-testing-tests-line-82)">-downgrade</text><text class="breeze-testing-tests-r4" x="158.6" y="2020.8" textLength="109.8" clip-path [...] +</text><text class="breeze-testing-tests-r5" x="0" y="2045.2" textLength="12.2" clip-path="url(#breeze-testing-tests-line-83)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2045.2" textLength="12.2" clip-path="url(#breeze-testing-tests-line-83)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2045.2" textLength="85.4" clip-path="url(#breeze-testing-tests-line-83)">-remove</text><text class="breeze-testing-tests-r4" x="122" y="2045.2" textLength="158.6" clip-path="ur [...] +</text><text class="breeze-testing-tests-r5" x="0" y="2069.6" textLength="12.2" clip-path="url(#breeze-testing-tests-line-84)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2069.6" textLength="12.2" clip-path="url(#breeze-testing-tests-line-84)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2069.6" textLength="61" clip-path="url(#breeze-testing-tests-line-84)">-skip</text><text class="breeze-testing-tests-r4" x="97.6" y="2069.6" textLength="244" clip-path="url(#br [...] +</text><text class="breeze-testing-tests-r5" x="0" y="2094" textLength="1464" clip-path="url(#breeze-testing-tests-line-85)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-tests-r1" x="1464" y="2094" textLength="12.2" clip-path="url(#breeze-testing-tests-line-85)"> +</text><text class="breeze-testing-tests-r5" x="0" y="2118.4" textLength="24.4" clip-path="url(#breeze-testing-tests-line-86)">╭─</text><text class="breeze-testing-tests-r5" x="24.4" y="2118.4" textLength="195.2" clip-path="url(#breeze-testing-tests-line-86)"> Common options </text><text class="breeze-testing-tests-r5" x="219.6" y="2118.4" textLength="1220" clip-path="url(#breeze-testing-tests-line-86)">────────────────────────────────────────────────────────────────────── [...] +</text><text class="breeze-testing-tests-r5" x="0" y="2142.8" textLength="12.2" clip-path="url(#breeze-testing-tests-line-87)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2142.8" textLength="12.2" clip-path="url(#breeze-testing-tests-line-87)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2142.8" textLength="97.6" clip-path="url(#breeze-testing-tests-line-87)">-verbose</text><text class="breeze-testing-tests-r6" x="158.6" y="2142.8" textLength="24.4" clip-path=" [...] +</text><text class="breeze-testing-tests-r5" x="0" y="2167.2" textLength="12.2" clip-path="url(#breeze-testing-tests-line-88)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2167.2" textLength="12.2" clip-path="url(#breeze-testing-tests-line-88)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2167.2" textLength="48.8" clip-path="url(#breeze-testing-tests-line-88)">-dry</text><text class="breeze-testing-tests-r4" x="85.4" y="2167.2" textLength="48.8" clip-path="url(# [...] +</text><text class="breeze-testing-tests-r5" x="0" y="2191.6" textLength="12.2" clip-path="url(#breeze-testing-tests-line-89)">│</text><text class="breeze-testing-tests-r4" x="24.4" y="2191.6" textLength="12.2" clip-path="url(#breeze-testing-tests-line-89)">-</text><text class="breeze-testing-tests-r4" x="36.6" y="2191.6" textLength="61" clip-path="url(#breeze-testing-tests-line-89)">-help</text><text class="breeze-testing-tests-r6" x="158.6" y="2191.6" textLength="24.4" clip-path="url(# [...] +</text><text class="breeze-testing-tests-r5" x="0" y="2216" textLength="1464" clip-path="url(#breeze-testing-tests-line-90)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-testing-tests-r1" x="1464" y="2216" textLength="12.2" clip-path="url(#breeze-testing-tests-line-90)"> </text> </g> </g> diff --git a/images/breeze/output_testing_tests.txt b/images/breeze/output_testing_tests.txt index b243afade7..6b81ec51d0 100644 --- a/images/breeze/output_testing_tests.txt +++ b/images/breeze/output_testing_tests.txt @@ -1 +1 @@ -4c38b2815f6a7606d05fe58fd9a8550a +f63db76413092aa6f822957edbf3fc8c diff --git a/kubernetes_tests/test_kubernetes_pod_operator.py b/kubernetes_tests/test_kubernetes_pod_operator.py index 249cf667e1..8d7dad9d12 100644 --- a/kubernetes_tests/test_kubernetes_pod_operator.py +++ b/kubernetes_tests/test_kubernetes_pod_operator.py @@ -26,7 +26,6 @@ from unittest import mock from unittest.mock import ANY, MagicMock from uuid import uuid4 -import pendulum import pytest from kubernetes import client from kubernetes.client import V1EnvVar, V1PodSecurityContext, V1SecurityContext, models as k8s @@ -53,7 +52,9 @@ POD_MANAGER_CLASS = "airflow.providers.cncf.kubernetes.utils.pod_manager.PodMana def create_context(task) -> Context: dag = DAG(dag_id="dag") - execution_date = timezone.datetime(2016, 1, 1, 1, 0, 0, tzinfo=pendulum.tz.timezone("Europe/Amsterdam")) + execution_date = timezone.datetime( + 2016, 1, 1, 1, 0, 0, tzinfo=timezone.parse_timezone("Europe/Amsterdam") + ) dag_run = DagRun( dag_id=dag.dag_id, execution_date=execution_date, diff --git a/newsfragments/36281.significant.rst b/newsfragments/36281.significant.rst new file mode 100644 index 0000000000..1207be0782 --- /dev/null +++ b/newsfragments/36281.significant.rst @@ -0,0 +1,4 @@ +Target version for core dependency ``pendulum`` package set to 3 + +Support for pendulum 2.1.2 will be saved for a while, presumably until the next feature version of Airflow. +It is advised to upgrade user code to use pendulum 3 as soon as possible. diff --git a/pyproject.toml b/pyproject.toml index b1c40d4b40..be12fcba93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,9 +123,7 @@ dependencies = [ "opentelemetry-exporter-otlp", "packaging>=14.0", "pathspec>=0.9.0", - # When (if) pendulum 3 released it would introduce changes in module/objects imports, - # since we are tightly coupled with pendulum library internally it will breaks Airflow functionality. - "pendulum>=2.0,<3.0", + "pendulum>=2.1.2,<4.0", "pluggy>=1.0", "psutil>=4.2.0", "pydantic>=2.3.0", diff --git a/scripts/ci/docker-compose/devcontainer.env b/scripts/ci/docker-compose/devcontainer.env index 4ef3fd0459..5c627c9ebf 100644 --- a/scripts/ci/docker-compose/devcontainer.env +++ b/scripts/ci/docker-compose/devcontainer.env @@ -68,6 +68,7 @@ START_AIRFLOW="false" SUSPENDED_PROVIDERS_FOLDERS="" TEST_TYPE= UPGRADE_BOTO="false" +DOWNGRADE_PENDULUM="false" DOWNGRADE_SQLALCHEMY="false" UPGRADE_TO_NEWER_DEPENDENCIES="false" VERBOSE="false" diff --git a/scripts/docker/entrypoint_ci.sh b/scripts/docker/entrypoint_ci.sh index 52d79f4154..b5bbe777ac 100755 --- a/scripts/docker/entrypoint_ci.sh +++ b/scripts/docker/entrypoint_ci.sh @@ -239,6 +239,19 @@ function check_download_sqlalchemy() { pip check } +# Download minimum supported version of pendulum to run tests with it +function check_download_pendulum() { + if [[ ${DOWNGRADE_PENDULUM=} != "true" ]]; then + return + fi + min_pendulum_version=$(grep "\"pendulum>=" pyproject.toml | sed "s/.*>=\([0-9\.]*\).*/\1/" | xargs) + echo + echo "${COLOR_BLUE}Downgrading pendulum to minimum supported version: ${min_pendulum_version}${COLOR_RESET}" + echo + pip install --root-user-action ignore "pendulum==${min_pendulum_version}" + pip check +} + # Check if we should run tests and run them if needed function check_run_tests() { if [[ ${RUN_TESTS=} != "true" ]]; then @@ -269,6 +282,7 @@ determine_airflow_to_use environment_initialization check_boto_upgrade check_download_sqlalchemy +check_download_pendulum check_run_tests "${@}" # If we are not running tests - just exec to bash shell diff --git a/tests/api_connexion/endpoints/test_dag_endpoint.py b/tests/api_connexion/endpoints/test_dag_endpoint.py index c02e8b0ff3..a29b86f4a6 100644 --- a/tests/api_connexion/endpoints/test_dag_endpoint.py +++ b/tests/api_connexion/endpoints/test_dag_endpoint.py @@ -20,6 +20,7 @@ import os import unittest.mock from datetime import datetime +import pendulum import pytest from airflow.api_connexion.exceptions import EXCEPTIONS_LINK_MAP @@ -46,6 +47,7 @@ DAG_ID = "test_dag" TASK_ID = "op1" DAG2_ID = "test_dag2" DAG3_ID = "test_dag3" +UTC_JSON_REPR = "UTC" if pendulum.__version__.startswith("3") else "Timezone('UTC')" @pytest.fixture(scope="module") @@ -316,7 +318,7 @@ class TestGetDagDetails(TestDagEndpoint): "tags": [], "template_searchpath": None, "timetable_description": None, - "timezone": "Timezone('UTC')", + "timezone": UTC_JSON_REPR, } assert response.json == expected @@ -367,7 +369,7 @@ class TestGetDagDetails(TestDagEndpoint): "tags": [], "template_searchpath": None, "timetable_description": None, - "timezone": "Timezone('UTC')", + "timezone": UTC_JSON_REPR, } assert response.json == expected @@ -418,7 +420,7 @@ class TestGetDagDetails(TestDagEndpoint): "tags": [], "template_searchpath": None, "timetable_description": None, - "timezone": "Timezone('UTC')", + "timezone": UTC_JSON_REPR, } assert response.json == expected @@ -478,7 +480,7 @@ class TestGetDagDetails(TestDagEndpoint): "tags": [], "template_searchpath": None, "timetable_description": None, - "timezone": "Timezone('UTC')", + "timezone": UTC_JSON_REPR, } response = self.client.get( f"/api/v1/dags/{self.dag_id}/details", environ_overrides={"REMOTE_USER": "test"} @@ -539,7 +541,7 @@ class TestGetDagDetails(TestDagEndpoint): "tags": [], "template_searchpath": None, "timetable_description": None, - "timezone": "Timezone('UTC')", + "timezone": UTC_JSON_REPR, } expected.update({"last_parsed": response.json["last_parsed"]}) assert response.json == expected diff --git a/tests/api_connexion/schemas/test_dag_schema.py b/tests/api_connexion/schemas/test_dag_schema.py index f3e54c0a96..df227eb5c6 100644 --- a/tests/api_connexion/schemas/test_dag_schema.py +++ b/tests/api_connexion/schemas/test_dag_schema.py @@ -18,6 +18,7 @@ from __future__ import annotations from datetime import datetime +import pendulum import pytest from airflow.api_connexion.schemas.dag_schema import ( @@ -29,6 +30,8 @@ from airflow.api_connexion.schemas.dag_schema import ( from airflow.models import DagModel, DagTag from airflow.models.dag import DAG +UTC_JSON_REPR = "UTC" if pendulum.__version__.startswith("3") else "Timezone('UTC')" + def test_serialize_test_dag_schema(url_safe_serializer): dag_model = DagModel( @@ -184,7 +187,7 @@ def test_serialize_test_dag_detail_schema(url_safe_serializer): "start_date": "2020-06-19T00:00:00+00:00", "tags": [{"name": "example1"}, {"name": "example2"}], "template_searchpath": None, - "timezone": "Timezone('UTC')", + "timezone": UTC_JSON_REPR, "max_active_runs": 16, "pickle_id": None, "end_date": None, diff --git a/tests/cli/commands/test_dag_command.py b/tests/cli/commands/test_dag_command.py index 4f16c381ad..1c1a7ff650 100644 --- a/tests/cli/commands/test_dag_command.py +++ b/tests/cli/commands/test_dag_command.py @@ -50,6 +50,10 @@ from tests.test_utils.config import conf_vars from tests.test_utils.db import clear_db_dags, clear_db_runs DEFAULT_DATE = timezone.make_aware(datetime(2015, 1, 1), timezone=timezone.utc) +if pendulum.__version__.startswith("3"): + DEFAULT_DATE_REPR = DEFAULT_DATE.isoformat(sep=" ") +else: + DEFAULT_DATE_REPR = DEFAULT_DATE.isoformat() # TODO: Check if tests needs side effects - locally there's missing DAG @@ -162,7 +166,7 @@ class TestCliDags: ) output = stdout.getvalue() - assert f"Dry run of DAG example_bash_operator on {DEFAULT_DATE.isoformat()}\n" in output + assert f"Dry run of DAG example_bash_operator on {DEFAULT_DATE_REPR}\n" in output assert "Task runme_0 located in DAG example_bash_operator\n" in output mock_run.assert_not_called() # Dry run shouldn't run the backfill @@ -235,12 +239,9 @@ class TestCliDags: output = stdout.getvalue() - assert ( - f"Dry run of DAG example_branch_python_operator_decorator on " - f"{DEFAULT_DATE.isoformat()}\n" in output - ) + assert f"Dry run of DAG example_branch_python_operator_decorator on {DEFAULT_DATE_REPR}\n" in output assert "Task run_this_first located in DAG example_branch_python_operator_decorator\n" in output - assert f"Dry run of DAG example_branch_operator on {DEFAULT_DATE.isoformat()}\n" in output + assert f"Dry run of DAG example_branch_operator on {DEFAULT_DATE_REPR}\n" in output assert "Task run_this_first located in DAG example_branch_operator\n" in output @mock.patch("airflow.cli.commands.dag_command.get_dag") diff --git a/tests/models/test_dag.py b/tests/models/test_dag.py index f7bf1ad6d0..de7fa87d5a 100644 --- a/tests/models/test_dag.py +++ b/tests/models/test_dag.py @@ -37,6 +37,7 @@ import pendulum import pytest import time_machine from dateutil.relativedelta import relativedelta +from pendulum.tz.timezone import Timezone from sqlalchemy import inspect from airflow import settings @@ -676,8 +677,8 @@ class TestDag: """ Make sure DST transitions are properly observed """ - local_tz = pendulum.timezone("Europe/Zurich") - start = local_tz.convert(datetime.datetime(2018, 10, 28, 2, 55), dst_rule=pendulum.PRE_TRANSITION) + local_tz = Timezone("Europe/Zurich") + start = local_tz.convert(datetime.datetime(2018, 10, 28, 2, 55, fold=0)) assert start.isoformat() == "2018-10-28T02:55:00+02:00", "Pre-condition: start date is in DST" utc = timezone.convert_to_utc(start) @@ -706,7 +707,7 @@ class TestDag: Make sure DST transitions are properly observed """ local_tz = pendulum.timezone("Europe/Zurich") - start = local_tz.convert(datetime.datetime(2018, 10, 27, 3), dst_rule=pendulum.PRE_TRANSITION) + start = local_tz.convert(datetime.datetime(2018, 10, 27, 3, fold=0)) utc = timezone.convert_to_utc(start) @@ -735,7 +736,7 @@ class TestDag: Make sure DST transitions are properly observed """ local_tz = pendulum.timezone("Europe/Zurich") - start = local_tz.convert(datetime.datetime(2018, 3, 25, 2), dst_rule=pendulum.PRE_TRANSITION) + start = local_tz.convert(datetime.datetime(2018, 3, 25, 2, fold=0)) utc = timezone.convert_to_utc(start) diff --git a/tests/providers/openlineage/plugins/test_utils.py b/tests/providers/openlineage/plugins/test_utils.py index 54710bcd9e..b7ced7a37c 100644 --- a/tests/providers/openlineage/plugins/test_utils.py +++ b/tests/providers/openlineage/plugins/test_utils.py @@ -23,7 +23,6 @@ import uuid from json import JSONEncoder from typing import Any -import pendulum import pytest from attrs import define from openlineage.client.utils import RedactMixin @@ -39,6 +38,7 @@ from airflow.providers.openlineage.utils.utils import ( to_json_encodable, url_to_https, ) +from airflow.utils import timezone from airflow.utils.log.secrets_masker import _secrets_masker from airflow.utils.state import State @@ -86,8 +86,8 @@ def test_get_dagrun_start_end(): state=State.NONE, run_id=run_id, data_interval=dag.get_next_data_interval(dag_model) ) assert dagrun.data_interval_start is not None - start_date_tz = datetime.datetime(2022, 1, 1, tzinfo=pendulum.tz.timezone("UTC")) - end_date_tz = datetime.datetime(2022, 1, 1, hour=2, tzinfo=pendulum.tz.timezone("UTC")) + start_date_tz = datetime.datetime(2022, 1, 1, tzinfo=timezone.utc) + end_date_tz = datetime.datetime(2022, 1, 1, hour=2, tzinfo=timezone.utc) assert dagrun.data_interval_start, dagrun.data_interval_end == (start_date_tz, end_date_tz) diff --git a/tests/sensors/test_time_sensor.py b/tests/sensors/test_time_sensor.py index 935d1cb128..54a0212a24 100644 --- a/tests/sensors/test_time_sensor.py +++ b/tests/sensors/test_time_sensor.py @@ -18,12 +18,10 @@ from __future__ import annotations from datetime import datetime, time -from unittest.mock import patch import pendulum import pytest import time_machine -from pendulum.tz.timezone import UTC from airflow.exceptions import TaskDeferred from airflow.models.dag import DAG @@ -33,7 +31,7 @@ from airflow.utils import timezone DEFAULT_TIMEZONE = "Asia/Singapore" # UTC+08:00 DEFAULT_DATE_WO_TZ = datetime(2015, 1, 1) -DEFAULT_DATE_WITH_TZ = datetime(2015, 1, 1, tzinfo=pendulum.tz.timezone(DEFAULT_TIMEZONE)) +DEFAULT_DATE_WITH_TZ = datetime(2015, 1, 1, tzinfo=timezone.parse_timezone(DEFAULT_TIMEZONE)) class TestTimeSensor: @@ -46,11 +44,11 @@ class TestTimeSensor: ], ) @time_machine.travel(timezone.datetime(2020, 1, 1, 23, 0).replace(tzinfo=timezone.utc)) - def test_timezone(self, default_timezone, start_date, expected): - with patch("airflow.settings.TIMEZONE", pendulum.timezone(default_timezone)): - dag = DAG("test", default_args={"start_date": start_date}) - op = TimeSensor(task_id="test", target_time=time(10, 0), dag=dag) - assert op.poke(None) == expected + def test_timezone(self, default_timezone, start_date, expected, monkeypatch): + monkeypatch.setattr("airflow.settings.TIMEZONE", timezone.parse_timezone(default_timezone)) + dag = DAG("test", default_args={"start_date": start_date}) + op = TimeSensor(task_id="test", target_time=time(10, 0), dag=dag) + assert op.poke(None) == expected class TestTimeSensorAsync: @@ -72,8 +70,7 @@ class TestTimeSensorAsync: with DAG("test_target_time_aware", start_date=timezone.datetime(2020, 1, 1, 23, 0)): aware_time = time(0, 1).replace(tzinfo=pendulum.local_timezone()) op = TimeSensorAsync(task_id="test", target_time=aware_time) - assert hasattr(op.target_datetime.tzinfo, "offset") - assert op.target_datetime.tzinfo.offset == 0 + assert op.target_datetime.tzinfo == timezone.utc def test_target_time_naive_dag_timezone(self): """ @@ -85,4 +82,4 @@ class TestTimeSensorAsync: ): op = TimeSensorAsync(task_id="test", target_time=pendulum.time(9, 0)) assert op.target_datetime.time() == pendulum.time(1, 0) - assert op.target_datetime.tzinfo == UTC + assert op.target_datetime.tzinfo == timezone.utc diff --git a/tests/serialization/serializers/test_serializers.py b/tests/serialization/serializers/test_serializers.py index 32e9787ccf..26027fdbf0 100644 --- a/tests/serialization/serializers/test_serializers.py +++ b/tests/serialization/serializers/test_serializers.py @@ -21,11 +21,13 @@ import decimal from unittest.mock import patch import numpy as np +import pendulum import pendulum.tz import pytest from dateutil.tz import tzutc from deltalake import DeltaTable from pendulum import DateTime +from pendulum.tz.timezone import FixedTimezone, Timezone from pyiceberg.catalog import Catalog from pyiceberg.io import FileIO from pyiceberg.table import Table @@ -39,6 +41,8 @@ if PY39: else: from backports.zoneinfo import ZoneInfo +PENDULUM3 = pendulum.__version__.startswith("3") + class TestSerializers: def test_datetime(self): @@ -227,3 +231,151 @@ class TestSerializers: assert i.version() == d.version() assert i._storage_options == d._storage_options assert d._storage_options is None + + @pytest.mark.skipif(not PENDULUM3, reason="Test case for pendulum~=3") + @pytest.mark.parametrize( + "ser_value, expected", + [ + pytest.param( + { + "__classname__": "pendulum.datetime.DateTime", + "__version__": 2, + "__data__": { + "timestamp": 1680307200.0, + "tz": { + "__classname__": "builtins.tuple", + "__version__": 1, + "__data__": ["UTC", "pendulum.tz.timezone.FixedTimezone", 1, True], + }, + }, + }, + pendulum.datetime(2023, 4, 1, tz=Timezone("UTC")), + id="in-utc-timezone", + ), + pytest.param( + { + "__classname__": "pendulum.datetime.DateTime", + "__version__": 2, + "__data__": { + "timestamp": 1680292800.0, + "tz": { + "__classname__": "builtins.tuple", + "__version__": 1, + "__data__": ["Asia/Tbilisi", "pendulum.tz.timezone.Timezone", 1, True], + }, + }, + }, + pendulum.datetime(2023, 4, 1, tz=Timezone("Asia/Tbilisi")), + id="non-dts-timezone", + ), + pytest.param( + { + "__classname__": "pendulum.datetime.DateTime", + "__version__": 2, + "__data__": { + "timestamp": 1680303600.0, + "tz": { + "__classname__": "builtins.tuple", + "__version__": 1, + "__data__": ["Europe/London", "pendulum.tz.timezone.Timezone", 1, True], + }, + }, + }, + pendulum.datetime(2023, 4, 1, tz=Timezone("Europe/London")), + id="dts-timezone", + ), + pytest.param( + { + "__classname__": "pendulum.datetime.DateTime", + "__version__": 2, + "__data__": { + "timestamp": 1680310800.0, + "tz": { + "__classname__": "builtins.tuple", + "__version__": 1, + "__data__": [-3600, "pendulum.tz.timezone.FixedTimezone", 1, True], + }, + }, + }, + pendulum.datetime(2023, 4, 1, tz=FixedTimezone(-3600)), + id="offset-timezone", + ), + ], + ) + def test_pendulum_2_to_3(self, ser_value, expected): + """Test deserialize objects in pendulum 3 which serialised in pendulum 2.""" + assert deserialize(ser_value) == expected + + @pytest.mark.skipif(PENDULUM3, reason="Test case for pendulum~=2") + @pytest.mark.parametrize( + "ser_value, expected", + [ + pytest.param( + { + "__classname__": "pendulum.datetime.DateTime", + "__version__": 2, + "__data__": { + "timestamp": 1680307200.0, + "tz": { + "__classname__": "builtins.tuple", + "__version__": 1, + "__data__": ["UTC", "pendulum.tz.timezone.Timezone", 1, True], + }, + }, + }, + pendulum.datetime(2023, 4, 1, tz=Timezone("UTC")), + id="in-utc-timezone", + ), + pytest.param( + { + "__classname__": "pendulum.datetime.DateTime", + "__version__": 2, + "__data__": { + "timestamp": 1680292800.0, + "tz": { + "__classname__": "builtins.tuple", + "__version__": 1, + "__data__": ["Asia/Tbilisi", "pendulum.tz.timezone.Timezone", 1, True], + }, + }, + }, + pendulum.datetime(2023, 4, 1, tz=Timezone("Asia/Tbilisi")), + id="non-dts-timezone", + ), + pytest.param( + { + "__classname__": "pendulum.datetime.DateTime", + "__version__": 2, + "__data__": { + "timestamp": 1680303600.0, + "tz": { + "__classname__": "builtins.tuple", + "__version__": 1, + "__data__": ["Europe/London", "pendulum.tz.timezone.Timezone", 1, True], + }, + }, + }, + pendulum.datetime(2023, 4, 1, tz=Timezone("Europe/London")), + id="dts-timezone", + ), + pytest.param( + { + "__classname__": "pendulum.datetime.DateTime", + "__version__": 2, + "__data__": { + "timestamp": 1680310800.0, + "tz": { + "__classname__": "builtins.tuple", + "__version__": 1, + "__data__": [-3600, "pendulum.tz.timezone.FixedTimezone", 1, True], + }, + }, + }, + pendulum.datetime(2023, 4, 1, tz=FixedTimezone(-3600)), + id="offset-timezone", + ), + ], + ) + def test_pendulum_3_to_2(self, ser_value, expected): + """Test deserialize objects in pendulum 2 which serialised in pendulum 3.""" + assert deserialize(ser_value) == expected diff --git a/tests/serialization/test_serialized_objects.py b/tests/serialization/test_serialized_objects.py index c059a8d236..a40e0d01ea 100644 --- a/tests/serialization/test_serialized_objects.py +++ b/tests/serialization/test_serialized_objects.py @@ -20,10 +20,10 @@ from __future__ import annotations import json from datetime import datetime, timedelta -import pendulum import pytest from dateutil import relativedelta from kubernetes.client import models as k8s +from pendulum.tz.timezone import Timezone from airflow.datasets import Dataset from airflow.exceptions import SerializationError @@ -142,7 +142,7 @@ def equal_time(a: datetime, b: datetime) -> bool: (1, None, equals), (datetime.utcnow(), DAT.DATETIME, equal_time), (timedelta(minutes=2), DAT.TIMEDELTA, equals), - (pendulum.tz.timezone("UTC"), DAT.TIMEZONE, lambda a, b: a.name == b.name), + (Timezone("UTC"), DAT.TIMEZONE, lambda a, b: a.name == b.name), (relativedelta.relativedelta(hours=+1), DAT.RELATIVEDELTA, lambda a, b: a.hours == b.hours), ({"test": "dict", "test-1": 1}, None, equals), (["array_item", 2], None, equals), diff --git a/tests/triggers/test_temporal.py b/tests/triggers/test_temporal.py index 655910394f..52cc2c64f6 100644 --- a/tests/triggers/test_temporal.py +++ b/tests/triggers/test_temporal.py @@ -64,9 +64,9 @@ def test_timedelta_trigger_serialization(): @pytest.mark.parametrize( "tz", [ - pendulum.tz.timezone("UTC"), - pendulum.tz.timezone("Europe/Paris"), - pendulum.tz.timezone("America/Toronto"), + timezone.parse_timezone("UTC"), + timezone.parse_timezone("Europe/Paris"), + timezone.parse_timezone("America/Toronto"), ], ) @pytest.mark.asyncio diff --git a/tests/utils/test_timezone.py b/tests/utils/test_timezone.py index ff5ad26f5a..df8af04604 100644 --- a/tests/utils/test_timezone.py +++ b/tests/utils/test_timezone.py @@ -21,13 +21,14 @@ import datetime import pendulum import pytest +from pendulum.tz.timezone import Timezone from airflow.utils import timezone -from airflow.utils.timezone import coerce_datetime +from airflow.utils.timezone import coerce_datetime, parse_timezone -CET = pendulum.tz.timezone("Europe/Paris") -EAT = pendulum.tz.timezone("Africa/Nairobi") # Africa/Nairobi -ICT = pendulum.tz.timezone("Asia/Bangkok") # Asia/Bangkok +CET = Timezone("Europe/Paris") +EAT = Timezone("Africa/Nairobi") # Africa/Nairobi +ICT = Timezone("Asia/Bangkok") # Asia/Bangkok UTC = timezone.utc @@ -117,3 +118,41 @@ class TestTimezone: ) def test_coerce_datetime(input_datetime, output_datetime): assert output_datetime == coerce_datetime(input_datetime) + + [email protected]( + "tz_name", + [ + pytest.param("Europe/Paris", id="CET"), + pytest.param("Africa/Nairobi", id="EAT"), + pytest.param("Asia/Bangkok", id="ICT"), + ], +) +def test_parse_timezone_iana(tz_name: str): + tz = parse_timezone(tz_name) + assert tz.name == tz_name + assert parse_timezone(tz_name) is tz + + [email protected]("tz_name", ["utc", "UTC", "uTc"]) +def test_parse_timezone_utc(tz_name): + tz = parse_timezone(tz_name) + assert tz.name == "UTC" + assert parse_timezone(tz_name) is tz + assert tz is timezone.utc, "Expected that UTC timezone is same object as `airflow.utils.timezone.utc`" + + [email protected]( + "tz_offset, expected_offset, expected_name", + [ + pytest.param(0, 0, "+00:00", id="zero-offset"), + pytest.param(-3600, -3600, "-01:00", id="1-hour-behind"), + pytest.param(19800, 19800, "+05:30", id="5.5-hours-ahead"), + ], +) +def test_parse_timezone_offset(tz_offset: int, expected_offset, expected_name): + tz = parse_timezone(tz_offset) + assert hasattr(tz, "offset") + assert tz.offset == expected_offset + assert tz.name == expected_name + assert parse_timezone(tz_offset) is tz
