Skip to content

Commit 154e0e9

Browse files
committed
fix(cli): keep migrate local and delegate duplicated commands
1 parent 87a5107 commit 154e0e9

4 files changed

Lines changed: 119 additions & 53 deletions

File tree

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,8 @@
1-
from .branch import run_branch_list, run_branch_status, run_branch_delete, run_branch_list_cached
2-
from .build import run_build
3-
from .clear import run_clear
4-
from .deploy import run_deploy
5-
from .dev import run_dev
61
from .generate import run_generate, runGenerate
7-
from .info import run_info
8-
from .init import run_init
9-
from .login import run_login
102
from .migrate import run_migrate
11-
from .open_dashboard import run_open_dashboard
12-
from .preview import run_preview, generate_preview_branch_name
13-
from .pull import run_pull
143

154
__all__ = [
16-
"run_branch_list",
17-
"run_branch_status",
18-
"run_branch_delete",
19-
"run_branch_list_cached",
20-
"run_build",
21-
"run_clear",
22-
"run_deploy",
23-
"run_dev",
245
"run_generate",
256
"runGenerate",
26-
"run_info",
27-
"run_init",
28-
"run_login",
297
"run_migrate",
30-
"run_open_dashboard",
31-
"run_preview",
32-
"generate_preview_branch_name",
33-
"run_pull",
348
]

src/tinybird_sdk/cli/index.py

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77

88
from .commands.generate import run_generate
9+
from .commands.migrate import run_migrate
910
from .output import output
1011

1112

@@ -47,45 +48,91 @@ def create_cli() -> argparse.ArgumentParser:
4748
generate_cmd.add_argument("--json", action="store_true")
4849
generate_cmd.add_argument("-o", "--output-dir")
4950

51+
migrate_cmd = sub.add_parser("migrate", help="Migrate Tinybird .datasource/.pipe files to Python resources")
52+
migrate_cmd.add_argument("patterns", nargs="+", help="Files, directories, or glob patterns to migrate")
53+
migrate_cmd.add_argument("--cwd", help="Working directory to resolve patterns from")
54+
migrate_cmd.add_argument("-o", "--out", help="Output file path for the generated migration module")
55+
migrate_cmd.add_argument("--dry-run", action="store_true", help="Generate output without writing files")
56+
migrate_cmd.add_argument("--force", action="store_true", help="Overwrite existing output file when needed")
57+
migrate_cmd.add_argument(
58+
"--strict",
59+
action=argparse.BooleanOptionalAction,
60+
default=True,
61+
help="Fail on migration issues (disable with --no-strict)",
62+
)
63+
migrate_cmd.add_argument("--json", action="store_true", help="Print migration result as JSON")
64+
5065
return parser
5166

5267

5368
def main(argv: list[str] | None = None) -> int:
5469
normalized_argv = list(argv) if argv is not None else list(sys.argv[1:])
5570

56-
# `generate` is owned by the SDK; all other commands are delegated to Tinybird CLI.
57-
if not normalized_argv or normalized_argv[0] != "generate":
71+
# SDK-owned commands stay local; all other commands are delegated to Tinybird CLI.
72+
if not normalized_argv or normalized_argv[0] not in {"generate", "migrate"}:
5873
return _run_installed_tinybird_cli(normalized_argv)
5974

6075
parser = create_cli()
6176
args = parser.parse_args(normalized_argv)
6277

63-
result = run_generate({"output_dir": args.output_dir})
64-
if not result.success:
65-
output.error(result.error or "Generate failed")
66-
return 1
78+
if args.command == "generate":
79+
result = run_generate({"output_dir": args.output_dir})
80+
if not result.success:
81+
output.error(result.error or "Generate failed")
82+
return 1
83+
84+
if args.json:
85+
_print_json(asdict(result))
86+
return 0
87+
88+
stats = result.stats or {
89+
"datasource_count": 0,
90+
"pipe_count": 0,
91+
"connection_count": 0,
92+
"total_count": 0,
93+
}
94+
print(
95+
"Generated "
96+
f"{stats['total_count']} resources "
97+
f"({stats['datasource_count']} datasources, "
98+
f"{stats['pipe_count']} pipes, "
99+
f"{stats['connection_count']} connections)"
100+
)
101+
if result.output_dir:
102+
print(f"Written to: {result.output_dir}")
103+
print(f"Completed in {output.format_duration(result.duration_ms)}")
104+
return 0
105+
106+
result = run_migrate(
107+
{
108+
"cwd": args.cwd,
109+
"patterns": args.patterns,
110+
"out": args.out,
111+
"strict": args.strict,
112+
"dry_run": args.dry_run,
113+
"force": args.force,
114+
}
115+
)
67116

68117
if args.json:
69-
_print_json(asdict(result))
118+
_print_json(result)
119+
return 0 if result["success"] else 1
120+
121+
if result["success"]:
122+
migrated_count = len(result.get("migrated") or [])
123+
print(f"Migrated {migrated_count} resources")
124+
if result.get("output_path"):
125+
print(f"Written to: {result['output_path']}")
70126
return 0
71127

72-
stats = result.stats or {
73-
"datasource_count": 0,
74-
"pipe_count": 0,
75-
"connection_count": 0,
76-
"total_count": 0,
77-
}
78-
print(
79-
"Generated "
80-
f"{stats['total_count']} resources "
81-
f"({stats['datasource_count']} datasources, "
82-
f"{stats['pipe_count']} pipes, "
83-
f"{stats['connection_count']} connections)"
84-
)
85-
if result.output_dir:
86-
print(f"Written to: {result.output_dir}")
87-
print(f"Completed in {output.format_duration(result.duration_ms)}")
88-
return 0
128+
errors = result.get("errors") or []
129+
if errors:
130+
output.error(f"Migrate failed with {len(errors)} error(s)")
131+
for error in errors:
132+
output.error(str(error))
133+
else:
134+
output.error("Migrate failed")
135+
return 1
89136

90137

91138
if __name__ == "__main__":

src/tinybird_sdk/migrate/runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def run_migrate(options: MigrateOptions | dict[str, Any]) -> MigrationResult:
242242
resource_kind=pipe.kind,
243243
message=(
244244
f'Materialized pipe references missing/unmigrated datasource '
245-
f'"{pipe.materialized_datasource or '(none)'}".'
245+
f'"{pipe.materialized_datasource or "(none)"}".'
246246
),
247247
)
248248
)
@@ -258,7 +258,7 @@ def run_migrate(options: MigrateOptions | dict[str, Any]) -> MigrationResult:
258258
resource_kind=pipe.kind,
259259
message=(
260260
f'Copy pipe references missing/unmigrated datasource '
261-
f'"{pipe.copy_target_datasource or '(none)'}".'
261+
f'"{pipe.copy_target_datasource or "(none)"}".'
262262
),
263263
)
264264
)

tests/test_cli_entrypoint.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,18 @@ def fake_import(name: str, *args, **kwargs):
5656
assert cli_index._run_installed_tinybird_cli(["build"]) == 1
5757

5858

59-
def test_cli_entrypoint_delegates_non_generate_commands(monkeypatch: pytest.MonkeyPatch) -> None:
59+
def test_cli_entrypoint_delegates_non_sdk_commands(monkeypatch: pytest.MonkeyPatch) -> None:
6060
monkeypatch.setattr(cli_index, "_run_installed_tinybird_cli", lambda argv: 7 if argv == ["build", "--dry-run"] else 1)
6161
monkeypatch.setattr(
6262
cli_index,
6363
"run_generate",
6464
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("generate should not run")),
6565
)
66+
monkeypatch.setattr(
67+
cli_index,
68+
"run_migrate",
69+
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("migrate should not run")),
70+
)
6671
assert cli_index.main(["build", "--dry-run"]) == 7
6772

6873

@@ -101,6 +106,31 @@ def test_cli_entrypoint_runs_generate_locally(monkeypatch: pytest.MonkeyPatch, c
101106
assert "Generated 2 resources (1 datasources, 1 pipes, 0 connections)" in out
102107

103108

109+
def test_cli_entrypoint_runs_migrate_locally(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
110+
monkeypatch.setattr(
111+
cli_index,
112+
"_run_installed_tinybird_cli",
113+
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("should not delegate migrate")),
114+
)
115+
monkeypatch.setattr(
116+
cli_index,
117+
"run_migrate",
118+
lambda *_args, **_kwargs: {
119+
"success": True,
120+
"output_path": "/tmp/tinybird.migration.py",
121+
"migrated": ["a", "b"],
122+
"errors": [],
123+
"dry_run": False,
124+
"output_content": None,
125+
},
126+
)
127+
128+
assert cli_index.main(["migrate", "legacy.datasource"]) == 0
129+
out = capsys.readouterr().out
130+
assert "Migrated 2 resources" in out
131+
assert "Written to: /tmp/tinybird.migration.py" in out
132+
133+
104134
def test_cli_entrypoint_runs_generate_json_output(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
105135
monkeypatch.setattr(
106136
cli_index,
@@ -141,3 +171,18 @@ def test_cli_entrypoint_generate_failure_returns_error(monkeypatch: pytest.Monke
141171
lambda *_args, **_kwargs: SimpleNamespace(success=False, error="boom", duration_ms=1),
142172
)
143173
assert cli_index.main(["generate"]) == 1
174+
175+
176+
def test_cli_entrypoint_migrate_failure_returns_error(monkeypatch: pytest.MonkeyPatch) -> None:
177+
_mute_output(monkeypatch)
178+
monkeypatch.setattr(
179+
cli_index,
180+
"_run_installed_tinybird_cli",
181+
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("should not delegate migrate")),
182+
)
183+
monkeypatch.setattr(
184+
cli_index,
185+
"run_migrate",
186+
lambda *_args, **_kwargs: {"success": False, "errors": ["boom"]},
187+
)
188+
assert cli_index.main(["migrate", "legacy.datasource"]) == 1

0 commit comments

Comments
 (0)