Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/migrations/v1_0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Constructing messages is simplified in v1.0. The old API required wrapping conte
**Note**:
* When using `File (bytes)` in v1.0, data serialization (via base64 encoding) is not required because A2A now uses Protobuf, which handles it automatically.
* In v1.0, `Part.DataPart.data` is renamed to `Part.data` and is of type `google.protobuf.Value`. Use `ParseDict` to convert a Python dict into a suitable value. See the examples below for more details.
* `google.protobuf.Value` stores every number as a double, so whole numbers in a data Part are read back as floats on every transport (`4326` becomes `4326.0`). This is a fidelity loss, not a spec violation, since JSON does not distinguish the two. On the receiving side, narrow floats for fields you model as integers (for example with `float.is_integer()`) and leave other fields alone.

**Before (v0.3):**
```python
Expand Down
10 changes: 10 additions & 0 deletions src/a2a/helpers/proto_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@ def new_data_part(
) -> Part:
"""Creates a Part with structured data (google.protobuf.Value).

Note:
``google.protobuf.Value`` stores every number as a double, so whole
numbers lose their integer type here and are read back as floats
(``4326`` becomes ``4326.0``). See ``get_data_parts``.

Args:
data: JSON-serializable data to embed (dict, list, str, etc.).
media_type: Optional MIME type of the part content (e.g., "text/plain", "application/json", "image/png").
Expand Down Expand Up @@ -407,6 +412,11 @@ def get_data_parts(parts: Sequence[Part]) -> list[Any]:
Each returned element is the Python object obtained by converting
the ``google.protobuf.Value`` back via ``MessageToDict``.

Note:
Whole numbers come back as floats (``4326`` becomes ``4326.0``)
because ``google.protobuf.Value`` stores numbers as doubles. For
fields you model as integers, narrow floats with ``is_integer()``.

Args:
parts: A sequence of ``Part`` objects.

Expand Down
11 changes: 11 additions & 0 deletions tests/helpers/test_proto_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,17 @@ def test_get_data_parts_empty() -> None:
assert get_data_parts(parts) == []


Comment thread
Iwaniukooo11 marked this conversation as resolved.
@pytest.mark.xfail(
reason='https://github.com/a2aproject/a2a-python/issues/1328',
strict=True,
)
def test_get_data_parts_keeps_whole_numbers_as_ints() -> None:
result = get_data_parts([new_data_part({'id': 4326, 'items': [240]})])
assert result == [{'id': 4326, 'items': [240]}]
assert isinstance(result[0]['id'], int)
assert isinstance(result[0]['items'][0], int)


def test_get_raw_parts() -> None:
parts = [
Part(raw=b'\x89PNG'),
Expand Down
Loading