Skip to content

Commit a90f296

Browse files
authored
Merge pull request #15 from tinybirdco/manum/fwd-796-define-cloud-branch-creation-in-tinybird-config
Allow to set branch mode in tinybird.config.json
2 parents f3d9d63 + fe8293a commit a90f296

14 files changed

Lines changed: 310 additions & 35 deletions

‎CHANGELOG.md‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
9-
10-
### Added
11-
- Professional repository baseline:
12-
- CI workflow with lint, type checks, tests, and secret scanning gates.
13-
- `pre-commit` with `ruff`, `mypy`, and `gitleaks`.
14-
- `Makefile`, `CODEOWNERS`, and PR template.
15-
- Initial `SECURITY.md` and packaging metadata improvements.
8+
## [0.1.11] - 2026-06-15
9+
10+
### Changed
11+
12+
- Relaxed bundled `tinybird` CLI dependency to `>=4.6.0,<4.7.0` to avoid resolution failures while keeping the SDK on the `4.6.x` line.
13+
- Updated branch data config handling to use `branch_data_mode`; legacy `branch_data_on_create` now triggers an explicit migration error.
14+
- `branch_data_mode` now only accepts `last_partition` as a user-facing value.
15+
- In `dev_mode=local`, branch data mode warnings are now shown only when `branch_data_mode` is explicitly set in `tinybird.config.json`.
16+
- `tinybird branch create` and `tinybird branch clear` now show a deprecation warning (instead of failing) when `--ignore-datasource` is passed, then continue by ignoring that flag.

