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

henry3260 pushed a commit to branch airflow-ctl/v0-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/airflow-ctl/v0-1-test by this 
push:
     new 4cf0dad5331 [airflow-ctl/v0-1-test] Stop airflowctl update commands 
from clearing fields left unset (#71333) (#73100)
4cf0dad5331 is described below

commit 4cf0dad5331acd2f6a1a10a042eb3530cf1df67a
Author: Henry Chen <[email protected]>
AuthorDate: Mon Sep 14 02:27:39 2026 +0800

    [airflow-ctl/v0-1-test] Stop airflowctl update commands from clearing 
fields left unset (#71333) (#73100)
    
    (cherry picked from commit 350ae2e10ac184113fa0aa08e72e72c672803e2a)
    
    Co-authored-by: Devansh Bhensdadia <[email protected]>
---
 airflow-ctl/src/airflowctl/api/operations.py       |  6 +--
 .../tests/airflow_ctl/api/test_operations.py       | 58 +++++++++++++++++++---
 2 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/airflow-ctl/src/airflowctl/api/operations.py 
b/airflow-ctl/src/airflowctl/api/operations.py
index 67d4c79adc1..8ce25e28120 100644
--- a/airflow-ctl/src/airflowctl/api/operations.py
+++ b/airflow-ctl/src/airflowctl/api/operations.py
@@ -533,7 +533,7 @@ class ConnectionsOperations(BaseOperations):
         try:
             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)
         except ServerResponseError as e:
@@ -775,7 +775,7 @@ class PoolsOperations(BaseOperations):
         """Update a pool."""
         try:
             self.response = self.client.patch(
-                f"pools/{pool_body.pool}", 
json=pool_body.model_dump(mode="json")
+                f"pools/{pool_body.pool}", 
json=pool_body.model_dump(mode="json", exclude_none=True)
             )
             return PoolResponse.model_validate_json(self.response.content)
         except ServerResponseError as e:
@@ -835,7 +835,7 @@ class VariablesOperations(BaseOperations):
         """Update a variable."""
         try:
             self.response = self.client.patch(
-                f"variables/{variable.key}", 
json=variable.model_dump(mode="json")
+                f"variables/{variable.key}", 
json=variable.model_dump(mode="json", exclude_none=True)
             )
             return VariableResponse.model_validate_json(self.response.content)
         except ServerResponseError as e:
diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py 
b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
index c2a4e40c15f..a6a2145f7ff 100644
--- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
+++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
@@ -87,6 +87,7 @@ from airflowctl.api.datamodels.generated import (
     PluginResponse,
     PoolBody,
     PoolCollectionResponse,
+    PoolPatchBody,
     PoolResponse,
     ProviderCollectionResponse,
     ProviderResponse,
@@ -882,14 +883,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(200, 
json=json.loads(self.connection_response.model_dump_json()))
@@ -898,6 +892,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,
@@ -1540,6 +1558,7 @@ class TestPoolsOperations:
         pools=[pool_response],
         total_entries=1,
     )
+    pool_patch_body = PoolPatchBody(pool=pool_name, description="description")
     pool_bulk_response = BulkResponse(
         create=BulkActionResponse(success=[pool_name], errors=[]),
         update=None,
@@ -1591,6 +1610,19 @@ class TestPoolsOperations:
         response = client.pools.delete(self.pool_name)
         assert response == self.pool_name
 
+    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(
@@ -1703,6 +1735,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