tinybird 0.0.1.dev262__py3-none-any.whl → 0.0.1.dev264__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.

@@ -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")
@@ -349,12 +349,31 @@ def create_deployment(
349
349
  promote_deployment(client.host, HEADERS, wait=wait)
350
350
 
351
351
 
352
+ def _build_data_movement_message(kind: str, source_mv_name: Optional[str]) -> str:
353
+ if kind == "backfill_with_mv_queries":
354
+ return f"Using Materialized Pipe {source_mv_name or ''}"
355
+ elif kind == "backfill_with_forward_query":
356
+ return "From live deployment using Forward Query"
357
+ else:
358
+ return ""
359
+
360
+
352
361
  def print_changes(result: dict, project: Project) -> None:
353
362
  deployment = result.get("deployment", {})
354
363
  resources_columns = ["status", "name", "type", "path"]
355
364
  resources: list[list[Union[str, None]]] = []
356
365
  tokens_columns = ["Change", "Token name", "Added permissions", "Removed permissions"]
357
366
  tokens: list[Tuple[str, str, str, str]] = []
367
+ data_movements_columns = ["Datasource", "Backfill type"]
368
+ data_movements = deployment.get("data_movements")
369
+ if data_movements is not None:
370
+ data_movements = [
371
+ (
372
+ dm.get("datasource_name"),
373
+ _build_data_movement_message(dm.get("kind"), dm.get("source_mv_name")),
374
+ )
375
+ for dm in data_movements
376
+ ]
358
377
 
359
378
  for ds in deployment.get("new_datasource_names", []):
360
379
  resources.append(["new", ds, "datasource", project.get_resource_path(ds, "datasource")])
@@ -415,3 +434,9 @@ def print_changes(result: dict, project: Project) -> None:
415
434
  echo_safe_humanfriendly_tables_format_smart_table(tokens, column_names=tokens_columns)
416
435
  else:
417
436
  click.echo(FeedbackManager.gray(message="* No changes in tokens to be deployed"))
437
+ if data_movements is not None:
438
+ if data_movements:
439
+ click.echo(FeedbackManager.info(message="\n Data that will be copied with this deployment:"))
440
+ echo_safe_humanfriendly_tables_format_smart_table(data_movements, column_names=data_movements_columns)
441
+ else:
442
+ click.echo(FeedbackManager.gray(message="* No data will be copied with this deployment"))
@@ -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.dev262
3
+ Version: 0.0.1.dev264
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -12,12 +12,12 @@ tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
12
12
  tinybird/tornado_template.py,sha256=jjNVDMnkYFWXflmT8KU_Ssbo5vR8KQq3EJMk5vYgXRw,41959
13
13
  tinybird/ch_utils/constants.py,sha256=fPgZtwbr1ymxaW7uqVWHKmAbt7uGj3SxCCS3xsEMJqA,4151
14
14
  tinybird/ch_utils/engine.py,sha256=4X1B-iuhdW_mxKnX_m3iCsxgP9RPVgR75g7yH1vsJ6A,40851
15
- tinybird/datafile/common.py,sha256=Xd_abjiTAK1oGxg2T59rLePNEdGMwSqe8PnoWEBJLyk,99161
15
+ tinybird/datafile/common.py,sha256=lN-fNCaxtFWQAd0WN5Q-mgljM7F8sYOBQcU1JJogw1g,105324
16
16
  tinybird/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
17
17
  tinybird/datafile/parse_connection.py,sha256=tRyn2Rpr1TeWet5BXmMoQgaotbGdYep1qiTak_OqC5E,1825
18
18
  tinybird/datafile/parse_datasource.py,sha256=ssW8QeFSgglVFi3sDZj_HgkJiTJ2069v2JgqnH3CkDE,1825
19
19
  tinybird/datafile/parse_pipe.py,sha256=xf4m0Tw44QWJzHzAm7Z7FwUoUUtr7noMYjU1NiWnX0k,3880
20
- tinybird/tb/__cli__.py,sha256=0prVU9s5VBR_0ep0okT2MF0GMOH7JG72BdEplvxz_Mg,247
20
+ tinybird/tb/__cli__.py,sha256=1Q1Mk8PoldzfuJEf_f-VHn7VSe0wfdgeKCPK_RcPvag,247
21
21
  tinybird/tb/check_pypi.py,sha256=Gp0HkHHDFMSDL6nxKlOY51z7z1Uv-2LRexNTZSHHGmM,552
22
22
  tinybird/tb/cli.py,sha256=FdDFEIayjmsZEVsVSSvRiVYn_FHOVg_zWQzchnzfWho,1008
23
23
  tinybird/tb/client.py,sha256=pJbdkWMXGAqKseNAvdsRRnl_c7I-DCMB0dWCQnG82nU,54146
@@ -25,15 +25,15 @@ tinybird/tb/config.py,sha256=mhMTGnMB5KcxGoh3dewIr2Jjsa6pHE183gCPAQWyp6o,3973
25
25
  tinybird/tb/modules/build.py,sha256=efD-vamK1NPaDo9R86Hn8be2DYoW0Hh5bZiH7knK5dk,7790
26
26
  tinybird/tb/modules/build_common.py,sha256=dthlaDn_CuwZnedQcUi7iIdDoHWfSbbbGQwiDgNcmC0,13062
27
27
  tinybird/tb/modules/cicd.py,sha256=0KLKccha9IP749QvlXBmzdWv1On3mFwMY4DUcJlBxiE,7326
28
- tinybird/tb/modules/cli.py,sha256=baeNPOu72L4aGI6ELsY8e2_eCq8ILOhTfd4qysz3KXk,16910
28
+ tinybird/tb/modules/cli.py,sha256=qZIp8Ez4sunQ3_lIj1QGc3yOp8-SYfNHASHpE_7Ji6Y,16978
29
29
  tinybird/tb/modules/common.py,sha256=tj6DR2yOqMMQ0PILwFGXmMogxdrbQCgj36HdSM611rs,82657
30
30
  tinybird/tb/modules/config.py,sha256=gK7rgaWTDd4ZKCrNEg_Uemr26EQjqWt6TjyQKujxOws,11462
31
31
  tinybird/tb/modules/connection.py,sha256=-MY56NUAai6EMC4-wpi7bT0_nz_SA8QzTmHkV7HB1IQ,17810
32
32
  tinybird/tb/modules/copy.py,sha256=dPZkcIDvxjJrlQUIvToO0vsEEEs4EYumbNV77-BzNoU,4404
33
33
  tinybird/tb/modules/create.py,sha256=pJxHXG69c9Z_21s-7VuJ3RZOF_nJU51LEwiAkvI3dZY,23251
34
- tinybird/tb/modules/datasource.py,sha256=cxq0VVjjidxq-v_JSIIAH7L90XNRctgNKsHRoQ_42OI,41632
34
+ tinybird/tb/modules/datasource.py,sha256=pae-ENeHYIF1HHYRSOziFC-2FPLUFa0KS60YpdlKCS8,41725
35
35
  tinybird/tb/modules/deployment.py,sha256=EDEVOqFk5fp0fvvs2jV0mT5POFd-i_8uZIUREwzAbEk,13199
36
- tinybird/tb/modules/deployment_common.py,sha256=CswUMfp5dW0NgiuP_ZGORqvqooHFYU2ZJaxOcpRFDT0,17866
36
+ tinybird/tb/modules/deployment_common.py,sha256=g-2wnR63tlxmTObs6xRjZRgOOaX0fFn3Jq4x21qI6aw,18975
37
37
  tinybird/tb/modules/deprecations.py,sha256=rrszC1f_JJeJ8mUxGoCxckQTJFBCR8wREf4XXXN-PRc,4507
38
38
  tinybird/tb/modules/dev_server.py,sha256=57FCKuWpErwYUYgHspYDkLWEm9F4pbvVOtMrFXX1fVU,10129
39
39
  tinybird/tb/modules/endpoint.py,sha256=ksRj6mfDb9Xv63PhTkV_uKSosgysHElqagg3RTt21Do,11958
@@ -63,28 +63,30 @@ tinybird/tb/modules/sink.py,sha256=dK2s__my0ePIUYrqBzhPSgdWN9rbpvP1G4dT7DJzz80,3
63
63
  tinybird/tb/modules/table.py,sha256=4XrtjM-N0zfNtxVkbvLDQQazno1EPXnxTyo7llivfXk,11035
64
64
  tinybird/tb/modules/telemetry.py,sha256=T9gtsQffWqG_4hRBaUJPzOfMkPwz7mH-R6Bn1XRYViA,11482
65
65
  tinybird/tb/modules/test.py,sha256=O2-mS4uMU6nPi7yWPpWzshAgOlYKiGS-tkM12pXQGMI,1906
66
- tinybird/tb/modules/test_common.py,sha256=LRgR2m2AgO5BYfdhboNL6ywIrRCffXsjzSwQE7A1W2Y,12459
66
+ tinybird/tb/modules/test_common.py,sha256=YZwAdSfYVXdvArfTc9tH-2QBOhb_XbnJ3eKvyXTJuEM,12717
67
67
  tinybird/tb/modules/token.py,sha256=DkXW9FNCLGBisXewfk195jTJ6B1Iz7zq3cEEac48aAs,12731
68
68
  tinybird/tb/modules/watch.py,sha256=No0bK1M1_3CYuMaIgylxf7vYFJ72lTJe3brz6xQ-mJo,8819
69
69
  tinybird/tb/modules/workspace.py,sha256=Q_8HcxMsNg8QG9aBlwcWS2umrDP5IkTIHqqz3sfmGuc,11341
70
70
  tinybird/tb/modules/workspace_members.py,sha256=5JdkJgfuEwbq-t6vxkBhYwgsiTDxF790wsa6Xfif9nk,8608
71
71
  tinybird/tb/modules/agent/__init__.py,sha256=i3oe3vDIWWPaicdCM0zs7D7BJ1W0k7th93ooskHAV00,54
72
- tinybird/tb/modules/agent/agent.py,sha256=Ye2v5sFDDT1tL54XTcpOid9V8s9EG8hpuTCLNQyxCHk,24502
72
+ tinybird/tb/modules/agent/agent.py,sha256=cHpPZiUeW525i9PxspGoilTc-1HPAHCCzxiYdcdJQ7c,28214
73
73
  tinybird/tb/modules/agent/animations.py,sha256=4WOC5_2BracttmMCrV0H91tXfWcUzQHBUaIJc5FA7tE,3490
74
- tinybird/tb/modules/agent/banner.py,sha256=7f97PeCPW-oW9mReQn3D0by8mnDhoc0VbfebEPRPI7c,3070
74
+ tinybird/tb/modules/agent/banner.py,sha256=2UpuuIqWxS0Ltab6i_FE4dkNxlJCdgKGCMtbwQGiLA8,7185
75
+ tinybird/tb/modules/agent/command_agent.py,sha256=Q4M5qV9j3aETrXswoWZ02ZWQZhJm42HGHO1ymJrfDnw,2665
75
76
  tinybird/tb/modules/agent/memory.py,sha256=O6Kumn9AyKxcTkhI45yjAUZ3ZIAibLOcNWoiEuLYeqY,3245
76
77
  tinybird/tb/modules/agent/models.py,sha256=LW1D27gjcd_jwFmghEzteCgToDfodX2B6B5S8BYbysw,735
77
- tinybird/tb/modules/agent/prompts.py,sha256=dFpbcKFb8u8O4elwV__RcKmjvTIG84mB4fzGnwVKtzg,28124
78
- tinybird/tb/modules/agent/utils.py,sha256=1X1cjP607StB0NFuT2CsPO7fBU05eI7LGeib-mkFVms,29390
78
+ tinybird/tb/modules/agent/prompts.py,sha256=NEIk3OK0xXm3QS1Iox4T1MMo19hLhfaICMEwzqVGW7E,31069
79
+ tinybird/tb/modules/agent/testing_agent.py,sha256=mH7ccuWeTnkpENCmwym9ubDFncMXQoSo0jq2pRxJVDY,2553
80
+ tinybird/tb/modules/agent/utils.py,sha256=VYuoodb05Ucq0b3qgIAf2KhOTTRD85_Pfab0xKwbg40,29610
79
81
  tinybird/tb/modules/agent/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
82
  tinybird/tb/modules/agent/tools/analyze.py,sha256=CR5LXg4fou-zYEksqnjpJ0icvxJVoKnTctoI1NRvqCM,3873
81
83
  tinybird/tb/modules/agent/tools/append.py,sha256=XA8ZeqxZcRL_0ZCd5FyggpWeH53mwTMby4lHV8wQa7c,6039
82
84
  tinybird/tb/modules/agent/tools/build.py,sha256=Hm-xDAP9ckMiKquT-DmDg5H0yxZefLOaWKANyoVSaEQ,846
83
- tinybird/tb/modules/agent/tools/create_datafile.py,sha256=-Luv7GrZ3tEdEFaTFiN2_b2WEkTVl2Tp437qsvNEwZU,6902
85
+ tinybird/tb/modules/agent/tools/create_datafile.py,sha256=M2DKTSDnAMs22oSVfBagjXQOTJejf5Kp-RHd15LDKqQ,6931
84
86
  tinybird/tb/modules/agent/tools/deploy.py,sha256=2hKj6LMYiDea6ventsBjE6ArECGIryTdo3X-LYo5oZI,1248
85
87
  tinybird/tb/modules/agent/tools/deploy_check.py,sha256=2Wr9hQfKPlhqhumOv5TNl_xFctvdq_DHZ2dI2h_LggY,1048
86
88
  tinybird/tb/modules/agent/tools/diff_resource.py,sha256=_9xHcDzCTKk_E1wKQbuktVqV6U9sA0kqYaBxWvtliX0,2613
87
- tinybird/tb/modules/agent/tools/execute_query.py,sha256=h8be9_lhyMy7UZoCWrExigP-cbq3lLnlLHD9SzDGBCM,2703
89
+ tinybird/tb/modules/agent/tools/execute_query.py,sha256=egUhKqRDF9C9458-rtu-yr-OeyvS0UrEUQwUgGd0Gts,3170
88
90
  tinybird/tb/modules/agent/tools/explore.py,sha256=ihALc_kBcsjrKT3hZyicqyIowB0g_K3AtNNi-5uz9-8,412
89
91
  tinybird/tb/modules/agent/tools/get_endpoint_stats.py,sha256=LiEK6ToyPDW2aI8ijclzuwdYAcFmwH-TyjqdFEzQWAc,1689
90
92
  tinybird/tb/modules/agent/tools/get_openapi_definition.py,sha256=mjIVVXtgvTs5LzOR8Bp4jB1XhLVMysHrHXawkErFdt8,2282
@@ -92,7 +94,8 @@ tinybird/tb/modules/agent/tools/mock.py,sha256=4JJ_45uWkbDhTGeKpwDv8L07ewsbF8u7q
92
94
  tinybird/tb/modules/agent/tools/plan.py,sha256=2KHLNkr2f1RfkbAR4mCVsv94LGosXd8-ky7v6BB1OtQ,985
93
95
  tinybird/tb/modules/agent/tools/preview_datafile.py,sha256=Gbao_FxhXstnUnngVQxztpizjugyfx1rOXTkw7Yabls,858
94
96
  tinybird/tb/modules/agent/tools/request_endpoint.py,sha256=xNYIEFI9sfq3SjevYqJaHH1EP6uEX5HHrbKS6qo3Imo,2852
95
- tinybird/tb/modules/agent/tools/test.py,sha256=0gXTfx6Yj1ZPDNDkKJj0RRhA6RQr6UtTM43KsHrdR_Y,4813
97
+ tinybird/tb/modules/agent/tools/run_command.py,sha256=321w5dYOnapMf-TFIkgizErllwB5Poc0RYOjBAQeMPc,1576
98
+ tinybird/tb/modules/agent/tools/test.py,sha256=CbGak_coopCTtqHoPWy-BwgLMIyEyeO34NTNkv18au4,6041
96
99
  tinybird/tb/modules/datafile/build.py,sha256=NFKBrusFLU0WJNCXePAFWiEDuTaXpwc0lHlOQWEJ43s,51117
97
100
  tinybird/tb/modules/datafile/build_common.py,sha256=2yNdxe49IMA9wNvl25NemY2Iaz8L66snjOdT64dm1is,4511
98
101
  tinybird/tb/modules/datafile/build_datasource.py,sha256=Ra8pVQBDafbFRUKlhpgohhTsRyp_ADKZJVG8Gd69idY,17227
@@ -113,8 +116,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
113
116
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
114
117
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
115
118
  tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
116
- tinybird-0.0.1.dev262.dist-info/METADATA,sha256=1iT43WWQIeJ-m391cjSMdLIL5aHzrSHDXlIs1us6bfM,1733
117
- tinybird-0.0.1.dev262.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
118
- tinybird-0.0.1.dev262.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
119
- tinybird-0.0.1.dev262.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
120
- tinybird-0.0.1.dev262.dist-info/RECORD,,
119
+ tinybird-0.0.1.dev264.dist-info/METADATA,sha256=PCl2AWV403ysmvj35_eYngYE_1wfUNqlUlBN5mxep5M,1733
120
+ tinybird-0.0.1.dev264.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
121
+ tinybird-0.0.1.dev264.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
122
+ tinybird-0.0.1.dev264.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
123
+ tinybird-0.0.1.dev264.dist-info/RECORD,,