diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1ecb0..c6b23c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- `append_datasource` with a local `file` now names the multipart form field after the detected format (`csv`, `ndjson` or `parquet`) instead of always using `csv`, so NDJSON and Parquet uploads are accepted by the API. + ## [0.4.0] - 2026-06-29 ### Added diff --git a/src/tinybird_sdk/api/api.py b/src/tinybird_sdk/api/api.py index 8ee18b5..2c4cdf5 100644 --- a/src/tinybird_sdk/api/api.py +++ b/src/tinybird_sdk/api/api.py @@ -272,7 +272,7 @@ def append_datasource( with open(file_path_str, "rb") as fp: file_content = fp.read() content_type, multipart = create_multipart_body( - files=[("csv", file_path_str, file_content, None)], + files=[(detected_format or "csv", file_path_str, file_content, None)], ) response = self.request( f"/v0/datasources?{urlencode(query)}", diff --git a/tests/test_api_parity.py b/tests/test_api_parity.py index 6f13da0..095092f 100644 --- a/tests/test_api_parity.py +++ b/tests/test_api_parity.py @@ -124,6 +124,35 @@ def fake_fetch(url: str, **kwargs: Any) -> _FakeResponse: assert api.truncate_datasource("events") == {} +@pytest.mark.parametrize( + ("filename", "field_name"), + [ + ("events.csv", "csv"), + ("events.ndjson", "ndjson"), + ("events.jsonl", "ndjson"), + ("events.parquet", "parquet"), + ("events.dat", "csv"), + ], +) +def test_append_file_uses_format_as_multipart_field_name( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path, filename: str, field_name: str +) -> None: + calls: list[dict[str, Any]] = [] + + def fake_fetch(_url: str, **kwargs: Any) -> _FakeResponse: + calls.append(kwargs) + return _FakeResponse(200, {"ok": True}) + + monkeypatch.setattr(api_module, "tinybird_fetch", fake_fetch) + api = TinybirdApi({"base_url": "https://api.tinybird.co", "token": "p.token"}) + + local_file = tmp_path / filename + local_file.write_bytes(b"data") + api.append_datasource("events", {"file": str(local_file)}) + + assert f'name="{field_name}"; filename="{filename}"'.encode() in calls[0]["body"] + + def test_append_requires_either_url_or_file() -> None: api = TinybirdApi({"base_url": "https://api.tinybird.co", "token": "p.token"}) with pytest.raises(ValueError, match="Either 'url' or 'file'"):