20
20
import json
21
21
import textwrap
22
22
import time
23
- from typing import Any
23
+ from typing import TYPE_CHECKING , Any , Sequence
24
24
from urllib .parse import urlencode
25
25
26
- import sqlalchemy as sqla
27
26
from flask import request , url_for
28
27
from flask .helpers import flash
29
28
from flask_appbuilder .forms import FieldConverter
37
36
from pendulum .datetime import DateTime
38
37
from pygments import highlight , lexers
39
38
from pygments .formatters import HtmlFormatter
39
+ from sqlalchemy import func , types
40
40
from sqlalchemy .ext .associationproxy import AssociationProxy
41
41
42
- from airflow import models
43
42
from airflow .exceptions import RemovedInAirflow3Warning
44
43
from airflow .models import errors
44
+ from airflow .models .dagrun import DagRun
45
45
from airflow .models .dagwarning import DagWarning
46
46
from airflow .models .taskinstance import TaskInstance
47
47
from airflow .utils import timezone
51
51
from airflow .www .forms import DateTimeWithTimezoneField
52
52
from airflow .www .widgets import AirflowDateTimePickerWidget
53
53
54
+ if TYPE_CHECKING :
55
+ from sqlalchemy .orm .query import Query
56
+ from sqlalchemy .sql .operators import ColumnOperators
57
+
54
58
55
59
def datetime_to_string (value : DateTime | None ) -> str | None :
56
60
if value is None :
@@ -129,7 +133,7 @@ def get_mapped_summary(parent_instance, task_instances):
129
133
}
130
134
131
135
132
- def encode_dag_run (dag_run : models . DagRun | None ) -> dict [str , Any ] | None :
136
+ def encode_dag_run (dag_run : DagRun | None ) -> dict [str , Any ] | None :
133
137
if not dag_run :
134
138
return None
135
139
@@ -436,6 +440,34 @@ def dag_run_link(attr):
436
440
return Markup ('<a href="{url}">{run_id}</a>' ).format (url = url , run_id = run_id )
437
441
438
442
443
+ def _get_run_ordering_expr (name : str ) -> ColumnOperators :
444
+ expr = DagRun .__table__ .columns [name ]
445
+ # Data interval columns are NULL for runs created before 2.3, but SQL's
446
+ # NULL-sorting logic would make those old runs always appear first. In a
447
+ # perfect world we'd want to sort by ``get_run_data_interval()``, but that's
448
+ # not efficient, so instead the columns are coalesced into execution_date,
449
+ # which is good enough in most cases.
450
+ if name in ("data_interval_start" , "data_interval_end" ):
451
+ expr = func .coalesce (expr , DagRun .execution_date )
452
+ return expr .desc ()
453
+
454
+
455
+ def sorted_dag_runs (query : Query , * , ordering : Sequence [str ], limit : int ) -> Sequence [DagRun ]:
456
+ """Produce DAG runs sorted by specified columns.
457
+
458
+ :param query: An ORM query object against *DagRun*.
459
+ :param ordering: Column names to sort the runs. should generally come from a
460
+ timetable's ``run_ordering``.
461
+ :param limit: Number of runs to limit to.
462
+ :return: A list of DagRun objects ordered by the specified columns. The list
463
+ contains only the *last* objects, but in *ascending* order.
464
+ """
465
+ ordering_exprs = (_get_run_ordering_expr (name ) for name in ordering )
466
+ runs = query .order_by (* ordering_exprs , DagRun .id .desc ()).limit (limit ).all ()
467
+ runs .reverse ()
468
+ return runs
469
+
470
+
439
471
def format_map_index (attr : dict ) -> str :
440
472
"""Format map index for list columns in model view."""
441
473
value = attr ['map_index' ]
@@ -651,7 +683,7 @@ def is_utcdatetime(self, col_name):
651
683
obj = self .list_columns [col_name ].type
652
684
return (
653
685
isinstance (obj , UtcDateTime )
654
- or isinstance (obj , sqla . types .TypeDecorator )
686
+ or isinstance (obj , types .TypeDecorator )
655
687
and isinstance (obj .impl , UtcDateTime )
656
688
)
657
689
return False
@@ -664,7 +696,7 @@ def is_extendedjson(self, col_name):
664
696
obj = self .list_columns [col_name ].type
665
697
return (
666
698
isinstance (obj , ExtendedJSON )
667
- or isinstance (obj , sqla . types .TypeDecorator )
699
+ or isinstance (obj , types .TypeDecorator )
668
700
and isinstance (obj .impl , ExtendedJSON )
669
701
)
670
702
return False
0 commit comments