From 726d8b714ff47d5cc29f41a2e767cbc525addad4 Mon Sep 17 00:00:00 2001 From: Stuart Buckingham Date: Sun, 30 Nov 2025 22:27:56 -0600 Subject: [PATCH 1/3] Return dag tags in alphabetical order --- .../api_fastapi/core_api/datamodels/dags.py | 10 +++ .../core_api/routes/public/test_dags.py | 64 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py index 5420a34c9a9d0..4dbce7884b82b 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py @@ -84,6 +84,16 @@ class DAGResponse(BaseModel): next_dagrun_run_after: datetime | None owners: list[str] + @field_validator("tags", mode="before") + @classmethod + def sort_tags(cls, v: Any) -> list[Any]: + """Sort tags alphabetically by name.""" + if v is None: + return [] + if isinstance(v, list): + return sorted(v, key=lambda tag: tag.name if hasattr(tag, "name") else str(tag)) + return v + @field_validator("owners", mode="before") @classmethod def get_owners(cls, v: Any) -> list[str] | None: diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py index 15a7395181557..90dd8c2cf889d 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py @@ -526,6 +526,40 @@ def test_get_dags_filter_has_import_errors(self, session, test_client, filter_va assert body["total_entries"] == 1 assert [dag["dag_id"] for dag in body["dags"]] == expected_ids + def test_get_dags_tags_sorted_alphabetically(self, session, test_client): + """Test that tags are returned in alphabetical order.""" + # Create a DAG with multiple tags in non-alphabetical order + dag_id = "test_dag_sorted_tags" + dag_model = DagModel( + dag_id=dag_id, + bundle_name="dag_maker", + fileloc=f"/tmp/{dag_id}.py", + is_stale=False, + ) + session.add(dag_model) + session.flush() + + # Add tags in non-alphabetical order + tag_names = ["zebra", "alpha", "mike", "bravo"] + for tag_name in tag_names: + tag = DagTag(name=tag_name, dag_id=dag_id) + session.add(tag) + + session.commit() + + response = test_client.get("/dags") + assert response.status_code == 200 + body = response.json() + + # Find our test DAG in the response + test_dag = next((d for d in body["dags"] if d["dag_id"] == dag_id), None) + assert test_dag is not None + + # Verify tags are sorted alphabetically + tag_names_in_response = [tag["name"] for tag in test_dag["tags"]] + expected_sorted_tags = sorted(tag_names) + assert tag_names_in_response == expected_sorted_tags + def test_get_dags_no_n_plus_one_queries(self, session, test_client): """Test that fetching DAGs with tags doesn't trigger n+1 queries.""" num_dags = 5 @@ -1214,6 +1248,36 @@ def test_get_dag( } assert res_json == expected + def test_get_dag_tags_sorted_alphabetically(self, session, test_client): + """Test that tags are returned in alphabetical order for a single DAG.""" + # Create a DAG with multiple tags in non-alphabetical order + dag_id = "test_dag_single_sorted_tags" + dag_model = DagModel( + dag_id=dag_id, + bundle_name="dag_maker", + fileloc=f"/tmp/{dag_id}.py", + is_stale=False, + ) + session.add(dag_model) + session.flush() + + # Add tags in non-alphabetical order + tag_names = ["zebra", "alpha", "mike", "bravo"] + for tag_name in tag_names: + tag = DagTag(name=tag_name, dag_id=dag_id) + session.add(tag) + + session.commit() + + response = test_client.get(f"/dags/{dag_id}") + assert response.status_code == 200 + res_json = response.json() + + # Verify tags are sorted alphabetically + tag_names_in_response = [tag["name"] for tag in res_json["tags"]] + expected_sorted_tags = sorted(tag_names) + assert tag_names_in_response == expected_sorted_tags + def test_get_dag_should_response_401(self, unauthenticated_test_client): response = unauthenticated_test_client.get(f"/dags/{DAG1_ID}") assert response.status_code == 401 From d452b73d2cfb3eed32a6159763621fe6c5c29cc4 Mon Sep 17 00:00:00 2001 From: Stuart Buckingham Date: Sun, 30 Nov 2025 22:27:56 -0600 Subject: [PATCH 2/3] Return dag tags in alphabetical order --- .../api_fastapi/core_api/datamodels/dags.py | 6 ++ .../core_api/routes/public/test_dags.py | 61 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py index 5420a34c9a9d0..b56f92262c39c 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dags.py @@ -28,6 +28,7 @@ AliasGenerator, ConfigDict, computed_field, + field_serializer, field_validator, ) @@ -84,6 +85,11 @@ class DAGResponse(BaseModel): next_dagrun_run_after: datetime | None owners: list[str] + @field_serializer("tags") + def serialize_tags(self, tags: list[DagTagResponse]) -> list[DagTagResponse]: + """Sort tags alphabetically by name.""" + return sorted(tags, key=lambda tag: tag.name) + @field_validator("owners", mode="before") @classmethod def get_owners(cls, v: Any) -> list[str] | None: diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py index 15a7395181557..b4e0564f49436 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py @@ -526,6 +526,40 @@ def test_get_dags_filter_has_import_errors(self, session, test_client, filter_va assert body["total_entries"] == 1 assert [dag["dag_id"] for dag in body["dags"]] == expected_ids + def test_get_dags_tags_sorted_alphabetically(self, session, test_client): + """Test that tags are returned in alphabetical order.""" + # Create a DAG with multiple tags in non-alphabetical order + dag_id = "test_dag_sorted_tags" + dag_model = DagModel( + dag_id=dag_id, + bundle_name="dag_maker", + fileloc=f"/tmp/{dag_id}.py", + is_stale=False, + ) + session.add(dag_model) + session.flush() + + # Add tags in non-alphabetical order + tag_names = ["zebra", "alpha", "mike", "bravo"] + for tag_name in tag_names: + tag = DagTag(name=tag_name, dag_id=dag_id) + session.add(tag) + + session.commit() + + response = test_client.get("/dags") + assert response.status_code == 200 + body = response.json() + + # Find our test DAG in the response + test_dag = next((d for d in body["dags"] if d["dag_id"] == dag_id), None) + assert test_dag is not None + + # Verify tags are sorted alphabetically + tag_names_in_response = [tag["name"] for tag in test_dag["tags"]] + expected_sorted_tags = sorted(tag_names) + assert tag_names_in_response == expected_sorted_tags + def test_get_dags_no_n_plus_one_queries(self, session, test_client): """Test that fetching DAGs with tags doesn't trigger n+1 queries.""" num_dags = 5 @@ -1214,6 +1248,33 @@ def test_get_dag( } assert res_json == expected + def test_get_dag_tags_sorted_alphabetically(self, session, test_client, dag_maker): + """Test that tags are returned in alphabetical order for a single DAG.""" + dag_id = "test_dag_single_sorted_tags" + + # Create a DAG using dag_maker + with dag_maker(dag_id=dag_id, schedule=None): + EmptyOperator(task_id="task1") + + dag_maker.sync_dagbag_to_db() + + # Add tags in non-alphabetical order + tag_names = ["zebra", "alpha", "mike", "bravo"] + for tag_name in tag_names: + tag = DagTag(name=tag_name, dag_id=dag_id) + session.add(tag) + + session.commit() + + response = test_client.get(f"/dags/{dag_id}") + assert response.status_code == 200 + res_json = response.json() + + # Verify tags are sorted alphabetically + tag_names_in_response = [tag["name"] for tag in res_json["tags"]] + expected_sorted_tags = sorted(tag_names) + assert tag_names_in_response == expected_sorted_tags + def test_get_dag_should_response_401(self, unauthenticated_test_client): response = unauthenticated_test_client.get(f"/dags/{DAG1_ID}") assert response.status_code == 401 From dcfc42243a379ae305a5c5f1c7a5b728732ffd4b Mon Sep 17 00:00:00 2001 From: Stuart Buckingham Date: Sun, 30 Nov 2025 23:12:08 -0600 Subject: [PATCH 3/3] duplicated test --- .../core_api/routes/public/test_dags.py | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py index b4e0564f49436..b0dc432ceedc1 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py @@ -526,40 +526,6 @@ def test_get_dags_filter_has_import_errors(self, session, test_client, filter_va assert body["total_entries"] == 1 assert [dag["dag_id"] for dag in body["dags"]] == expected_ids - def test_get_dags_tags_sorted_alphabetically(self, session, test_client): - """Test that tags are returned in alphabetical order.""" - # Create a DAG with multiple tags in non-alphabetical order - dag_id = "test_dag_sorted_tags" - dag_model = DagModel( - dag_id=dag_id, - bundle_name="dag_maker", - fileloc=f"/tmp/{dag_id}.py", - is_stale=False, - ) - session.add(dag_model) - session.flush() - - # Add tags in non-alphabetical order - tag_names = ["zebra", "alpha", "mike", "bravo"] - for tag_name in tag_names: - tag = DagTag(name=tag_name, dag_id=dag_id) - session.add(tag) - - session.commit() - - response = test_client.get("/dags") - assert response.status_code == 200 - body = response.json() - - # Find our test DAG in the response - test_dag = next((d for d in body["dags"] if d["dag_id"] == dag_id), None) - assert test_dag is not None - - # Verify tags are sorted alphabetically - tag_names_in_response = [tag["name"] for tag in test_dag["tags"]] - expected_sorted_tags = sorted(tag_names) - assert tag_names_in_response == expected_sorted_tags - def test_get_dags_no_n_plus_one_queries(self, session, test_client): """Test that fetching DAGs with tags doesn't trigger n+1 queries.""" num_dags = 5