You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2025/08/30 11:36:02 UTC
(airflow) branch v3-0-test updated: Fix pushing None as an XCom value (#55080) (#55095)
This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-0-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-0-test by this push:
new 3b8ba3b355a Fix pushing None as an XCom value (#55080) (#55095)
3b8ba3b355a is described below
commit 3b8ba3b355a9aa0316e7fc74e5d25602713ec1a9
Author: Jens Scheffler <95...@users.noreply.github.com>
AuthorDate: Sat Aug 30 13:35:56 2025 +0200
Fix pushing None as an XCom value (#55080) (#55095)
fixes #55062
(cherry picked from commit b4cf3afe2ab6b724f4261a20da31c61410acb93a)
Co-authored-by: Kalyan R <ka...@live.com>
---
.../src/airflow/api_fastapi/execution_api/routes/xcoms.py | 4 ++--
airflow-core/src/airflow/ui/src/pages/XCom/XComEntry.tsx | 10 +++++++---
.../unit/api_fastapi/execution_api/versions/head/test_xcoms.py | 10 +++++++---
3 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/routes/xcoms.py b/airflow-core/src/airflow/api_fastapi/execution_api/routes/xcoms.py
index eae3539f593..332591d2d1d 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/routes/xcoms.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/routes/xcoms.py
@@ -333,6 +333,7 @@ def set_xcom(
run_id: str,
task_id: str,
key: str,
+ session: SessionDep,
value: Annotated[
JsonValue,
Body(
@@ -352,8 +353,7 @@ def set_xcom(
},
},
),
- ],
- session: SessionDep,
+ ] = None,
map_index: Annotated[int, Query()] = -1,
mapped_length: Annotated[
int | None, Query(description="Number of mapped tasks this value expands into")
diff --git a/airflow-core/src/airflow/ui/src/pages/XCom/XComEntry.tsx b/airflow-core/src/airflow/ui/src/pages/XCom/XComEntry.tsx
index 342a0a4e909..c349c8b70dc 100644
--- a/airflow-core/src/airflow/ui/src/pages/XCom/XComEntry.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/XCom/XComEntry.tsx
@@ -39,6 +39,10 @@ export const XComEntry = ({ dagId, mapIndex, runId, taskId, xcomKey }: XComEntry
taskId,
xcomKey,
});
+ // When deserialize=true, the API returns a stringified representation
+ // so we don't need to JSON.stringify it again
+ const xcomValue = data?.value;
+ const valueFormatted = typeof xcomValue === "string" ? xcomValue : JSON.stringify(xcomValue, undefined, 4);
return isLoading ? (
<Skeleton
@@ -50,11 +54,11 @@ export const XComEntry = ({ dagId, mapIndex, runId, taskId, xcomKey }: XComEntry
) : (data?.value?.length ?? 0) > 0 ? (
<HStack>
<Text>{data?.value}</Text>
- {Boolean(data?.value) ? (
- <ClipboardRoot value={data?.value ?? ""}>
+ {xcomValue === undefined || xcomValue === null ? undefined : (
+ <ClipboardRoot value={valueFormatted}>
<ClipboardIconButton />
</ClipboardRoot>
- ) : undefined}
+ )}
</HStack>
) : undefined;
};
diff --git a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_xcoms.py b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_xcoms.py
index 2403e649cbf..b5811d91992 100644
--- a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_xcoms.py
+++ b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_xcoms.py
@@ -331,13 +331,17 @@ class TestXComsSetEndpoint:
('{"key2": "value2"}', '{"key2": "value2"}'),
('{"key2": "value2", "key3": ["value3"]}', '{"key2": "value2", "key3": ["value3"]}'),
('["value1"]', '["value1"]'),
+ (None, None),
],
)
def test_xcom_set(self, client, create_task_instance, session, value, expected_value):
"""
- Test that XCom value is set correctly. The value is passed as a JSON string in the request body.
- XCom.set then uses json.dumps to serialize it and store the value in the database.
- This is done so that Task SDK in multiple languages can use the same API to set XCom values.
+ Test that XCom value is set correctly. The request body can be either:
+ - a JSON string (e.g. '"value"', '{"k":"v"}', '[1]'), which is stored as-is (a string) in the DB
+ - the JSON literal null, which is stored as a None
+
+ This mirrors the Execution API contract where the body is the JSON value itself; Task SDKs may send
+ pre-serialized JSON strings or null.
"""
ti = create_task_instance()
session.commit()