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
*/