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(