From edfcf95f50ca7a71790a7f643d6ad5a3a1cbea69 Mon Sep 17 00:00:00 2001 From: Bugra Ozturk Date: Sun, 29 Sep 2024 18:53:44 +0200 Subject: [PATCH 1/7] Include connections router and migrate delete a connection endpoint to fastapi --- .../endpoints/connection_endpoint.py | 2 + airflow/api_fastapi/openapi/v1-generated.yaml | 41 +++++++++++++ airflow/api_fastapi/views/public/__init__.py | 2 + .../api_fastapi/views/public/connections.py | 47 ++++++++++++++ airflow/ui/openapi-gen/queries/common.ts | 12 +++- airflow/ui/openapi-gen/queries/queries.ts | 46 +++++++++++++- .../ui/openapi-gen/requests/services.gen.ts | 30 +++++++++ airflow/ui/openapi-gen/requests/types.gen.ts | 33 ++++++++++ .../views/public/test_connections.py | 61 +++++++++++++++++++ 9 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 airflow/api_fastapi/views/public/connections.py create mode 100644 tests/api_fastapi/views/public/test_connections.py diff --git a/airflow/api_connexion/endpoints/connection_endpoint.py b/airflow/api_connexion/endpoints/connection_endpoint.py index c17a9280d78f8..b28c9dfcafa79 100644 --- a/airflow/api_connexion/endpoints/connection_endpoint.py +++ b/airflow/api_connexion/endpoints/connection_endpoint.py @@ -40,6 +40,7 @@ from airflow.secrets.environment_variables import CONN_ENV_PREFIX from airflow.security import permissions from airflow.utils import helpers +from airflow.utils.api_migration import mark_fastapi_migration_done from airflow.utils.log.action_logger import action_event_from_permission from airflow.utils.session import NEW_SESSION, provide_session from airflow.utils.strings import get_random_string @@ -53,6 +54,7 @@ RESOURCE_EVENT_PREFIX = "connection" +@mark_fastapi_migration_done @security.requires_access_connection("DELETE") @provide_session @action_logging( diff --git a/airflow/api_fastapi/openapi/v1-generated.yaml b/airflow/api_fastapi/openapi/v1-generated.yaml index 64e475aeb6baa..505958d65701e 100644 --- a/airflow/api_fastapi/openapi/v1-generated.yaml +++ b/airflow/api_fastapi/openapi/v1-generated.yaml @@ -198,6 +198,47 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' + /public/connections/{connection_id}: + delete: + tags: + - CONNECTION + summary: Delete Connection + description: Delete a connection entry. + operationId: delete_connection_public_connections__connection_id__delete + parameters: + - name: connection_id + in: path + required: true + schema: + type: string + title: Connection Id + responses: + '204': + description: Successful Response + '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: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' components: schemas: DAGCollectionResponse: diff --git a/airflow/api_fastapi/views/public/__init__.py b/airflow/api_fastapi/views/public/__init__.py index b6466536c3359..5f1d9f5eacfd1 100644 --- a/airflow/api_fastapi/views/public/__init__.py +++ b/airflow/api_fastapi/views/public/__init__.py @@ -19,9 +19,11 @@ from fastapi import APIRouter +from airflow.api_fastapi.views.public.connections import connections_router from airflow.api_fastapi.views.public.dags import dags_router public_router = APIRouter(prefix="/public") public_router.include_router(dags_router) +public_router.include_router(connections_router) diff --git a/airflow/api_fastapi/views/public/connections.py b/airflow/api_fastapi/views/public/connections.py new file mode 100644 index 0000000000000..3f77ee6554592 --- /dev/null +++ b/airflow/api_fastapi/views/public/connections.py @@ -0,0 +1,47 @@ +# 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 fastapi import APIRouter, Depends, HTTPException +from sqlalchemy import select +from sqlalchemy.orm import Session +from typing_extensions import Annotated + +from airflow.api_fastapi.db import get_session +from airflow.api_fastapi.openapi.exceptions import create_openapi_http_exception_doc +from airflow.models import Connection + +connections_router = APIRouter(tags=["CONNECTION"]) + + +@connections_router.delete( + "/connections/{connection_id}", + status_code=204, + responses=create_openapi_http_exception_doc([401, 403, 404]), +) +def delete_connection( + *, + connection_id: str, + session: Annotated[Session, Depends(get_session)], +): + """Delete a connection entry.""" + connection = session.scalar(select(Connection).filter_by(conn_id=connection_id)) + + if connection is None: + raise HTTPException(404, f"The Connection with connection_id: `{connection_id}` was not found") + + session.delete(connection) diff --git a/airflow/ui/openapi-gen/queries/common.ts b/airflow/ui/openapi-gen/queries/common.ts index b8021fed9be3c..f57f4c4ea09cc 100644 --- a/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow/ui/openapi-gen/queries/common.ts @@ -1,7 +1,11 @@ // generated with @7nohe/openapi-react-query-codegen@1.6.0 import { UseQueryResult } from "@tanstack/react-query"; -import { DagService, DatasetService } from "../requests/services.gen"; +import { + ConnectionService, + DagService, + DatasetService, +} from "../requests/services.gen"; import { DagRunState } from "../requests/types.gen"; export type DatasetServiceNextRunDatasetsUiNextRunDatasetsDagIdGetDefaultResponse = @@ -79,3 +83,9 @@ export const UseDagServiceGetDagsPublicDagsGetKeyFn = ( export type DagServicePatchDagPublicDagsDagIdPatchMutationResult = Awaited< ReturnType >; +export type ConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDeleteMutationResult = + Awaited< + ReturnType< + typeof ConnectionService.deleteConnectionPublicConnectionsConnectionIdDelete + > + >; diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index b771fccfeb947..d76ce0e42b521 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -6,7 +6,11 @@ import { UseQueryOptions, } from "@tanstack/react-query"; -import { DagService, DatasetService } from "../requests/services.gen"; +import { + ConnectionService, + DagService, + DatasetService, +} from "../requests/services.gen"; import { DAGPatchBody, DagRunState } from "../requests/types.gen"; import * as Common from "./common"; @@ -168,3 +172,43 @@ export const useDagServicePatchDagPublicDagsDagIdPatch = < }) as unknown as Promise, ...options, }); +/** + * Delete Connection + * Delete a connection entry. + * @param data The data for the request. + * @param data.connectionId + * @returns void Successful Response + * @throws ApiError + */ +export const useConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDelete = + < + TData = Common.ConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDeleteMutationResult, + TError = unknown, + TContext = unknown, + >( + options?: Omit< + UseMutationOptions< + TData, + TError, + { + connectionId: string; + }, + TContext + >, + "mutationFn" + >, + ) => + useMutation< + TData, + TError, + { + connectionId: string; + }, + TContext + >({ + mutationFn: ({ connectionId }) => + ConnectionService.deleteConnectionPublicConnectionsConnectionIdDelete({ + connectionId, + }) as unknown as Promise, + ...options, + }); diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow/ui/openapi-gen/requests/services.gen.ts index 37a4d11873acf..38be6a8c2d75d 100644 --- a/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow/ui/openapi-gen/requests/services.gen.ts @@ -9,6 +9,8 @@ import type { GetDagsPublicDagsGetResponse, PatchDagPublicDagsDagIdPatchData, PatchDagPublicDagsDagIdPatchResponse, + DeleteConnectionPublicConnectionsConnectionIdDeleteData, + DeleteConnectionPublicConnectionsConnectionIdDeleteResponse, } from "./types.gen"; export class DatasetService { @@ -111,3 +113,31 @@ export class DagService { }); } } + +export class ConnectionService { + /** + * Delete Connection + * Delete a connection entry. + * @param data The data for the request. + * @param data.connectionId + * @returns void Successful Response + * @throws ApiError + */ + public static deleteConnectionPublicConnectionsConnectionIdDelete( + data: DeleteConnectionPublicConnectionsConnectionIdDeleteData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/public/connections/{connection_id}", + path: { + connection_id: data.connectionId, + }, + errors: { + 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 16977004e79d6..5255d36eb896f 100644 --- a/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow/ui/openapi-gen/requests/types.gen.ts @@ -119,6 +119,12 @@ export type PatchDagPublicDagsDagIdPatchData = { export type PatchDagPublicDagsDagIdPatchResponse = DAGResponse; +export type DeleteConnectionPublicConnectionsConnectionIdDeleteData = { + connectionId: string; +}; + +export type DeleteConnectionPublicConnectionsConnectionIdDeleteResponse = void; + export type $OpenApiTs = { "/ui/next_run_datasets/{dag_id}": { get: { @@ -183,4 +189,31 @@ export type $OpenApiTs = { }; }; }; + "/public/connections/{connection_id}": { + delete: { + req: DeleteConnectionPublicConnectionsConnectionIdDeleteData; + res: { + /** + * Successful Response + */ + 204: void; + /** + * Unauthorized + */ + 401: HTTPExceptionResponse; + /** + * Forbidden + */ + 403: HTTPExceptionResponse; + /** + * Not Found + */ + 404: HTTPExceptionResponse; + /** + * Validation Error + */ + 422: HTTPValidationError; + }; + }; + }; }; diff --git a/tests/api_fastapi/views/public/test_connections.py b/tests/api_fastapi/views/public/test_connections.py new file mode 100644 index 0000000000000..10719f5aa5691 --- /dev/null +++ b/tests/api_fastapi/views/public/test_connections.py @@ -0,0 +1,61 @@ +# 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 + +import pytest + +from airflow.models import Connection +from airflow.utils.session import provide_session +from tests.test_utils.db import clear_db_connections + +TEST_CONN_ID = "test_connection_id" +TEST_CONN_TYPE = "test_type" + + +@provide_session +def _create_connection(session) -> None: + connection_model = Connection(conn_id=TEST_CONN_ID, conn_type=TEST_CONN_TYPE) + session.add(connection_model) + + +class TestConnectionEndpoint: + @pytest.fixture(autouse=True) + def setup(self) -> None: + clear_db_connections(False) + + def teardown_method(self) -> None: + clear_db_connections() + + def create_connection(self): + _create_connection() + + +class TestDeleteConnection(TestConnectionEndpoint): + def test_delete_should_respond_204(self, test_client, session): + self.create_connection() + conns = session.query(Connection).all() + assert len(conns) == 1 + response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") + assert response.status_code == 204 + connection = session.query(Connection).all() + assert len(connection) == 0 + + def test_delete_should_respond_404(self, test_client): + response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") + assert response.status_code == 404 + body = response.json() + assert f"The Connection with connection_id: `{TEST_CONN_ID}` was not found" == body["detail"] From 277f5e81faa7c16dab1df4715e6366c387217085 Mon Sep 17 00:00:00 2001 From: Bugra Ozturk Date: Sun, 29 Sep 2024 19:17:29 +0200 Subject: [PATCH 2/7] Mark tests as db_test --- tests/api_fastapi/views/public/test_connections.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/api_fastapi/views/public/test_connections.py b/tests/api_fastapi/views/public/test_connections.py index 10719f5aa5691..b229b77b7e470 100644 --- a/tests/api_fastapi/views/public/test_connections.py +++ b/tests/api_fastapi/views/public/test_connections.py @@ -45,6 +45,7 @@ def create_connection(self): class TestDeleteConnection(TestConnectionEndpoint): + @pytest.mark.db_test def test_delete_should_respond_204(self, test_client, session): self.create_connection() conns = session.query(Connection).all() @@ -54,6 +55,7 @@ def test_delete_should_respond_204(self, test_client, session): connection = session.query(Connection).all() assert len(connection) == 0 + @pytest.mark.db_test def test_delete_should_respond_404(self, test_client): response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") assert response.status_code == 404 From e2caaaeccfd3bf65d1f0e6cb03478f3fb2848ece Mon Sep 17 00:00:00 2001 From: Bugra Ozturk Date: Sun, 29 Sep 2024 20:13:13 +0200 Subject: [PATCH 3/7] Use only pyfixture session --- .../views/public/test_connections.py | 58 ++++++++----------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/tests/api_fastapi/views/public/test_connections.py b/tests/api_fastapi/views/public/test_connections.py index b229b77b7e470..c59c60c975daf 100644 --- a/tests/api_fastapi/views/public/test_connections.py +++ b/tests/api_fastapi/views/public/test_connections.py @@ -19,45 +19,35 @@ import pytest from airflow.models import Connection -from airflow.utils.session import provide_session from tests.test_utils.db import clear_db_connections TEST_CONN_ID = "test_connection_id" TEST_CONN_TYPE = "test_type" -@provide_session -def _create_connection(session) -> None: - connection_model = Connection(conn_id=TEST_CONN_ID, conn_type=TEST_CONN_TYPE) - session.add(connection_model) - - -class TestConnectionEndpoint: - @pytest.fixture(autouse=True) - def setup(self) -> None: - clear_db_connections(False) - - def teardown_method(self) -> None: - clear_db_connections() +@pytest.fixture(autouse=True) +def setup() -> None: + clear_db_connections(False) - def create_connection(self): - _create_connection() - -class TestDeleteConnection(TestConnectionEndpoint): - @pytest.mark.db_test - def test_delete_should_respond_204(self, test_client, session): - self.create_connection() - conns = session.query(Connection).all() - assert len(conns) == 1 - response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") - assert response.status_code == 204 - connection = session.query(Connection).all() - assert len(connection) == 0 - - @pytest.mark.db_test - def test_delete_should_respond_404(self, test_client): - response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") - assert response.status_code == 404 - body = response.json() - assert f"The Connection with connection_id: `{TEST_CONN_ID}` was not found" == body["detail"] +# Delete a Connection Endpoint Tests +@pytest.mark.db_test +def test_delete_should_respond_204(test_client, session): + connection_model = Connection(conn_id=TEST_CONN_ID, conn_type=TEST_CONN_TYPE) + session.add(connection_model) + session.commit() + + conns = session.query(Connection).all() + assert len(conns) == 1 + response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") + assert response.status_code == 204 + connection = session.query(Connection).all() + assert len(connection) == 0 + + +@pytest.mark.db_test +def test_delete_should_respond_404(test_client): + response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") + assert response.status_code == 404 + body = response.json() + assert f"The Connection with connection_id: `{TEST_CONN_ID}` was not found" == body["detail"] From 935e09d3628d0c6503a9c414ba5fa48031b6618b Mon Sep 17 00:00:00 2001 From: Bugra Ozturk Date: Sun, 29 Sep 2024 22:28:19 +0200 Subject: [PATCH 4/7] make method async --- .../api_fastapi/views/public/connections.py | 2 +- .../views/public/test_connections.py | 58 +++++++++++-------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/airflow/api_fastapi/views/public/connections.py b/airflow/api_fastapi/views/public/connections.py index 3f77ee6554592..dd61dabb4da6b 100644 --- a/airflow/api_fastapi/views/public/connections.py +++ b/airflow/api_fastapi/views/public/connections.py @@ -33,7 +33,7 @@ status_code=204, responses=create_openapi_http_exception_doc([401, 403, 404]), ) -def delete_connection( +async def delete_connection( *, connection_id: str, session: Annotated[Session, Depends(get_session)], diff --git a/tests/api_fastapi/views/public/test_connections.py b/tests/api_fastapi/views/public/test_connections.py index c59c60c975daf..b229b77b7e470 100644 --- a/tests/api_fastapi/views/public/test_connections.py +++ b/tests/api_fastapi/views/public/test_connections.py @@ -19,35 +19,45 @@ import pytest from airflow.models import Connection +from airflow.utils.session import provide_session from tests.test_utils.db import clear_db_connections TEST_CONN_ID = "test_connection_id" TEST_CONN_TYPE = "test_type" -@pytest.fixture(autouse=True) -def setup() -> None: - clear_db_connections(False) - - -# Delete a Connection Endpoint Tests -@pytest.mark.db_test -def test_delete_should_respond_204(test_client, session): +@provide_session +def _create_connection(session) -> None: connection_model = Connection(conn_id=TEST_CONN_ID, conn_type=TEST_CONN_TYPE) session.add(connection_model) - session.commit() - - conns = session.query(Connection).all() - assert len(conns) == 1 - response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") - assert response.status_code == 204 - connection = session.query(Connection).all() - assert len(connection) == 0 - - -@pytest.mark.db_test -def test_delete_should_respond_404(test_client): - response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") - assert response.status_code == 404 - body = response.json() - assert f"The Connection with connection_id: `{TEST_CONN_ID}` was not found" == body["detail"] + + +class TestConnectionEndpoint: + @pytest.fixture(autouse=True) + def setup(self) -> None: + clear_db_connections(False) + + def teardown_method(self) -> None: + clear_db_connections() + + def create_connection(self): + _create_connection() + + +class TestDeleteConnection(TestConnectionEndpoint): + @pytest.mark.db_test + def test_delete_should_respond_204(self, test_client, session): + self.create_connection() + conns = session.query(Connection).all() + assert len(conns) == 1 + response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") + assert response.status_code == 204 + connection = session.query(Connection).all() + assert len(connection) == 0 + + @pytest.mark.db_test + def test_delete_should_respond_404(self, test_client): + response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") + assert response.status_code == 404 + body = response.json() + assert f"The Connection with connection_id: `{TEST_CONN_ID}` was not found" == body["detail"] From a6ff050dc0f9c0fcd23a0bb99f391a3b5a05eda2 Mon Sep 17 00:00:00 2001 From: Bugra Ozturk Date: Sun, 29 Sep 2024 22:29:56 +0200 Subject: [PATCH 5/7] setup method to setup_attrs --- tests/api_fastapi/views/public/test_connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api_fastapi/views/public/test_connections.py b/tests/api_fastapi/views/public/test_connections.py index b229b77b7e470..0b789ac0af80a 100644 --- a/tests/api_fastapi/views/public/test_connections.py +++ b/tests/api_fastapi/views/public/test_connections.py @@ -34,7 +34,7 @@ def _create_connection(session) -> None: class TestConnectionEndpoint: @pytest.fixture(autouse=True) - def setup(self) -> None: + def setup_attrs(self) -> None: clear_db_connections(False) def teardown_method(self) -> None: From a02bfbab1b416618bd1ed3ef1dc8114948885b02 Mon Sep 17 00:00:00 2001 From: Bugra Ozturk Date: Mon, 30 Sep 2024 18:37:24 +0200 Subject: [PATCH 6/7] Convert APIRouter tags, make setup method unified --- airflow/api_fastapi/openapi/v1-generated.yaml | 2 +- .../api_fastapi/views/public/connections.py | 5 +- airflow/ui/openapi-gen/queries/common.ts | 12 ++++- airflow/ui/openapi-gen/queries/queries.ts | 46 ++++++++++++++++++- .../views/public/test_connections.py | 6 +-- 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/airflow/api_fastapi/openapi/v1-generated.yaml b/airflow/api_fastapi/openapi/v1-generated.yaml index 08fc8434b81dc..b3d641f52355f 100644 --- a/airflow/api_fastapi/openapi/v1-generated.yaml +++ b/airflow/api_fastapi/openapi/v1-generated.yaml @@ -322,7 +322,7 @@ paths: /public/connections/{connection_id}: delete: tags: - - CONNECTION + - Connection summary: Delete Connection description: Delete a connection entry. operationId: delete_connection_public_connections__connection_id__delete diff --git a/airflow/api_fastapi/views/public/connections.py b/airflow/api_fastapi/views/public/connections.py index dd61dabb4da6b..42184cf8901c5 100644 --- a/airflow/api_fastapi/views/public/connections.py +++ b/airflow/api_fastapi/views/public/connections.py @@ -21,11 +21,11 @@ from sqlalchemy.orm import Session from typing_extensions import Annotated -from airflow.api_fastapi.db import get_session +from airflow.api_fastapi.db.common import get_session from airflow.api_fastapi.openapi.exceptions import create_openapi_http_exception_doc from airflow.models import Connection -connections_router = APIRouter(tags=["CONNECTION"]) +connections_router = APIRouter(tags=["Connection"]) @connections_router.delete( @@ -34,7 +34,6 @@ responses=create_openapi_http_exception_doc([401, 403, 404]), ) async def delete_connection( - *, connection_id: str, session: Annotated[Session, Depends(get_session)], ): diff --git a/airflow/ui/openapi-gen/queries/common.ts b/airflow/ui/openapi-gen/queries/common.ts index b1508c86c0c4b..23957b9334c9c 100644 --- a/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow/ui/openapi-gen/queries/common.ts @@ -1,7 +1,11 @@ // generated with @7nohe/openapi-react-query-codegen@1.6.0 import { UseQueryResult } from "@tanstack/react-query"; -import { AssetService, DagService } from "../requests/services.gen"; +import { + AssetService, + ConnectionService, + DagService, +} from "../requests/services.gen"; import { DagRunState } from "../requests/types.gen"; export type AssetServiceNextRunAssetsUiNextRunDatasetsDagIdGetDefaultResponse = @@ -82,3 +86,9 @@ export type DagServicePatchDagsPublicDagsPatchMutationResult = Awaited< export type DagServicePatchDagPublicDagsDagIdPatchMutationResult = Awaited< ReturnType >; +export type ConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDeleteMutationResult = + Awaited< + ReturnType< + typeof ConnectionService.deleteConnectionPublicConnectionsConnectionIdDelete + > + >; diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index 5eda2a3d0e4d2..6ccf912e3aa98 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -6,7 +6,11 @@ import { UseQueryOptions, } from "@tanstack/react-query"; -import { AssetService, DagService } from "../requests/services.gen"; +import { + AssetService, + ConnectionService, + DagService, +} from "../requests/services.gen"; import { DAGPatchBody, DagRunState } from "../requests/types.gen"; import * as Common from "./common"; @@ -251,3 +255,43 @@ export const useDagServicePatchDagPublicDagsDagIdPatch = < }) as unknown as Promise, ...options, }); +/** + * Delete Connection + * Delete a connection entry. + * @param data The data for the request. + * @param data.connectionId + * @returns void Successful Response + * @throws ApiError + */ +export const useConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDelete = + < + TData = Common.ConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDeleteMutationResult, + TError = unknown, + TContext = unknown, + >( + options?: Omit< + UseMutationOptions< + TData, + TError, + { + connectionId: string; + }, + TContext + >, + "mutationFn" + >, + ) => + useMutation< + TData, + TError, + { + connectionId: string; + }, + TContext + >({ + mutationFn: ({ connectionId }) => + ConnectionService.deleteConnectionPublicConnectionsConnectionIdDelete({ + connectionId, + }) as unknown as Promise, + ...options, + }); diff --git a/tests/api_fastapi/views/public/test_connections.py b/tests/api_fastapi/views/public/test_connections.py index 0b789ac0af80a..cfdca1d67984d 100644 --- a/tests/api_fastapi/views/public/test_connections.py +++ b/tests/api_fastapi/views/public/test_connections.py @@ -22,6 +22,8 @@ from airflow.utils.session import provide_session from tests.test_utils.db import clear_db_connections +pytestmark = pytest.mark.db_test + TEST_CONN_ID = "test_connection_id" TEST_CONN_TYPE = "test_type" @@ -34,7 +36,7 @@ def _create_connection(session) -> None: class TestConnectionEndpoint: @pytest.fixture(autouse=True) - def setup_attrs(self) -> None: + def setup(self) -> None: clear_db_connections(False) def teardown_method(self) -> None: @@ -45,7 +47,6 @@ def create_connection(self): class TestDeleteConnection(TestConnectionEndpoint): - @pytest.mark.db_test def test_delete_should_respond_204(self, test_client, session): self.create_connection() conns = session.query(Connection).all() @@ -55,7 +56,6 @@ def test_delete_should_respond_204(self, test_client, session): connection = session.query(Connection).all() assert len(connection) == 0 - @pytest.mark.db_test def test_delete_should_respond_404(self, test_client): response = test_client.delete(f"/public/connections/{TEST_CONN_ID}") assert response.status_code == 404 From d30d43e341647372cb477b7cc86afeda9b483464 Mon Sep 17 00:00:00 2001 From: Bugra Ozturk Date: Tue, 1 Oct 2024 17:58:49 +0200 Subject: [PATCH 7/7] Use AirflowRouter over fastapi.APIRouter --- airflow/api_fastapi/openapi/v1-generated.yaml | 2 +- airflow/api_fastapi/views/public/__init__.py | 2 + .../api_fastapi/views/public/connections.py | 5 +- airflow/ui/openapi-gen/queries/common.ts | 9 ++-- airflow/ui/openapi-gen/queries/queries.ts | 51 +++++++++---------- .../ui/openapi-gen/requests/services.gen.ts | 30 +++++++++++ airflow/ui/openapi-gen/requests/types.gen.ts | 6 +-- 7 files changed, 67 insertions(+), 38 deletions(-) diff --git a/airflow/api_fastapi/openapi/v1-generated.yaml b/airflow/api_fastapi/openapi/v1-generated.yaml index 1edff18163369..a54e0e4ca57dd 100644 --- a/airflow/api_fastapi/openapi/v1-generated.yaml +++ b/airflow/api_fastapi/openapi/v1-generated.yaml @@ -325,7 +325,7 @@ paths: - Connection summary: Delete Connection description: Delete a connection entry. - operationId: delete_connection_public_connections__connection_id__delete + operationId: delete_connection parameters: - name: connection_id in: path diff --git a/airflow/api_fastapi/views/public/__init__.py b/airflow/api_fastapi/views/public/__init__.py index 1c2511fc82ac2..9c0eefebb875e 100644 --- a/airflow/api_fastapi/views/public/__init__.py +++ b/airflow/api_fastapi/views/public/__init__.py @@ -17,6 +17,7 @@ from __future__ import annotations +from airflow.api_fastapi.views.public.connections import connections_router from airflow.api_fastapi.views.public.dags import dags_router from airflow.api_fastapi.views.router import AirflowRouter @@ -24,3 +25,4 @@ public_router.include_router(dags_router) +public_router.include_router(connections_router) diff --git a/airflow/api_fastapi/views/public/connections.py b/airflow/api_fastapi/views/public/connections.py index 42184cf8901c5..d418e10026796 100644 --- a/airflow/api_fastapi/views/public/connections.py +++ b/airflow/api_fastapi/views/public/connections.py @@ -16,16 +16,17 @@ # under the License. from __future__ import annotations -from fastapi import APIRouter, Depends, HTTPException +from fastapi import Depends, HTTPException from sqlalchemy import select from sqlalchemy.orm import Session from typing_extensions import Annotated from airflow.api_fastapi.db.common import get_session from airflow.api_fastapi.openapi.exceptions import create_openapi_http_exception_doc +from airflow.api_fastapi.views.router import AirflowRouter from airflow.models import Connection -connections_router = APIRouter(tags=["Connection"]) +connections_router = AirflowRouter(tags=["Connection"]) @connections_router.delete( diff --git a/airflow/ui/openapi-gen/queries/common.ts b/airflow/ui/openapi-gen/queries/common.ts index 18e1ce51e6de3..fcddded7dc121 100644 --- a/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow/ui/openapi-gen/queries/common.ts @@ -80,9 +80,6 @@ export type DagServicePatchDagsMutationResult = Awaited< export type DagServicePatchDagMutationResult = Awaited< ReturnType >; -export type ConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDeleteMutationResult = - Awaited< - ReturnType< - typeof ConnectionService.deleteConnectionPublicConnectionsConnectionIdDelete - > - >; +export type ConnectionServiceDeleteConnectionMutationResult = Awaited< + ReturnType +>; diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index b55aa5b03f137..f83c151b91e23 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -259,35 +259,34 @@ export const useDagServicePatchDag = < * @returns void Successful Response * @throws ApiError */ -export const useConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDelete = - < - TData = Common.ConnectionServiceDeleteConnectionPublicConnectionsConnectionIdDeleteMutationResult, - TError = unknown, - TContext = unknown, - >( - options?: Omit< - UseMutationOptions< - TData, - TError, - { - connectionId: string; - }, - TContext - >, - "mutationFn" - >, - ) => - useMutation< +export const useConnectionServiceDeleteConnection = < + TData = Common.ConnectionServiceDeleteConnectionMutationResult, + TError = unknown, + TContext = unknown, +>( + options?: Omit< + UseMutationOptions< TData, TError, { connectionId: string; }, TContext - >({ - mutationFn: ({ connectionId }) => - ConnectionService.deleteConnectionPublicConnectionsConnectionIdDelete({ - connectionId, - }) as unknown as Promise, - ...options, - }); + >, + "mutationFn" + >, +) => + useMutation< + TData, + TError, + { + connectionId: string; + }, + TContext + >({ + mutationFn: ({ connectionId }) => + ConnectionService.deleteConnection({ + connectionId, + }) as unknown as Promise, + ...options, + }); diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow/ui/openapi-gen/requests/services.gen.ts index be216bd534c61..24c960d2b7d5f 100644 --- a/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow/ui/openapi-gen/requests/services.gen.ts @@ -11,6 +11,8 @@ import type { PatchDagsResponse, PatchDagData, PatchDagResponse, + DeleteConnectionData, + DeleteConnectionResponse, } from "./types.gen"; export class AssetService { @@ -159,3 +161,31 @@ export class DagService { }); } } + +export class ConnectionService { + /** + * Delete Connection + * Delete a connection entry. + * @param data The data for the request. + * @param data.connectionId + * @returns void Successful Response + * @throws ApiError + */ + public static deleteConnection( + data: DeleteConnectionData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/public/connections/{connection_id}", + path: { + connection_id: data.connectionId, + }, + errors: { + 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 91d2fd5134e10..b38d5c00a69f3 100644 --- a/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow/ui/openapi-gen/requests/types.gen.ts @@ -134,11 +134,11 @@ export type PatchDagData = { export type PatchDagResponse = DAGResponse; -export type DeleteConnectionPublicConnectionsConnectionIdDeleteData = { +export type DeleteConnectionData = { connectionId: string; }; -export type DeleteConnectionPublicConnectionsConnectionIdDeleteResponse = void; +export type DeleteConnectionResponse = void; export type $OpenApiTs = { "/ui/next_run_datasets/{dag_id}": { @@ -235,7 +235,7 @@ export type $OpenApiTs = { }; "/public/connections/{connection_id}": { delete: { - req: DeleteConnectionPublicConnectionsConnectionIdDeleteData; + req: DeleteConnectionData; res: { /** * Successful Response