tinybird 0.0.1.dev246__py3-none-any.whl → 0.0.1.dev248__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.
- tinybird/ch_utils/constants.py +2 -0
- tinybird/prompts.py +2 -0
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/modules/agent/agent.py +107 -18
- tinybird/tb/modules/agent/models.py +6 -0
- tinybird/tb/modules/agent/prompts.py +57 -29
- tinybird/tb/modules/agent/tools/append.py +55 -0
- tinybird/tb/modules/agent/tools/build.py +1 -0
- tinybird/tb/modules/agent/tools/create_datafile.py +8 -3
- tinybird/tb/modules/agent/tools/deploy.py +1 -1
- tinybird/tb/modules/agent/tools/mock.py +59 -0
- tinybird/tb/modules/agent/tools/plan.py +1 -1
- tinybird/tb/modules/agent/tools/read_fixture_data.py +28 -0
- tinybird/tb/modules/agent/utils.py +296 -3
- tinybird/tb/modules/build.py +4 -1
- tinybird/tb/modules/build_common.py +2 -3
- tinybird/tb/modules/cli.py +9 -1
- tinybird/tb/modules/create.py +1 -1
- tinybird/tb/modules/feedback_manager.py +1 -0
- tinybird/tb/modules/llm.py +1 -1
- tinybird/tb/modules/login.py +6 -301
- tinybird/tb/modules/login_common.py +310 -0
- tinybird/tb/modules/mock.py +3 -69
- tinybird/tb/modules/mock_common.py +71 -0
- tinybird/tb/modules/project.py +9 -0
- {tinybird-0.0.1.dev246.dist-info → tinybird-0.0.1.dev248.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev246.dist-info → tinybird-0.0.1.dev248.dist-info}/RECORD +30 -25
- {tinybird-0.0.1.dev246.dist-info → tinybird-0.0.1.dev248.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev246.dist-info → tinybird-0.0.1.dev248.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev246.dist-info → tinybird-0.0.1.dev248.dist-info}/top_level.txt +0 -0
tinybird/ch_utils/constants.py
CHANGED
tinybird/prompts.py
CHANGED
|
@@ -776,6 +776,8 @@ datasource_instructions = """
|
|
|
776
776
|
- Use always json paths to define the schema. Example: `user_id` String `json:$.user_id`,
|
|
777
777
|
- Array columns are supported with a special syntax. Example: `items` Array(String) `json:$.items[:]`
|
|
778
778
|
- If the datasource is using an S3 or GCS connection, they need to set IMPORT_CONNECTION_NAME, IMPORT_BUCKET_URI and IMPORT_SCHEDULE (GCS @on-demand only, S3 supports @auto too)
|
|
779
|
+
- Unless the user asks for them, do not include ENGINE_PARTITION_KEY and ENGINE_PRIMARY_KEY.
|
|
780
|
+
- DateTime64 type without precision is not supported. Use DateTime64(3) instead.
|
|
779
781
|
</datasource_file_instructions>
|
|
780
782
|
"""
|
|
781
783
|
|
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.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '0.0.1.dev248'
|
|
8
|
+
__revision__ = '8eed30e'
|
|
@@ -2,6 +2,7 @@ import subprocess
|
|
|
2
2
|
import sys
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from functools import partial
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
import click
|
|
@@ -28,34 +29,41 @@ from tinybird.tb.client import TinyB
|
|
|
28
29
|
from tinybird.tb.modules.agent.animations import ThinkingAnimation
|
|
29
30
|
from tinybird.tb.modules.agent.banner import display_banner
|
|
30
31
|
from tinybird.tb.modules.agent.memory import clear_history, load_history
|
|
31
|
-
from tinybird.tb.modules.agent.models import create_model
|
|
32
|
+
from tinybird.tb.modules.agent.models import create_model, model_costs
|
|
32
33
|
from tinybird.tb.modules.agent.prompts import (
|
|
33
34
|
datafile_instructions,
|
|
34
35
|
plan_instructions,
|
|
35
36
|
resources_prompt,
|
|
36
37
|
sql_instructions,
|
|
37
38
|
)
|
|
39
|
+
from tinybird.tb.modules.agent.tools.append import append
|
|
38
40
|
from tinybird.tb.modules.agent.tools.build import build
|
|
39
41
|
from tinybird.tb.modules.agent.tools.create_datafile import create_datafile
|
|
40
42
|
from tinybird.tb.modules.agent.tools.deploy import deploy
|
|
41
43
|
from tinybird.tb.modules.agent.tools.deploy_check import deploy_check
|
|
42
44
|
from tinybird.tb.modules.agent.tools.explore import explore_data
|
|
45
|
+
from tinybird.tb.modules.agent.tools.mock import mock
|
|
43
46
|
from tinybird.tb.modules.agent.tools.plan import plan
|
|
44
47
|
from tinybird.tb.modules.agent.tools.preview_datafile import preview_datafile
|
|
48
|
+
from tinybird.tb.modules.agent.tools.read_fixture_data import read_fixture_data
|
|
45
49
|
from tinybird.tb.modules.agent.utils import TinybirdAgentContext
|
|
46
50
|
from tinybird.tb.modules.build_common import process as build_process
|
|
47
|
-
from tinybird.tb.modules.common import _get_tb_client
|
|
51
|
+
from tinybird.tb.modules.common import _analyze, _get_tb_client
|
|
52
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
48
53
|
from tinybird.tb.modules.deployment_common import create_deployment
|
|
49
|
-
from tinybird.tb.modules.exceptions import CLIBuildException
|
|
54
|
+
from tinybird.tb.modules.exceptions import CLIBuildException, CLIMockException
|
|
50
55
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
51
56
|
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
57
|
+
from tinybird.tb.modules.login_common import login
|
|
58
|
+
from tinybird.tb.modules.mock_common import append_mock_data, create_mock_data
|
|
52
59
|
from tinybird.tb.modules.project import Project
|
|
53
60
|
|
|
54
61
|
|
|
55
62
|
class TinybirdAgent:
|
|
56
|
-
def __init__(self, token: str, host: str, project: Project):
|
|
63
|
+
def __init__(self, token: str, host: str, project: Project, dangerously_skip_permissions: bool):
|
|
57
64
|
self.token = token
|
|
58
65
|
self.host = host
|
|
66
|
+
self.dangerously_skip_permissions = dangerously_skip_permissions
|
|
59
67
|
self.project = project
|
|
60
68
|
self.messages: list[ModelMessage] = []
|
|
61
69
|
self.agent = Agent(
|
|
@@ -68,7 +76,7 @@ You are an interactive CLI tool that helps users with data engineering tasks. Us
|
|
|
68
76
|
|
|
69
77
|
# Tone and style
|
|
70
78
|
You should be concise, direct, and to the point.
|
|
71
|
-
Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting.
|
|
79
|
+
Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting. Do not use emojis.
|
|
72
80
|
Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
|
|
73
81
|
If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
|
|
74
82
|
IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do.
|
|
@@ -94,6 +102,9 @@ You have access to the following tools:
|
|
|
94
102
|
5. `build` - Build the project.
|
|
95
103
|
6. `deploy` - Deploy the project to Tinybird Cloud.
|
|
96
104
|
7. `deploy_check` - Check if the project can be deployed to Tinybird Cloud before deploying it.
|
|
105
|
+
8. `mock` - Create mock data for a landing datasource.
|
|
106
|
+
9. `read_fixture_data` - Read a fixture data file present in the project folder.
|
|
107
|
+
10. `append` - Append existing fixture to a datasource.
|
|
97
108
|
|
|
98
109
|
# When creating or updating datafiles:
|
|
99
110
|
1. Use `plan` tool to plan the creation or update of resources.
|
|
@@ -106,6 +117,7 @@ You have access to the following tools:
|
|
|
106
117
|
8. If the datafile was created successfully, but the built failed, try to fix the error and repeat the process.
|
|
107
118
|
|
|
108
119
|
IMPORTANT: If the user cancels some of the steps or there is an error in file creation, DO NOT continue with the plan. Stop the process and wait for the user before using any other tool.
|
|
120
|
+
IMPORTANT: Every time you finish a plan and start a new resource creation or update process, create a new plan before starting with the changes.
|
|
109
121
|
|
|
110
122
|
# When planning the creation or update of resources:
|
|
111
123
|
{plan_instructions}
|
|
@@ -150,6 +162,9 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
|
150
162
|
Tool(build, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
151
163
|
Tool(deploy, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
152
164
|
Tool(deploy_check, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
165
|
+
Tool(mock, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
166
|
+
Tool(read_fixture_data, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
167
|
+
Tool(append, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
153
168
|
],
|
|
154
169
|
)
|
|
155
170
|
|
|
@@ -158,7 +173,7 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
|
158
173
|
return self.messages[-5:] if len(self.messages) > 5 else self.messages
|
|
159
174
|
|
|
160
175
|
def run(self, user_prompt: str, config: dict[str, Any], project: Project) -> None:
|
|
161
|
-
user_prompt = f"{user_prompt}\n\n
|
|
176
|
+
user_prompt = f"{user_prompt}\n\n{resources_prompt(project)}"
|
|
162
177
|
client = TinyB(token=self.token, host=self.host)
|
|
163
178
|
folder = self.project.folder
|
|
164
179
|
|
|
@@ -172,34 +187,71 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
|
172
187
|
build_project=partial(build_project, project=project, config=config),
|
|
173
188
|
deploy_project=partial(deploy_project, project=project, config=config),
|
|
174
189
|
deploy_check_project=partial(deploy_check_project, project=project, config=config),
|
|
190
|
+
mock_data=partial(mock_data, project=project, config=config),
|
|
191
|
+
append_data=partial(append_data, config=config),
|
|
192
|
+
analyze_fixture=partial(analyze_fixture, config=config),
|
|
175
193
|
get_project_files=project.get_project_files,
|
|
176
194
|
folder=folder,
|
|
177
195
|
thinking_animation=thinking_animation,
|
|
178
196
|
workspace_name=self.project.workspace_name,
|
|
197
|
+
dangerously_skip_permissions=self.dangerously_skip_permissions,
|
|
179
198
|
),
|
|
180
199
|
message_history=self.messages,
|
|
181
200
|
)
|
|
182
201
|
new_messages = result.new_messages()
|
|
183
202
|
self.messages.extend(new_messages)
|
|
184
203
|
thinking_animation.stop()
|
|
185
|
-
|
|
204
|
+
usage = result.usage()
|
|
205
|
+
request_tokens = usage.request_tokens or 0
|
|
206
|
+
response_tokens = usage.response_tokens or 0
|
|
207
|
+
total_tokens = usage.total_tokens or 0
|
|
208
|
+
cost = (
|
|
209
|
+
request_tokens * model_costs["input_cost_per_token"]
|
|
210
|
+
+ response_tokens * model_costs["output_cost_per_token"]
|
|
211
|
+
)
|
|
212
|
+
click.echo()
|
|
186
213
|
click.echo(result.output)
|
|
187
214
|
click.echo("\n")
|
|
215
|
+
click.echo(f"Input tokens: {request_tokens}")
|
|
216
|
+
click.echo(f"Output tokens: {response_tokens}")
|
|
217
|
+
click.echo(f"Total tokens: {total_tokens}")
|
|
218
|
+
click.echo(f"Cost: ${cost:.6f}")
|
|
188
219
|
|
|
189
220
|
|
|
190
|
-
def run_agent(config: dict[str, Any], project: Project):
|
|
191
|
-
|
|
221
|
+
def run_agent(config: dict[str, Any], project: Project, dangerously_skip_permissions: bool):
|
|
222
|
+
token = config.get("token", None)
|
|
223
|
+
host = config.get("host", None)
|
|
192
224
|
|
|
193
225
|
try:
|
|
194
|
-
token
|
|
195
|
-
|
|
196
|
-
|
|
226
|
+
if not token or not host:
|
|
227
|
+
yes = click.confirm(
|
|
228
|
+
FeedbackManager.warning(
|
|
229
|
+
message="Tinybird Code requires authentication. Do you want to authenticate now? [Y/n]"
|
|
230
|
+
),
|
|
231
|
+
prompt_suffix="",
|
|
232
|
+
show_default=False,
|
|
233
|
+
default=True,
|
|
234
|
+
)
|
|
235
|
+
if yes:
|
|
236
|
+
click.echo()
|
|
237
|
+
login(host, auth_host="https://cloud.tinybird.co", workspace=None, interactive=False, method="browser")
|
|
238
|
+
click.echo()
|
|
239
|
+
cli_config = CLIConfig.get_project_config()
|
|
240
|
+
token = cli_config.get_token()
|
|
241
|
+
host = cli_config.get_host()
|
|
242
|
+
|
|
243
|
+
if not token or not host:
|
|
244
|
+
click.echo(
|
|
245
|
+
FeedbackManager.error(message="Tinybird Code requires authentication. Run 'tb login' first.")
|
|
246
|
+
)
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
display_banner()
|
|
250
|
+
agent = TinybirdAgent(token, host, project, dangerously_skip_permissions)
|
|
197
251
|
click.echo()
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
else:
|
|
202
|
-
click.echo(FeedbackManager.info(message="Run /login to authenticate"))
|
|
252
|
+
click.echo(FeedbackManager.info(message="Describe what you want to create and I'll help you build it"))
|
|
253
|
+
click.echo(FeedbackManager.info(message="Run /help for more commands"))
|
|
254
|
+
|
|
203
255
|
click.echo()
|
|
204
256
|
|
|
205
257
|
except Exception as e:
|
|
@@ -211,7 +263,7 @@ def run_agent(config: dict[str, Any], project: Project):
|
|
|
211
263
|
while True:
|
|
212
264
|
try:
|
|
213
265
|
user_input = prompt(
|
|
214
|
-
[("class:prompt", "tb » ")],
|
|
266
|
+
[("class:prompt", f"tb ({project.workspace_name}) » ")],
|
|
215
267
|
history=load_history(),
|
|
216
268
|
cursor=CursorShape.BLOCK,
|
|
217
269
|
style=Style.from_dict(
|
|
@@ -289,3 +341,40 @@ def deploy_check_project(config: dict[str, Any], project: Project) -> None:
|
|
|
289
341
|
wait=True,
|
|
290
342
|
auto=True,
|
|
291
343
|
)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def append_data(config: dict[str, Any], datasource_name: str, path: str) -> None:
|
|
347
|
+
client = get_tinybird_local_client(config, test=False, silent=False)
|
|
348
|
+
append_mock_data(client, datasource_name, path)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def mock_data(
|
|
352
|
+
config: dict[str, Any], project: Project, datasource_name: str, data_format: str, rows: int
|
|
353
|
+
) -> list[dict[str, Any]]:
|
|
354
|
+
client = get_tinybird_local_client(config, test=False, silent=False)
|
|
355
|
+
cli_config = CLIConfig.get_project_config()
|
|
356
|
+
datasource_path = project.get_resource_path(datasource_name, "datasource")
|
|
357
|
+
|
|
358
|
+
if not datasource_path:
|
|
359
|
+
raise CLIMockException(f"Datasource {datasource_name} not found")
|
|
360
|
+
|
|
361
|
+
datasource_content = Path(datasource_path).read_text()
|
|
362
|
+
prompt = ""
|
|
363
|
+
return create_mock_data(
|
|
364
|
+
datasource_name,
|
|
365
|
+
datasource_content,
|
|
366
|
+
rows,
|
|
367
|
+
prompt,
|
|
368
|
+
cli_config,
|
|
369
|
+
config,
|
|
370
|
+
cli_config.get_user_token() or "",
|
|
371
|
+
client,
|
|
372
|
+
data_format,
|
|
373
|
+
project.folder,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def analyze_fixture(config: dict[str, Any], fixture_path: str) -> dict[str, Any]:
|
|
378
|
+
local_client = get_tinybird_local_client(config, test=False, silent=True)
|
|
379
|
+
meta, _data = _analyze(fixture_path, local_client, Path(fixture_path).suffix.lstrip("."))
|
|
380
|
+
return meta
|
|
@@ -8,7 +8,7 @@ When asked to create a plan, you MUST respond with this EXACT format and NOTHING
|
|
|
8
8
|
Plan description: [One sentence describing what will be built]
|
|
9
9
|
|
|
10
10
|
Steps:
|
|
11
|
-
1. Connection: [name] - [description]
|
|
11
|
+
1. Connection: [name] - [description]
|
|
12
12
|
2. Datasource: [name] - [description] - Depends on: [connection_name (optional)]
|
|
13
13
|
3. Endpoint: [name] - [description] - Depends on: [resources]
|
|
14
14
|
4. Materialized pipe: [name] - [description] - Depends on: [resources]
|
|
@@ -16,9 +16,15 @@ Steps:
|
|
|
16
16
|
6. Sink: [name] - [description] - Depends on: [resources]
|
|
17
17
|
7. Copy: [name] - [description] - Depends on: [resources]
|
|
18
18
|
8. Build project
|
|
19
|
+
9. Generate mock data: [datasource_name]
|
|
20
|
+
10. Append existing fixture: [fixture_pathname] - Target: [datasource_name]
|
|
19
21
|
|
|
22
|
+
<dev_notes>
|
|
20
23
|
You can skip steps where resources will not be created or updated.
|
|
21
|
-
Always add
|
|
24
|
+
Always add 'Build project' step after generating resources.
|
|
25
|
+
Always add 'Generate mock data' step after building project if a landing datasource was created.
|
|
26
|
+
Always add 'Append existing fixture' step after building project if a fixture file was provided at the beginning of the plan.
|
|
27
|
+
</dev_notes>
|
|
22
28
|
|
|
23
29
|
Resource dependencies:
|
|
24
30
|
[resource_name]: [resources]
|
|
@@ -89,31 +95,53 @@ datafile_instructions = """
|
|
|
89
95
|
|
|
90
96
|
def resources_prompt(project: Project) -> str:
|
|
91
97
|
files = project.get_project_files()
|
|
98
|
+
fixture_files = project.get_fixture_files()
|
|
92
99
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
100
|
+
resources_content = "# Existing resources in the project:\n"
|
|
101
|
+
if files:
|
|
102
|
+
paths = [Path(file_path) for file_path in files]
|
|
103
|
+
|
|
104
|
+
resources_content += "\n".join(
|
|
105
|
+
[
|
|
106
|
+
f"""
|
|
107
|
+
<resource>
|
|
108
|
+
<path>{file_path.relative_to(project.folder)}</path>
|
|
109
|
+
<type>{get_resource_type(file_path)}</type>
|
|
110
|
+
<name>{file_path.stem}</name>
|
|
111
|
+
<content>{file_path.read_text()}</content>
|
|
112
|
+
</resource>
|
|
113
|
+
"""
|
|
114
|
+
for file_path in paths
|
|
115
|
+
]
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
resources_content += "No resources found"
|
|
119
|
+
|
|
120
|
+
fixture_content = "# Fixture files in the project:\n"
|
|
121
|
+
if fixture_files:
|
|
122
|
+
paths = [Path(file_path) for file_path in fixture_files]
|
|
123
|
+
fixture_content += "\n".join(
|
|
124
|
+
[
|
|
125
|
+
f"""
|
|
126
|
+
<fixture>
|
|
127
|
+
<path>{file_path.relative_to(project.folder)}</path>
|
|
128
|
+
<name>{file_path.stem}</name>
|
|
129
|
+
</fixture>
|
|
130
|
+
"""
|
|
131
|
+
for file_path in paths
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
fixture_content += "No fixture files found"
|
|
136
|
+
|
|
137
|
+
return resources_content + "\n" + fixture_content
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_resource_type(path: Path) -> str:
|
|
141
|
+
if path.suffix.lower() == ".pipe":
|
|
142
|
+
return Project.get_pipe_type(str(path))
|
|
143
|
+
elif path.suffix.lower() == ".datasource":
|
|
144
|
+
return "datasource"
|
|
145
|
+
elif path.suffix.lower() == ".connection":
|
|
146
|
+
return "connection"
|
|
147
|
+
return "unknown"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from pydantic_ai import RunContext
|
|
3
|
+
|
|
4
|
+
from tinybird.tb.modules.agent.utils import TinybirdAgentContext, show_options
|
|
5
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_append_confirmation(datasource_name: str) -> bool:
|
|
9
|
+
"""Get user confirmation for appending existing fixture"""
|
|
10
|
+
while True:
|
|
11
|
+
result = show_options(
|
|
12
|
+
options=["Yes, append existing fixture", "No, and tell Tinybird Code what to do"],
|
|
13
|
+
title=f"Do you want to append existing fixture for datasource {datasource_name}?",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if result is None: # Cancelled
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
if result.startswith("Yes"):
|
|
20
|
+
return True
|
|
21
|
+
elif result.startswith("No"):
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def append(ctx: RunContext[TinybirdAgentContext], datasource_name: str, fixture_pathname: str) -> str:
|
|
28
|
+
"""Append existing fixture to a datasource
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
datasource_name: Name of the datasource to append fixture to
|
|
32
|
+
fixture_pathname: Path to the fixture file to append
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
str: Message indicating the success or failure of the appending
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
ctx.deps.thinking_animation.stop()
|
|
39
|
+
confirmation = ctx.deps.dangerously_skip_permissions or get_append_confirmation(datasource_name)
|
|
40
|
+
ctx.deps.thinking_animation.start()
|
|
41
|
+
|
|
42
|
+
if not confirmation:
|
|
43
|
+
return "User rejected appending existing fixture. Skipping..."
|
|
44
|
+
|
|
45
|
+
ctx.deps.thinking_animation.stop()
|
|
46
|
+
click.echo(FeedbackManager.highlight(message=f"\n» Appending {fixture_pathname} to {datasource_name}..."))
|
|
47
|
+
ctx.deps.append_data(datasource_name=datasource_name, path=fixture_pathname)
|
|
48
|
+
click.echo(FeedbackManager.success(message=f"✓ Data appended to {datasource_name}"))
|
|
49
|
+
ctx.deps.thinking_animation.start()
|
|
50
|
+
return f"Data appended to {datasource_name}"
|
|
51
|
+
except Exception as e:
|
|
52
|
+
ctx.deps.thinking_animation.stop()
|
|
53
|
+
click.echo(FeedbackManager.error(message=e))
|
|
54
|
+
ctx.deps.thinking_animation.start()
|
|
55
|
+
return f"Error appending fixture {fixture_pathname} to {datasource_name}: {e}"
|
|
@@ -9,6 +9,7 @@ def build(ctx: RunContext[TinybirdAgentContext]) -> str:
|
|
|
9
9
|
"""Build the project"""
|
|
10
10
|
try:
|
|
11
11
|
ctx.deps.thinking_animation.stop()
|
|
12
|
+
click.echo(FeedbackManager.highlight(message="\n» Building project..."))
|
|
12
13
|
ctx.deps.build_project(test=False, silent=False)
|
|
13
14
|
ctx.deps.thinking_animation.start()
|
|
14
15
|
return "Project built successfully"
|
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
import click
|
|
4
4
|
from pydantic_ai import RunContext
|
|
5
5
|
|
|
6
|
-
from tinybird.tb.modules.agent.utils import Datafile, TinybirdAgentContext, show_options
|
|
6
|
+
from tinybird.tb.modules.agent.utils import Datafile, TinybirdAgentContext, create_terminal_box, show_options
|
|
7
7
|
from tinybird.tb.modules.exceptions import CLIBuildException
|
|
8
8
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
9
9
|
|
|
@@ -39,11 +39,16 @@ def create_datafile(ctx: RunContext[TinybirdAgentContext], resource: Datafile) -
|
|
|
39
39
|
"""
|
|
40
40
|
try:
|
|
41
41
|
ctx.deps.thinking_animation.stop()
|
|
42
|
-
click.echo(resource.content)
|
|
43
42
|
resource.pathname = resource.pathname.removeprefix("/")
|
|
44
43
|
path = Path(ctx.deps.folder) / resource.pathname
|
|
44
|
+
content = resource.content
|
|
45
45
|
exists = str(path) in ctx.deps.get_project_files()
|
|
46
|
-
|
|
46
|
+
if exists:
|
|
47
|
+
content = create_terminal_box(path.read_text(), resource.content, title=resource.pathname)
|
|
48
|
+
else:
|
|
49
|
+
content = create_terminal_box(resource.content, title=resource.pathname)
|
|
50
|
+
click.echo(content)
|
|
51
|
+
confirmation = ctx.deps.dangerously_skip_permissions or get_resource_confirmation(resource, exists)
|
|
47
52
|
|
|
48
53
|
if not confirmation:
|
|
49
54
|
ctx.deps.thinking_animation.start()
|
|
@@ -28,7 +28,7 @@ def deploy(ctx: RunContext[TinybirdAgentContext]) -> str:
|
|
|
28
28
|
"""Deploy the project"""
|
|
29
29
|
try:
|
|
30
30
|
ctx.deps.thinking_animation.stop()
|
|
31
|
-
confirmation = get_deploy_confirmation()
|
|
31
|
+
confirmation = ctx.deps.dangerously_skip_permissions or get_deploy_confirmation()
|
|
32
32
|
ctx.deps.thinking_animation.start()
|
|
33
33
|
|
|
34
34
|
if not confirmation:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from pydantic_ai import RunContext
|
|
3
|
+
|
|
4
|
+
from tinybird.tb.modules.agent.utils import TinybirdAgentContext, show_options
|
|
5
|
+
from tinybird.tb.modules.datafile.fixture import persist_fixture
|
|
6
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_mock_confirmation(datasource_name: str) -> bool:
|
|
10
|
+
"""Get user confirmation for creating mock data"""
|
|
11
|
+
while True:
|
|
12
|
+
result = show_options(
|
|
13
|
+
options=["Yes, create mock data", "No, and tell Tinybird Code what to do"],
|
|
14
|
+
title=f"Do you want to generate mock data for datasource {datasource_name}?",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if result is None: # Cancelled
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
if result.startswith("Yes"):
|
|
21
|
+
return True
|
|
22
|
+
elif result.startswith("No"):
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def mock(ctx: RunContext[TinybirdAgentContext], datasource_name: str, data_format: str, rows: int) -> str:
|
|
29
|
+
"""Create mock data for a datasource
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
datasource_name: Name of the datasource to create mock data for
|
|
33
|
+
data_format: Format of the mock data to create. Options: ndjson, csv
|
|
34
|
+
rows: Number of rows to create. If not provided, the default is 10
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: Message indicating the success or failure of the mock data generation
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
ctx.deps.thinking_animation.stop()
|
|
41
|
+
confirmation = ctx.deps.dangerously_skip_permissions or get_mock_confirmation(datasource_name)
|
|
42
|
+
ctx.deps.thinking_animation.start()
|
|
43
|
+
|
|
44
|
+
if not confirmation:
|
|
45
|
+
return "User rejected mock data generation. Skipping..."
|
|
46
|
+
|
|
47
|
+
ctx.deps.thinking_animation.stop()
|
|
48
|
+
click.echo(FeedbackManager.highlight(message=f"\n» Generating mock data for {datasource_name}..."))
|
|
49
|
+
data = ctx.deps.mock_data(datasource_name=datasource_name, data_format=data_format, rows=rows)
|
|
50
|
+
fixture_path = persist_fixture(datasource_name, data, ctx.deps.folder, format=data_format)
|
|
51
|
+
ctx.deps.append_data(datasource_name=datasource_name, path=str(fixture_path))
|
|
52
|
+
click.echo(FeedbackManager.success(message=f"✓ Data generated for {datasource_name}"))
|
|
53
|
+
ctx.deps.thinking_animation.start()
|
|
54
|
+
return f"Mock data generated successfully for datasource {datasource_name}"
|
|
55
|
+
except Exception as e:
|
|
56
|
+
ctx.deps.thinking_animation.stop()
|
|
57
|
+
click.echo(FeedbackManager.error(message=e))
|
|
58
|
+
ctx.deps.thinking_animation.start()
|
|
59
|
+
return f"Error generating mock data: {e}"
|
|
@@ -35,7 +35,7 @@ def plan(ctx: RunContext[TinybirdAgentContext], plan: str) -> str:
|
|
|
35
35
|
try:
|
|
36
36
|
ctx.deps.thinking_animation.stop()
|
|
37
37
|
click.echo(plan)
|
|
38
|
-
confirmation = get_plan_confirmation()
|
|
38
|
+
confirmation = ctx.deps.dangerously_skip_permissions or get_plan_confirmation()
|
|
39
39
|
ctx.deps.thinking_animation.start()
|
|
40
40
|
|
|
41
41
|
if not confirmation:
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pydantic_ai import RunContext
|
|
5
|
+
|
|
6
|
+
from tinybird.tb.modules.agent.utils import TinybirdAgentContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def read_fixture_data(ctx: RunContext[TinybirdAgentContext], fixture_pathname: str):
|
|
10
|
+
"""Read fixture data in the project folder
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
fixture_pathname (str): a path to a fixture file. Required.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
str: The content of the fixture data file.
|
|
17
|
+
"""
|
|
18
|
+
fixture_path = Path(ctx.deps.folder) / fixture_pathname.lstrip("/")
|
|
19
|
+
|
|
20
|
+
if not fixture_path.exists():
|
|
21
|
+
return f"No fixture data found for {fixture_pathname}. Please check the name of the fixture and try again."
|
|
22
|
+
|
|
23
|
+
response = ctx.deps.analyze_fixture(fixture_path=str(fixture_path))
|
|
24
|
+
# limit content to first 10 rows
|
|
25
|
+
data = response["preview"]["data"][:10]
|
|
26
|
+
columns = response["analysis"]["columns"]
|
|
27
|
+
|
|
28
|
+
return f"#Result of analysis of {fixture_pathname}:\n##Columns:\n{json.dumps(columns)}\n##Data sample:\n{json.dumps(data)}"
|