This is an automated email from the ASF dual-hosted git repository.
taragolis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 2ffa6e4c4c Add support of Pendulum 3 (#36281)
2ffa6e4c4c is described below
commit 2ffa6e4c4c9dc129daa54491d5af8f535cd0d479
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]>
---
.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 b3bbb8c0d4..45c2ccf7ef 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1166,11 +1166,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()
@@ -1542,6 +1592,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 8e32b09a71..8d8aef97c4 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -909,6 +909,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
@@ -938,6 +950,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 395898f367..859d78b0ab 100644
--- a/airflow/models/dag.py
+++ b/airflow/models/dag.py
@@ -139,7 +139,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
@@ -214,7 +214,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)
@@ -533,7 +533,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 773f83caf8..a1b9c32a99 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
from sqlalchemy import create_engine, exc, text
from sqlalchemy.orm import scoped_session, sessionmaker
@@ -39,6 +38,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
@@ -49,13 +49,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 45bfe3640f..9dd6cad7fe 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 d335ab940d..9d9b248ec7 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, event, nullsfirst, tuple_
from sqlalchemy.dialects import mysql
@@ -32,7 +31,7 @@ from sqlalchemy.types import JSON, Text, TypeDecorator
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
@@ -44,8 +43,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 d8ca0f5d4d..35a46999ab 100644
--- a/dev/breeze/src/airflow_breeze/commands/common_options.py
+++ b/dev/breeze/src/airflow_breeze/commands/common_options.py
@@ -150,6 +150,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 899eba05a2..92882a781a 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,
@@ -247,6 +248,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
@@ -292,6 +294,7 @@ def shell(
database_isolation: bool,
db_reset: bool,
downgrade_sqlalchemy: bool,
+ downgrade_pendulum: bool,
docker_host: str | None,
executor: str,
extra_args: tuple,
@@ -351,6 +354,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 fc0247b9d5..11b81756f7 100644
--- a/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
@@ -157,6 +157,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 c0cea0ee36..beb1254823 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,
@@ -469,6 +470,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
@@ -510,6 +512,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
@@ -545,6 +548,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
@@ -586,6 +590,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,
@@ -628,6 +633,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 84976c4f85..09daa0b451 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
@@ -78,6 +78,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",
],
@@ -125,6 +126,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",
],
@@ -175,6 +177,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 2cf35cd42c..24203bdbff 100644
--- a/dev/breeze/src/airflow_breeze/params/shell_params.py
+++ b/dev/breeze/src/airflow_breeze/params/shell_params.py
@@ -136,6 +136,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
@@ -482,6 +483,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 84844cf1ac..a8edc07feb 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 3002.3999999999996"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 3026.7999999999997"
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="2951.3999999999996" />
+ <rect x="0" y="0" width="1463.0" height="2975.7999999999997" />
</clipPath>
<clipPath id="breeze-shell-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -405,9 +405,12 @@
<clipPath id="breeze-shell-line-119">
<rect x="0" y="2905.1" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-shell-line-120">
+ <rect x="0" y="2929.5" 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="3000.4" 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="3024.8" 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"/>
@@ -521,23 +524,24 @@
</text><text class="breeze-shell-r5" x="0" y="2484.4" textLength="24.4"
clip-path="url(#breeze-shell-line-101)">╭─</text><text class="breeze-shell-r5"
x="24.4" y="2484.4" textLength="500.2"
clip-path="url(#breeze-shell-line-101)"> Upgrading/downgrading selected packages </text><text
class="breeze-shell-r5" x="524.6" y="2484.4" textLength="915"
clip-path="url(#breeze-shell-line-101)">───────────────────────────────────────────────────────────────────────────</text><tex
[...]
</text><text class="breeze-shell-r5" x="0" y="2508.8" textLength="12.2"
clip-path="url(#breeze-shell-line-102)">│</text><text class="breeze-shell-r4"
x="24.4" y="2508.8" textLength="12.2"
clip-path="url(#breeze-shell-line-102)">-</text><text class="breeze-shell-r4"
x="36.6" y="2508.8" textLength="97.6"
clip-path="url(#breeze-shell-line-102)">-upgrade</text><text
class="breeze-shell-r4" x="134.2" y="2508.8" textLength="61"
clip-path="url(#breeze-shell-line-102)">-boto</text><text class="b [...]
</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="122"
clip-path="url(#breeze-shell-line-103)">-downgrade</text><text
class="breeze-shell-r4" x="158.6" y="2533.2" textLength="134.2"
clip-path="url(#breeze-shell-line-103)">-sqlalchemy</text><tex [...]
-</text><text class="breeze-shell-r5" x="0" y="2557.6" textLength="1464"
clip-path="url(#breeze-shell-line-104)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-shell-r1" x="1464" y="2557.6" textLength="12.2"
clip-path="url(#breeze-shell-line-104)">
-</text><text class="breeze-shell-r5" x="0" y="2582" textLength="24.4"
clip-path="url(#breeze-shell-line-105)">╭─</text><text class="breeze-shell-r5"
x="24.4" y="2582" textLength="183"
clip-path="url(#breeze-shell-line-105)"> DB test flags </text><text
class="breeze-shell-r5" x="207.4" y="2582" textLength="1232.2"
clip-path="url(#breeze-shell-line-105)">─────────────────────────────────────────────────────────────────────────────────────────────────────</text><text
cla [...]
-</text><text class="breeze-shell-r5" x="0" y="2606.4" textLength="12.2"
clip-path="url(#breeze-shell-line-106)">│</text><text class="breeze-shell-r4"
x="24.4" y="2606.4" textLength="12.2"
clip-path="url(#breeze-shell-line-106)">-</text><text class="breeze-shell-r4"
x="36.6" y="2606.4" textLength="48.8"
clip-path="url(#breeze-shell-line-106)">-run</text><text
class="breeze-shell-r4" x="85.4" y="2606.4" textLength="170.8"
clip-path="url(#breeze-shell-line-106)">-db-tests-only</text><text c [...]
-</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="61"
clip-path="url(#breeze-shell-line-107)">-skip</text><text
class="breeze-shell-r4" x="97.6" y="2630.8" textLength="109.8"
clip-path="url(#breeze-shell-line-107)">-db-tests</text><text class=" [...]
-</text><text class="breeze-shell-r5" x="0" y="2655.2" textLength="1464"
clip-path="url(#breeze-shell-line-108)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-shell-r1" x="1464" y="2655.2" textLength="12.2"
clip-path="url(#breeze-shell-line-108)">
-</text><text class="breeze-shell-r5" x="0" y="2679.6" textLength="24.4"
clip-path="url(#breeze-shell-line-109)">╭─</text><text class="breeze-shell-r5"
x="24.4" y="2679.6" textLength="183"
clip-path="url(#breeze-shell-line-109)"> Other options </text><text
class="breeze-shell-r5" x="207.4" y="2679.6" textLength="1232.2"
clip-path="url(#breeze-shell-line-109)">─────────────────────────────────────────────────────────────────────────────────────────────────────</text><text
cl [...]
-</text><text class="breeze-shell-r5" x="0" y="2704" textLength="12.2"
clip-path="url(#breeze-shell-line-110)">│</text><text class="breeze-shell-r4"
x="24.4" y="2704" textLength="12.2"
clip-path="url(#breeze-shell-line-110)">-</text><text class="breeze-shell-r4"
x="36.6" y="2704" textLength="97.6"
clip-path="url(#breeze-shell-line-110)">-forward</text><text
class="breeze-shell-r4" x="134.2" y="2704" textLength="146.4"
clip-path="url(#breeze-shell-line-110)">-credentials</text><text class= [...]
-</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="48.8"
clip-path="url(#breeze-shell-line-111)">-max</text><text
class="breeze-shell-r4" x="85.4" y="2728.4" textLength="61"
clip-path="url(#breeze-shell-line-111)">-time</text><text class="breeze [...]
-</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-r7"
x="353.8" y="2752.8" textLength="1049.2"
clip-path="url(#breeze-shell-line-112)">(INTEGER RANGE)                                        &
[...]
-</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="97.6"
clip-path="url(#breeze-shell-line-113)">-verbose</text><text
class="breeze-shell-r4" x="134.2" y="2777.2" textLength="109.8"
clip-path="url(#breeze-shell-line-113)">-commands</text><text c [...]
-</text><text class="breeze-shell-r5" x="0" y="2801.6" textLength="1464"
clip-path="url(#breeze-shell-line-114)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-shell-r1" x="1464" y="2801.6" textLength="12.2"
clip-path="url(#breeze-shell-line-114)">
-</text><text class="breeze-shell-r5" x="0" y="2826" textLength="24.4"
clip-path="url(#breeze-shell-line-115)">╭─</text><text class="breeze-shell-r5"
x="24.4" y="2826" textLength="195.2"
clip-path="url(#breeze-shell-line-115)"> Common options </text><text
class="breeze-shell-r5" x="219.6" y="2826" textLength="1220"
clip-path="url(#breeze-shell-line-115)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text
class="b [...]
-</text><text class="breeze-shell-r5" x="0" y="2850.4" textLength="12.2"
clip-path="url(#breeze-shell-line-116)">│</text><text class="breeze-shell-r4"
x="24.4" y="2850.4" textLength="12.2"
clip-path="url(#breeze-shell-line-116)">-</text><text class="breeze-shell-r4"
x="36.6" y="2850.4" textLength="85.4"
clip-path="url(#breeze-shell-line-116)">-answer</text><text
class="breeze-shell-r6" x="158.6" y="2850.4" textLength="24.4"
clip-path="url(#breeze-shell-line-116)">-a</text><text class="bre [...]
-</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="48.8"
clip-path="url(#breeze-shell-line-117)">-dry</text><text
class="breeze-shell-r4" x="85.4" y="2874.8" textLength="48.8"
clip-path="url(#breeze-shell-line-117)">-run</text><text class="breez [...]
-</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="97.6"
clip-path="url(#breeze-shell-line-118)">-verbose</text><text
class="breeze-shell-r6" x="158.6" y="2899.2" textLength="24.4"
clip-path="url(#breeze-shell-line-118)">-v</text><text class="br [...]
-</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="61"
clip-path="url(#breeze-shell-line-119)">-help</text><text
class="breeze-shell-r6" x="158.6" y="2923.6" textLength="24.4"
clip-path="url(#breeze-shell-line-119)">-h</text><text class="breeze- [...]
-</text><text class="breeze-shell-r5" x="0" y="2948" textLength="1464"
clip-path="url(#breeze-shell-line-120)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-shell-r1" x="1464" y="2948" textLength="12.2"
clip-path="url(#breeze-shell-line-120)">
+</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="109.8"
clip-path="url(#breeze-shell-line-104)">-pendulum</text><text [...]
+</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>
</g>
</g>
diff --git a/images/breeze/output_shell.txt b/images/breeze/output_shell.txt
index 55d2e3494b..0f3e91992c 100644
--- a/images/breeze/output_shell.txt
+++ b/images/breeze/output_shell.txt
@@ -1 +1 @@
-71fc371306c18db6f5091357dfcb5d4b
+d0b4fa36683eaa51744c631715f9ffe5
diff --git a/images/breeze/output_testing_db-tests.svg
b/images/breeze/output_testing_db-tests.svg
index 5ab4ce2a82..25dd453d6a 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 1806.8"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 1831.1999999999998"
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="1755.8" />
+ <rect x="0" y="0" width="1463.0" height="1780.1999999999998" />
</clipPath>
<clipPath id="breeze-testing-db-tests-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -258,9 +258,12 @@
<clipPath id="breeze-testing-db-tests-line-70">
<rect x="0" y="1709.5" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-testing-db-tests-line-71">
+ <rect x="0" y="1733.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="1804.8" 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="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>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -334,14 +337,15 @@
</text><text class="breeze-testing-db-tests-r5" x="0" y="1508.4"
textLength="12.2"
clip-path="url(#breeze-testing-db-tests-line-61)">│</text><text
class="breeze-testing-db-tests-r5" x="414.8" y="1508.4" textLength="1024.8"
clip-path="url(#breeze-testing-db-tests-line-61)">[default: selected]                                
[...]
</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-r4" x="24.4" y="1532.8" textLength="12.2"
clip-path="url(#breeze-testing-db-tests-line-62)">-</text><text
class="breeze-testing-db-tests-r4" x="36.6" y="1532.8" textLength="97.6"
clip-path="url(#breeze-testing-db-tests-line-62)">-upgrade</text><text
class="breeze-testing-db-tests-r4" x="134.2" y="1532.8" textLeng [...]
</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="122"
clip-path="url(#breeze-testing-db-tests-line-63)">-downgrade</text><text
class="breeze-testing-db-tests-r4" x="158.6" y="1557.2" textLen [...]
-</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="85.4"
clip-path="url(#breeze-testing-db-tests-line-64)">-remove</text><text
class="breeze-testing-db-tests-r4" x="122" y="1581.6" textLength= [...]
-</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="61"
clip-path="url(#breeze-testing-db-tests-line-65)">-skip</text><text
class="breeze-testing-db-tests-r4" x="97.6" y="1606" textLength="244" clip-
[...]
-</text><text class="breeze-testing-db-tests-r5" x="0" y="1630.4"
textLength="1464"
clip-path="url(#breeze-testing-db-tests-line-66)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-db-tests-r1" x="1464" y="1630.4" textLength="12.2"
clip-path="url(#breeze-testing-db-tests-line-66)">
-</text><text class="breeze-testing-db-tests-r5" x="0" y="1654.8"
textLength="24.4"
clip-path="url(#breeze-testing-db-tests-line-67)">╭─</text><text
class="breeze-testing-db-tests-r5" x="24.4" y="1654.8" textLength="195.2"
clip-path="url(#breeze-testing-db-tests-line-67)"> Common options </text><text
class="breeze-testing-db-tests-r5" x="219.6" y="1654.8" textLength="1220"
clip-path="url(#breeze-testing-db-tests-line-67)">────────────────────────────────────────────────────
[...]
-</text><text class="breeze-testing-db-tests-r5" x="0" y="1679.2"
textLength="12.2"
clip-path="url(#breeze-testing-db-tests-line-68)">│</text><text
class="breeze-testing-db-tests-r4" x="24.4" y="1679.2" textLength="12.2"
clip-path="url(#breeze-testing-db-tests-line-68)">-</text><text
class="breeze-testing-db-tests-r4" x="36.6" y="1679.2" textLength="97.6"
clip-path="url(#breeze-testing-db-tests-line-68)">-verbose</text><text
class="breeze-testing-db-tests-r7" x="158.6" y="1679.2" textLeng [...]
-</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="48.8"
clip-path="url(#breeze-testing-db-tests-line-69)">-dry</text><text
class="breeze-testing-db-tests-r4" x="85.4" y="1703.6" textLength="4 [...]
-</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="61"
clip-path="url(#breeze-testing-db-tests-line-70)">-help</text><text
class="breeze-testing-db-tests-r7" x="158.6" y="1728" textLength="24.4" cli
[...]
-</text><text class="breeze-testing-db-tests-r5" x="0" y="1752.4"
textLength="1464"
clip-path="url(#breeze-testing-db-tests-line-71)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-db-tests-r1" x="1464" y="1752.4" textLength="12.2"
clip-path="url(#breeze-testing-db-tests-line-71)">
+</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>
</g>
</g>
diff --git a/images/breeze/output_testing_db-tests.txt
b/images/breeze/output_testing_db-tests.txt
index faadf67633..1700b42b34 100644
--- a/images/breeze/output_testing_db-tests.txt
+++ b/images/breeze/output_testing_db-tests.txt
@@ -1 +1 @@
-080df5305da046794b7d3d7edb72a645
+4b0ba29824ebb5cb2580b66ab022067e
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 0244b7300a..9c40f203e3 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 2221.6"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 2246.0"
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="2170.6" />
+ <rect x="0" y="0" width="1463.0" height="2195.0" />
</clipPath>
<clipPath id="breeze-testing-tests-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -309,9 +309,12 @@
<clipPath id="breeze-testing-tests-line-87">
<rect x="0" y="2124.3" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-testing-tests-line-88">
+ <rect x="0" y="2148.7" 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="2219.6" 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="2244" 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"/>
@@ -402,14 +405,15 @@
</text><text class="breeze-testing-tests-r5" x="0" y="1923.2"
textLength="12.2" clip-path="url(#breeze-testing-tests-line-78)">│</text><text
class="breeze-testing-tests-r5" x="414.8" y="1923.2" textLength="1024.8"
clip-path="url(#breeze-testing-tests-line-78)">[default: selected]                                  
[...]
</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-r4" x="24.4" y="1947.6" textLength="12.2"
clip-path="url(#breeze-testing-tests-line-79)">-</text><text
class="breeze-testing-tests-r4" x="36.6" y="1947.6" textLength="97.6"
clip-path="url(#breeze-testing-tests-line-79)">-upgrade</text><text
class="breeze-testing-tests-r4" x="134.2" y="1947.6" textLength="61"
clip-path="ur [...]
</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="122"
clip-path="url(#breeze-testing-tests-line-80)">-downgrade</text><text
class="breeze-testing-tests-r4" x="158.6" y="1972" textLength="134.2"
clip-path="url(#b [...]
-</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="85.4"
clip-path="url(#breeze-testing-tests-line-81)">-remove</text><text
class="breeze-testing-tests-r4" x="122" y="1996.4" textLength="158.6"
clip-path="ur [...]
-</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="61"
clip-path="url(#breeze-testing-tests-line-82)">-skip</text><text
class="breeze-testing-tests-r4" x="97.6" y="2020.8" textLength="244"
clip-path="url(#br [...]
-</text><text class="breeze-testing-tests-r5" x="0" y="2045.2"
textLength="1464"
clip-path="url(#breeze-testing-tests-line-83)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-tests-r1" x="1464" y="2045.2" textLength="12.2"
clip-path="url(#breeze-testing-tests-line-83)">
-</text><text class="breeze-testing-tests-r5" x="0" y="2069.6"
textLength="24.4" clip-path="url(#breeze-testing-tests-line-84)">╭─</text><text
class="breeze-testing-tests-r5" x="24.4" y="2069.6" textLength="195.2"
clip-path="url(#breeze-testing-tests-line-84)"> Common options </text><text
class="breeze-testing-tests-r5" x="219.6" y="2069.6" textLength="1220"
clip-path="url(#breeze-testing-tests-line-84)">──────────────────────────────────────────────────────────────────────
[...]
-</text><text class="breeze-testing-tests-r5" x="0" y="2094" textLength="12.2"
clip-path="url(#breeze-testing-tests-line-85)">│</text><text
class="breeze-testing-tests-r4" x="24.4" y="2094" textLength="12.2"
clip-path="url(#breeze-testing-tests-line-85)">-</text><text
class="breeze-testing-tests-r4" x="36.6" y="2094" textLength="97.6"
clip-path="url(#breeze-testing-tests-line-85)">-verbose</text><text
class="breeze-testing-tests-r6" x="158.6" y="2094" textLength="24.4"
clip-path="url(#bre [...]
-</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="48.8"
clip-path="url(#breeze-testing-tests-line-86)">-dry</text><text
class="breeze-testing-tests-r4" x="85.4" y="2118.4" textLength="48.8"
clip-path="url(# [...]
-</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="61"
clip-path="url(#breeze-testing-tests-line-87)">-help</text><text
class="breeze-testing-tests-r6" x="158.6" y="2142.8" textLength="24.4"
clip-path="url(# [...]
-</text><text class="breeze-testing-tests-r5" x="0" y="2167.2"
textLength="1464"
clip-path="url(#breeze-testing-tests-line-88)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-tests-r1" x="1464" y="2167.2" textLength="12.2"
clip-path="url(#breeze-testing-tests-line-88)">
+</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="109.8"
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>
</g>
</g>
diff --git a/images/breeze/output_testing_tests.txt
b/images/breeze/output_testing_tests.txt
index 524423ad2f..9bf63945be 100644
--- a/images/breeze/output_testing_tests.txt
+++ b/images/breeze/output_testing_tests.txt
@@ -1 +1 @@
-b94ea9ea9ea473d49e1708e63fe4d4a6
+93dd0156785e1dd5261b555a278a5ebd
diff --git a/kubernetes_tests/test_kubernetes_pod_operator.py
b/kubernetes_tests/test_kubernetes_pod_operator.py
index 13ae3835aa..eb50adda68 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 65730eac15..0d558a7a34 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -119,9 +119,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 0518e35b0d..9531286a33 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