Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/tinybird_sdk/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}",
Expand Down
29 changes: 29 additions & 0 deletions tests/test_api_parity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'"):
Expand Down