tinybird 0.0.1.dev282__py3-none-any.whl → 0.0.1.dev284__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.

@@ -257,4 +257,5 @@ VALID_QUERY_FORMATS = (
257
257
  "Native",
258
258
  "RowBinaryWithNamesAndTypes",
259
259
  "TabSeparated",
260
+ "JSONCompactEachRowWithNamesAndTypes",
260
261
  )
@@ -184,7 +184,7 @@ class CopyParameters(Parameters):
184
184
 
185
185
  class MaterializedParameters(Parameters):
186
186
  MANDATORY_ATTRIBUTES = PipeParameters.MANDATORY_ATTRIBUTES.union({"datasource"})
187
- ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(MANDATORY_ATTRIBUTES)
187
+ ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(MANDATORY_ATTRIBUTES).union({"deployment_method"})
188
188
 
189
189
 
190
190
  class SinkParameters(Parameters):
@@ -294,6 +294,13 @@ class Datafile:
294
294
  def set_kind(self, kind: DatafileKind):
295
295
  self.kind = kind
296
296
 
297
+ def validate_standard_node(self, node: Dict[str, Any]):
298
+ for key in node.keys():
299
+ if key not in PipeParameters.valid_params():
300
+ raise DatafileValidationError(
301
+ f"Standard node {repr(node['name'])} has an invalid attribute ({PipeParameters.canonical_name(key)})"
302
+ )
303
+
297
304
  def validate_copy_node(self, node: Dict[str, Any]):
298
305
  if missing := [param for param in CopyParameters.required_params() if param not in node]:
