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 1ceb7ed47b5 Fix slots negative infinity (#61140)
1ceb7ed47b5 is described below

commit 1ceb7ed47b50c83f45ec4bc893fa59b45cb3b69d
Author: kamran Imaz <[email protected]>
AuthorDate: Wed Feb 11 16:44:18 2026 +0530

    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
    
    ---------
    
    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           | 51 ++++++++++++++++------
 .../src/airflowctl/api/datamodels/generated.py     |  6 +--
 6 files changed, 77 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 077ddbe5505..a661308d443 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, 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)]
     team_name: str | None
 
@@ -66,7 +78,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
     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 847409b0076..11c8007ada6 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,8 +12041,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
@@ -12091,7 +12092,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:
@@ -12121,8 +12123,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 8b0bf124203..e0b53fb6d6f 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,8 +4597,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: [
@@ -4673,7 +4674,8 @@ export const $PoolPatchBody = {
             anyOf: [
                 {
                     type: 'integer',
-                    exclusiveMinimum: 0
+                    minimum: -1,
+                    description: 'Number of slots. Use -1 for unlimited.'
                 },
                 {
                     type: 'null'
@@ -4730,8 +4732,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 b98c076f66a..c409b984d10 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
@@ -1193,6 +1193,9 @@ export type PluginResponse = {
  */
 export type PoolBody = {
     name: string;
+    /**
+     * Number of slots. Use -1 for unlimited.
+     */
     slots: number;
     description?: string | null;
     include_deferred?: boolean;
@@ -1223,6 +1226,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 bb34660f0e0..e2ef8720735 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
@@ -292,15 +292,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
@@ -380,10 +372,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:
@@ -498,6 +490,39 @@ class TestPostPool(TestPoolsEndpoint):
         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_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 fbb0881b909..921709c0b68 100644
--- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
+++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
@@ -633,14 +633,14 @@ 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
     team_name: Annotated[TeamName | None, Field(title="Team Name")] = None
 
 
 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):
@@ -664,7 +664,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")]

Reply via email to