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 4a0364a632a Pool API improve slots validation (#61071)
4a0364a632a is described below
commit 4a0364a632a93e5daa72afcebd13f2c1a5734289
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Tue Jan 27 12:23:44 2026 +0100
Pool API improve slots validation (#61071)
---
.../api_fastapi/core_api/datamodels/pools.py | 6 +++---
.../core_api/openapi/v2-rest-api-generated.yaml | 3 +++
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 5 ++++-
.../core_api/routes/public/test_pools.py | 21 ++++++++++++++++++++-
.../src/airflowctl/api/datamodels/generated.py | 10 +++++++---
5 files changed, 37 insertions(+), 8 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/pools.py
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/pools.py
index 0e56684081a..077ddbe5505 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/pools.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/pools.py
@@ -20,7 +20,7 @@ from __future__ import annotations
from collections.abc import Callable, Iterable
from typing import Annotated
-from pydantic import BeforeValidator, Field
+from pydantic import BeforeValidator, Field, PositiveInt
from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel
@@ -38,7 +38,7 @@ class BasePool(BaseModel):
"""Base serializer for Pool."""
pool: str = Field(serialization_alias="name")
- slots: int
+ slots: PositiveInt
description: str | None = Field(default=None)
include_deferred: bool
@@ -66,7 +66,7 @@ class PoolPatchBody(StrictBaseModel):
"""Pool serializer for patch bodies."""
name: str | None = Field(default=None, alias="pool")
- slots: int | None = None
+ slots: PositiveInt | None = None
description: str | None = None
include_deferred: bool | None = None
team_name: str | None = Field(max_length=50, default=None)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
index 148f6479871..d361a37c217 100644
---
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
+++
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
@@ -12041,6 +12041,7 @@ components:
title: Name
slots:
type: integer
+ exclusiveMinimum: 0.0
title: Slots
description:
anyOf:
@@ -12090,6 +12091,7 @@ components:
slots:
anyOf:
- type: integer
+ exclusiveMinimum: 0.0
- type: 'null'
title: Slots
description:
@@ -12119,6 +12121,7 @@ components:
title: Name
slots:
type: integer
+ exclusiveMinimum: 0.0
title: Slots
description:
anyOf:
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index 6d78335fbc5..cf35d310853 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -4597,6 +4597,7 @@ export const $PoolBody = {
},
slots: {
type: 'integer',
+ exclusiveMinimum: 0,
title: 'Slots'
},
description: {
@@ -4671,7 +4672,8 @@ export const $PoolPatchBody = {
slots: {
anyOf: [
{
- type: 'integer'
+ type: 'integer',
+ exclusiveMinimum: 0
},
{
type: 'null'
@@ -4728,6 +4730,7 @@ export const $PoolResponse = {
},
slots: {
type: 'integer',
+ exclusiveMinimum: 0,
title: 'Slots'
},
description: {
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py
index 6d8d924ae2b..bb34660f0e0 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py
@@ -285,6 +285,24 @@ class TestPatchPool(TestPoolsEndpoint):
],
},
),
+ # Negative slot number
+ (
+ POOL1_NAME,
+ {},
+ {"slots": -10},
+ 422,
+ {
+ "detail": [
+ {
+ "ctx": {"gt": 0},
+ "input": -10,
+ "loc": ["body", "slots"],
+ "msg": "Input should be greater than 0",
+ "type": "greater_than",
+ },
+ ],
+ },
+ ),
# Partial body on default_pool
(
Pool.DEFAULT_POOL_NAME,
@@ -364,7 +382,8 @@ class TestPatchPool(TestPoolsEndpoint):
if response.status_code == 422:
for error in body["detail"]:
# pydantic version can vary in tests (lower constraints), we
do not assert the url.
- del error["url"]
+ if "url" in error:
+ del error["url"]
assert body == expected_response
if response.status_code == 200:
diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
index ed5c0e47843..4a653867a6c 100644
--- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
+++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
@@ -633,12 +633,16 @@ class PoolBody(BaseModel):
extra="forbid",
)
name: Annotated[str, Field(max_length=256, title="Name")]
- slots: Annotated[int, Field(title="Slots")]
+ slots: Annotated[int, Field(gt=0, title="Slots")]
description: Annotated[str | None, Field(title="Description")] = None
include_deferred: Annotated[bool | None, Field(title="Include Deferred")]
= False
team_name: Annotated[TeamName | None, Field(title="Team Name")] = None
+class Slots(RootModel[int]):
+ root: Annotated[int, Field(gt=0, title="Slots")]
+
+
class PoolPatchBody(BaseModel):
"""
Pool serializer for patch bodies.
@@ -648,7 +652,7 @@ class PoolPatchBody(BaseModel):
extra="forbid",
)
pool: Annotated[str | None, Field(title="Pool")] = None
- slots: Annotated[int | None, Field(title="Slots")] = None
+ slots: Annotated[Slots | None, Field(title="Slots")] = None
description: Annotated[str | None, Field(title="Description")] = None
include_deferred: Annotated[bool | None, Field(title="Include Deferred")]
= None
team_name: Annotated[TeamName | None, Field(title="Team Name")] = None
@@ -660,7 +664,7 @@ class PoolResponse(BaseModel):
"""
name: Annotated[str, Field(title="Name")]
- slots: Annotated[int, Field(title="Slots")]
+ slots: Annotated[int, Field(gt=0, title="Slots")]
description: Annotated[str | None, Field(title="Description")] = None
include_deferred: Annotated[bool, Field(title="Include Deferred")]
occupied_slots: Annotated[int, Field(title="Occupied Slots")]