299
306
  raise DatafileValidationError(
@@ -412,13 +419,6 @@ class Datafile:
412
419
 
413
420
  def validate(self):
414
421
  if self.kind == DatafileKind.pipe:
415
- # TODO(eclbg):
416
- # [x] node names are unique
417
- # [x] SQL in all nodes
418
- # [x] Materialized nodes have target datasource
419
- # [x] Only one materialized node
420
- # [x] Only one node of any specific type
421
- # (rbarbadillo): there's a HUGE amount of validations in api_pipes.py, we should somehow merge them
422
422
  non_standard_nodes_count = 0
423
423
  for node in self.nodes:
424
424
  node_type = node.get("type", "").lower()
@@ -432,10 +432,13 @@ class Datafile:
432
432
  self.validate_copy_node(node)
433
433
  if node_type == PipeNodeTypes.DATA_SINK:
434
434
  self.validate_sink_node(node)
435
+ if node_type in {PipeNodeTypes.STANDARD, ""}:
436
+ self.validate_standard_node(node)
435
437
  if node_type not in VALID_PIPE_NODE_TYPES:
436
438
  raise DatafileValidationError(
437
439
  f"Invalid node '{repr(node['name'])}' of type ({node_type}). Allowed node types: {VISIBLE_PIPE_NODE_TYPES}"
438
440
  )
441
+
439
442
  for token in self.tokens:
440
443
  if token["permission"].upper() != "READ":
441
444
  raise DatafileValidationError(
@@ -1983,6 +1986,7 @@ def parse(
1983
1986
  "include": include,
1984
1987
  "sql": sql("sql"),
1985
1988
  "version": version,
1989
+ "deployment_method": assign_var("deployment_method", allowed_values={"alter"}),
1986
1990
  "export_connection_name": assign_var("export_connection_name"),
1987
1991
  "export_schedule": assign_var("export_schedule"),
1988
1992
  "export_bucket_uri": assign_var("export_bucket_uri"),
tinybird/tb/__cli__.py CHANGED
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/forward/commands'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev282'
8
- __revision__ = '77eb85f'
7
+ __version__ = '0.0.1.dev284'
8
+ __revision__ = '8beb9ee'
@@ -28,6 +28,7 @@ from tinybird.tb.modules.agent.memory import (
28
28
  get_last_messages_from_last_user_prompt,
29
29
  save_messages,
30
30
  )
31
+ from tinybird.tb.modules.agent.mock_agent import MockAgent
31
32
  from tinybird.tb.modules.agent.models import create_model
32
33
  from tinybird.tb.modules.agent.prompts import (
33
34
  agent_system_prompt,
@@ -45,7 +46,6 @@ from tinybird.tb.modules.agent.tools.deploy_check import deploy_check
45
46
  from tinybird.tb.modules.agent.tools.diff_resource import diff_resource
46
47
  from tinybird.tb.modules.agent.tools.get_endpoint_stats import get_endpoint_stats
47
48
  from tinybird.tb.modules.agent.tools.get_openapi_definition import get_openapi_definition
48
- from tinybird.tb.modules.agent.tools.mock import mock
49
49
  from tinybird.tb.modules.agent.tools.plan import plan
50
50
  from tinybird.tb.modules.agent.tools.secret import create_or_update_secrets
51
51
  from tinybird.tb.modules.agent.utils import AgentRunCancelled, TinybirdAgentContext, show_confirmation, show_input
@@ -111,7 +111,6 @@ class TinybirdAgent:
111
111
  Tool(build, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
112
112
  Tool(deploy, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
113
113
  Tool(deploy_check, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
114
- Tool(mock, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
115
114
  Tool(analyze_file, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
116
115
  Tool(analyze_url, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
117
116
  Tool(append_file, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
@@ -166,6 +165,16 @@ class TinybirdAgent:
166
165
  workspace_id=workspace_id,
167
166
  project=self.project,
168
167
  )
168
+ self.mock_agent = MockAgent(
169
+ dangerously_skip_permissions=self.dangerously_skip_permissions,
170
+ prompt_mode=prompt_mode,
171
+ thinking_animation=self.thinking_animation,
172
+ token=self.token,
173
+ user_token=self.user_token,
174
+ host=self.host,
175
+ workspace_id=workspace_id,
176
+ project=self.project,
177
+ )
169
178
 
170
179
  @self.agent.tool
171
180
  def manage_tests(ctx: RunContext[TinybirdAgentContext], task: str) -> str:
@@ -210,6 +219,25 @@ class TinybirdAgent:
210
219
  result = self.explore_agent.run(task, deps=ctx.deps, usage=ctx.usage)
211
220
  return result.output or "No result returned"
212
221
 
222
+ @self.agent.tool
223
+ def mock(
224
+ ctx: RunContext[TinybirdAgentContext], datasource_name: str, rows: int, data_format: str, task: str
225
+ ) -> str:
226
+ """Generate mock data for a datasource.
227
+
228
+ Args:
229
+ datasource_name (str): The datasource name to generate mock data for. Required.
230
+ rows (int): Number of rows to create. If not provided, the default is 10. Required.
231
+ data_format (str): Format of the mock data to create. Options: ndjson, csv. Required.
232
+ task (str): Extra details about how to generate the mock data (nested json if any, sample row to help with the generation, etc). Required.
233
+
234
+ Returns:
235
+ str: The result of the mock data generation.
236
+ """
237
+ user_input = f"Datasource name: {datasource_name}\nRows: {rows}\nData format: {data_format}\nTask: {task}"
238
+ result = self.mock_agent.run(user_input, deps=ctx.deps, usage=ctx.usage)
239
+ return result.output or "No result returned"
240
+
213
241
  @self.agent.instructions
214
242
  def get_local_host(ctx: RunContext[TinybirdAgentContext]) -> str:
215
243
  return f"""
@@ -355,20 +383,18 @@ class TinybirdAgent:
355
383
  workspace_id = config.get("id", "")
356
384
  workspace = client.workspace(workspace_id, with_organization=True, version="v1")
357
385
  limits_data = client.organization_limits(workspace["organization"]["id"])
358
- ai_requests_limits = limits_data.get("limits", {}).get("ai_requests", {})
359
- current_ai_requests = ai_requests_limits.get("quantity") or 0
360
- max_ai_requests = ai_requests_limits.get("max") or 0
361
- remaining_requests = max(max_ai_requests - current_ai_requests, 0)
362
- current_ai_requests = min(max_ai_requests, current_ai_requests)
363
- if not max_ai_requests:
386
+ ai_credits_limits = limits_data.get("limits", {}).get("ai_credits", {})
387
+ current_ai_credits = ai_credits_limits.get("quantity") or 0
388
+ ai_credits = ai_credits_limits.get("max") or 0
389
+ remaining_credits = max(ai_credits - current_ai_credits, 0)
390
+ current_ai_credits = min(ai_credits, current_ai_credits)
391
+ if not ai_credits:
364
392
  return
365
- warning_threshold = max_ai_requests * 0.8
366
- message_color = (
367
- FeedbackManager.warning if current_ai_requests >= warning_threshold else FeedbackManager.gray
368
- )
393
+ warning_threshold = ai_credits * 0.8
394
+ message_color = FeedbackManager.warning if current_ai_credits >= warning_threshold else FeedbackManager.gray
369
395
  click.echo(
370
396
  message_color(
371
- message=f"{remaining_requests} requests left ({current_ai_requests}/{max_ai_requests}). You can continue using Tinybird Code. Limits will be enforced soon."
397
+ message=f"{remaining_credits} credits left ({current_ai_credits}/{ai_credits}). You can continue using Tinybird Code. Limits will be enforced soon."
372
398
  )
373
399
  )
374
400
  except Exception:
@@ -50,6 +50,11 @@ You can do the following:
50
50
  - Executing SQL queries against Tinybird Cloud or Tinybird Local.
51
51
  - Requesting endpoints in Tinybird Cloud or Tinybird Local.
52
52
  - Visualizing data as a chart using execute_query tool with the `script` parameter.
53
+
54
+ IMPORTANT: Use always the last environment used in previous queries or endpoint requests (cloud_or_local: str). If you don't have any information about the last environment, use None.
55
+ IMPORTANT: If some resource is not found in a environment, you can use the `diff_resource` tool to check the status across environments.
56
+
57
+ Once you finish the task, return a valid response for the task to complete.
53
58
  """,
54
59
  tone_and_style_instructions,
55
60
  explore_data_instructions,
@@ -0,0 +1,206 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic_ai import Agent, RunContext, Tool
4
+ from pydantic_ai.messages import ModelMessage
5
+ from pydantic_ai.usage import Usage
6
+
7
+ from tinybird.tb.modules.agent.animations import ThinkingAnimation
8
+ from tinybird.tb.modules.agent.models import create_model
9
+ from tinybird.tb.modules.agent.prompts import resources_prompt
10
+ from tinybird.tb.modules.agent.tools.mock import generate_mock_fixture
11
+ from tinybird.tb.modules.agent.utils import TinybirdAgentContext
12
+ from tinybird.tb.modules.project import Project
13
+
14
+ mock_sql_instructions = """
15
+ ## When generating the SQL query to generate mock data
16
+
17
+ Given the schema for a Tinybird datasource, return a valid clickhouse sql query to generate some random data that matches that schema.
18
+ Response format MUST be just a valid clickhouse sql query.
19
+
20
+ <example>
21
+ <example_datasource_schema>
22
+ SCHEMA >
23
+ experience_gained Int16 `json:$.experience_gained`,
24
+ level Int16 `json:$.level`,
25
+ monster_kills Int16 `json:$.monster_kills`,
26
+ player_id String `json:$.player_id`,
27
+ pvp_kills Int16 `json:$.pvp_kills`,
28
+ quest_completions Int16 `json:$.quest_completions`,
29
+ timestamp DateTime `json:$.timestamp`
30
+ </example_datasource_schema>
31
+ <example_output>
32
+
33
+ SELECT
34
+ rand() % 1000 AS experience_gained, -- Random experience gained between 0 and 999
35
+ 1 + rand() % 100 AS level, -- Random level between 1 and 100
36
+ rand() % 500 AS monster_kills, -- Random monster kills between 0 and 499
37
+ concat('player_', toString(rand() % 10000)) AS player_id, -- Random player IDs like "player_1234"
38
+ rand() % 50 AS pvp_kills, -- Random PvP kills between 0 and 49
39
+ rand() % 200 AS quest_completions, -- Random quest completions between 0 and 199
40
+ now() - rand() % 86400 AS timestamp -- Random timestamp within the last day
41
+ FROM numbers(10)
42
+ </example_output>
43
+ </example>
44
+
45
+ <instructions>
46
+ - The query MUST return a random sample of data that matches the schema.
47
+ - The query MUST return a valid clickhouse sql query.
48
+ - The query MUST be valid for clickhouse and Tinybird.
49
+ - FROM numbers([number_of_rows]) part is mandatory.
50
+ - If json paths are present (e.g. `userAgent` String `json:$.userAgent`), rely on the json paths to generate the sample record.
51
+ - If the schema has nested json paths (e.g. `json:$.location.country`), generate nested JSON objects accordingly.
52
+ - Use recent dates to avoid generating dates that are too far in the past.
53
+ - Do NOT include ```clickhouse or ```sql or any other wrapping text to the sql query.
54
+ - Do NOT use any of these functions: elementAt
55
+ - Do NOT add a semicolon at the end of the query
56
+ - Do NOT add any FORMAT at the end of the query, because it will be added later by Tinybird.
57
+ - Do not use any function that is not present in the list of general functions, character insensitive functions and aggregate functions.
58
+ - If the function is not present in the list, the sql query will fail, so avoid at all costs to use any function that is not present in the list.
59
+ </instructions>
60
+
61
+ <more_examples>
62
+ # Examples with different schemas, like an array field or a nested JSON field:
63
+
64
+ ## Example schema with an array field:
65
+
66
+ ### Schema:
67
+
68
+ SCHEMA >
69
+ `order_id` UInt64 `json:$.order_id`,
70
+ `customer_id` UInt64 `json:$.customer_id`,
71
+ `order_date` DateTime `json:$.order_date`,
72
+ `total_amount` Float64 `json:$.total_amount`,
73
+ `items` Array(String) `json:$.items[:]` // This is an array field
74
+
75
+ ### Desired final output of the query:
76
+ {
77
+ "order_id": 123456,
78
+ "customer_id": 7890,
79
+ "order_date": "2024-11-30T10:30:00.000Z",
80
+ "total_amount": 150.0,
81
+ "items": ["item1", "item2", "item3"]
82
+ }
83
+
84
+ ### Example SQL output with an array field:
85
+
86
+ SELECT
87
+ concat('ord_', toString(rand() % 10000)) AS order_id,
88
+ concat('cust_', toString(rand() % 10000)) AS customer_id,
89
+ now() - rand() % 86400 AS order_date,
90
+ rand() % 1000 AS total_amount,
91
+ arrayMap(x -> concat('item_', toString(x)), range(1, rand() % 5 + 1)) AS items
92
+ FROM numbers(ROWS)
93
+
94
+ ## Example schema with nested JSON paths:
95
+
96
+ ### Schema:
97
+
98
+ SCHEMA >
99
+ `timestamp` DateTime `json:$.timestamp`,
100
+ `location_country` String `json:$.location.country`,
101
+ `location_region` String `json:$.location.region`,
102
+ `location_city` String `json:$.location.city`,
103
+ `location_latitude` String `json:$.location.latitude`,
104
+ `location_longitude` String `json:$.location.longitude`
105
+
106
+ ### Important: Understanding JSON paths
107
+ When you see json paths like `json:$.location.country`, it means the data should be structured as nested JSON:
108
+ - `json:$.location.country` → the country field is nested inside the location object
109
+ - `json:$.location.region` → the region field is nested inside the location object
110
+
111
+ ### Desired final output structure:
112
+ {
113
+ "timestamp": "2024-11-30T10:30:00.000Z",
114
+ "location": {
115
+ "country": "United States",
116
+ "region": "California",
117
+ "city": "San Francisco",
118
+ "latitude": "37.7749",
119
+ "longitude": "-122.4194"
120
+ }
121
+ }
122
+
123
+ ### Example SQL output for nested JSON paths:
124
+
125
+ SELECT
126
+ timestamp,
127
+ CAST(concat('{
128
+ "country": "', country, '",
129
+ "region": "', region, '",
130
+ "city": "', city, '",
131
+ "latitude": "', latitude, '",
132
+ "longitude": "', longitude, '"
133
+ }'), 'JSON') AS location
134
+ FROM
135
+ (
136
+ SELECT
137
+ now() - rand() % 86400 AS timestamp,
138
+ ['United States', 'Canada', 'United Kingdom', 'Germany', 'France'][(rand() % 5) + 1] AS country,
139
+ ['California', 'Texas', 'New York', 'Ontario', 'London'][(rand() % 5) + 1] AS region,
140
+ ['San Francisco', 'Los Angeles', 'New York', 'Toronto', 'London'][(rand() % 5) + 1] AS city,
141
+ toString(round(rand() * 180 - 90, 4)) AS latitude,
142
+ toString(round(rand() * 360 - 180, 4)) AS longitude
143
+ FROM numbers(10)
144
+ )
145
+ </more_examples>
146
+ """
147
+
148
+
149
+ class MockAgent:
150
+ def __init__(
151
+ self,
152
+ dangerously_skip_permissions: bool,
153
+ prompt_mode: bool,
154
+ token: str,
155
+ user_token: str,
156
+ host: str,
157
+ workspace_id: str,
158
+ project: Project,
159
+ thinking_animation: ThinkingAnimation,
160
+ ):
161
+ self.dangerously_skip_permissions = dangerously_skip_permissions or prompt_mode
162
+ self.token = token
163
+ self.user_token = user_token
164
+ self.host = host
165
+ self.workspace_id = workspace_id
166
+ self.project = project
167
+ self.thinking_animation = thinking_animation
168
+ self.messages: list[ModelMessage] = []
169
+ self.agent = Agent(
170
+ deps_type=TinybirdAgentContext,
171
+ instructions=[
172
+ f"""
173
+ You are part of Tinybird Code, an agentic CLI that can help users to work with Tinybird.
174
+ You are a sub-agent of the main Tinybird Code agent. You are responsible for generating mock data for a datasource.
175
+ You will be given a datasource name and you will use `generate_mock_fixture` tool to generate a sql query to execute to generate the mock data.
176
+ When finish return the result of the mock data generation: the path of the fixture file, the number of rows generated, the datasource name and if the data was appended to the datasource.
177
+
178
+ # Info
179
+ Today is {datetime.now().strftime("%Y-%m-%d")}
180
+ """,
181
+ mock_sql_instructions,
182
+ ],
183
+ tools=[
184
+ Tool(
185
+ generate_mock_fixture,
186
+ docstring_format="google",
187
+ require_parameter_descriptions=True,
188
+ takes_ctx=True,
189
+ ),
190
+ ],
191
+ )
192
+
193
+ @self.agent.instructions
194
+ def get_project_files(ctx: RunContext[TinybirdAgentContext]) -> str:
195
+ return resources_prompt(self.project)
196
+
197
+ def run(self, task: str, deps: TinybirdAgentContext, usage: Usage):
198
+ result = self.agent.run_sync(
199
+ task,
200
+ deps=deps,
201
+ usage=usage,
202
+ message_history=self.messages,
203
+ model=create_model(self.user_token, self.host, self.workspace_id, run_id=deps.run_id),
204
+ )
205
+ self.messages.extend(result.new_messages())
206
+ return result
@@ -19,7 +19,7 @@ from tinybird.tb.modules.feedback_manager import FeedbackManager
19
19
  def create_datafile(
20
20
  ctx: RunContext[TinybirdAgentContext], name: str, type: str, description: str, content: str, pathname: str
21
21
  ) -> str:
22
- """Given a resource representation, create or update a datafile in the project folder
22
+ """Given a resource representation, create or update a datafile (.datasource, .connection, .pipe) in the project folder
23
23
 
24
24
  Args:
25
25
  name (str): The name of the datafile. Required.
@@ -35,7 +35,12 @@ def deploy(ctx: RunContext[TinybirdAgentContext], allow_destructive_operations:
35
35
  ctx.deps.thinking_animation.start()
36
36
  return f"User did not confirm deployment and gave the following feedback: {feedback}"
37
37
 
38
- click.echo(FeedbackManager.highlight(message="» Deploying project..."))
38
+ allow_destructive_operations_flag = " --allow-destructive-operations" if allow_destructive_operations else ""
39
+ click.echo(
40
+ FeedbackManager.highlight(
41
+ message=f"» Running command: tb --cloud deploy{allow_destructive_operations_flag}"
42
+ )
43
+ )
39
44
  ctx.deps.deploy_project(allow_destructive_operations=allow_destructive_operations)
40
45
  click.echo(FeedbackManager.success(message="✓ Project deployed successfully"))
41
46
  ctx.deps.thinking_animation.start()
@@ -1,7 +1,9 @@
1
+ import click
1
2
  from pydantic_ai import RunContext
2
3
 
3
4
  from tinybird.tb.modules.agent.utils import TinybirdAgentContext, show_confirmation, show_input
4
5
  from tinybird.tb.modules.exceptions import CLIDeploymentException
6
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
5
7
 
6
8
 
7
9
  def deploy_check(ctx: RunContext[TinybirdAgentContext]) -> str:
@@ -18,6 +20,7 @@ def deploy_check(ctx: RunContext[TinybirdAgentContext]) -> str:
18
20
  ctx.deps.thinking_animation.start()
19
21
  return f"User did not confirm deployment check and gave the following feedback: {feedback}"
20
22
 
23
+ click.echo(FeedbackManager.highlight(message="» Running command: tb --cloud deploy --check"))
21
24
  ctx.deps.deploy_check_project()
22
25
  ctx.deps.thinking_animation.start()
23
26
  return "Project can be deployed"
@@ -8,7 +8,7 @@ import click
8
8
  import humanfriendly
9
9
  from pydantic_ai import RunContext
10
10
 
11
- from tinybird.tb.modules.agent.utils import TinybirdAgentContext
11
+ from tinybird.tb.modules.agent.utils import TinybirdAgentContext, show_env_options
12
12
  from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_pretty_table
13
13
  from tinybird.tb.modules.feedback_manager import FeedbackManager
14
14
 
@@ -35,23 +35,24 @@ def execute_query(
35
35
  ctx: RunContext[TinybirdAgentContext],
36
36
  query: str,
37
37
  task: str,
38
- cloud: Optional[bool] = None,
38
+ cloud_or_local: Optional[str] = None,
39
39
  script: Optional[str] = None,
40
40
  export_format: Optional[str] = None,
41
+ explanation_why_not_know_about_last_environment: Optional[str] = None,
41
42
  ):
42
43
  """Execute a query and return the result as a table, chart or exported file.
43
44
 
44
45
  Args:
45
46
  query (str): The query to execute. Required.
46
47
  task (str): The purpose of the query. Required.
47
- cloud (bool): Whether to execute the query on cloud or local. If None (user didn't specify), will ask user to clarify. Defaults to local (False) in dangerous skip permissions mode.
48
+ cloud_or_local (str): Whether to execute the query on cloud or local. Use the last environment used in previous queries or endpoint requests. If you don't have any information about the last environment, use None. Options: cloud, local.
48
49
  script (str): Python script using plotext to render the query results as a chart. The script will have access to 'data' (list of dicts), 'meta' (list of column info dicts), 'terminal_width' and 'terminal_height' variables. Always use plt.theme("clear") for transparent background and plt.plot_size(terminal_width, terminal_height) for proper sizing. For bar charts, use the simple versions: plt.simple_bar(), plt.simple_multiple_bar(), and plt.simple_stacked_bar(). Optional.
49
50
  export_format (str): The format to export the query results to. Options: csv, json, ndjson. Optional.
51
+ explanation_why_not_know_about_last_environment (str): Why you don't know about the last environment used in previous queries or endpoint requests. Required.
50
52
 
51
53
  Returns:
52
54
  str: The result of the query.
53
55
  """
54
-
55
56
  try:
56
57
  for forbidden_command in forbidden_commands:
57
58
  if forbidden_command in query.lower():
@@ -61,24 +62,21 @@ def execute_query(
61
62
  if query.lower().startswith(forbidden_command):
62
63
  return f"Error executing query: {forbidden_command} is not allowed."
63
64
 
64
- # Handle cloud parameter - ask user if uncertain and not in dangerous skip mode
65
- if cloud is None:
65
+ # Handle cloud_or_local parameter - ask user if uncertain and not in dangerous skip mode
66
+ if cloud_or_local is None:
66
67
  if ctx.deps.dangerously_skip_permissions:
67
68
  # Default to local when in dangerous skip mode
68
- cloud = False
69
+ cloud_or_local = "local"
69
70
  else:
70
71
  # Ask the user to choose execution mode
71
- from tinybird.tb.modules.agent.utils import show_env_options
72
72
 
73
73
  cloud = show_env_options(ctx)
74
74
  if cloud is None:
75
75
  return "Query execution cancelled by user."
76
+ cloud_or_local = "cloud" if cloud else "local"
76
77
 
77
- cloud_or_local = "cloud" if cloud else "local"
78
78
  ctx.deps.thinking_animation.stop()
79
-
80
79
  click.echo(FeedbackManager.highlight(message=f"» Executing query to {cloud_or_local}:\n{query}\n"))
81
-
82
80
  is_templating = query.strip().startswith("%")
83
81
  query_format = "JSON"
84
82
  if export_format == "csv":
@@ -94,7 +92,7 @@ def execute_query(
94
92
  else:
95
93
  query = f"SELECT * FROM ({query}) FORMAT {query_format}"
96
94
 
97
- execute_query = ctx.deps.execute_query_cloud if cloud else ctx.deps.execute_query_local
95
+ execute_query = ctx.deps.execute_query_cloud if cloud_or_local == "cloud" else ctx.deps.execute_query_local
98
96
  result = execute_query(query=query)
99
97
  if export_format:
100
98
  file_extension = f".{export_format}"
@@ -4,50 +4,98 @@ from pydantic_ai import RunContext
4
4
  from tinybird.tb.modules.agent.utils import (
5
5
  AgentRunCancelled,
6
6
  TinybirdAgentContext,
7
+ create_terminal_box,
7
8
  show_confirmation,
8
9
  show_input,
9
10
  )
10
- from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_pretty_table
11
+ from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_pretty_table, format_data_to_ndjson
11
12
  from tinybird.tb.modules.datafile.fixture import persist_fixture
12
13
  from tinybird.tb.modules.feedback_manager import FeedbackManager
13
14
 
14
15
 
15
- def mock(
16
- ctx: RunContext[TinybirdAgentContext], datasource_name: str, data_format: str, rows: int, description: str
16
+ def generate_mock_fixture(
17
+ ctx: RunContext[TinybirdAgentContext], datasource_name: str, sql: str, data_format: str, rows: int, task: str
17
18
  ) -> str:
18
- """Create mock data for a datasource
19
+ """Given a datasource name and a sql query to execute, generate a fixture file with mock data and append it to the datasource.
19
20
 
20
21
  Args:
21
22
  datasource_name (str): Name of the datasource to create mock data for. Required.
23
+ sql (str): SQL query to execute to generate the mock data. Required.
22
24
  data_format (str): Format of the mock data to create. Options: ndjson, csv. Required.
23
25
  rows (int): Number of rows to create. If not provided, the default is 10. Required.
24
- description (str): Extra details about how to generate the mock data (nested json if any, sample row to help with the generation, etc). You can use this to fix issues with the mock data generation. Required.
26
+ task (str): Extra details about how to generate the mock data (nested json if any, sample row to help with the generation, etc). You can use this to fix issues with the mock data generation. Required.
25
27
 
26
28
  Returns:
27
29
  str: Message indicating the success or failure of the mock data generation
28
30
  """
29
31
  try:
30
32
  ctx.deps.thinking_animation.stop()
31
- cloud_or_local = "Local"
33
+
34
+ click.echo(FeedbackManager.highlight(message=f"» Generating mock data for datasource '{datasource_name}'..."))
35
+ try:
36
+ sql_format = "JSON" if data_format == "ndjson" else "CSV"
37
+ sql = f"SELECT * FROM ({sql}) LIMIT {rows} FORMAT {sql_format}"
38
+ result = ctx.deps.execute_query_local(query=sql)
39
+ except Exception as e:
40
+ click.echo(
41
+ FeedbackManager.error(message="✗ Failed to generate a valid SQL query for generating mock data.\n{e}")
42
+ )
43
+ ctx.deps.thinking_animation.start()
44
+ return f"Failed to generate a valid sql for generating mock data for datasource '{datasource_name}'. SQL: {sql}\nError: {e}"
45
+
46
+ preview_content = ""
47
+ if sql_format == "JSON":
48
+ data = result.get("data", [])[:rows]
49
+ preview_content = str(format_data_to_ndjson(data[:10]))
50
+
51
+ if len(data) != rows:
52
+ raise Exception(
53
+ f"Failed to generate a valid sql for generating mock data for datasource '{datasource_name}'. Rows generated: {len(data)} != {rows}. SQL: {sql}\nError: {result}"
54
+ )
55
+
56
+ error_response = result.get("error", None)
57
+ if error_response:
58
+ raise Exception(
59
+ f"Failed to generate a valid sql for generating mock data for datasource '{datasource_name}'. SQL: {sql}\nError: {error_response}"
60
+ )
61
+
62
+ else:
63
+ data = result
64
+ preview_content = str(data[:1000])
65
+
66
+ if isinstance(data, dict):
67
+ data = data.get("data", [])[:rows]
68
+ preview_content = str(format_data_to_ndjson(data[:10]))
69
+
70
+ content = create_terminal_box(preview_content, title=f"fixtures/{datasource_name}.{data_format}")
71
+ click.echo(content)
72
+ click.echo("Showing a preview of the file.\n")
32
73
  confirmation = show_confirmation(
33
- title=f"Generate mock data for datasource '{datasource_name}' in Tinybird {cloud_or_local}?",
74
+ title=f"Create fixture file for datasource '{datasource_name}'?",
34
75
  skip_confirmation=ctx.deps.dangerously_skip_permissions,
35
76
  )
36
77
 
37
78
  if confirmation == "review":
38
79
  feedback = show_input(ctx.deps.workspace_name)
39
80
  ctx.deps.thinking_animation.start()
40
- return f"User did not confirm mock data for datasource '{datasource_name}' in Tinybird {cloud_or_local} and gave the following feedback: {feedback}"
81
+ return f"User did not confirm mock data for datasource '{datasource_name}' in Tinybird Local and gave the following feedback: {feedback}"
41
82
 
42
- click.echo(FeedbackManager.highlight(message=f"» Generating mock data for {datasource_name}..."))
43
- data = ctx.deps.mock_data(
44
- datasource_name=datasource_name, data_format=data_format, rows=rows, context=description
45
- )
46
83
  fixture_path = persist_fixture(datasource_name, data, ctx.deps.folder, format=data_format)
84
+ fixture_path_name = f"fixtures/{fixture_path.name}"
85
+ click.echo(FeedbackManager.success(message=f"✓ {fixture_path_name} created"))
86
+ confirmation = show_confirmation(
87
+ title=f"Append {fixture_path_name} to datasource '{datasource_name}'?",
88
+ skip_confirmation=ctx.deps.dangerously_skip_permissions,
89
+ )
90
+ if confirmation == "review":
91
+ feedback = show_input(ctx.deps.workspace_name)
92
+ ctx.deps.thinking_animation.start()
93
+ return f"Mock data was generated in {fixture_path_name} but user did not confirm appending {fixture_path_name} to datasource '{datasource_name}' in Tinybird Local and gave the following feedback: {feedback}"
94
+
47
95
  ctx.deps.append_data_local(datasource_name=datasource_name, path=str(fixture_path))
48
- click.echo(FeedbackManager.success(message=f"✓ Data generated for {datasource_name}"))
96
+ click.echo(FeedbackManager.success(message=f"✓ Data appended to datasource '{datasource_name}'"))
49
97
  ctx.deps.thinking_animation.start()
50
- return f"Mock data generated successfully for datasource '{datasource_name}' in Tinybird {cloud_or_local}"
98
+ return f"Mock data generated in {fixture_path_name} and appended to datasource '{datasource_name}' in Tinybird Local"
51
99
  except AgentRunCancelled as e:
52
100
  raise e
53
101
  except Exception as e:
@@ -84,4 +132,4 @@ def mock(
84
132
  error_message = error_message + "\nBuild the project again."
85
133
 
86
134
  ctx.deps.thinking_animation.start()
87
- return f"Error generating mock data for datasource '{datasource_name}' in Tinybird {cloud_or_local}: {error_message}"
135
+ return f"Error generating mock data for datasource '{datasource_name}' in Tinybird Local: {error_message}"
@@ -13,30 +13,32 @@ def request_endpoint(
13
13
  ctx: RunContext[TinybirdAgentContext],
14
14
  endpoint_name: str,
15
15
  params: Optional[dict[str, str]] = None,
16
- cloud: Optional[bool] = None,
16
+ cloud_or_local: Optional[str] = None,
17
+ explanation_why_not_know_about_last_environment: Optional[str] = None,
17
18
  ):
18
19
  """Request an endpoint:
19
20
 
20
21
  Args:
21
22
  endpoint_name (str): The name of the endpoint to request. Required.
22
23
  params (dict): The parameters to pass to the endpoint. Optional.
23
- cloud (bool): Whether to request the endpoint on cloud or local. If None (user didn't specify), will ask user to clarify. Defaults to local (False) in dangerous skip permissions mode.
24
+ cloud_or_local (str): Whether to request the endpoint on cloud or local. Use the last environment used in previous queries or endpoint requests. If you don't have any information about the last environment, use None. Options: cloud, local. Optional.
25
+ explanation_why_not_know_about_last_environment (str): Why you don't know about the last environment used in previous queries or endpoint requests. Required.
24
26
 
25
27
  Returns:
26
28
  str: The result of the query.
27
29
  """
28
30
  try:
29
31
  # Handle cloud parameter - ask user if uncertain and not in dangerous skip mode
30
- if cloud is None:
32
+ if cloud_or_local is None:
31
33
  if ctx.deps.dangerously_skip_permissions:
32
34
  # Default to local when in dangerous skip mode
33
- cloud = False
35
+ cloud_or_local = "local"
34
36
  else:
35
37
  # Ask the user to choose execution mode
36
38
  cloud = show_env_options(ctx)
37
39
  if cloud is None:
38
40
  return "Endpoint request cancelled by user."
39
- cloud_or_local = "cloud" if cloud else "local"
41
+ cloud_or_local = "cloud" if cloud else "local"
40
42
  ctx.deps.thinking_animation.stop()
41
43
  with_params = f" with params {params}" if params else ""
42
44
  click.echo(
@@ -45,7 +47,9 @@ def request_endpoint(
45
47
  )
46
48
  )
47
49
 
48
- request_endpoint = ctx.deps.request_endpoint_cloud if cloud else ctx.deps.request_endpoint_local
50
+ request_endpoint = (
51
+ ctx.deps.request_endpoint_cloud if cloud_or_local == "cloud" else ctx.deps.request_endpoint_local
52
+ )
49
53
  result = request_endpoint(endpoint_name=endpoint_name, params=params)
50
54
 
51
55
  # Apply output limiting using the utility function
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev282
3
+ Version: 0.0.1.dev284
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -10,14 +10,14 @@ tinybird/sql_template_fmt.py,sha256=KUHdj5rYCYm_rKKdXYSJAE9vIyXUQLB0YSZnUXHeBlY,
10
10
  tinybird/sql_toolset.py,sha256=19WPr4S3SR--Iw-VPgmDnLmhOZyHhTxnj3-_Yq3OgEU,19767
11
11
  tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
12
12
  tinybird/tornado_template.py,sha256=jjNVDMnkYFWXflmT8KU_Ssbo5vR8KQq3EJMk5vYgXRw,41959
13
- tinybird/ch_utils/constants.py,sha256=fPgZtwbr1ymxaW7uqVWHKmAbt7uGj3SxCCS3xsEMJqA,4151
13
+ tinybird/ch_utils/constants.py,sha256=v5-nkXHUhysu4i9Z4WVv0-sBbh6xSYUH5q5xHSY2xTI,4194
14
14
  tinybird/ch_utils/engine.py,sha256=4X1B-iuhdW_mxKnX_m3iCsxgP9RPVgR75g7yH1vsJ6A,40851
15
- tinybird/datafile/common.py,sha256=lN-fNCaxtFWQAd0WN5Q-mgljM7F8sYOBQcU1JJogw1g,105324
15
+ tinybird/datafile/common.py,sha256=uRSQnSS3yv1iuJRZHgenphe-g1TEjZr04ICcl7QYcZw,105521
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=fmFyVdes693OS-QP_zm9KaWfwKBNHpRdThEjryzzN7s,247
20
+ tinybird/tb/__cli__.py,sha256=g2YCN8x2pycUNCskVWGzPefjdg8_c5qjgIUIH_fe-dw,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=IQRaInDjOwr9Fzaz3_xXc3aUGqh94tM2lew7IZbB9eM,53733
@@ -69,13 +69,14 @@ tinybird/tb/modules/watch.py,sha256=No0bK1M1_3CYuMaIgylxf7vYFJ72lTJe3brz6xQ-mJo,
69
69
  tinybird/tb/modules/workspace.py,sha256=USsG8YEXlwf7F2PjTMCuQ2lB8ya-erbv8VywNJYq6mc,11173
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=TWaYS5od0ns0XKu9UKURdzSmDKxQk-Dm0oxLO8PtLic,33940
72
+ tinybird/tb/modules/agent/agent.py,sha256=x9nV6NaTqqvcDrg1GvbDleuyajxHbSQ_x7nEHHH2vnk,35207
73
73
  tinybird/tb/modules/agent/animations.py,sha256=4WOC5_2BracttmMCrV0H91tXfWcUzQHBUaIJc5FA7tE,3490
74
74
  tinybird/tb/modules/agent/banner.py,sha256=l6cO5Fi7lbVKp-GsBP8jf3IkjOWxg2jpAt9NBCy0WR8,4085
75
75
  tinybird/tb/modules/agent/command_agent.py,sha256=Wcdtmo7vJZ5EbBFW9J7zPCME0ShG_KqF6-qHmMB1XXk,3103
76
76
  tinybird/tb/modules/agent/compactor.py,sha256=BK5AxZFhrp3xWnsRnYaleiYoIWtVNc-_m650Hsopt8g,13841
77
- tinybird/tb/modules/agent/explore_agent.py,sha256=HkzKmggfSMz7S3RSeKnZXufq-z_U0tTQJpF7JfNIaGQ,3504
77
+ tinybird/tb/modules/agent/explore_agent.py,sha256=W5pp99wixVSyb66qEVwBv8rZWpJ7JgzjJ_sN4d9u4Gg,3903
78
78
  tinybird/tb/modules/agent/memory.py,sha256=vBewB_64L_wHoT4tLT6UX2uxcHwSY880QZ26F9rPqXs,3793
79
+ tinybird/tb/modules/agent/mock_agent.py,sha256=zbAZfAqdSLUtMr2VqO0erWpzjT2F1tTcuYjvHb-gvbA,8023
79
80
  tinybird/tb/modules/agent/models.py,sha256=eokO8XlY-kVJOsbqiVporGUAOCyKAXCO5xgTEK9SM6Y,2208
80
81
  tinybird/tb/modules/agent/prompts.py,sha256=kc_47G9iEyf2p7_C-71Fa5JEwyH2RSYc4vzBHCc1JKE,39971
81
82
  tinybird/tb/modules/agent/testing_agent.py,sha256=AtwtJViH7805i7djyBgDb7SSUtDyJnw0TWJu6lBFsrg,2953
@@ -84,16 +85,16 @@ tinybird/tb/modules/agent/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
84
85
  tinybird/tb/modules/agent/tools/analyze.py,sha256=CR5LXg4fou-zYEksqnjpJ0icvxJVoKnTctoI1NRvqCM,3873
85
86
  tinybird/tb/modules/agent/tools/append.py,sha256=6uByExBpb9rVzB0tebWyLFbfkjEPSNxIGFbZrJTaGVI,8056
86
87
  tinybird/tb/modules/agent/tools/build.py,sha256=Hm-xDAP9ckMiKquT-DmDg5H0yxZefLOaWKANyoVSaEQ,846
87
- tinybird/tb/modules/agent/tools/datafile.py,sha256=LvfDtEdgEWAeirTcx7yO6mfYshEuhWr2M1hoISIbg-g,10641
88
- tinybird/tb/modules/agent/tools/deploy.py,sha256=uDhg5VHC5nEMMBt5LXpcicVepGz67R3p-_5QN45vCFE,1807
89
- tinybird/tb/modules/agent/tools/deploy_check.py,sha256=2Wr9hQfKPlhqhumOv5TNl_xFctvdq_DHZ2dI2h_LggY,1048
88
+ tinybird/tb/modules/agent/tools/datafile.py,sha256=cXLVKEXCL37UKGRRhVVMzDcIsU1IWPLoKciAOHTRtZY,10675
89
+ tinybird/tb/modules/agent/tools/deploy.py,sha256=6Vmm0lCG8XKE2iUF_ZJrOqXbTFhoe3anPzYCFehQ3_E,2027
90
+ tinybird/tb/modules/agent/tools/deploy_check.py,sha256=pE3d9TPtXVKZjYbU0G6ORAGI86lN5K_4JKUriClERbM,1229
90
91
  tinybird/tb/modules/agent/tools/diff_resource.py,sha256=_9xHcDzCTKk_E1wKQbuktVqV6U9sA0kqYaBxWvtliX0,2613
91
- tinybird/tb/modules/agent/tools/execute_query.py,sha256=59Z_9Rl1NPRxMI3e-rIG8kfKRWgDgFt4j2GYO_Tzu7w,8845
92
+ tinybird/tb/modules/agent/tools/execute_query.py,sha256=DL2jsZ0jaEqFIkGoiWfR-IUAwsgoF0D-_JUhq7xe4gA,9145
92
93
  tinybird/tb/modules/agent/tools/get_endpoint_stats.py,sha256=r2FrXg1L1s_Llr1tPdJ6k_gu6qw7qLsAXOkbz3eTk1g,2307
93
94
  tinybird/tb/modules/agent/tools/get_openapi_definition.py,sha256=4TIMO2XzHBMhpt9zIWRfjjPZbThT8r_iPS4CVHcItE0,2904
94
- tinybird/tb/modules/agent/tools/mock.py,sha256=RvdsKIr0vKEs91GuK5vKg0fDj8SI-cdcX4XqgvnSwuQ,4508
95
+ tinybird/tb/modules/agent/tools/mock.py,sha256=kBRRdMZny7dsU2ncZrPGLT6APVfVfB8tXUJwbV7WVmE,7022
95
96
  tinybird/tb/modules/agent/tools/plan.py,sha256=2KHLNkr2f1RfkbAR4mCVsv94LGosXd8-ky7v6BB1OtQ,985
96
- tinybird/tb/modules/agent/tools/request_endpoint.py,sha256=xseEDQez2xfnPWNOoGnRmHB2KR9WLCx_q-vzS6NtaOY,3972
97
+ tinybird/tb/modules/agent/tools/request_endpoint.py,sha256=bsLWrMn-ofJM3nn9vm8j_U8fdopVd3H5L0ii6ji-Kuw,4359
97
98
  tinybird/tb/modules/agent/tools/run_command.py,sha256=ypvIU0j1XVUWghqt-dpWHm3GQIYsZwE7kRHC3Wau_H0,1708
98
99
  tinybird/tb/modules/agent/tools/secret.py,sha256=wUeM-5CCjXiwLEF-H121VypOw3_77OMoZthJedPENl4,4254
99
100
  tinybird/tb/modules/agent/tools/test.py,sha256=4XuEWVHLOTSO51Z9xJ08dTjk0j3IWY_JlPtSBO5aaUs,10373
@@ -117,8 +118,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
117
118
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
118
119
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
119
120
  tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
120
- tinybird-0.0.1.dev282.dist-info/METADATA,sha256=zMcjvggtB0gbH4I8m8x4OSImErFK4X55gmDX2R7i9Y8,1811
121
- tinybird-0.0.1.dev282.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
122
- tinybird-0.0.1.dev282.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
123
- tinybird-0.0.1.dev282.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
124
- tinybird-0.0.1.dev282.dist-info/RECORD,,
121
+ tinybird-0.0.1.dev284.dist-info/METADATA,sha256=wuhdBNroFRewEN3GLUeK5WRSMTH6nXdkibWoT1kLoo8,1811
122
+ tinybird-0.0.1.dev284.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
123
+ tinybird-0.0.1.dev284.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
124
+ tinybird-0.0.1.dev284.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
125
+ tinybird-0.0.1.dev284.dist-info/RECORD,,