This is an automated email from the ASF dual-hosted git repository.

henry3260 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 350ae2e10ac Stop airflowctl update commands from clearing fields left 
unset (#71333)
350ae2e10ac is described below

commit 350ae2e10ac184113fa0aa08e72e72c672803e2a
Author: Devansh Bhensdadia <[email protected]>
AuthorDate: Sun Sep 13 23:26:49 2026 +0530

    Stop airflowctl update commands from clearing fields left unset (#71333)
---
 airflow-ctl/src/airflowctl/api/operations.py       | 10 ++--
 .../tests/airflow_ctl/api/test_operations.py       | 56 +++++++++++++++++++---
 2 files changed, 56 insertions(+), 10 deletions(-)

diff --git a/airflow-ctl/src/airflowctl/api/operations.py 
b/airflow-ctl/src/airflowctl/api/operations.py
index a800fa5390c..17f72f2d5d4 100644
--- a/airflow-ctl/src/airflowctl/api/operations.py
+++ b/airflow-ctl/src/airflowctl/api/operations.py
@@ -465,7 +465,7 @@ class ConnectionsOperations(BaseOperations):
         """Update a connection."""
         self.response = self.client.patch(
             f"connections/{connection.connection_id}",
-            json=connection.model_dump(mode="json", by_alias=True),
+            json=connection.model_dump(mode="json", by_alias=True, 
exclude_none=True),
         )
         return ConnectionResponse.model_validate_json(self.response.content)
 
@@ -687,7 +687,9 @@ class PoolsOperations(BaseOperations):
 
     def update(self, pool_body: PoolPatchBody) -> PoolResponse | 
ServerResponseError:
         """Update a pool."""
-        self.response = self.client.patch(f"pools/{pool_body.pool}", 
json=pool_body.model_dump(mode="json"))
+        self.response = self.client.patch(
+            f"pools/{pool_body.pool}", json=pool_body.model_dump(mode="json", 
exclude_none=True)
+        )
         return PoolResponse.model_validate_json(self.response.content)
 
 
@@ -801,7 +803,9 @@ class VariablesOperations(BaseOperations):
 
     def update(self, variable: VariableBody) -> VariableResponse | 
ServerResponseError:
         """Update a variable."""
-        self.response = self.client.patch(f"variables/{variable.key}", 
json=variable.model_dump(mode="json"))
+        self.response = self.client.patch(
+            f"variables/{variable.key}", json=variable.model_dump(mode="json", 
exclude_none=True)
+        )
         return VariableResponse.model_validate_json(self.response.content)
 
 
diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py 
b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
index 6e52ba1a049..583c6ff2342 100644
--- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
+++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
@@ -968,14 +968,7 @@ class TestConnectionsOperations:
             assert request_body == {
                 "connection_id": self.connection_id,
                 "conn_type": self.conn_type,
-                "description": None,
-                "host": None,
-                "login": None,
                 "schema": self.schema_,
-                "port": None,
-                "password": None,
-                "extra": None,
-                "team_name": None,
             }
             assert "schema_" not in request_body
             return httpx.Response(
@@ -986,6 +979,30 @@ class TestConnectionsOperations:
         response = client.connections.update(connection=connection)
         assert response == self.connection_response
 
+    def test_update_omits_unset_fields_from_request_body(self):
+        # The API treats every key present in a PATCH body as an intentional 
value, so sending
+        # unset fields as null clears the stored login, port, schema and 
description.
+        connection = ConnectionBody(
+            connection_id=self.connection_id,
+            conn_type=self.conn_type,
+            host="new-host",
+        )
+
+        def handle_request(request: httpx.Request) -> httpx.Response:
+            assert request.url.path == 
f"/api/v2/connections/{self.connection_id}"
+            assert json.loads(request.content.decode()) == {
+                "connection_id": self.connection_id,
+                "conn_type": self.conn_type,
+                "host": "new-host",
+            }
+            return httpx.Response(
+                200, 
json=json.loads(self.connection_response.model_dump_json(by_alias=True))
+            )
+
+        client = make_api_client(transport=httpx.MockTransport(handle_request))
+        response = client.connections.update(connection=connection)
+        assert response == self.connection_response
+
     def test_test(self):
         connection_test_response = ConnectionTestResponse(
             status=True,
@@ -1800,6 +1817,19 @@ class TestPoolsOperations:
         response = client.pools.update(pool_body=self.pool_patch_body)
         assert response == self.pool_response
 
+    def test_update_omits_unset_fields_from_request_body(self):
+        def handle_request(request: httpx.Request) -> httpx.Response:
+            assert request.url.path == f"/api/v2/pools/{self.pool_name}"
+            assert json.loads(request.content.decode()) == {
+                "pool": self.pool_name,
+                "description": "description",
+            }
+            return httpx.Response(200, 
json=json.loads(self.pool_response.model_dump_json()))
+
+        client = make_api_client(transport=httpx.MockTransport(handle_request))
+        response = client.pools.update(pool_body=self.pool_patch_body)
+        assert response == self.pool_response
+
 
 class TestProvidersOperations:
     provider_response = ProviderResponse(
@@ -2069,6 +2099,18 @@ class TestVariablesOperations:
         response = client.variables.update(variable=self.variable)
         assert response == self.variable_response
 
+    def test_update_omits_unset_fields_from_request_body(self):
+        variable = VariableBody.model_validate({"key": self.key, "value": 
"new-value"})
+
+        def handle_request(request: httpx.Request) -> httpx.Response:
+            assert request.url.path == f"/api/v2/variables/{self.key}"
+            assert json.loads(request.content.decode()) == {"key": self.key, 
"value": "new-value"}
+            return httpx.Response(200, 
json=json.loads(self.variable_response.model_dump_json()))
+
+        client = make_api_client(transport=httpx.MockTransport(handle_request))
+        response = client.variables.update(variable=variable)
+        assert response == self.variable_response
+
 
 class TestVersionOperations:
     version_info = VersionInfo(

Reply via email to