This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-1-test by this push:
new 4c95dd677c9 [v3-1-test] Fix slots negative infinity (#61140) (#61768)
4c95dd677c9 is described below
commit 4c95dd677c9d2f764a04ad6b7cd1c5db8c98282e
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Feb 16 17:50:38 2026 +0100
[v3-1-test] Fix slots negative infinity (#61140) (#61768)
* [v3-1-test] Fix slots negative infinity (#61140)
* added deprecation of project_id parameter
mentioned about deprication of project_id parameter
* Fix Pool API to support unlimited slots -1/infinity
* removed bigquery.rst
* fixed when return inf for json comaptibility
* removed infinity and make sured that only pool slots >=-1(-1 for infinity)
* some reformatting
* in test_post_pool_rejects_infinity_string to post to pools
* pools use -1 as unlimited slots
* Revert "pools use -1 as unlimited slots"
This reverts commit 99f86811bbfcd4b8fa2fa879189618b10547a55a.
* Fix CI
---------
(cherry picked from commit 1ceb7ed47b50c83f45ec4bc893fa59b45cb3b69d)
Co-authored-by: kamran Imaz <[email protected]>
Co-authored-by: kamran imaz <[email protected]>
Co-authored-by: pierrejeambrun <[email protected]>
* Fix CI
---------
Co-authored-by: kamran Imaz <[email protected]>
Co-authored-by: kamran imaz <[email protected]>
Co-authored-by: pierrejeambrun <[email protected]>
---
.../api_fastapi/core_api/datamodels/pools.py | 20 +++++++--
.../core_api/openapi/v2-rest-api-generated.yaml | 9 ++--
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 13 +++---
.../airflow/ui/openapi-gen/requests/types.gen.ts | 6 +++
.../core_api/routes/public/test_pools.py | 52 ++++++++++++++++------
.../src/airflowctl/api/datamodels/generated.py | 6 +--
6 files changed, 78 insertions(+), 28 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 cde5746b42b..8ebdc99b66e 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
from typing import Annotated
-from pydantic import BeforeValidator, Field, PositiveInt
+from pydantic import BeforeValidator, Field
from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel
@@ -34,15 +34,27 @@ def _call_function(function: Callable[[], int]) -> int:
return function()
+PoolSlots = Annotated[
+ int,
+ Field(ge=-1, description="Number of slots. Use -1 for unlimited."),
+]
+
+
class BasePool(BaseModel):
"""Base serializer for Pool."""
pool: str = Field(serialization_alias="name")
- slots: PositiveInt
+ slots: PoolSlots
description: str | None = Field(default=None)
include_deferred: bool
+def _sanitize_open_slots(value) -> int:
+ if isinstance(value, float) and value == float("inf"):
+ return -1
+ return value
+
+
class PoolResponse(BasePool):
"""Pool serializer for responses."""
@@ -50,7 +62,7 @@ class PoolResponse(BasePool):
running_slots: Annotated[int, BeforeValidator(_call_function)]
queued_slots: Annotated[int, BeforeValidator(_call_function)]
scheduled_slots: Annotated[int, BeforeValidator(_call_function)]
- open_slots: Annotated[int, BeforeValidator(_call_function)]
+ open_slots: Annotated[int, BeforeValidator(lambda v:
_sanitize_open_slots(_call_function(v)))]
deferred_slots: Annotated[int, BeforeValidator(_call_function)]
@@ -65,7 +77,7 @@ class PoolPatchBody(StrictBaseModel):
"""Pool serializer for patch bodies."""
name: str | None = Field(default=None, alias="pool")
- slots: PositiveInt | None = None
+ slots: PoolSlots | None = None
description: str | None = None
include_deferred: bool | None = 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 0bd1bf28dc4..0f4fa84182c 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
@@ -11433,8 +11433,9 @@ components:
title: Name
slots:
type: integer
- exclusiveMinimum: 0.0
+ minimum: -1.0
title: Slots
+ description: Number of slots. Use -1 for unlimited.
description:
anyOf:
- type: string
@@ -11477,7 +11478,8 @@ components:
slots:
anyOf:
- type: integer
- exclusiveMinimum: 0.0
+ minimum: -1.0
+ description: Number of slots. Use -1 for unlimited.
- type: 'null'
title: Slots
description:
@@ -11501,8 +11503,9 @@ components:
title: Name
slots:
type: integer
- exclusiveMinimum: 0.0
+ minimum: -1.0
title: Slots
+ description: Number of slots. Use -1 for unlimited.
description:
anyOf:
- type: string
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 54c6d40dbfe..c87b7a34f75 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
@@ -4248,8 +4248,9 @@ export const $PoolBody = {
},
slots: {
type: 'integer',
- exclusiveMinimum: 0,
- title: 'Slots'
+ minimum: -1,
+ title: 'Slots',
+ description: 'Number of slots. Use -1 for unlimited.'
},
description: {
anyOf: [
@@ -4312,7 +4313,8 @@ export const $PoolPatchBody = {
anyOf: [
{
type: 'integer',
- exclusiveMinimum: 0
+ minimum: -1,
+ description: 'Number of slots. Use -1 for unlimited.'
},
{
type: 'null'
@@ -4357,8 +4359,9 @@ export const $PoolResponse = {
},
slots: {
type: 'integer',
- exclusiveMinimum: 0,
- title: 'Slots'
+ minimum: -1,
+ title: 'Slots',
+ description: 'Number of slots. Use -1 for unlimited.'
},
description: {
anyOf: [
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index 726ed514a2b..29e7b3f6031 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -1129,6 +1129,9 @@ export type PluginResponse = {
*/
export type PoolBody = {
name: string;
+ /**
+ * Number of slots. Use -1 for unlimited.
+ */
slots: number;
description?: string | null;
include_deferred?: boolean;
@@ -1157,6 +1160,9 @@ export type PoolPatchBody = {
*/
export type PoolResponse = {
name: string;
+ /**
+ * Number of slots. Use -1 for unlimited.
+ */
slots: number;
description?: string | null;
include_deferred: boolean;
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 2abd7e23835..48b5dccf0e1 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
@@ -17,6 +17,7 @@
from __future__ import annotations
import pytest
+from sqlalchemy import func, select
from airflow.models.pool import Pool
from airflow.utils.session import provide_session
@@ -266,15 +267,7 @@ class TestPatchPool(TestPoolsEndpoint):
{"slots": -10},
422,
{
- "detail": [
- {
- "ctx": {"gt": 0},
- "input": -10,
- "loc": ["body", "slots"],
- "msg": "Input should be greater than 0",
- "type": "greater_than",
- },
- ],
+ "detail": "Slots must be greater than or equal to -1. Use
-1 for unlimited.",
},
),
# Partial body on default_pool
@@ -351,10 +344,10 @@ class TestPatchPool(TestPoolsEndpoint):
body = response.json()
if response.status_code == 422:
- for error in body["detail"]:
- # pydantic version can vary in tests (lower constraints), we
do not assert the url.
- if "url" in error:
- del error["url"]
+ detail = response.json().get("detail")
+ assert detail is not None
+ assert "slots" in str(detail)
+ return
assert body == expected_response
if response.status_code == 200:
@@ -443,6 +436,39 @@ class TestPostPool(TestPoolsEndpoint):
assert session.query(Pool).count() == n_pools + 1
check_last_log(session, dag_id=None, event="post_pool",
logical_date=None)
+ def test_post_pool_allows_unlimited_slots(self, test_client, session):
+ self.create_pools()
+ n_pools = session.scalar(select(func.count()).select_from(Pool))
+
+ response = test_client.post(
+ "/pools",
+ json={
+ "name": "unlimited_pool",
+ "slots": -1,
+ "description": "Unlimited pool",
+ "include_deferred": False,
+ },
+ )
+
+ assert response.status_code == 201
+ body = response.json()
+ assert body["name"] == "unlimited_pool"
+ assert body["slots"] == -1
+ assert body["open_slots"] == -1
+ assert session.scalar(select(func.count()).select_from(Pool)) ==
n_pools + 1
+ check_last_log(session, dag_id=None, event="post_pool",
logical_date=None)
+
+ def test_post_pool_rejects_infinity_string(self, test_client, session):
+ response = test_client.post(
+ "/pools",
+ json={
+ "name": "bad_pool",
+ "slots": "infinity",
+ "include_deferred": False,
+ },
+ )
+ assert response.status_code == 422
+
def test_should_respond_401(self, unauthenticated_test_client):
response = unauthenticated_test_client.post("/pools", json={})
assert response.status_code == 401
diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
index c8f11ec0e60..e5cf62a73e3 100644
--- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
+++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
@@ -614,13 +614,13 @@ class PoolBody(BaseModel):
extra="forbid",
)
name: Annotated[str, Field(max_length=256, title="Name")]
- slots: Annotated[int, Field(gt=0, title="Slots")]
+ slots: Annotated[int, Field(description="Number of slots. Use -1 for
unlimited.", ge=-1, title="Slots")]
description: Annotated[str | None, Field(title="Description")] = None
include_deferred: Annotated[bool | None, Field(title="Include Deferred")]
= False
class Slots(RootModel[int]):
- root: Annotated[int, Field(gt=0, title="Slots")]
+ root: Annotated[int, Field(description="Number of slots. Use -1 for
unlimited.", ge=-1, title="Slots")]
class PoolPatchBody(BaseModel):
@@ -643,7 +643,7 @@ class PoolResponse(BaseModel):
"""
name: Annotated[str, Field(title="Name")]
- slots: Annotated[int, Field(gt=0, title="Slots")]
+ slots: Annotated[int, Field(description="Number of slots. Use -1 for
unlimited.", ge=-1, 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")]