This is an automated email from the ASF dual-hosted git repository.

kaxilnaik pushed a commit to branch v3-0-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-0-test by this push:
     new b64d00dfade Fix OpenAPI schema for `get_log` API (#50547) (#51357)
b64d00dfade is described below

commit b64d00dfadefa7de0bcf361ba4dd8d829806ae64
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Tue Jun 3 12:51:44 2025 +0200

    Fix OpenAPI schema for `get_log` API (#50547) (#51357)
    
    * Fix openapi schema for get_log API
    
    * Fix test_log
    
    (cherry picked from commit 08cc57d5bba8a045d34bce39b28ac21af691c3a9)
    
    Co-authored-by: LIU ZHE YOU <[email protected]>
---
 .../src/airflow/api_fastapi/common/headers.py      | 27 ++++++++++++++++++++++
 .../src/airflow/api_fastapi/common/types.py        |  1 +
 .../core_api/openapi/v1-rest-api-generated.yaml    |  8 ++++---
 .../api_fastapi/core_api/routes/public/log.py      | 13 ++++++-----
 .../src/airflow/ui/openapi-gen/queries/common.ts   |  2 +-
 .../ui/openapi-gen/queries/ensureQueryData.ts      |  2 +-
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts |  2 +-
 .../src/airflow/ui/openapi-gen/queries/queries.ts  |  2 +-
 .../src/airflow/ui/openapi-gen/queries/suspense.ts |  2 +-
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  2 +-
 .../api_fastapi/core_api/routes/public/test_log.py | 14 +++++------
 11 files changed, 52 insertions(+), 23 deletions(-)

diff --git a/airflow-core/src/airflow/api_fastapi/common/headers.py 
b/airflow-core/src/airflow/api_fastapi/common/headers.py
index 7d1a0fa6961..13567e32bdc 100644
--- a/airflow-core/src/airflow/api_fastapi/common/headers.py
+++ b/airflow-core/src/airflow/api_fastapi/common/headers.py
@@ -47,3 +47,30 @@ def header_accept_json_or_text_depends(
 
 
 HeaderAcceptJsonOrText = Annotated[Mimetype, 
Depends(header_accept_json_or_text_depends)]
+
+
+def header_accept_json_or_ndjson_depends(
+    accept: Annotated[
+        str,
+        Header(
+            json_schema_extra={
+                "type": "string",
+                "enum": [Mimetype.JSON, Mimetype.NDJSON, Mimetype.ANY],
+            }
+        ),
+    ] = Mimetype.ANY,
+) -> Mimetype:
+    if accept.startswith(Mimetype.ANY):
+        return Mimetype.ANY
+    if accept.startswith(Mimetype.JSON):
+        return Mimetype.JSON
+    if accept.startswith(Mimetype.NDJSON) or accept.startswith(Mimetype.ANY):
+        return Mimetype.NDJSON
+
+    raise HTTPException(
+        status_code=status.HTTP_406_NOT_ACCEPTABLE,
+        detail="Only application/json or application/x-ndjson is supported",
+    )
+
+
+HeaderAcceptJsonOrNdjson = Annotated[Mimetype, 
Depends(header_accept_json_or_ndjson_depends)]
diff --git a/airflow-core/src/airflow/api_fastapi/common/types.py 
b/airflow-core/src/airflow/api_fastapi/common/types.py
index 0b431dfdef4..18e5dc7387d 100644
--- a/airflow-core/src/airflow/api_fastapi/common/types.py
+++ b/airflow-core/src/airflow/api_fastapi/common/types.py
@@ -72,6 +72,7 @@ class Mimetype(str, Enum):
 
     TEXT = "text/plain"
     JSON = "application/json"
+    NDJSON = "application/x-ndjson"
     ANY = "*/*"
 
 
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-rest-api-generated.yaml
 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-rest-api-generated.yaml
index ff6725b266b..74784659110 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-rest-api-generated.yaml
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-rest-api-generated.yaml
@@ -6354,7 +6354,7 @@ paths:
           type: string
           enum:
           - application/json
-          - text/plain
+          - application/x-ndjson
           - '*/*'
           default: '*/*'
           title: Accept
@@ -6365,10 +6365,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/TaskInstancesLogResponse'
-            text/plain:
+            application/x-ndjson:
               schema:
                 type: string
-                example: 'content
+                example: '{"content": "content"}
+
+                  {"content": "content"}
 
                   '
         '401':
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/log.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/log.py
index 3873cadf69d..05313e2b69b 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/log.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/log.py
@@ -28,7 +28,7 @@ from sqlalchemy.sql import select
 
 from airflow.api_fastapi.common.dagbag import DagBagDep
 from airflow.api_fastapi.common.db.common import SessionDep
-from airflow.api_fastapi.common.headers import HeaderAcceptJsonOrText
+from airflow.api_fastapi.common.headers import HeaderAcceptJsonOrNdjson
 from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.common.types import Mimetype
 from airflow.api_fastapi.core_api.datamodels.log import 
TaskInstancesLogResponse
@@ -43,13 +43,14 @@ task_instances_log_router = AirflowRouter(
     tags=["Task Instance"], 
prefix="/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances"
 )
 
-text_example_response_for_get_log = {
-    Mimetype.TEXT: {
+ndjson_example_response_for_get_log = {
+    Mimetype.NDJSON: {
         "schema": {
             "type": "string",
             "example": textwrap.dedent(
                 """\
-    content
+    {"content": "content"}
+    {"content": "content"}
     """
             ),
         }
@@ -63,7 +64,7 @@ text_example_response_for_get_log = {
         **create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]),
         status.HTTP_200_OK: {
             "description": "Successful Response",
-            "content": text_example_response_for_get_log,
+            "content": ndjson_example_response_for_get_log,
         },
     },
     dependencies=[Depends(requires_access_dag("GET", 
DagAccessEntity.TASK_LOGS))],
@@ -75,7 +76,7 @@ def get_log(
     dag_run_id: str,
     task_id: str,
     try_number: PositiveInt,
-    accept: HeaderAcceptJsonOrText,
+    accept: HeaderAcceptJsonOrNdjson,
     request: Request,
     dag_bag: DagBagDep,
     session: SessionDep,
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 5af1938bea0..a7efa4868c8 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -1267,7 +1267,7 @@ export const UseTaskInstanceServiceGetLogKeyFn = (
     token,
     tryNumber,
   }: {
-    accept?: "application/json" | "text/plain" | "*/*";
+    accept?: "application/json" | "*/*" | "application/x-ndjson";
     dagId: string;
     dagRunId: string;
     fullContent?: boolean;
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index 1ec386a67f9..f02690c160b 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1744,7 +1744,7 @@ export const ensureUseTaskInstanceServiceGetLogData = (
     token,
     tryNumber,
   }: {
-    accept?: "application/json" | "text/plain" | "*/*";
+    accept?: "application/json" | "*/*" | "application/x-ndjson";
     dagId: string;
     dagRunId: string;
     fullContent?: boolean;
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index c67b0e525cb..b9039a10f37 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1744,7 +1744,7 @@ export const prefetchUseTaskInstanceServiceGetLog = (
     token,
     tryNumber,
   }: {
-    accept?: "application/json" | "text/plain" | "*/*";
+    accept?: "application/json" | "*/*" | "application/x-ndjson";
     dagId: string;
     dagRunId: string;
     fullContent?: boolean;
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index 620dab69dc6..30b49d52aa3 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -2081,7 +2081,7 @@ export const useTaskInstanceServiceGetLog = <
     token,
     tryNumber,
   }: {
-    accept?: "application/json" | "text/plain" | "*/*";
+    accept?: "application/json" | "*/*" | "application/x-ndjson";
     dagId: string;
     dagRunId: string;
     fullContent?: boolean;
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index 767004d4663..d525b0a662c 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -2058,7 +2058,7 @@ export const useTaskInstanceServiceGetLogSuspense = <
     token,
     tryNumber,
   }: {
-    accept?: "application/json" | "text/plain" | "*/*";
+    accept?: "application/json" | "*/*" | "application/x-ndjson";
     dagId: string;
     dagRunId: string;
     fullContent?: boolean;
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index 18322378fd5..b04ce36ef78 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -2386,7 +2386,7 @@ export type PatchTaskInstanceDryRunData = {
 export type PatchTaskInstanceDryRunResponse = TaskInstanceCollectionResponse;
 
 export type GetLogData = {
-  accept?: "application/json" | "text/plain" | "*/*";
+  accept?: "application/json" | "application/x-ndjson" | "*/*";
   dagId: string;
   dagRunId: string;
   fullContent?: boolean;
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_log.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_log.py
index a906edc8b2d..1b10e4c16a9 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_log.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_log.py
@@ -213,9 +213,7 @@ class TestTaskInstancesLog:
             ),
         ],
     )
-    def test_should_respond_200_text_plain(
-        self, request_url, expected_filename, extra_query_string, try_number
-    ):
+    def test_should_respond_200_ndjson(self, request_url, expected_filename, 
extra_query_string, try_number):
         expected_filename = expected_filename.replace("LOG_DIR", 
str(self.log_dir))
 
         key = self.app.state.secret_key
@@ -225,7 +223,7 @@ class TestTaskInstancesLog:
         response = self.client.get(
             request_url,
             params={"token": token, **extra_query_string},
-            headers={"Accept": "text/plain"},
+            headers={"Accept": "application/x-ndjson"},
         )
         assert response.status_code == 200
 
@@ -281,7 +279,7 @@ class TestTaskInstancesLog:
         response = self.client.get(
             request_url,
             params={"token": token, **extra_query_string},
-            headers={"Accept": "text/plain"},
+            headers={"Accept": "application/x-ndjson"},
         )
 
         assert response.status_code == 200
@@ -316,7 +314,7 @@ class TestTaskInstancesLog:
             response = self.client.get(
                 f"/dags/{self.DAG_ID}/dagRuns/{self.RUN_ID}/"
                 
f"taskInstances/{self.TASK_ID}/logs/{try_number}?full_content=True",
-                headers={"Accept": "text/plain"},
+                headers={"Accept": "application/x-ndjson"},
             )
 
             assert "1st line" in response.content.decode("utf-8")
@@ -384,7 +382,7 @@ class TestTaskInstancesLog:
         response = self.client.get(
             
f"/dags/{self.DAG_ID}/dagRuns/{self.RUN_ID}/taskInstances/{self.MAPPED_TASK_ID}/logs/1",
             params={"token": token},
-            headers={"Accept": "text/plain"},
+            headers={"Accept": "application/x-ndjson"},
         )
         assert response.status_code == 404
         assert response.json()["detail"] == "TaskInstance not found"
@@ -397,7 +395,7 @@ class TestTaskInstancesLog:
         response = self.client.get(
             
f"/dags/{self.DAG_ID}/dagRuns/{self.RUN_ID}/taskInstances/{self.TASK_ID}/logs/1",
             params={"token": token, "map_index": 0},
-            headers={"Accept": "text/plain"},
+            headers={"Accept": "application/x-ndjson"},
         )
         assert response.status_code == 404
         assert response.json()["detail"] == "TaskInstance not found"

Reply via email to