tinybird 0.0.1.dev261__py3-none-any.whl → 0.0.1.dev263__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tinybird might be problematic. Click here for more details.

@@ -0,0 +1,59 @@
1
+ from pydantic_ai import Agent, RunContext, Tool
2
+ from pydantic_ai.messages import ModelMessage
3
+ from pydantic_ai.usage import Usage
4
+
5
+ from tinybird.tb.modules.agent.animations import ThinkingAnimation
6
+ from tinybird.tb.modules.agent.models import create_model
7
+ from tinybird.tb.modules.agent.prompts import tests_files_prompt
8
+ from tinybird.tb.modules.agent.tools.run_command import run_command
9
+ from tinybird.tb.modules.agent.utils import TinybirdAgentContext
10
+ from tinybird.tb.modules.project import Project
11
+
12
+
13
+ class CommandAgent:
14
+ def __init__(
15
+ self,
16
+ token: str,
17
+ user_token: str,
18
+ host: str,
19
+ workspace_id: str,
20
+ project: Project,
21
+ dangerously_skip_permissions: bool,
22
+ prompt_mode: bool,
23
+ thinking_animation: ThinkingAnimation,
24
+ ):
25
+ self.token = token
26
+ self.user_token = user_token
27
+ self.host = host
28
+ self.dangerously_skip_permissions = dangerously_skip_permissions or prompt_mode
29
+ self.project = project
30
+ self.thinking_animation = thinking_animation
31
+ self.messages: list[ModelMessage] = []
32
+ self.agent = Agent(
33
+ model=create_model(user_token, host, workspace_id),
34
+ deps_type=TinybirdAgentContext,
35
+ instructions=[
36
+ """
37
+ You are part of Tinybird Code, an agentic CLI that can help users to work with Tinybird.
38
+ You are a sub-agent of the main Tinybird Code agent. You are responsible for running commands on the user's machine.
39
+ You will be given a task to perform and you will use `run_command` tool to complete it.
40
+ If you do not find a command that can solve the task, just say that there is no command that can solve the task.
41
+ You can run `-h` in every level of the command to get help. E.g. `tb -h`, `tb datasource -h`, `tb datasource ls -h`.
42
+ When you need to access Tinybird Cloud, add the `--cloud` flag. E.g. `tb --cloud datasource ls`.
43
+ Token and host are not required to add to the commands.
44
+ Always run first help commands to be sure that the commands you are running is not interactive.
45
+ """,
46
+ ],
47
+ tools=[
48
+ Tool(run_command, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
49
+ ],
50
+ )
51
+
52
+ @self.agent.instructions
53
+ def get_tests_files(ctx: RunContext[TinybirdAgentContext]) -> str:
54
+ return tests_files_prompt(self.project)
55
+
56
+ def run(self, task: str, deps: TinybirdAgentContext, usage: Usage):
57
+ result = self.agent.run_sync(task, deps=deps, usage=usage, message_history=self.messages)
58
+ self.messages.extend(result.new_messages())
59
+ return result
@@ -16,7 +16,6 @@ from tinybird.prompts import (
16
16
  pipe_instructions,
17
17
  s3_connection_example,
18
18
  sink_pipe_instructions,
19
- test_instructions,
20
19
  )
21
20
  from tinybird.tb.modules.project import Project
22
21
 
@@ -89,10 +88,7 @@ sql_instructions = """
89
88
  - Use node names as table names only when nodes are present in the same file.
90
89
  - Do not reference the current node name in the SQL.
91
90
  - SQL queries only accept SELECT statements with conditions, aggregations, joins, etc.
92
- - Do NOT use CREATE TABLE, INSERT INTO, CREATE DATABASE, SHOW TABLES, etc.
93
- - Use ONLY SELECT statements in the SQL section.
94
- - INSERT INTO is not supported in SQL section.
95
- - Do NOT query system.<table_name> tables.
91
+ - ONLY SELECT statements are allowed in any sql query.
96
92
  - When using functions try always ClickHouse functions first, then SQL functions.
97
93
  - Parameters are never quoted in any case.
98
94
  - Use the following syntax in the SQL section for the iceberg table function: iceberg('s3://bucket/path/to/table', {{tb_secret('aws_access_key_id')}}, {{tb_secret('aws_secret_access_key')}})
@@ -115,7 +111,6 @@ datafile_instructions = """
115
111
  def resources_prompt(project: Project) -> str:
116
112
  files = project.get_project_files()
117
113
  fixture_files = project.get_fixture_files()
118
- test_files = project.get_test_files()
119
114
 
120
115
  resources_content = "# Existing resources in the project:\n"
121
116
  if files:
@@ -148,6 +143,29 @@ def resources_prompt(project: Project) -> str:
148
143
  else:
149
144
  fixture_content += "No fixture files found"
150
145
 
146
+ return resources_content + "\n" + fixture_content
147
+
148
+
149
+ def tests_files_prompt(project: Project) -> str:
150
+ files = project.get_project_files()
151
+ test_files = project.get_test_files()
152
+
153
+ resources_content = "# Existing resources in the project:\n"
154
+ if files:
155
+ resources: list[dict[str, Any]] = []
156
+ for filename in files:
157
+ file_path = Path(filename)
158
+ resource = {
159
+ "path": str(file_path.relative_to(project.folder)),
160
+ "type": get_resource_type(file_path),
161
+ "name": file_path.stem,
162
+ "content": file_path.read_text(),
163
+ }
164
+ resources.append(resource)
165
+ resources_content = format_as_xml(resources, root_tag="resources", item_tag="resource")
166
+ else:
167
+ resources_content += "No resources found"
168
+
151
169
  test_content = "# Test files in the project:\n"
152
170
  if test_files:
153
171
  tests: list[dict[str, Any]] = []
@@ -163,7 +181,7 @@ def resources_prompt(project: Project) -> str:
163
181
  else:
164
182
  test_content += "No test files found"
165
183
 
166
- return resources_content + "\n" + fixture_content + "\n" + test_content
184
+ return resources_content + "\n" + test_content
167
185
 
168
186
 
169
187
  def get_resource_type(path: Path) -> str:
@@ -522,14 +540,62 @@ FROM small_table
522
540
  JOIN (SELECT id, col FROM huge_table WHERE id IN (SELECT id FROM small_table)) filtered
523
541
  ON small_table.id = filtered.id
524
542
  ```
525
- """
526
543
 
544
+ <dev_notes>
545
+ IMPORTANT: DO NOT USE THE FOLLOWING WHEN QUERYING:
546
+ - CREATE TABLE, INSERT INTO, CREATE DATABASE, SHOW TABLES, TRUNCATE TABLE, DELETE FROM, SHOW DATASOURCES, etc. are not allowed.
547
+ - ONLY use SELECT statements.
548
+ - currentDatabase is not allowed.
549
+ - system tables are not allowed: system.tables, system.datasources, information_schema.tables...
550
+ </dev_notes>
551
+ """
527
552
 
528
- agent_system_prompt = f"""
529
- You are a Tinybird Code, an agentic CLI that can help users to work with Tinybird.
530
553
 
531
- You are an interactive CLI tool that helps users with data engineering tasks. Use the instructions below and the tools available to you to assist the user.
554
+ test_instructions = """
555
+ # Working with test files:
556
+ - The test file name must match the name of the pipe it is testing.
557
+ - Every scenario name must be unique inside the test file.
558
+ - When looking for the parameters available, you will find them in the pipe file in the following format: {{{{String(my_param_name, default_value)}}}}.
559
+ - If the resource has no parameters, generate a single test with empty parameters.
560
+ - The format of the parameters is the following: param1=value1&param2=value2&param3=value3
561
+ - If some parameters are provided by the user and you need to use them, preserve in the same format as they were provided, like case sensitive
562
+ - Test as many scenarios as possible.
563
+ - Create tests only when the user explicitly asks for it with prompts like "Create tests for this endpoint" or "Create tests for this pipe".
564
+ - If the user asks for "testing an endpoint" or "call an endpoint", just request to the endpoint.
565
+ - The data that the tests are using is the data provided in the fixtures folder, so do not use `execute_query` or `request_endpoint` tools to analyze the data.
566
+ - MANDATORY: Before creating the test, analyze the fixture files that the tables of the endpoint are using so you can create relevant tests.
567
+ - IMPORTANT: expected_result field should always be an empty string, because it will be filled by the `create_test` tool.
568
+ - If the endpoint does not have parameters, you can omit parameters and generate a single test.
569
+ - The format of the test file is the following:
570
+ <test_file_format>
571
+ - name: kpis_single_day
572
+ description: Test hourly granularity for a single day
573
+ parameters: date_from=2024-01-01&date_to=2024-01-01
574
+ expected_result: ''
575
+
576
+ - name: kpis_date_range
577
+ description: Test daily granularity for a date range
578
+ parameters: date_from=2024-01-01&date_to=2024-01-31
579
+ expected_result: ''
580
+
581
+ - name: kpis_default_range
582
+ description: Test default behavior without date parameters (last 7 days)
583
+ parameters: ''
584
+ expected_result: ''
585
+
586
+ - name: kpis_fixed_time
587
+ description: Test with fixed timestamp for consistent testing
588
+ parameters: fixed_time=2024-01-15T12:00:00
589
+ expected_result: ''
590
+
591
+ - name: kpis_single_day
592
+ description: Test single day with hourly granularity
593
+ parameters: date_from=2024-01-01&date_to=2024-01-01
594
+ expected_result: ''
595
+ </test_file_format>
596
+ """
532
597
 
598
+ tone_and_style_instructions = """
533
599
  # Tone and style
534
600
  You should be concise, direct, and to the point. Maintain a professional tone. Do not use emojis.
535
601
  Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting.
@@ -548,6 +614,15 @@ Do not add additional code explanation summary unless requested by the user. Aft
548
614
 
549
615
  # Code style
550
616
  IMPORTANT: DO NOT ADD ANY COMMENTS unless asked by the user.
617
+ """
618
+
619
+
620
+ agent_system_prompt = f"""
621
+ You are a Tinybird Code, an agentic CLI that can help users to work with Tinybird.
622
+
623
+ You are an interactive CLI tool that helps users with data engineering tasks. Use the instructions below and the tools available to you to assist the user.
624
+
625
+ {tone_and_style_instructions}
551
626
 
552
627
  # Tools
553
628
  You have access to the following tools:
@@ -662,15 +737,11 @@ GCS: {gcs_connection_example}
662
737
  - `DateTime` parameters accept values in format `YYYY-MM-DD HH:MM:SS`
663
738
  - `Date` parameters accept values in format `YYYYMMDD`
664
739
 
665
- # Working with tests:
666
- {test_instructions}
667
- <dev_notes>
668
- - Create tests only when the user explicitly asks for it with prompts like "Create tests for this endpoint" or "Create tests for this pipe".
669
- - If the user asks for "testing an endpoint" or "call an endpoint", just request to the endpoint.
670
- - The data that the tests are using is the data provided in the fixtures folder.
671
- - Querying data or requesting endpoints won't return the data that the tests are using.
672
- - MANDATORY: Before creating the test, analyze the fixture files that the tables of the endpoint are using so you can create relevant tests.
673
- </dev_notes>
740
+ # Working with test files:
741
+ - Use `manage_tests` tool to create, update or run tests.
742
+
743
+ # Working with commands:
744
+ - If you dont have a tool that can solve the task, use `run_command` tool to check if the task can be solved with a normal tinybird cli command.
674
745
 
675
746
  # Info
676
747
  Today is {datetime.now().strftime("%Y-%m-%d")}
@@ -0,0 +1,62 @@
1
+ from pydantic_ai import Agent, RunContext, Tool
2
+ from pydantic_ai.messages import ModelMessage
3
+ from pydantic_ai.usage import Usage
4
+
5
+ from tinybird.tb.modules.agent.animations import ThinkingAnimation
6
+ from tinybird.tb.modules.agent.models import create_model
7
+ from tinybird.tb.modules.agent.prompts import test_instructions, tests_files_prompt, tone_and_style_instructions
8
+ from tinybird.tb.modules.agent.tools.test import create_tests as create_tests_tool
9
+ from tinybird.tb.modules.agent.tools.test import run_tests as run_tests_tool
10
+ from tinybird.tb.modules.agent.utils import TinybirdAgentContext
11
+ from tinybird.tb.modules.project import Project
12
+
13
+
14
+ class TestingAgent:
15
+ def __init__(
16
+ self,
17
+ token: str,
18
+ user_token: str,
19
+ host: str,
20
+ workspace_id: str,
21
+ project: Project,
22
+ dangerously_skip_permissions: bool,
23
+ prompt_mode: bool,
24
+ thinking_animation: ThinkingAnimation,
25
+ ):
26
+ self.token = token
27
+ self.user_token = user_token
28
+ self.host = host
29
+ self.dangerously_skip_permissions = dangerously_skip_permissions or prompt_mode
30
+ self.project = project
31
+ self.thinking_animation = thinking_animation
32
+ self.messages: list[ModelMessage] = []
33
+ self.agent = Agent(
34
+ model=create_model(user_token, host, workspace_id),
35
+ deps_type=TinybirdAgentContext,
36
+ instructions=[
37
+ """
38
+ You are part of Tinybird Code, an agentic CLI that can help users to work with Tinybird.
39
+ You are a sub-agent of the main Tinybird Code agent. You are responsible for managing test files.
40
+ You can do the following:
41
+ - Create new test files.
42
+ - Update existing test files.
43
+ - Run tests.
44
+ """,
45
+ tone_and_style_instructions,
46
+ test_instructions,
47
+ ],
48
+ tools=[
49
+ Tool(create_tests_tool, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
50
+ Tool(run_tests_tool, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
51
+ ],
52
+ )
53
+
54
+ @self.agent.instructions
55
+ def get_tests_files(ctx: RunContext[TinybirdAgentContext]) -> str:
56
+ return tests_files_prompt(self.project)
57
+
58
+ def run(self, task: str, deps: TinybirdAgentContext, usage: Usage):
59
+ result = self.agent.run_sync(task, deps=deps, usage=usage, message_history=self.messages)
60
+ new_messages = result.new_messages()
61
+ self.messages.extend(new_messages)
62
+ return result
@@ -56,7 +56,7 @@ def create_datafile(ctx: RunContext[TinybirdAgentContext], resource: Datafile) -
56
56
  action_text = "created" if not exists else "updated"
57
57
  click.echo(FeedbackManager.success(message=f"✓ {resource.pathname} {action_text}"))
58
58
  ctx.deps.thinking_animation.start()
59
- return f"{action_text} {resource.pathname}"
59
+ return f"{action_text} {resource.pathname}. Project built successfully."
60
60
  except AgentRunCancelled as e:
61
61
  raise e
62
62
  except CLIBuildException as e:
@@ -6,6 +6,20 @@ from tinybird.tb.modules.agent.utils import TinybirdAgentContext
6
6
  from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_pretty_table
7
7
  from tinybird.tb.modules.feedback_manager import FeedbackManager
8
8
 
9
+ forbidden_commands = [
10
+ "currentDatabase()",
11
+ "create table",
12
+ "insert into",
13
+ "create database",
14
+ "show tables",
15
+ "show datasources",
16
+ "truncate table",
17
+ "delete from",
18
+ "system.tables",
19
+ "system.datasources",
20
+ "information_schema.tables",
21
+ ]
22
+
9
23
 
10
24
  def execute_query(ctx: RunContext[TinybirdAgentContext], query: str, task: str, cloud: bool = False):
11
25
  """Execute a query:
@@ -19,6 +33,10 @@ def execute_query(ctx: RunContext[TinybirdAgentContext], query: str, task: str,
19
33
  str: The result of the query.
20
34
  """
21
35
  try:
36
+ for forbidden_command in forbidden_commands:
37
+ if forbidden_command in query.lower():
38
+ return f"Error executing query: {forbidden_command} is not allowed."
39
+
22
40
  cloud_or_local = "cloud" if cloud else "local"
23
41
  ctx.deps.thinking_animation.stop()
24
42
 
@@ -0,0 +1,38 @@
1
+ import subprocess
2
+
3
+ import click
4
+ from pydantic_ai import RunContext
5
+
6
+ from tinybird.tb.modules.agent.utils import AgentRunCancelled, TinybirdAgentContext, show_confirmation, show_input
7
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
8
+
9
+
10
+ def run_command(ctx: RunContext[TinybirdAgentContext], command: str):
11
+ """Run a tinybird CLI command with the given arguments
12
+
13
+ Args:
14
+ command (str): The command to run. Required. Examples: `tb --local sql "select 1"`, `tb --cloud datasource ls`, `tb --help`
15
+ """
16
+ try:
17
+ ctx.deps.thinking_animation.stop()
18
+ confirmation = show_confirmation(
19
+ title=f"Run command: {command}?", skip_confirmation=ctx.deps.dangerously_skip_permissions
20
+ )
21
+
22
+ if confirmation == "review":
23
+ feedback = show_input(ctx.deps.workspace_name)
24
+ ctx.deps.thinking_animation.start()
25
+ return f"User did not confirm the command and gave the following feedback: {feedback}"
26
+
27
+ click.echo(FeedbackManager.highlight(message=f"» Running command: {command}"))
28
+ command = command.replace("tb", "tb --no-version-warning")
29
+ result = subprocess.run(command, shell=True, capture_output=True, text=True)
30
+ click.echo(result.stdout)
31
+ ctx.deps.thinking_animation.start()
32
+ return result.stdout
33
+ except AgentRunCancelled as e:
34
+ raise e
35
+ except Exception as e:
36
+ click.echo(FeedbackManager.error(message=f"Error running command: {e}"))
37
+ ctx.deps.thinking_animation.start()
38
+ return f"Error running command: {e}"
@@ -11,7 +11,9 @@ from tinybird.tb.modules.agent.utils import (
11
11
  show_confirmation,
12
12
  show_input,
13
13
  )
14
+ from tinybird.tb.modules.exceptions import CLIBuildException
14
15
  from tinybird.tb.modules.feedback_manager import FeedbackManager
16
+ from tinybird.tb.modules.test_common import dump_tests, parse_tests
15
17
 
16
18
 
17
19
  def create_tests(ctx: RunContext[TinybirdAgentContext], pipe_name: str, test_content: str) -> str:
@@ -27,11 +29,32 @@ def create_tests(ctx: RunContext[TinybirdAgentContext], pipe_name: str, test_con
27
29
  running_tests = False
28
30
  try:
29
31
  ctx.deps.thinking_animation.stop()
32
+ ctx.deps.build_project_test(silent=True)
30
33
  path = Path(ctx.deps.folder) / "tests" / f"{pipe_name}.yaml"
31
34
  current_test_content: Optional[str] = None
32
35
  if path.exists():
33
36
  current_test_content = path.read_text()
34
37
 
38
+ pipe_tests_content = parse_tests(test_content)
39
+ for test in pipe_tests_content:
40
+ test_params = test["parameters"].split("?")[1] if "?" in test["parameters"] else test["parameters"]
41
+ response = None
42
+ try:
43
+ response = ctx.deps.get_pipe_data_test(pipe_name=pipe_name, test_params=test_params)
44
+ except Exception:
45
+ continue
46
+
47
+ if response.status_code >= 400:
48
+ test["expected_http_status"] = response.status_code
49
+ test["expected_result"] = response.json()["error"]
50
+ else:
51
+ if "expected_http_status" in test:
52
+ del test["expected_http_status"]
53
+
54
+ test["expected_result"] = response.text or ""
55
+
56
+ test_content = dump_tests(pipe_tests_content)
57
+
35
58
  if current_test_content:
36
59
  content = create_terminal_box(current_test_content, new_content=test_content, title=path.name)
37
60
  else:
@@ -107,6 +130,11 @@ def run_tests(ctx: RunContext[TinybirdAgentContext], pipe_name: Optional[str] =
107
130
  return f"All tests in the project ran successfully\n{test_output}"
108
131
  except AgentRunCancelled as e:
109
132
  raise e
133
+ except CLIBuildException as e:
134
+ ctx.deps.thinking_animation.stop()
135
+ click.echo(FeedbackManager.error(message=e))
136
+ ctx.deps.thinking_animation.start()
137
+ return f"Error building project: {e}"
110
138
  except Exception as e:
111
139
  error_message = str(e)
112
140
  test_exit_code = "test_error__error__"
@@ -20,6 +20,7 @@ from prompt_toolkit.shortcuts import PromptSession
20
20
  from prompt_toolkit.styles import Style as PromptStyle
21
21
  from pydantic import BaseModel, Field
22
22
  from pydantic_ai import RunContext
23
+ from requests import Response
23
24
 
24
25
  from tinybird.tb.modules.agent.memory import load_history
25
26
  from tinybird.tb.modules.feedback_manager import FeedbackManager
@@ -40,6 +41,7 @@ class TinybirdAgentContext(BaseModel):
40
41
  get_project_files: Callable[[], List[str]]
41
42
  explore_data: Callable[[str], str]
42
43
  build_project: Callable[..., None]
44
+ build_project_test: Callable[..., None]
43
45
  deploy_project: Callable[[], None]
44
46
  deploy_check_project: Callable[[], None]
45
47
  mock_data: Callable[..., list[dict[str, Any]]]
@@ -50,6 +52,7 @@ class TinybirdAgentContext(BaseModel):
50
52
  execute_query_local: Callable[..., dict[str, Any]]
51
53
  request_endpoint_cloud: Callable[..., dict[str, Any]]
52
54
  request_endpoint_local: Callable[..., dict[str, Any]]
55
+ get_pipe_data_test: Callable[..., Response]
53
56
  get_datasource_datafile_cloud: Callable[..., str]
54
57
  get_datasource_datafile_local: Callable[..., str]
55
58
  get_pipe_datafile_cloud: Callable[..., str]
@@ -714,13 +717,15 @@ class AgentRunCancelled(Exception):
714
717
  pass
715
718
 
716
719
 
717
- def show_confirmation(title: str, skip_confirmation: bool = False) -> ConfirmationResult:
720
+ def show_confirmation(title: str, skip_confirmation: bool = False, show_review: bool = True) -> ConfirmationResult:
718
721
  if skip_confirmation:
719
722
  return "yes"
720
723
 
721
724
  while True:
722
725
  result = show_options(
723
- options=["Yes, continue", "No, tell Tinybird Code what to do", "Cancel"],
726
+ options=["Yes, continue", "No, tell Tinybird Code what to do", "Cancel"]
727
+ if show_review
728
+ else ["Yes, continue", "Cancel"],
724
729
  title=title,
725
730
  )
726
731
 
@@ -399,21 +399,23 @@ def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, staging
399
399
  command_always_test = ["test"]
400
400
 
401
401
  if (
402
- show_warnings
403
- and (cloud or command in commands_always_cloud)
402
+ (cloud or command in commands_always_cloud)
404
403
  and command not in commands_always_local
405
404
  and command not in command_always_test
406
405
  ):
407
- click.echo(
408
- FeedbackManager.gray(message=f"Running against Tinybird Cloud: Workspace {config.get('name', 'default')}")
409
- )
406
+ if show_warnings:
407
+ click.echo(
408
+ FeedbackManager.gray(
409
+ message=f"Running against Tinybird Cloud: Workspace {config.get('name', 'default')}"
410
+ )
411
+ )
410
412
 
411
413
  method = None
412
414
  if ctx.params.get("token"):
413
415
  method = "token via --token option"
414
416
  elif os.environ.get("TB_TOKEN"):
415
417
  method = "token from TB_TOKEN environment variable"
416
- if method:
418
+ if method and show_warnings:
417
419
  click.echo(FeedbackManager.gray(message=f"Authentication method: {method}"))
418
420
 
419
421
  return _get_tb_client(config.get("token", ""), config["host"], staging=staging)
@@ -95,7 +95,7 @@ def datasource_ls(ctx: Context, match: Optional[str], format_: str):
95
95
  shared_from,
96
96
  name,
97
97
  humanfriendly.format_number(stats.get("row_count")) if stats.get("row_count", None) else "-",
98
- humanfriendly.format_size(int(stats.get("bytes"))) if stats.get("bytes", None) else "-",
98
+ humanfriendly.format_size(int(stats.get("bytes", 0))) if stats.get("bytes", None) else "-",
99
99
  t["created_at"][:-7],
100
100
  t["updated_at"][:-7],
101
101
  t.get("service", ""),
@@ -455,6 +455,8 @@ def datasource_truncate(ctx, datasource_name, yes, cascade):
455
455
  except Exception as e:
456
456
  raise CLIDatasourceException(FeedbackManager.error_exception(error=e))
457
457
  click.echo(FeedbackManager.success_truncate_datasource(datasource=cascade_ds))
458
+ else:
459
+ click.echo(FeedbackManager.info(message="Operation cancelled by user"))
458
460
 
459
461
 
460
462
  @datasource.command(name="delete")
@@ -188,14 +188,27 @@ def deployment_group() -> None:
188
188
  default=None,
189
189
  help="URL of the template to use for the deployment. Example: https://github.com/tinybirdco/web-analytics-starter-kit/tree/main/tinybird",
190
190
  )
191
+ @click.option(
192
+ "-v",
193
+ "--verbose",
194
+ is_flag=True,
195
+ default=False,
196
+ help="Show verbose output. Disabled by default.",
197
+ )
191
198
  @click.pass_context
192
199
  def deployment_create(
193
- ctx: click.Context, wait: bool, auto: bool, check: bool, allow_destructive_operations: bool, template: Optional[str]
200
+ ctx: click.Context,
201
+ wait: bool,
202
+ auto: bool,
203
+ check: bool,
204
+ allow_destructive_operations: bool,
205
+ template: Optional[str],
206
+ verbose: bool,
194
207
  ) -> None:
195
208
  """
196
209
  Validate and deploy the project server side.
197
210
  """
198
- create_deployment_cmd(ctx, wait, auto, check, allow_destructive_operations, template)
211
+ create_deployment_cmd(ctx, wait, auto, check, allow_destructive_operations, template, verbose)
199
212
 
200
213
 
201
214
  @deployment_group.command(name="ls")
@@ -337,14 +350,27 @@ def deployment_discard(ctx: click.Context, wait: bool) -> None:
337
350
  default=None,
338
351
  help="URL of the template to use for the deployment. Example: https://github.com/tinybirdco/web-analytics-starter-kit/tree/main/tinybird",
339
352
  )
353
+ @click.option(
354
+ "-v",
355
+ "--verbose",
356
+ is_flag=True,
357
+ default=False,
358
+ help="Show verbose output. Disabled by default.",
359
+ )
340
360
  @click.pass_context
341
361
  def deploy(
342
- ctx: click.Context, wait: bool, auto: bool, check: bool, allow_destructive_operations: bool, template: Optional[str]
362
+ ctx: click.Context,
363
+ wait: bool,
364
+ auto: bool,
365
+ check: bool,
366
+ allow_destructive_operations: bool,
367
+ template: Optional[str],
368
+ verbose: bool,
343
369
  ) -> None:
344
370
  """
345
371
  Deploy the project.
346
372
  """
347
- create_deployment_cmd(ctx, wait, auto, check, allow_destructive_operations, template)
373
+ create_deployment_cmd(ctx, wait, auto, check, allow_destructive_operations, template, verbose)
348
374
 
349
375
 
350
376
  def create_deployment_cmd(
@@ -354,6 +380,7 @@ def create_deployment_cmd(
354
380
  check: Optional[bool] = None,
355
381
  allow_destructive_operations: Optional[bool] = None,
356
382
  template: Optional[str] = None,
383
+ verbose: bool = False,
357
384
  ) -> None:
358
385
  project: Project = ctx.ensure_object(dict)["project"]
359
386
  if template:
@@ -378,4 +405,4 @@ def create_deployment_cmd(
378
405
  click.echo(FeedbackManager.success(message="Template downloaded successfully"))
379
406
  client = ctx.ensure_object(dict)["client"]
380
407
  config: Dict[str, Any] = ctx.ensure_object(dict)["config"]
381
- create_deployment(project, client, config, wait, auto, check, allow_destructive_operations)
408
+ create_deployment(project, client, config, wait, auto, verbose, check, allow_destructive_operations)
@@ -194,6 +194,7 @@ def create_deployment(
194
194
  config: Dict[str, Any],
195
195
  wait: bool,
196
196
  auto: bool,
197
+ verbose: bool = False,
197
198
  check: Optional[bool] = None,
198
199
  allow_destructive_operations: Optional[bool] = None,
199
200
  ) -> None:
@@ -247,9 +248,12 @@ def create_deployment(
247
248
  if f.get("level", "").upper() == "ERROR":
248
249
  feedback_func = FeedbackManager.error
249
250
  feedback_icon = ""
250
- else:
251
+ elif f.get("level", "").upper() == "WARNING":
251
252
  feedback_func = FeedbackManager.warning
252
253
  feedback_icon = "△ "
254
+ elif verbose and f.get("level", "").upper() == "INFO":
255
+ feedback_func = FeedbackManager.info
256
+ feedback_icon = ""
253
257
  resource = f.get("resource")
254
258
  resource_bit = f"{resource}: " if resource else ""
255
259
  click.echo(feedback_func(message=f"{feedback_icon}{f.get('level')}: {resource_bit}{f.get('message')}"))
@@ -133,6 +133,15 @@ def create_test(
133
133
  return tests
134
134
 
135
135
 
136
+ def parse_tests(tests_content: str) -> List[Dict[str, Any]]:
137
+ return yaml.safe_load(tests_content)
138
+
139
+
140
+ def dump_tests(tests: List[Dict[str, Any]]) -> str:
141
+ yaml_str = yaml.safe_dump(tests, sort_keys=False)
142
+ return yaml_str.replace("- name:", "\n- name:")
143
+
144
+
136
145
  def update_test(pipe: str, project: Project, client: TinyB) -> None:
137
146
  try:
138
147
  folder = project.folder
@@ -149,7 +158,7 @@ def update_test(pipe: str, project: Project, client: TinyB) -> None:
149
158
 
150
159
  click.echo(FeedbackManager.highlight(message=f"\n» Updating tests expectations for {pipe_name} endpoint..."))
151
160
  pipe_tests_path = Path(project.folder) / pipe_tests_path
152
- pipe_tests_content = yaml.safe_load(pipe_tests_path.read_text())
161
+ pipe_tests_content = parse_tests(pipe_tests_path.read_text())
153
162
  for test in pipe_tests_content:
154
163
  test_params = test["parameters"].split("?")[1] if "?" in test["parameters"] else test["parameters"]
155
164
  response = None
@@ -197,7 +206,7 @@ def run_tests(name: Tuple[str, ...], project: Project, client: TinyB) -> None:
197
206
  def run_test(test_file) -> Optional[str]:
198
207
  test_file_path = Path(test_file)
199
208
  click.echo(FeedbackManager.info(message=f"* {test_file_path.stem}{test_file_path.suffix}"))
200
- test_file_content = yaml.safe_load(test_file_path.read_text())
209
+ test_file_content = parse_tests(test_file_path.read_text())
201
210
  test_file_errors = ""
202
211
  for test in test_file_content:
203
212
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev261
3
+ Version: 0.0.1.dev263
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird