Skip to content

Commit

Permalink
Add HTTPException openapi documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
pierrejeambrun committed Sep 26, 2024
1 parent 1c61d13 commit cb5bf69
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 10 deletions.
16 changes: 16 additions & 0 deletions airflow/api_fastapi/openapi/__init__.py
Original file line number Diff line number Diff line change
@@ -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.
41 changes: 41 additions & 0 deletions airflow/api_fastapi/openapi/exceptions.py
Original file line number Diff line number Diff line change
@@ -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 HTTPExceptionExampleModel(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": HTTPExceptionExampleModel} for status_code in responses_status_code}
36 changes: 36 additions & 0 deletions airflow/api_fastapi/openapi/v1-generated.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,30 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/DAGResponse'
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionExampleModel'
description: Bad Request
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionExampleModel'
description: Unauthorized
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionExampleModel'
description: Forbidden
'404':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionExampleModel'
description: Not Found
'422':
description: Validation Error
content:
Expand Down Expand Up @@ -362,6 +386,18 @@ components:
title: DagTagPydantic
description: Serializable representation of the DagTag ORM SqlAlchemyModel used
by internal API.
HTTPExceptionExampleModel:
properties:
detail:
anyOf:
- type: string
- type: object
title: Detail
type: object
required:
- detail
title: HTTPExceptionExampleModel
description: HTTPException Model used for error response.
HTTPValidationError:
properties:
detail:
Expand Down
29 changes: 29 additions & 0 deletions airflow/api_fastapi/serializers/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 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, ValidationError


class HTTPExceptionModel(BaseModel):
"""Payload returned for an HTTPException."""

details: str | dict


ValidationError
14 changes: 6 additions & 8 deletions airflow/api_fastapi/views/public/dags.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from typing_extensions import Annotated

from airflow.api_fastapi.db import apply_filters_to_select, get_session
from airflow.api_fastapi.openapi.exceptions import create_openapi_http_exception_doc
from airflow.api_fastapi.parameters import (
QueryDagDisplayNamePatternSearch,
QueryDagIdPatternSearch,
Expand Down Expand Up @@ -71,16 +72,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,
Expand Down
2 changes: 0 additions & 2 deletions airflow/api_fastapi/views/ui/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
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,
Expand Down
20 changes: 20 additions & 0 deletions airflow/ui/openapi-gen/requests/schemas.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,26 @@ export const $DagTagPydantic = {
"Serializable representation of the DagTag ORM SqlAlchemyModel used by internal API.",
} as const;

export const $HTTPExceptionExampleModel = {
properties: {
detail: {
anyOf: [
{
type: "string",
},
{
type: "object",
},
],
title: "Detail",
},
},
type: "object",
required: ["detail"],
title: "HTTPExceptionExampleModel",
description: "HTTPException Model used for error response.",
} as const;

export const $HTTPValidationError = {
properties: {
detail: {
Expand Down
4 changes: 4 additions & 0 deletions airflow/ui/openapi-gen/requests/services.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ export class DagService {
body: data.requestBody,
mediaType: "application/json",
errors: {
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
422: "Validation Error",
},
});
Expand Down
27 changes: 27 additions & 0 deletions airflow/ui/openapi-gen/requests/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ export type DagTagPydantic = {
dag_id: string;
};

/**
* HTTPException Model used for error response.
*/
export type HTTPExceptionExampleModel = {
detail:
| string
| {
[key: string]: unknown;
};
};

export type HTTPValidationError = {
detail?: Array<ValidationError>;
};
Expand Down Expand Up @@ -139,6 +150,22 @@ export type $OpenApiTs = {
* Successful Response
*/
200: DAGResponse;
/**
* Bad Request
*/
400: HTTPExceptionExampleModel;
/**
* Unauthorized
*/
401: HTTPExceptionExampleModel;
/**
* Forbidden
*/
403: HTTPExceptionExampleModel;
/**
* Not Found
*/
404: HTTPExceptionExampleModel;
/**
* Validation Error
*/
Expand Down

0 comments on commit cb5bf69

Please sign in to comment.