Skip to content

Commit 1310046

Browse files
pierrejeambrunpull[bot]
authored andcommitted
AIP-84 Delete Pool (#43165)
1 parent 71e908b commit 1310046

File tree

9 files changed

+292
-3
lines changed

9 files changed

+292
-3
lines changed

airflow/api_connexion/endpoints/pool_endpoint.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from airflow.api_connexion.parameters import apply_sorting, check_limit, format_parameters
3131
from airflow.api_connexion.schemas.pool_schema import PoolCollection, pool_collection_schema, pool_schema
3232
from airflow.models.pool import Pool
33+
from airflow.utils.api_migration import mark_fastapi_migration_done
3334
from airflow.utils.session import NEW_SESSION, provide_session
3435
from airflow.www.decorators import action_logging
3536

@@ -39,6 +40,7 @@
3940
from airflow.api_connexion.types import APIResponse, UpdateMask
4041

4142

43+
@mark_fastapi_migration_done
4244
@security.requires_access_pool("DELETE")
4345
@action_logging
4446
@provide_session
@@ -118,9 +120,11 @@ def patch_pool(
118120
# there is no way field is None here (UpdateMask is a List[str])
119121
# so if pool_schema.declared_fields[field].attribute is None file is returned
120122
update_mask = [
121-
pool_schema.declared_fields[field].attribute # type: ignore[misc]
122-
if pool_schema.declared_fields[field].attribute
123-
else field
123+
(
124+
pool_schema.declared_fields[field].attribute # type: ignore[misc]
125+
if pool_schema.declared_fields[field].attribute
126+
else field
127+
)
124128
for field in update_mask
125129
]
126130
except KeyError as err:

airflow/api_fastapi/core_api/openapi/v1-generated.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,53 @@ paths:
10101010
application/json:
10111011
schema:
10121012
$ref: '#/components/schemas/HealthInfoSchema'
1013+
/public/pools/{pool_name}:
1014+
delete:
1015+
tags:
1016+
- Pool
1017+
summary: Delete Pool
1018+
description: Delete a pool entry.
1019+
operationId: delete_pool
1020+
parameters:
1021+
- name: pool_name
1022+
in: path
1023+
required: true
1024+
schema:
1025+
type: string
1026+
title: Pool Name
1027+
responses:
1028+
'204':
1029+
description: Successful Response
1030+
'400':
1031+
content:
1032+
application/json:
1033+
schema:
1034+
$ref: '#/components/schemas/HTTPExceptionResponse'
1035+
description: Bad Request
1036+
'401':
1037+
content:
1038+
application/json:
1039+
schema:
1040+
$ref: '#/components/schemas/HTTPExceptionResponse'
1041+
description: Unauthorized
1042+
'403':
1043+
content:
1044+
application/json:
1045+
schema:
1046+
$ref: '#/components/schemas/HTTPExceptionResponse'
1047+
description: Forbidden
1048+
'404':
1049+
content:
1050+
application/json:
1051+
schema:
1052+
$ref: '#/components/schemas/HTTPExceptionResponse'
1053+
description: Not Found
1054+
'422':
1055+
description: Validation Error
1056+
content:
1057+
application/json:
1058+
schema:
1059+
$ref: '#/components/schemas/HTTPValidationError'
10131060
components:
10141061
schemas:
10151062
BaseInfoSchema:

airflow/api_fastapi/core_api/routes/public/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from airflow.api_fastapi.core_api.routes.public.dag_run import dag_run_router
2323
from airflow.api_fastapi.core_api.routes.public.dags import dags_router
2424
from airflow.api_fastapi.core_api.routes.public.monitor import monitor_router
25+
from airflow.api_fastapi.core_api.routes.public.pools import pools_router
2526
from airflow.api_fastapi.core_api.routes.public.variables import variables_router
2627

2728
public_router = AirflowRouter(prefix="/public")
@@ -32,3 +33,4 @@
3233
public_router.include_router(variables_router)
3334
public_router.include_router(dag_run_router)
3435
public_router.include_router(monitor_router)
36+
public_router.include_router(pools_router)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
from __future__ import annotations
18+
19+
from fastapi import Depends, HTTPException
20+
from sqlalchemy import delete
21+
from sqlalchemy.orm import Session
22+
from typing_extensions import Annotated
23+
24+
from airflow.api_fastapi.common.db.common import get_session
25+
from airflow.api_fastapi.common.router import AirflowRouter
26+
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
27+
from airflow.models.pool import Pool
28+
29+
pools_router = AirflowRouter(tags=["Pool"], prefix="/pools")
30+
31+
32+
@pools_router.delete(
33+
"/{pool_name}",
34+
status_code=204,
35+
responses=create_openapi_http_exception_doc([400, 401, 403, 404]),
36+
)
37+
async def delete_pool(
38+
pool_name: str,
39+
session: Annotated[Session, Depends(get_session)],
40+
):
41+
"""Delete a pool entry."""
42+
if pool_name == "default_pool":
43+
raise HTTPException(400, "Default Pool can't be deleted")
44+
45+
affected_count = session.execute(delete(Pool).where(Pool.pool == pool_name)).rowcount
46+
47+
if affected_count == 0:
48+
raise HTTPException(404, f"The Pool with name: `{pool_name}` was not found")

airflow/ui/openapi-gen/queries/common.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
DagService,
99
DashboardService,
1010
MonitorService,
11+
PoolService,
1112
VariableService,
1213
} from "../requests/services.gen";
1314
import { DagRunState } from "../requests/types.gen";
@@ -269,3 +270,6 @@ export type VariableServiceDeleteVariableMutationResult = Awaited<
269270
export type DagRunServiceDeleteDagRunMutationResult = Awaited<
270271
ReturnType<typeof DagRunService.deleteDagRun>
271272
>;
273+
export type PoolServiceDeletePoolMutationResult = Awaited<
274+
ReturnType<typeof PoolService.deletePool>
275+
>;

airflow/ui/openapi-gen/queries/queries.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
DagService,
1414
DashboardService,
1515
MonitorService,
16+
PoolService,
1617
VariableService,
1718
} from "../requests/services.gen";
1819
import { DAGPatchBody, DagRunState, VariableBody } from "../requests/types.gen";
@@ -763,3 +764,40 @@ export const useDagRunServiceDeleteDagRun = <
763764
}) as unknown as Promise<TData>,
764765
...options,
765766
});
767+
/**
768+
* Delete Pool
769+
* Delete a pool entry.
770+
* @param data The data for the request.
771+
* @param data.poolName
772+
* @returns void Successful Response
773+
* @throws ApiError
774+
*/
775+
export const usePoolServiceDeletePool = <
776+
TData = Common.PoolServiceDeletePoolMutationResult,
777+
TError = unknown,
778+
TContext = unknown,
779+
>(
780+
options?: Omit<
781+
UseMutationOptions<
782+
TData,
783+
TError,
784+
{
785+
poolName: string;
786+
},
787+
TContext
788+
>,
789+
"mutationFn"
790+
>,
791+
) =>
792+
useMutation<
793+
TData,
794+
TError,
795+
{
796+
poolName: string;
797+
},
798+
TContext
799+
>({
800+
mutationFn: ({ poolName }) =>
801+
PoolService.deletePool({ poolName }) as unknown as Promise<TData>,
802+
...options,
803+
});

