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

pierrejeambrun 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 9a4b492f6d AIP-84 Add HTTPException openapi documentation (#42508)
9a4b492f6d is described below

commit 9a4b492f6df969458439173c2cc3c5edc0819a5e
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Fri Sep 27 20:27:27 2024 +0800

    AIP-84 Add HTTPException openapi documentation (#42508)
    
    * Add HTTPException openapi documentation
    
    * Update following code review
---
 airflow/api_fastapi/openapi/__init__.py         | 16 ++++++++++
 airflow/api_fastapi/openapi/exceptions.py       | 41 +++++++++++++++++++++++++
 airflow/api_fastapi/openapi/v1-generated.yaml   | 36 ++++++++++++++++++++++
 airflow/api_fastapi/views/public/dags.py        | 14 ++++-----
 airflow/api_fastapi/views/ui/datasets.py        |  2 --
 airflow/ui/openapi-gen/requests/schemas.gen.ts  | 20 ++++++++++++
 airflow/ui/openapi-gen/requests/services.gen.ts |  4 +++
 airflow/ui/openapi-gen/requests/types.gen.ts    | 27 ++++++++++++++++
 8 files changed, 150 insertions(+), 10 deletions(-)

diff --git a/airflow/api_fastapi/openapi/__init__.py 
b/airflow/api_fastapi/openapi/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/airflow/api_fastapi/openapi/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/airflow/api_fastapi/openapi/exceptions.py 
b/airflow/api_fastapi/openapi/exceptions.py
new file mode 100644
index 0000000000..b3eaf204cc
--- /dev/null
+++ b/airflow/api_fastapi/openapi/exceptions.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 pydantic import BaseModel
+
+
+class HTTPExceptionResponse(BaseModel):
+    """HTTPException Model used for error response."""
+
+    detail: str | dict
+
+
+def create_openapi_http_exception_doc(responses_status_code: list[int]) -> 
dict:
+    """
+    Will create additional response example for errors raised by the endpoint.
+
+    There is no easy way to introspect the code and automatically see what 
HTTPException are actually
+    raised by the endpoint implementation. This piece of documentation needs 
to be kept
+    in sync with the endpoint code manually.
+
+    Validation error i.e 422 are natively added to the openapi documentation 
by FastAPI.
+    """
+    responses_status_code = sorted(responses_status_code)
+
+    return {status_code: {"model": HTTPExceptionResponse} for status_code in 
responses_status_code}
diff --git a/airflow/api_fastapi/openapi/v1-generated.yaml 
b/airflow/api_fastapi/openapi/v1-generated.yaml
index f488825449..64e475aeb6 100644
--- a/airflow/api_fastapi/openapi/v1-generated.yaml
+++ b/airflow/api_fastapi/openapi/v1-generated.yaml
@@ -168,6 +168,30 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/DAGResponse'
+        '400':
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPExceptionResponse'
+          description: Bad Request
+        '401':
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPExceptionResponse'
+          description: Unauthorized
+        '403':
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPExceptionResponse'
+          description: Forbidden
+        '404':
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPExceptionResponse'
+          description: Not Found
         '422':
           description: Validation Error
           content:
@@ -386,6 +410,18 @@ components:
       title: DagTagPydantic
       description: Serializable representation of the DagTag ORM 
SqlAlchemyModel used
         by internal API.
+    HTTPExceptionResponse:
+      properties:
+        detail:
+          anyOf:
+          - type: string
+          - type: object
+          title: Detail
+      type: object
+      required:
+      - detail
+      title: HTTPExceptionResponse
+      description: HTTPException Model used for error response.
     HTTPValidationError:
       properties:
         detail:
diff --git a/airflow/api_fastapi/views/public/dags.py 
b/airflow/api_fastapi/views/public/dags.py
index 07ab968adc..a9fe87eef0 100644
--- a/airflow/api_fastapi/views/public/dags.py
+++ b/airflow/api_fastapi/views/public/dags.py
@@ -23,6 +23,7 @@ from sqlalchemy.orm import Session
 from typing_extensions import Annotated
 
 from airflow.api_fastapi.db import apply_filters_to_select, get_session, 
latest_dag_run_per_dag_id_cte
+from airflow.api_fastapi.openapi.exceptions import 
create_openapi_http_exception_doc
 from airflow.api_fastapi.parameters import (
     QueryDagDisplayNamePatternSearch,
     QueryDagIdPatternSearch,
@@ -95,16 +96,13 @@ async def get_dags(
 
     dags = session.scalars(dags_query).all()
 
-    try:
-        return DAGCollectionResponse(
-            dags=[DAGResponse.model_validate(dag, from_attributes=True) for 
dag in dags],
-            total_entries=total_entries,
-        )
-    except ValueError as e:
-        raise HTTPException(400, f"DAGCollectionSchema error: {str(e)}")
+    return DAGCollectionResponse(
+        dags=[DAGResponse.model_validate(dag, from_attributes=True) for dag in 
dags],
+        total_entries=total_entries,
+    )
 
 
-@dags_router.patch("/dags/{dag_id}")
+@dags_router.patch("/dags/{dag_id}", 
responses=create_openapi_http_exception_doc([400, 401, 403, 404]))
 async def patch_dag(
     dag_id: str,
     patch_body: DAGPatchBody,
diff --git a/airflow/api_fastapi/views/ui/datasets.py 
b/airflow/api_fastapi/views/ui/datasets.py
index 484385031a..f5dd2cacb1 100644
--- a/airflow/api_fastapi/views/ui/datasets.py
+++ b/airflow/api_fastapi/views/ui/datasets.py
@@ -29,8 +29,6 @@ from airflow.models.dataset import 
DagScheduleDatasetReference, DatasetDagRunQue
 datasets_router = APIRouter(tags=["Dataset"])
 
 
-# Ultimately we want async routes, with async sqlalchemy session / context 
manager.
-# Additional effort to make airflow utility code async, not handled for now 
and most likely part of the AIP-70
 @datasets_router.get("/next_run_datasets/{dag_id}", include_in_schema=False)
 async def next_run_datasets(
     dag_id: str,
diff --git a/airflow/ui/openapi-gen/requests/schemas.gen.ts 
b/airflow/ui/openapi-gen/requests/schemas.gen.ts
index d9ce0528c3..e8c9b5d70c 100644
--- a/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -317,6 +317,26 @@ export const $DagTagPydantic = {
     "Serializable representation of the DagTag ORM SqlAlchemyModel used by 
internal API.",
 } as const;
 
+export const $HTTPExceptionResponse = {
+  properties: {
+    detail: {
+      anyOf: [
+        {
+          type: "string",
+        },
+        {
+          type: "object",
+        },
+      ],
+      title: "Detail",
+    },
+  },
+  type: "object",
+  required: ["detail"],
+  title: "HTTPExceptionResponse",
+  description: "HTTPException Model used for error response.",
+} as const;
+
 export const $HTTPValidationError = {
   properties: {
     detail: {
diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts 
b/airflow/ui/openapi-gen/requests/services.gen.ts
index 9c261b3039..37a4d11873 100644
--- a/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -102,6 +102,10 @@ export class DagService {
       body: data.requestBody,
       mediaType: "application/json",
       errors: {
+        400: "Bad Request",
+        401: "Unauthorized",
+        403: "Forbidden",
+        404: "Not Found",
         422: "Validation Error",
       },
     });
diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow/ui/openapi-gen/requests/types.gen.ts
index 803bcd8427..16977004e7 100644
--- a/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -67,6 +67,17 @@ export type DagTagPydantic = {
   dag_id: string;
 };
 
+/**
+ * HTTPException Model used for error response.
+ */
+export type HTTPExceptionResponse = {
+  detail:
+    | string
+    | {
+        [key: string]: unknown;
+      };
+};
+
 export type HTTPValidationError = {
   detail?: Array<ValidationError>;
 };
@@ -149,6 +160,22 @@ export type $OpenApiTs = {
          * Successful Response
          */
         200: DAGResponse;
+        /**
+         * Bad Request
+         */
+        400: HTTPExceptionResponse;
+        /**
+         * Unauthorized
+         */
+        401: HTTPExceptionResponse;
+        /**
+         * Forbidden
+         */
+        403: HTTPExceptionResponse;
+        /**
+         * Not Found
+         */
+        404: HTTPExceptionResponse;
         /**
          * Validation Error
          */

Reply via email to