This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 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 30006eb9f8c Fix Dag run duration stats crash on PostgreSQL 14+ (#70833)
30006eb9f8c is described below
commit 30006eb9f8c76b42beb349b5c1d859678a641253
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Mon Aug 3 07:31:37 2026 +0200
Fix Dag run duration stats crash on PostgreSQL 14+ (#70833)
PostgreSQL 14 changed EXTRACT(epoch FROM ...) to return numeric rather than
double precision, so DagRun.duration is hydrated as Decimal on Postgres 14+.
The duration-stats percentile interpolation then multiplies a Decimal by a
float, which Python disallows, so the Dag run stats endpoint raises a TypeError
for any Dag that has completed runs.
---
.../api_fastapi/core_api/services/ui/dag_run.py | 4 ++-
.../core_api/services/ui/test_dag_run.py | 41 ++++++++++++++++++++++
2 files changed, 44 insertions(+), 1 deletion(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
index a9ba75417c8..2a3ab183252 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
@@ -35,7 +35,9 @@ def compute_duration_stats(durations: list[float]) ->
DurationStats | None:
if not durations:
return None
- sorted_d = sorted(durations)
+ # On Postgres 14+, DagRun.duration comes back as Decimal (EXTRACT(epoch
...) returns
+ # numeric); coerce to float so the percentile interpolation below (Decimal
* float) works.
+ sorted_d = sorted(float(d) for d in durations)
counts = Counter(round(d) for d in sorted_d)
max_count = max(counts.values())
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py
b/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py
new file mode 100644
index 00000000000..afa9c5277e2
--- /dev/null
+++ b/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py
@@ -0,0 +1,41 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from decimal import Decimal
+
+import pytest
+
+from airflow.api_fastapi.core_api.services.ui.dag_run import
compute_duration_stats
+
+
[email protected]("cast", [float, Decimal], ids=["float", "decimal"])
+def test_compute_duration_stats_handles_float_and_decimal(cast):
+ """Durations arrive as Decimal on Postgres 14+ (EXTRACT(epoch ...) returns
numeric) and as
+ float/int on other backends; the stats must compute for either without
raising TypeError."""
+ durations = [cast(v) for v in (10, 20, 20, 30, 40)]
+
+ stats = compute_duration_stats(durations)
+
+ assert stats is not None
+ assert stats.mean == 24.0
+ assert stats.mode == 20.0
+ assert stats.p50 == 20.0
+ assert stats.p90 == 36.0
+ assert stats.p95 == 38.0
+ assert stats.p99 == 39.6