airflow/ui/openapi-gen/requests/services.gen.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import type {
4040
DeleteDagRunData,
4141
DeleteDagRunResponse,
4242
GetHealthResponse,
43+
DeletePoolData,
44+
DeletePoolResponse,
4345
} from "./types.gen";
4446

4547
export class AssetService {
@@ -592,3 +594,32 @@ export class MonitorService {
592594
});
593595
}
594596
}
597+
598+
export class PoolService {
599+
/**
600+
* Delete Pool
601+
* Delete a pool entry.
602+
* @param data The data for the request.
603+
* @param data.poolName
604+
* @returns void Successful Response
605+
* @throws ApiError
606+
*/
607+
public static deletePool(
608+
data: DeletePoolData,
609+
): CancelablePromise<DeletePoolResponse> {
610+
return __request(OpenAPI, {
611+
method: "DELETE",
612+
url: "/public/pools/{pool_name}",
613+
path: {
614+
pool_name: data.poolName,
615+
},
616+
errors: {
617+
400: "Bad Request",
618+
401: "Unauthorized",
619+
403: "Forbidden",
620+
404: "Not Found",
621+
422: "Validation Error",
622+
},
623+
});
624+
}
625+
}

airflow/ui/openapi-gen/requests/types.gen.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,12 @@ export type DeleteDagRunResponse = void;
466466

