You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ka...@apache.org on 2025/08/30 08:43:18 UTC
(airflow) branch main updated: Fix pushing None as an XCom value (#55080)
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik 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 b4cf3afe2ab Fix pushing None as an XCom value (#55080)
b4cf3afe2ab is described below
commit b4cf3afe2ab6b724f4261a20da31c61410acb93a
Author: Kalyan R <ka...@live.com>
AuthorDate: Sat Aug 30 14:13:11 2025 +0530
Fix pushing None as an XCom value (#55080)
fixes #55062
---
.../src/airflow/api_fastapi/execution_api/routes/xcoms.py | 4 ++--
airflow-core/src/airflow/ui/src/pages/XCom/XComEntry.tsx | 13 +++++++------
.../api_fastapi/execution_api/versions/head/test_xcoms.py | 10 +++++++---
3 files changed, 16 insertions(+), 11 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 230e9837de1..576f6c4e0dc 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
@@ -326,6 +326,7 @@ def set_xcom(
run_id: str,
task_id: str,
key: Annotated[str, StringConstraints(min_length=1)],
+ session: SessionDep,
value: Annotated[
JsonValue,
Body(
@@ -345,8 +346,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 510397ac607..05fbd4c2c1a 100644
--- a/airflow-core/src/airflow/ui/src/pages/XCom/XComEntry.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/XCom/XComEntry.tsx
@@ -74,8 +74,9 @@ export const XComEntry = ({ dagId, mapIndex, runId, taskId, xcomKey }: XComEntry
});
// When deserialize=true, the API returns a stringified representation
// so we don't need to JSON.stringify it again
- const valueFormatted =
- typeof data?.value === "string" ? data.value : JSON.stringify(data?.value, undefined, 4);
+ const xcomValue = data?.value;
+ const isObjectOrArray = Array.isArray(xcomValue) || (xcomValue !== null && typeof xcomValue === "object");
+ const valueFormatted = typeof xcomValue === "string" ? xcomValue : JSON.stringify(xcomValue, undefined, 4);
return isLoading ? (
<Skeleton
@@ -86,16 +87,16 @@ export const XComEntry = ({ dagId, mapIndex, runId, taskId, xcomKey }: XComEntry
/>
) : (
<HStack>
- {["array", "object"].includes(typeof data?.value) ? (
- <RenderedJsonField content={data?.value as object} enableClipboard={false} />
+ {isObjectOrArray ? (
+ <RenderedJsonField content={xcomValue as object} enableClipboard={false} />
) : (
<Text>{renderTextWithLinks(valueFormatted)}</Text>
)}
- {Boolean(data?.value) ? (
+ {xcomValue === undefined || xcomValue === null ? undefined : (
<ClipboardRoot value={valueFormatted}>
<ClipboardIconButton />
</ClipboardRoot>
- ) : undefined}
+ )}
</HStack>
);
};
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 0841a76ed1a..f9408c808f2 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()