‎pyproject.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "tinybird-sdk"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
description = "Python SDK for Tinybird Forward"
55
readme = "README.md"
66
license = "MIT"
@@ -9,7 +9,7 @@ authors = [
99
]
1010
requires-python = ">=3.11"
1111
dependencies = [
12-
"tinybird==4.6.0",
12+
"tinybird>=4.6.0,<4.7.0",
1313
]
1414
classifiers = [
1515
"Development Status :: 4 - Beta",

‎src/tinybird_sdk/api/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
)
1414
from .branches import (
1515
TinybirdBranch,
16+
CreateBranchOptions,
1617
BranchApiConfig,
1718
BranchApiError,
1819
create_branch,

‎src/tinybird_sdk/api/branches.py‎

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from __future__ import annotations
22

33
import time
4-
from dataclasses import asdict
5-
from dataclasses import dataclass
4+
from dataclasses import asdict, dataclass
65
from typing import Any
76
from urllib.parse import urlencode
87

8+
from ..cli.config_types import BranchDataMode
99
from .fetcher import tinybird_fetch
1010

11-
LAST_PARTITION = "last_partition"
12-
1311

1412
@dataclass(frozen=True, slots=True)
1513
class BranchApiConfig:
@@ -25,6 +23,11 @@ class TinybirdBranch:
2523
token: str | None = None
2624

2725

26+
@dataclass(frozen=True, slots=True)
27+
class CreateBranchOptions:
28+
branch_data_mode: BranchDataMode | None = None
29+
30+
2831
class BranchApiError(Exception):
2932
def __init__(self, message: str, status: int, body: Any = None):
3033
super().__init__(message)
@@ -68,9 +71,14 @@ def _poll_job(
6871
raise BranchApiError(f"Job '{job_id}' timed out after {max_attempts} attempts", 408)
6972

7073

71-
def create_branch(config: BranchApiConfig | dict[str, Any], name: str) -> TinybirdBranch:
74+
def create_branch(
75+
config: BranchApiConfig | dict[str, Any], name: str, options: CreateBranchOptions | None = None
76+
) -> TinybirdBranch:
7277
normalized = config if isinstance(config, BranchApiConfig) else BranchApiConfig(**config)
73-
url = f"{normalized.base_url.rstrip('/')}/v1/environments?{urlencode({'name': name})}"
78+
params = {"name": name}
79+
if options and options.branch_data_mode:
80+
params["data"] = options.branch_data_mode
81+
url = f"{normalized.base_url.rstrip('/')}/v1/environments?{urlencode(params)}"
7482
response = tinybird_fetch(url, method="POST", headers=_headers(normalized.token))
7583

7684
if not response.ok:
@@ -152,19 +160,23 @@ def branch_exists(config: BranchApiConfig | dict[str, Any], name: str) -> bool:
152160
return any(branch.name == name for branch in branches)
153161

154162

155-
def get_or_create_branch(config: BranchApiConfig | dict[str, Any], name: str) -> dict[str, Any]:
163+
def get_or_create_branch(
164+
config: BranchApiConfig | dict[str, Any], name: str, options: CreateBranchOptions | None = None
165+
) -> dict[str, Any]:
156166
normalized = config if isinstance(config, BranchApiConfig) else BranchApiConfig(**config)
157167
try:
158168
branch = get_branch(normalized, name)
159169
return {**asdict(branch), "was_created": False}
160170
except BranchApiError as error:
161171
if error.status == 404:
162-
branch = create_branch(normalized, name)
172+
branch = create_branch(normalized, name, options=options)
163173
return {**asdict(branch), "was_created": True}
164174
raise
165175

166176

167-
def clear_branch(config: BranchApiConfig | dict[str, Any], name: str) -> TinybirdBranch:
177+
def clear_branch(
178+
config: BranchApiConfig | dict[str, Any], name: str, options: CreateBranchOptions | None = None
179+
) -> TinybirdBranch:
168180
normalized = config if isinstance(config, BranchApiConfig) else BranchApiConfig(**config)
169181
delete_branch(normalized, name)
170-
return create_branch(normalized, name)
182+
return create_branch(normalized, name, options=options)

‎src/tinybird_sdk/cli/commands/build.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import time
66
from typing import Any
77

8-
from ...api.branches import get_or_create_branch
8+
from ...api.branches import CreateBranchOptions, get_or_create_branch
99
from ...api.build import build_to_tinybird
1010
from ...api.dashboard import get_branch_dashboard_url, get_local_dashboard_url
1111
from ...api.local import (
@@ -138,9 +138,14 @@ def run_build(options: BuildCommandOptions | dict[str, Any] | None = None) -> Bu
138138

139139
if not normalized.token_override:
140140
try:
141+
branch_options = None
142+
branch_value = config.get("branch_data_mode")
143+
if branch_value and config.get("dev_mode") != "local":
144+
branch_options = CreateBranchOptions(branch_data_mode=branch_value)
141145
branch = get_or_create_branch(
142146
{"base_url": config["base_url"], "token": config["token"]},
143147
config["tinybird_branch"],
148+
options=branch_options,
144149
)
145150
if not branch.get("token"):
146151
return BuildCommandResult(

‎src/tinybird_sdk/cli/commands/clear.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import time
66
from typing import Any
77

8-
from ...api.branches import clear_branch
8+
from ...api.branches import CreateBranchOptions, clear_branch
99
from ...api.local import clear_local_workspace, get_local_tokens, get_local_workspace_name
1010
from ..config import load_config_async
1111

@@ -60,8 +60,15 @@ def run_clear(options: ClearCommandOptions | dict[str, Any] | None = None) -> Cl
6060
duration_ms=int(time.time() * 1000) - start,
6161
)
6262

63+
branch_options = None
64+
branch_value = config.get("branch_data_mode")
65+
if branch_value and config.get("dev_mode") != "local":
66+
branch_options = CreateBranchOptions(branch_data_mode=branch_value)
67+
6368
clear_branch(
64-
{"base_url": config["base_url"], "token": config["token"]}, config["tinybird_branch"]
69+
{"base_url": config["base_url"], "token": config["token"]},
70+
config["tinybird_branch"],
71+
options=branch_options,
6572
)
6673
return ClearResult(
6774
success=True,

‎src/tinybird_sdk/cli/commands/preview.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import time
66
from typing import Any
77

8-
from ...api.branches import create_branch, delete_branch, get_branch
8+
from ...api.branches import CreateBranchOptions, create_branch, delete_branch, get_branch
99
from ...api.build import build_to_tinybird
1010
from ...api.deploy import deploy_to_main
1111
from ...api.local import LocalNotRunningError, get_local_tokens, get_or_create_local_workspace
@@ -151,8 +151,14 @@ def run_preview(
151151
except Exception:
152152
pass
153153

154+
branch_options = None
155+
branch_value = config.get("branch_data_mode")
156+
if branch_value and config.get("dev_mode") != "local":
157+
branch_options = CreateBranchOptions(branch_data_mode=branch_value)
154158
branch = create_branch(
155-
{"base_url": config["base_url"], "token": config["token"]}, preview_branch_name
159+
{"base_url": config["base_url"], "token": config["token"]},
160+
preview_branch_name,
161+
options=branch_options,
156162
)
157163
except Exception as error:
158164
return PreviewCommandResult(

‎src/tinybird_sdk/cli/config.py‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
from typing import Any
99

1010
from .config_loader import load_config_file
11-
from .config_types import DevMode, TinybirdConfig
11+
from .config_types import (
12+
BRANCH_DATA_MODE_VALUES,
13+
BranchDataModeEnum,
14+
DevMode,
15+
TinybirdConfig,
16+
)
1217
from .git import get_current_git_branch, get_tinybird_branch_name, is_main_branch
1318

1419
DEFAULT_BASE_URL = "https://api.tinybird.co"
@@ -34,6 +39,27 @@ class ResolvedConfig:
3439
tinybird_branch: str | None
3540
is_main_branch: bool
3641
dev_mode: DevMode
42+
branch_data_mode: str | None
43+
44+
45+
def _resolve_branch_data_mode(raw: dict[str, Any]) -> tuple[str | None, bool]:
46+
if "branch_data_on_create" in raw:
47+
raise ValueError("`branch_data_on_create` has been renamed to `branch_data_mode`.")
48+
49+
value = raw.get("branch_data_mode")
50+
if value is None:
51+
return BranchDataModeEnum.LAST_PARTITION.value, False
52+
if not isinstance(value, str):
53+
raise ValueError("branch_data_mode must be a string.")
54+
55+
mode = value.strip().lower()
56+
if not mode:
57+
return BranchDataModeEnum.LAST_PARTITION.value, False
58+
if mode not in BRANCH_DATA_MODE_VALUES:
59+
raise ValueError(
60+
f"Invalid branch_data_mode '{value}'. Allowed values are: {', '.join(BRANCH_DATA_MODE_VALUES)}."
61+
)
62+
return mode, True
3763

3864

3965
def load_env_files(directory: str) -> None:
@@ -176,6 +202,14 @@ def _resolve_config(config: TinybirdConfig, config_path: str) -> ResolvedConfig:
176202
or DEFAULT_BASE_URL
177203
)
178204

205+
branch_data_mode, branch_data_mode_explicit = _resolve_branch_data_mode(asdict(config))
206+
dev_mode = config.dev_mode or "branch"
207+
if branch_data_mode_explicit and branch_data_mode and dev_mode == "local":
208+
print(
209+
"Warning: branch_data_mode is set in tinybird.config.json but dev_mode='local'. "
210+
"Branch data settings only apply to cloud branches."
211+
)
212+
179213
return ResolvedConfig(
180214
include=include,
181215
token=token,
@@ -185,7 +219,8 @@ def _resolve_config(config: TinybirdConfig, config_path: str) -> ResolvedConfig:
185219
git_branch=get_current_git_branch(),
186220
tinybird_branch=get_tinybird_branch_name(),
187221
is_main_branch=is_main_branch(),
188-
dev_mode=config.dev_mode or "branch",
222+
dev_mode=dev_mode,
223+
branch_data_mode=branch_data_mode,
189224
)
190225

191226

‎src/tinybird_sdk/cli/config_types.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass, field
4+
from enum import StrEnum
45
from typing import Literal
56

67
DevMode = Literal["branch", "local"]
8+
BranchDataMode = Literal["last_partition"]
9+
BRANCH_DATA_MODE_VALUES: tuple[str, ...] = ("last_partition",)
10+
11+
12+
class BranchDataModeEnum(StrEnum):
13+
LAST_PARTITION = "last_partition"
714

815

916
@dataclass(frozen=True, slots=True)
@@ -13,3 +20,4 @@ class TinybirdConfig:
1320
token: str | None = None
1421
base_url: str | None = None
1522
dev_mode: DevMode | None = None
23+
branch_data_mode: str | None = None

‎src/tinybird_sdk/client/base.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Any, cast
55

66
from ..api.api import TinybirdApi, TinybirdApiError
7-
from ..api.branches import get_or_create_branch
7+
from ..api.branches import CreateBranchOptions, get_or_create_branch
88
from ..cli.config import load_config_async
99
from .preview import get_preview_branch_name, is_preview_environment
1010
from .tokens import TokensNamespace
@@ -159,12 +159,18 @@ def _resolve_branch_context(self) -> ClientContext:
159159
)
160160

161161
branch_name = config["tinybird_branch"]
162+
branch_options = None
163+
branch_value = config.get("branch_data_mode")
164+
if branch_value and config.get("dev_mode") != "local":
165+
branch_options = CreateBranchOptions(branch_data_mode=branch_value)
166+
162167
branch = get_or_create_branch(
163168
{
164169
"base_url": self._config["base_url"],
165170
"token": self._config["token"],
166171
},
167172
branch_name,
173+
options=branch_options,
168174
)
169175

170176
if not branch.get("token"):

0 commit comments

Comments
 (0)