467467
export type GetHealthResponse = HealthInfoSchema;
468468

469+
export type DeletePoolData = {
470+
poolName: string;
471+
};
472+
473+
export type DeletePoolResponse = void;
474+
469475
export type $OpenApiTs = {
470476
"/ui/next_run_assets/{dag_id}": {
471477
get: {
@@ -937,4 +943,35 @@ export type $OpenApiTs = {
937943
};
938944
};
939945
};
946+
"/public/pools/{pool_name}": {
947+
delete: {
948+
req: DeletePoolData;
949+
res: {
950+
/**
951+
* Successful Response
952+
*/
953+
204: void;
954+
/**
955+
* Bad Request
956+
*/
957+
400: HTTPExceptionResponse;
958+
/**
959+
* Unauthorized
960+
*/
961+
401: HTTPExceptionResponse;
962+
/**
963+
* Forbidden
964+
*/
965+
403: HTTPExceptionResponse;
966+
/**
967+
* Not Found
968+
*/
969+
404: HTTPExceptionResponse;
970+
/**
971+
* Validation Error
972+
*/
973+
422: HTTPValidationError;
974+
};
975+
};
976+
};
940977
};
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
from __future__ import annotations
18+
19+
import pytest
20+
21+
from airflow.models.pool import Pool
22+
from airflow.utils.session import provide_session
23+
24+
from tests_common.test_utils.db import clear_db_pools
25+
26+
pytestmark = [pytest.mark.db_test, pytest.mark.skip_if_database_isolation_mode]
27+
28+
POOL1_NAME = "pool1"
29+
POOL1_SLOT = 3
30+
POOL1_INCLUDE_DEFERRED = True
31+
32+
33+
POOL2_NAME = "pool2"
34+
POOL2_SLOT = 10
35+
POOL2_INCLUDE_DEFERRED = False
36+
POOL2_DESCRIPTION = "Some Description"
37+
38+
39+
@provide_session
40+
def _create_pools(session) -> None:
41+
pool1 = Pool(pool=POOL1_NAME, slots=POOL1_SLOT, include_deferred=POOL1_INCLUDE_DEFERRED)
42+
pool2 = Pool(pool=POOL2_NAME, slots=POOL2_SLOT, include_deferred=POOL2_INCLUDE_DEFERRED)
43+
session.add_all([pool1, pool2])
44+
45+
46+
class TestPools:
47+
@pytest.fixture(autouse=True)
48+
def setup(self) -> None:
49+
clear_db_pools()
50+
51+
def teardown_method(self) -> None:
52+
clear_db_pools()
53+
54+
def create_pools(self):
55+
_create_pools()
56+
57+
58+
class TestDeletePool(TestPools):
59+
def test_delete_should_respond_204(self, test_client, session):
60+
self.create_pools()
61+
pools = session.query(Pool).all()
62+
assert len(pools) == 3
63+
response = test_client.delete(f"/public/pools/{POOL1_NAME}")
64+
assert response.status_code == 204
65+
pools = session.query(Pool).all()
66+
assert len(pools) == 2
67+
68+
def test_delete_should_respond_400(self, test_client):
69+
response = test_client.delete("/public/pools/default_pool")
70+
assert response.status_code == 400
71+
body = response.json()
72+
assert "Default Pool can't be deleted" == body["detail"]
73+
74+
def test_delete_should_respond_404(self, test_client):
75+
response = test_client.delete(f"/public/pools/{POOL1_NAME}")
76+
assert response.status_code == 404
77+
body = response.json()
78+
assert f"The Pool with name: `{POOL1_NAME}` was not found" == body["detail"]

0 commit comments

Comments
 (0)