tinybird 0.0.1.dev254__py3-none-any.whl → 0.0.1.dev256__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/prompts.py +1 -2
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/modules/agent/agent.py +104 -23
- tinybird/tb/modules/agent/memory.py +62 -0
- tinybird/tb/modules/agent/prompts.py +138 -28
- tinybird/tb/modules/agent/tools/append.py +27 -4
- tinybird/tb/modules/agent/tools/deploy.py +0 -2
- tinybird/tb/modules/agent/tools/deploy_check.py +0 -3
- tinybird/tb/modules/agent/tools/diff_resource.py +51 -0
- tinybird/tb/modules/agent/tools/execute_query.py +5 -4
- tinybird/tb/modules/agent/tools/get_endpoint_stats.py +1 -1
- tinybird/tb/modules/agent/tools/mock.py +1 -1
- tinybird/tb/modules/agent/tools/preview_datafile.py +1 -1
- tinybird/tb/modules/agent/tools/request_endpoint.py +4 -4
- tinybird/tb/modules/agent/utils.py +8 -2
- tinybird/tb/modules/common.py +17 -0
- {tinybird-0.0.1.dev254.dist-info → tinybird-0.0.1.dev256.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev254.dist-info → tinybird-0.0.1.dev256.dist-info}/RECORD +21 -20
- {tinybird-0.0.1.dev254.dist-info → tinybird-0.0.1.dev256.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev254.dist-info → tinybird-0.0.1.dev256.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev254.dist-info → tinybird-0.0.1.dev256.dist-info}/top_level.txt +0 -0
tinybird/prompts.py
CHANGED
|
@@ -689,7 +689,6 @@ materialized_pipe_instructions = """
|
|
|
689
689
|
- TYPE MATERIALIZED is the type of the pipe and it is mandatory for materialized pipes.
|
|
690
690
|
- The content of the .pipe file must follow the materialized_pipe_content format.
|
|
691
691
|
- Use State modifier for the aggregated columns in the pipe.
|
|
692
|
-
- Keep the SQL query simple and avoid using complex queries with joins, subqueries, etc.
|
|
693
692
|
</materialized_pipe_instructions>
|
|
694
693
|
<materialized_pipe_content>
|
|
695
694
|
NODE daily_sales
|
|
@@ -812,11 +811,11 @@ TYPE endpoint
|
|
|
812
811
|
"""
|
|
813
812
|
|
|
814
813
|
pipe_instructions = """
|
|
814
|
+
Follow these instructions when creating or updating any type of .pipe file:
|
|
815
815
|
<pipe_file_instructions>
|
|
816
816
|
- The pipe names must be unique.
|
|
817
817
|
- Nodes do NOT use the same name as the Pipe they belong to. So if the pipe name is "my_pipe", the nodes must be named different like "my_pipe_node_1", "my_pipe_node_2", etc.
|
|
818
818
|
- Node names MUST be different from the resource names in the project.
|
|
819
|
-
- Avoid more than one node per pipe unless it is really necessary or requested by the user.
|
|
820
819
|
- No indentation is allowed for property names: DESCRIPTION, NODE, SQL, TYPE, etc.
|
|
821
820
|
- Allowed TYPE values are: endpoint, copy, materialized, sink.
|
|
822
821
|
- Add always the output node in the TYPE section or in the last node of the pipe.
|
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.dev256'
|
|
8
|
+
__revision__ = '78e1011'
|
|
@@ -26,13 +26,14 @@ from tinybird.prompts import (
|
|
|
26
26
|
from tinybird.tb.client import TinyB
|
|
27
27
|
from tinybird.tb.modules.agent.animations import ThinkingAnimation
|
|
28
28
|
from tinybird.tb.modules.agent.banner import display_banner
|
|
29
|
-
from tinybird.tb.modules.agent.memory import clear_history
|
|
29
|
+
from tinybird.tb.modules.agent.memory import clear_history, clear_messages, load_messages, save_messages
|
|
30
30
|
from tinybird.tb.modules.agent.models import create_model, model_costs
|
|
31
31
|
from tinybird.tb.modules.agent.prompts import (
|
|
32
32
|
datafile_instructions,
|
|
33
33
|
endpoint_optimization_instructions,
|
|
34
34
|
plan_instructions,
|
|
35
35
|
resources_prompt,
|
|
36
|
+
sql_agent_instructions,
|
|
36
37
|
sql_instructions,
|
|
37
38
|
)
|
|
38
39
|
from tinybird.tb.modules.agent.tools.analyze import analyze_file, analyze_url
|
|
@@ -41,6 +42,7 @@ from tinybird.tb.modules.agent.tools.build import build
|
|
|
41
42
|
from tinybird.tb.modules.agent.tools.create_datafile import create_datafile
|
|
42
43
|
from tinybird.tb.modules.agent.tools.deploy import deploy
|
|
43
44
|
from tinybird.tb.modules.agent.tools.deploy_check import deploy_check
|
|
45
|
+
from tinybird.tb.modules.agent.tools.diff_resource import diff_resource
|
|
44
46
|
from tinybird.tb.modules.agent.tools.execute_query import execute_query
|
|
45
47
|
from tinybird.tb.modules.agent.tools.get_endpoint_stats import get_endpoint_stats
|
|
46
48
|
from tinybird.tb.modules.agent.tools.get_openapi_definition import get_openapi_definition
|
|
@@ -53,7 +55,7 @@ from tinybird.tb.modules.build_common import process as build_process
|
|
|
53
55
|
from tinybird.tb.modules.common import _analyze, _get_tb_client
|
|
54
56
|
from tinybird.tb.modules.config import CLIConfig
|
|
55
57
|
from tinybird.tb.modules.deployment_common import create_deployment
|
|
56
|
-
from tinybird.tb.modules.exceptions import CLIBuildException, CLIMockException
|
|
58
|
+
from tinybird.tb.modules.exceptions import CLIBuildException, CLIDeploymentException, CLIMockException
|
|
57
59
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
58
60
|
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
59
61
|
from tinybird.tb.modules.login_common import login
|
|
@@ -76,7 +78,8 @@ class TinybirdAgent:
|
|
|
76
78
|
self.host = host
|
|
77
79
|
self.dangerously_skip_permissions = dangerously_skip_permissions
|
|
78
80
|
self.project = project
|
|
79
|
-
|
|
81
|
+
# we load the last 5 messages to manage token usage
|
|
82
|
+
self.messages: list[ModelMessage] = load_messages()[-5:]
|
|
80
83
|
self.agent = Agent(
|
|
81
84
|
model=create_model(user_token, host, workspace_id),
|
|
82
85
|
deps_type=TinybirdAgentContext,
|
|
@@ -121,6 +124,7 @@ You have access to the following tools:
|
|
|
121
124
|
12. `get_openapi_definition` - Get the OpenAPI definition for all endpoints that are built/deployed to Tinybird Cloud or Local.
|
|
122
125
|
13. `execute_query` - Execute a query against Tinybird Cloud or Local.
|
|
123
126
|
13. `request_endpoint` - Request an endpoint against Tinybird Cloud or Local.
|
|
127
|
+
14. `diff_resource` - Diff the content of a resource in Tinybird Cloud vs Tinybird Local vs Project local file.
|
|
124
128
|
|
|
125
129
|
# When creating or updating datafiles:
|
|
126
130
|
1. Use `plan` tool to plan the creation or update of resources.
|
|
@@ -172,6 +176,7 @@ IMPORTANT: Every time you finish a plan and start a new resource creation or upd
|
|
|
172
176
|
{copy_pipe_instructions}
|
|
173
177
|
|
|
174
178
|
# Working with SQL queries:
|
|
179
|
+
{sql_agent_instructions}
|
|
175
180
|
{sql_instructions}
|
|
176
181
|
|
|
177
182
|
# Working with connections files:
|
|
@@ -182,6 +187,23 @@ Kafka: {kafka_connection_example}
|
|
|
182
187
|
S3: {s3_connection_example}
|
|
183
188
|
GCS: {gcs_connection_example}
|
|
184
189
|
|
|
190
|
+
# When executing a query or requesting/testing an endpoint:
|
|
191
|
+
- You need to be sure that the selected resource is updated to the last version in the environment you are working on.
|
|
192
|
+
- Use `diff_resource` tool to compare the content of the resource to compare the differences between environments.
|
|
193
|
+
- Project local file is the source of truth.
|
|
194
|
+
- If the resource is not present or updated to the last version in Tinybird Local, it means you need to build the project.
|
|
195
|
+
- If the resource is not present or updated to the last version in Tinybird Cloud, it means you need to deploy the project.
|
|
196
|
+
|
|
197
|
+
# How to use apppend tools:
|
|
198
|
+
- Use append as part of the creation of a new landing datasource if the user provided a file or an external url
|
|
199
|
+
- Use append if user explicitly asks for it
|
|
200
|
+
- Do not append data if user requests to test an endpoint
|
|
201
|
+
|
|
202
|
+
# How to use `mock` tool:
|
|
203
|
+
- Use `mock` tool as part of the creation of a new landing datasource if the user did not provided a file or an external url
|
|
204
|
+
- Use `mock` tool if user explicitly asks for it
|
|
205
|
+
- Do not use `mock` tool if user requests to test an endpoint.
|
|
206
|
+
|
|
185
207
|
# Info
|
|
186
208
|
Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
187
209
|
""",
|
|
@@ -208,6 +230,7 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
|
208
230
|
),
|
|
209
231
|
Tool(execute_query, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
210
232
|
Tool(request_endpoint, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
233
|
+
Tool(diff_resource, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
211
234
|
],
|
|
212
235
|
)
|
|
213
236
|
|
|
@@ -233,10 +256,16 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
|
233
256
|
mock_data=partial(mock_data, project=project, config=config),
|
|
234
257
|
append_data=partial(append_data, config=config),
|
|
235
258
|
analyze_fixture=partial(analyze_fixture, config=config),
|
|
236
|
-
|
|
237
|
-
|
|
259
|
+
execute_query_cloud=partial(execute_query_cloud, config=config),
|
|
260
|
+
execute_query_local=partial(execute_query_local, config=config),
|
|
238
261
|
request_endpoint_cloud=partial(request_endpoint_cloud, config=config),
|
|
239
262
|
request_endpoint_local=partial(request_endpoint_local, config=config),
|
|
263
|
+
get_datasource_datafile_cloud=partial(get_datasource_datafile_cloud, config=config),
|
|
264
|
+
get_datasource_datafile_local=partial(get_datasource_datafile_local, config=config),
|
|
265
|
+
get_pipe_datafile_cloud=partial(get_pipe_datafile_cloud, config=config),
|
|
266
|
+
get_pipe_datafile_local=partial(get_pipe_datafile_local, config=config),
|
|
267
|
+
get_connection_datafile_cloud=partial(get_connection_datafile_cloud, config=config),
|
|
268
|
+
get_connection_datafile_local=partial(get_connection_datafile_local, config=config),
|
|
240
269
|
get_project_files=project.get_project_files,
|
|
241
270
|
folder=folder,
|
|
242
271
|
thinking_animation=thinking_animation,
|
|
@@ -250,6 +279,7 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
|
250
279
|
)
|
|
251
280
|
new_messages = result.new_messages()
|
|
252
281
|
self.messages.extend(new_messages)
|
|
282
|
+
save_messages(new_messages)
|
|
253
283
|
thinking_animation.stop()
|
|
254
284
|
usage = result.usage()
|
|
255
285
|
request_tokens = usage.request_tokens or 0
|
|
@@ -293,9 +323,12 @@ def run_agent(
|
|
|
293
323
|
login(host, auth_host="https://cloud.tinybird.co", workspace=None, interactive=False, method="browser")
|
|
294
324
|
click.echo()
|
|
295
325
|
cli_config = CLIConfig.get_project_config()
|
|
326
|
+
config = {**config, **cli_config.to_dict()}
|
|
296
327
|
token = cli_config.get_token()
|
|
297
328
|
user_token = cli_config.get_user_token()
|
|
298
329
|
host = cli_config.get_host()
|
|
330
|
+
workspace_id = cli_config.get("id", "")
|
|
331
|
+
workspace_name = cli_config.get("name", "")
|
|
299
332
|
|
|
300
333
|
if not token or not host or not user_token:
|
|
301
334
|
click.echo(
|
|
@@ -339,6 +372,7 @@ def run_agent(
|
|
|
339
372
|
break
|
|
340
373
|
elif user_input.lower() == "/clear":
|
|
341
374
|
clear_history()
|
|
375
|
+
clear_messages()
|
|
342
376
|
continue
|
|
343
377
|
elif user_input.lower() == "/login":
|
|
344
378
|
click.echo()
|
|
@@ -381,26 +415,25 @@ def build_project(config: dict[str, Any], project: Project, silent: bool = True,
|
|
|
381
415
|
|
|
382
416
|
def deploy_project(config: dict[str, Any], project: Project) -> None:
|
|
383
417
|
client = _get_tb_client(config["token"], config["host"])
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
418
|
+
try:
|
|
419
|
+
create_deployment(
|
|
420
|
+
project=project,
|
|
421
|
+
client=client,
|
|
422
|
+
config=config,
|
|
423
|
+
wait=True,
|
|
424
|
+
auto=True,
|
|
425
|
+
allow_destructive_operations=False,
|
|
426
|
+
)
|
|
427
|
+
except SystemExit as e:
|
|
428
|
+
raise CLIDeploymentException(e.args[0])
|
|
392
429
|
|
|
393
430
|
|
|
394
431
|
def deploy_check_project(config: dict[str, Any], project: Project) -> None:
|
|
395
432
|
client = _get_tb_client(config["token"], config["host"])
|
|
396
|
-
|
|
397
|
-
project=project,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
check=True,
|
|
401
|
-
wait=True,
|
|
402
|
-
auto=True,
|
|
403
|
-
)
|
|
433
|
+
try:
|
|
434
|
+
create_deployment(project=project, client=client, config=config, check=True, wait=True, auto=True)
|
|
435
|
+
except SystemExit as e:
|
|
436
|
+
raise CLIDeploymentException(e.args[0])
|
|
404
437
|
|
|
405
438
|
|
|
406
439
|
def append_data(config: dict[str, Any], datasource_name: str, path: str) -> None:
|
|
@@ -444,12 +477,12 @@ def analyze_fixture(config: dict[str, Any], fixture_path: str, format: str = "js
|
|
|
444
477
|
return meta
|
|
445
478
|
|
|
446
479
|
|
|
447
|
-
def
|
|
480
|
+
def execute_query_cloud(config: dict[str, Any], query: str, pipe_name: Optional[str] = None) -> dict[str, Any]:
|
|
448
481
|
client = _get_tb_client(config["token"], config["host"])
|
|
449
482
|
return client.query(sql=query, pipeline=pipe_name)
|
|
450
483
|
|
|
451
484
|
|
|
452
|
-
def
|
|
485
|
+
def execute_query_local(config: dict[str, Any], query: str, pipe_name: Optional[str] = None) -> dict[str, Any]:
|
|
453
486
|
local_client = get_tinybird_local_client(config, test=False, silent=True)
|
|
454
487
|
return local_client.query(sql=query, pipeline=pipe_name)
|
|
455
488
|
|
|
@@ -466,3 +499,51 @@ def request_endpoint_local(
|
|
|
466
499
|
) -> dict[str, Any]:
|
|
467
500
|
local_client = get_tinybird_local_client(config, test=False, silent=True)
|
|
468
501
|
return local_client.pipe_data(endpoint_name, format="json", params=params)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def get_datasource_datafile_cloud(config: dict[str, Any], datasource_name: str) -> str:
|
|
505
|
+
try:
|
|
506
|
+
client = _get_tb_client(config["token"], config["host"])
|
|
507
|
+
return client.datasource_file(datasource_name)
|
|
508
|
+
except Exception:
|
|
509
|
+
return "Datasource not found"
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def get_datasource_datafile_local(config: dict[str, Any], datasource_name: str) -> str:
|
|
513
|
+
try:
|
|
514
|
+
local_client = get_tinybird_local_client(config, test=False, silent=True)
|
|
515
|
+
return local_client.datasource_file(datasource_name)
|
|
516
|
+
except Exception:
|
|
517
|
+
return "Datasource not found"
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def get_pipe_datafile_cloud(config: dict[str, Any], pipe_name: str) -> str:
|
|
521
|
+
try:
|
|
522
|
+
client = _get_tb_client(config["token"], config["host"])
|
|
523
|
+
return client.pipe_file(pipe_name)
|
|
524
|
+
except Exception:
|
|
525
|
+
return "Pipe not found"
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def get_pipe_datafile_local(config: dict[str, Any], pipe_name: str) -> str:
|
|
529
|
+
try:
|
|
530
|
+
local_client = get_tinybird_local_client(config, test=False, silent=True)
|
|
531
|
+
return local_client.pipe_file(pipe_name)
|
|
532
|
+
except Exception:
|
|
533
|
+
return "Pipe not found"
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def get_connection_datafile_cloud(config: dict[str, Any], connection_name: str) -> str:
|
|
537
|
+
try:
|
|
538
|
+
client = _get_tb_client(config["token"], config["host"])
|
|
539
|
+
return client.connection_file(connection_name)
|
|
540
|
+
except Exception:
|
|
541
|
+
return "Connection not found"
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def get_connection_datafile_local(config: dict[str, Any], connection_name: str) -> str:
|
|
545
|
+
try:
|
|
546
|
+
local_client = get_tinybird_local_client(config, test=False, silent=True)
|
|
547
|
+
return local_client.connection_file(connection_name)
|
|
548
|
+
except Exception:
|
|
549
|
+
return "Connection not found"
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
5
|
+
import click
|
|
4
6
|
from prompt_toolkit.history import FileHistory
|
|
7
|
+
from pydantic_ai.messages import ModelMessage, ModelMessagesTypeAdapter
|
|
8
|
+
from pydantic_core import to_jsonable_python
|
|
9
|
+
|
|
10
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
5
11
|
|
|
6
12
|
|
|
7
13
|
def get_history_file_path():
|
|
@@ -39,3 +45,59 @@ def clear_history():
|
|
|
39
45
|
"""Clear the history file"""
|
|
40
46
|
history_file = get_history_file_path()
|
|
41
47
|
history_file.unlink(missing_ok=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def clear_messages():
|
|
51
|
+
"""Clear the messages file"""
|
|
52
|
+
messages_file = get_messages_file_path()
|
|
53
|
+
messages_file.unlink(missing_ok=True)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_messages_file_path():
|
|
57
|
+
"""Get the history file path based on current working directory"""
|
|
58
|
+
# Get current working directory
|
|
59
|
+
cwd = Path.cwd()
|
|
60
|
+
|
|
61
|
+
# Get user's home directory
|
|
62
|
+
home = Path.home()
|
|
63
|
+
|
|
64
|
+
# Calculate relative path from home to current directory
|
|
65
|
+
try:
|
|
66
|
+
relative_path = cwd.relative_to(home)
|
|
67
|
+
except ValueError:
|
|
68
|
+
# If current directory is not under home, use absolute path components
|
|
69
|
+
relative_path = Path(*cwd.parts[1:]) if cwd.is_absolute() else cwd
|
|
70
|
+
|
|
71
|
+
# Create history directory structure
|
|
72
|
+
history_dir = home / ".tinybird" / "projects" / relative_path
|
|
73
|
+
history_dir.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
# Return history file path
|
|
76
|
+
return history_dir / "messages.json"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def load_messages() -> list[ModelMessage]:
|
|
80
|
+
try:
|
|
81
|
+
messages_file = get_messages_file_path()
|
|
82
|
+
messages_file.touch()
|
|
83
|
+
if not messages_file.exists():
|
|
84
|
+
messages_file.touch(exist_ok=True)
|
|
85
|
+
messages_file.write_text("[]")
|
|
86
|
+
return []
|
|
87
|
+
with open(messages_file, "r") as f:
|
|
88
|
+
messages_json = json.loads(f.read() or "[]")
|
|
89
|
+
return ModelMessagesTypeAdapter.validate_python(messages_json)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
click.echo(FeedbackManager.error(message=f"Could not load previous messages: {e}"))
|
|
92
|
+
messages_file.unlink(missing_ok=True)
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def save_messages(new_messages: list[ModelMessage]):
|
|
97
|
+
messages_file = get_messages_file_path()
|
|
98
|
+
messages_file.touch(exist_ok=True)
|
|
99
|
+
messages = load_messages()
|
|
100
|
+
messages.extend(new_messages)
|
|
101
|
+
messages_json = to_jsonable_python(messages)
|
|
102
|
+
with open(messages_file, "w") as f:
|
|
103
|
+
f.write(json.dumps(messages_json))
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic_ai import format_as_xml
|
|
2
5
|
|
|
3
6
|
from tinybird.tb.modules.project import Project
|
|
4
7
|
|
|
@@ -73,9 +76,10 @@ sql_instructions = """
|
|
|
73
76
|
- Use node names as table names only when nodes are present in the same file.
|
|
74
77
|
- Do not reference the current node name in the SQL.
|
|
75
78
|
- SQL queries only accept SELECT statements with conditions, aggregations, joins, etc.
|
|
76
|
-
- Do NOT use CREATE TABLE, INSERT INTO, CREATE DATABASE, etc.
|
|
79
|
+
- Do NOT use CREATE TABLE, INSERT INTO, CREATE DATABASE, SHOW TABLES, etc.
|
|
77
80
|
- Use ONLY SELECT statements in the SQL section.
|
|
78
81
|
- INSERT INTO is not supported in SQL section.
|
|
82
|
+
- Do NOT query system.<table_name> tables.
|
|
79
83
|
- When using functions try always ClickHouse functions first, then SQL functions.
|
|
80
84
|
- Parameters are never quoted in any case.
|
|
81
85
|
- 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')}})
|
|
@@ -101,38 +105,32 @@ def resources_prompt(project: Project) -> str:
|
|
|
101
105
|
|
|
102
106
|
resources_content = "# Existing resources in the project:\n"
|
|
103
107
|
if files:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"""
|
|
116
|
-
for file_path in paths
|
|
117
|
-
]
|
|
118
|
-
)
|
|
108
|
+
resources: list[dict[str, Any]] = []
|
|
109
|
+
for filename in files:
|
|
110
|
+
file_path = Path(filename)
|
|
111
|
+
resource = {
|
|
112
|
+
"path": str(file_path.relative_to(project.folder)),
|
|
113
|
+
"type": get_resource_type(file_path),
|
|
114
|
+
"name": file_path.stem,
|
|
115
|
+
"content": file_path.read_text(),
|
|
116
|
+
}
|
|
117
|
+
resources.append(resource)
|
|
118
|
+
resources_content = format_as_xml(resources, root_tag="resources", item_tag="resource")
|
|
119
119
|
else:
|
|
120
120
|
resources_content += "No resources found"
|
|
121
121
|
|
|
122
122
|
fixture_content = "# Fixture files in the project:\n"
|
|
123
123
|
if fixture_files:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
]
|
|
135
|
-
)
|
|
124
|
+
fixtures: list[dict[str, Any]] = []
|
|
125
|
+
for filename in fixture_files:
|
|
126
|
+
file_path = Path(filename)
|
|
127
|
+
fixture = {
|
|
128
|
+
"path": str(file_path.relative_to(project.folder)),
|
|
129
|
+
"name": file_path.stem,
|
|
130
|
+
}
|
|
131
|
+
fixtures.append(fixture)
|
|
132
|
+
fixture_content = format_as_xml(fixtures, root_tag="fixtures", item_tag="fixture")
|
|
133
|
+
|
|
136
134
|
else:
|
|
137
135
|
fixture_content += "No fixture files found"
|
|
138
136
|
|
|
@@ -384,3 +382,115 @@ SQL >
|
|
|
384
382
|
5. **JOIN carefully** - Consider alternatives and optimize when necessary
|
|
385
383
|
</endpoint_optimization_instructions>
|
|
386
384
|
"""
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
sql_agent_instructions = """
|
|
388
|
+
# SQL Best Practices Rules
|
|
389
|
+
|
|
390
|
+
## Core Principles
|
|
391
|
+
1. **The best data is the data you don't write** - Don't save unnecessary data
|
|
392
|
+
2. **The second best data is the one you don't read** - Filter as early as possible
|
|
393
|
+
3. **Sequential reads are much faster** - Use proper indexes and sorting keys
|
|
394
|
+
4. **The less data you process after read, the better** - Select only needed columns
|
|
395
|
+
5. **Perform complex operations later in the processing pipeline** - Filter before joins/aggregations
|
|
396
|
+
|
|
397
|
+
## SQL Query Rules
|
|
398
|
+
|
|
399
|
+
### 1. Filter Placement Rules
|
|
400
|
+
- **ALWAYS** apply WHERE filters before ORDER BY clauses
|
|
401
|
+
- **ALWAYS** apply WHERE filters before GROUP BY operations
|
|
402
|
+
- **ALWAYS** filter data at the earliest possible point in the query
|
|
403
|
+
- **NEVER** sort data before filtering it
|
|
404
|
+
|
|
405
|
+
### 2. Column Selection Rules
|
|
406
|
+
- **NEVER** use SELECT * in production queries
|
|
407
|
+
- **ALWAYS** specify only the columns you need
|
|
408
|
+
- **ALWAYS** minimize the number of columns retrieved to reduce memory usage
|
|
409
|
+
|
|
410
|
+
### 3. Sorting and Index Rules
|
|
411
|
+
- **ALWAYS** filter by ENGINE_SORTING_KEY columns first (typically date/time columns)
|
|
412
|
+
- **ALWAYS** order filtering conditions from most to least selective
|
|
413
|
+
- **ALWAYS** use columns in ENGINE_SORTING_KEY for WHERE clauses when possible
|
|
414
|
+
- **NEVER** use functions on indexed columns in WHERE clauses (e.g., avoid DATE_FORMAT, EXTRACT)
|
|
415
|
+
|
|
416
|
+
### 4. Join Optimization Rules
|
|
417
|
+
- **ALWAYS** pre-filter data before JOIN operations
|
|
418
|
+
- **NEVER** join tables with more than 1 million rows without filtering
|
|
419
|
+
- **ALWAYS** filter the right-side table in joins using subqueries
|
|
420
|
+
- **PREFERRED** pattern for large joins:
|
|
421
|
+
```sql
|
|
422
|
+
-- Good: Pre-filter right table
|
|
423
|
+
FROM left_table AS left
|
|
424
|
+
INNER JOIN (
|
|
425
|
+
SELECT id, column FROM right_table
|
|
426
|
+
WHERE id IN (SELECT id FROM left_table)
|
|
427
|
+
) AS right ON left.id = right.id
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### 5. Aggregation Rules
|
|
431
|
+
- **NEVER** use nested aggregate functions (e.g., MAX(AVG(column)))
|
|
432
|
+
- **ALWAYS** use subqueries instead of nested aggregates
|
|
433
|
+
- **ALWAYS** filter data before GROUP BY operations
|
|
434
|
+
- **ALWAYS** perform aggregations as late as possible in the query
|
|
435
|
+
|
|
436
|
+
### 6. Complex Operations Order
|
|
437
|
+
- **ALWAYS** follow this operation order:
|
|
438
|
+
1. Filter (WHERE)
|
|
439
|
+
2. Select only needed columns
|
|
440
|
+
3. Join (if necessary)
|
|
441
|
+
4. Group/Aggregate (if necessary)
|
|
442
|
+
5. Sort (ORDER BY)
|
|
443
|
+
6. Limit
|
|
444
|
+
|
|
445
|
+
### 7. Aggregate Function Rules
|
|
446
|
+
- **ALWAYS** use -Merge combinators (countMerge, avgMerge, etc.) when querying AggregateFunction columns
|
|
447
|
+
- **ALWAYS** apply -Merge functions as late as possible in the pipeline
|
|
448
|
+
- **NEVER** select AggregateFunction columns without the appropriate -Merge combinator
|
|
449
|
+
|
|
450
|
+
### 8. Performance Rules
|
|
451
|
+
- **AVOID** full table scans - always include WHERE clauses
|
|
452
|
+
- **AVOID** reading more than 1GB of data in a single query
|
|
453
|
+
- **AVOID** operations that load large datasets into memory
|
|
454
|
+
- **MINIMIZE** the number of rows processed at each step
|
|
455
|
+
|
|
456
|
+
### 9. Memory Optimization Rules
|
|
457
|
+
- **REDUCE** column count when hitting memory limits
|
|
458
|
+
- **AVOID** cross JOINs that generate excessive rows
|
|
459
|
+
- **FILTER** before massive GROUP BY operations
|
|
460
|
+
- **CHUNK** large populate operations (they run in 1M row chunks)
|
|
461
|
+
|
|
462
|
+
### 10. Query Pattern Examples
|
|
463
|
+
|
|
464
|
+
**BAD Pattern - Filtering after sorting:**
|
|
465
|
+
```sql
|
|
466
|
+
SELECT * FROM table ORDER BY date WHERE condition = true
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**GOOD Pattern - Filtering before sorting:**
|
|
470
|
+
```sql
|
|
471
|
+
SELECT column1, column2 FROM table WHERE condition = true ORDER BY date
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**BAD Pattern - Nested aggregates:**
|
|
475
|
+
```sql
|
|
476
|
+
SELECT MAX(AVG(amount)) FROM table
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**GOOD Pattern - Using subquery:**
|
|
480
|
+
```sql
|
|
481
|
+
SELECT MAX(avg_amount) FROM (SELECT AVG(amount) as avg_amount FROM table)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**BAD Pattern - Unfiltered join:**
|
|
485
|
+
```sql
|
|
486
|
+
SELECT * FROM small_table JOIN huge_table ON small_table.id = huge_table.id
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**GOOD Pattern - Pre-filtered join:**
|
|
490
|
+
```sql
|
|
491
|
+
SELECT needed_columns
|
|
492
|
+
FROM small_table
|
|
493
|
+
JOIN (SELECT id, col FROM huge_table WHERE id IN (SELECT id FROM small_table)) filtered
|
|
494
|
+
ON small_table.id = filtered.id
|
|
495
|
+
```
|
|
496
|
+
"""
|
|
@@ -41,10 +41,12 @@ def append_file(ctx: RunContext[TinybirdAgentContext], datasource_name: str, fix
|
|
|
41
41
|
ctx.deps.thinking_animation.start()
|
|
42
42
|
return f"Data appended to {datasource_name}"
|
|
43
43
|
except Exception as e:
|
|
44
|
+
error_message = str(e)
|
|
44
45
|
ctx.deps.thinking_animation.stop()
|
|
45
|
-
click.echo(FeedbackManager.error(message=
|
|
46
|
+
click.echo(FeedbackManager.error(message=error_message))
|
|
47
|
+
error_message = handle_quarantine_error(ctx, error_message, datasource_name)
|
|
46
48
|
ctx.deps.thinking_animation.start()
|
|
47
|
-
return f"Error appending fixture {fixture_pathname} to {datasource_name}: {
|
|
49
|
+
return f"Error appending fixture {fixture_pathname} to {datasource_name}: {error_message}"
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def append_url(ctx: RunContext[TinybirdAgentContext], datasource_name: str, fixture_url: str) -> str:
|
|
@@ -81,7 +83,28 @@ def append_url(ctx: RunContext[TinybirdAgentContext], datasource_name: str, fixt
|
|
|
81
83
|
ctx.deps.thinking_animation.start()
|
|
82
84
|
return f"Data appended to {datasource_name}"
|
|
83
85
|
except Exception as e:
|
|
86
|
+
error_message = str(e)
|
|
84
87
|
ctx.deps.thinking_animation.stop()
|
|
85
|
-
click.echo(FeedbackManager.error(message=
|
|
88
|
+
click.echo(FeedbackManager.error(message=error_message))
|
|
89
|
+
error_message = handle_quarantine_error(ctx, error_message, datasource_name)
|
|
86
90
|
ctx.deps.thinking_animation.start()
|
|
87
|
-
return f"Error appending URL {fixture_url} to {datasource_name}: {
|
|
91
|
+
return f"Error appending URL {fixture_url} to {datasource_name}: {error_message}"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def handle_quarantine_error(ctx: RunContext[TinybirdAgentContext], error_message: str, datasource_name: str) -> str:
|
|
95
|
+
try:
|
|
96
|
+
if "in quarantine" in error_message:
|
|
97
|
+
click.echo(FeedbackManager.highlight(message=f"\n» Looking for errors in {datasource_name}_quarantine..."))
|
|
98
|
+
query = (
|
|
99
|
+
f"select * from {datasource_name}_quarantine order by insertion_date desc limit 5 FORMAT CSVWithNames"
|
|
100
|
+
)
|
|
101
|
+
quarantine_data = ctx.deps.execute_query_local(query=query)
|
|
102
|
+
error_message = (
|
|
103
|
+
error_message
|
|
104
|
+
+ f"\nThese are the first 5 rows of the quarantine table for datasource '{datasource_name}':\n{quarantine_data}. Use again `mock` tool but add this issue to the context."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
except Exception as quarantine_error:
|
|
108
|
+
error_message = error_message + f"\nError accessing to {datasource_name}_quarantine: {quarantine_error}"
|
|
109
|
+
|
|
110
|
+
return error_message
|
|
@@ -30,7 +30,5 @@ def deploy(ctx: RunContext[TinybirdAgentContext]) -> str:
|
|
|
30
30
|
ctx.deps.thinking_animation.start()
|
|
31
31
|
return "Project deployed successfully"
|
|
32
32
|
except Exception as e:
|
|
33
|
-
ctx.deps.thinking_animation.stop()
|
|
34
|
-
click.echo(FeedbackManager.error(message=e))
|
|
35
33
|
ctx.deps.thinking_animation.start()
|
|
36
34
|
return f"Error depoying project: {e}"
|
|
@@ -2,7 +2,6 @@ import click
|
|
|
2
2
|
from pydantic_ai import RunContext
|
|
3
3
|
|
|
4
4
|
from tinybird.tb.modules.agent.utils import TinybirdAgentContext, show_confirmation, show_input
|
|
5
|
-
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
def deploy_check(ctx: RunContext[TinybirdAgentContext]) -> str:
|
|
@@ -28,7 +27,5 @@ def deploy_check(ctx: RunContext[TinybirdAgentContext]) -> str:
|
|
|
28
27
|
ctx.deps.thinking_animation.start()
|
|
29
28
|
return "Project can be deployed"
|
|
30
29
|
except Exception as e:
|
|
31
|
-
ctx.deps.thinking_animation.stop()
|
|
32
|
-
click.echo(FeedbackManager.error(message=e))
|
|
33
30
|
ctx.deps.thinking_animation.start()
|
|
34
31
|
return f"Project cannot be deployed: {e}"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from pydantic_ai import RunContext
|
|
5
|
+
|
|
6
|
+
from tinybird.tb.modules.agent.utils import Datafile, TinybirdAgentContext
|
|
7
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def diff_resource(ctx: RunContext[TinybirdAgentContext], resource: Datafile) -> str:
|
|
11
|
+
"""Diff the content of a resource in Tinybird Cloud vs Tinybird Local vs Project local file
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
resource (Datafile): The resource to diff. Required.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Datafile: The diff of the resource.
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
ctx.deps.thinking_animation.stop()
|
|
21
|
+
click.echo(
|
|
22
|
+
FeedbackManager.highlight(message=f"\n» Comparing content of {resource.pathname} with Tinybird Cloud")
|
|
23
|
+
)
|
|
24
|
+
resource.pathname = resource.pathname.removeprefix("/")
|
|
25
|
+
project_file_path = Path(ctx.deps.folder) / resource.pathname
|
|
26
|
+
if not project_file_path.exists():
|
|
27
|
+
raise Exception(f"Resource {resource.pathname} not found in project")
|
|
28
|
+
|
|
29
|
+
project_file_content = project_file_path.read_text()
|
|
30
|
+
if resource.type == "datasource":
|
|
31
|
+
cloud_content = ctx.deps.get_datasource_datafile_cloud(datasource_name=resource.name)
|
|
32
|
+
local_content = ctx.deps.get_datasource_datafile_local(datasource_name=resource.name)
|
|
33
|
+
elif resource.type == "connection":
|
|
34
|
+
cloud_content = ctx.deps.get_connection_datafile_cloud(connection_name=resource.name)
|
|
35
|
+
local_content = ctx.deps.get_connection_datafile_local(connection_name=resource.name)
|
|
36
|
+
elif resource.type in ["endpoint", "materialized", "sink", "copy"]:
|
|
37
|
+
cloud_content = ctx.deps.get_pipe_datafile_cloud(pipe_name=resource.name)
|
|
38
|
+
local_content = ctx.deps.get_pipe_datafile_local(pipe_name=resource.name)
|
|
39
|
+
else:
|
|
40
|
+
raise Exception(f"{resource.type} is not a valid extension")
|
|
41
|
+
|
|
42
|
+
needs_to_build = project_file_content != local_content
|
|
43
|
+
needs_to_deploy = project_file_content != cloud_content
|
|
44
|
+
ctx.deps.thinking_animation.start()
|
|
45
|
+
diff = f"# Diff of resource {resource.name}:\n"
|
|
46
|
+
diff += f"## Tinybird Cloud: {'Deploy needed. Resource does not exist or needs to be updated. Run `deploy` tool to deploy the resource.' if needs_to_deploy else 'Nothing to deploy.'}\n"
|
|
47
|
+
diff += f"## Tinybird Local: {'Build needed. Resource does not exist or needs to be updated. Run `build` tool to build the resource.' if needs_to_build else 'Nothing to build.'}\n"
|
|
48
|
+
return diff
|
|
49
|
+
except Exception as e:
|
|
50
|
+
ctx.deps.thinking_animation.start()
|
|
51
|
+
return f"Could not diff resource {resource.pathname}: {e}"
|
|
@@ -3,7 +3,7 @@ import humanfriendly
|
|
|
3
3
|
from pydantic_ai import RunContext
|
|
4
4
|
|
|
5
5
|
from tinybird.tb.modules.agent.utils import TinybirdAgentContext
|
|
6
|
-
from tinybird.tb.modules.common import
|
|
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
9
|
|
|
@@ -32,7 +32,7 @@ def execute_query(ctx: RunContext[TinybirdAgentContext], query: str, task: str,
|
|
|
32
32
|
else:
|
|
33
33
|
query = f"SELECT * FROM ({query}) {query_format}"
|
|
34
34
|
|
|
35
|
-
execute_query = ctx.deps.
|
|
35
|
+
execute_query = ctx.deps.execute_query_cloud if cloud else ctx.deps.execute_query_local
|
|
36
36
|
result = execute_query(query=query)
|
|
37
37
|
stats = result["statistics"]
|
|
38
38
|
seconds = stats["elapsed"]
|
|
@@ -44,9 +44,10 @@ def execute_query(ctx: RunContext[TinybirdAgentContext], query: str, task: str,
|
|
|
44
44
|
if not result["data"]:
|
|
45
45
|
click.echo(FeedbackManager.info_no_rows())
|
|
46
46
|
else:
|
|
47
|
-
|
|
48
|
-
data=[d.values() for d in result["data"]], column_names=result["data"][0].keys()
|
|
47
|
+
echo_safe_humanfriendly_tables_format_pretty_table(
|
|
48
|
+
data=[d.values() for d in result["data"][:10]], column_names=result["data"][0].keys()
|
|
49
49
|
)
|
|
50
|
+
click.echo("Showing first 10 results\n")
|
|
50
51
|
ctx.deps.thinking_animation.start()
|
|
51
52
|
result["data"] = result["data"][:10]
|
|
52
53
|
return f"Result for task '{task}' in {cloud_or_local} environment: {result}. The user is being shown the full result in the console but this message only contains the first 10 rows."
|
|
@@ -42,7 +42,7 @@ def get_endpoint_stats(
|
|
|
42
42
|
FORMAT JSON
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
-
execute_query = ctx.deps.
|
|
45
|
+
execute_query = ctx.deps.execute_query_cloud if cloud else ctx.deps.execute_query_local
|
|
46
46
|
|
|
47
47
|
result = execute_query(query=query)
|
|
48
48
|
click.echo(FeedbackManager.success(message="✓ Done!"))
|
|
@@ -60,7 +60,7 @@ def mock(
|
|
|
60
60
|
FeedbackManager.highlight(message=f"\n» Looking for errors in {datasource_name}_quarantine...")
|
|
61
61
|
)
|
|
62
62
|
query = f"select * from {datasource_name}_quarantine order by insertion_date desc limit 5 FORMAT CSVWithNames"
|
|
63
|
-
quarantine_data = ctx.deps.
|
|
63
|
+
quarantine_data = ctx.deps.execute_query_local(query=query)
|
|
64
64
|
error_message = (
|
|
65
65
|
error_message
|
|
66
66
|
+ f"\nThese are the first 5 rows of the quarantine table for datasource '{datasource_name}':\n{quarantine_data}. Use again `mock` tool but add this issue to the context."
|
|
@@ -5,7 +5,7 @@ import humanfriendly
|
|
|
5
5
|
from pydantic_ai import RunContext
|
|
6
6
|
|
|
7
7
|
from tinybird.tb.modules.agent.utils import TinybirdAgentContext
|
|
8
|
-
from tinybird.tb.modules.common import
|
|
8
|
+
from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_pretty_table
|
|
9
9
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
10
10
|
|
|
11
11
|
|
|
@@ -48,10 +48,10 @@ def request_endpoint(
|
|
|
48
48
|
if not result["data"]:
|
|
49
49
|
click.echo(FeedbackManager.info_no_rows())
|
|
50
50
|
else:
|
|
51
|
-
|
|
52
|
-
data=[d.values() for d in result["data"][:
|
|
51
|
+
echo_safe_humanfriendly_tables_format_pretty_table(
|
|
52
|
+
data=[d.values() for d in result["data"][:10]], column_names=result["data"][0].keys()
|
|
53
53
|
)
|
|
54
|
-
click.echo("Showing first
|
|
54
|
+
click.echo("Showing first 10 results\n")
|
|
55
55
|
ctx.deps.thinking_animation.start()
|
|
56
56
|
return f"Result for endpoint {endpoint_name} with params {params} in {cloud_or_local} environment: {result}. Do not show result is already shown in the console."
|
|
57
57
|
except Exception as e:
|
|
@@ -41,10 +41,16 @@ class TinybirdAgentContext(BaseModel):
|
|
|
41
41
|
mock_data: Callable[..., list[dict[str, Any]]]
|
|
42
42
|
append_data: Callable[..., None]
|
|
43
43
|
analyze_fixture: Callable[..., dict[str, Any]]
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
execute_query_cloud: Callable[..., dict[str, Any]]
|
|
45
|
+
execute_query_local: Callable[..., dict[str, Any]]
|
|
46
46
|
request_endpoint_cloud: Callable[..., dict[str, Any]]
|
|
47
47
|
request_endpoint_local: Callable[..., dict[str, Any]]
|
|
48
|
+
get_datasource_datafile_cloud: Callable[..., str]
|
|
49
|
+
get_datasource_datafile_local: Callable[..., str]
|
|
50
|
+
get_pipe_datafile_cloud: Callable[..., str]
|
|
51
|
+
get_pipe_datafile_local: Callable[..., str]
|
|
52
|
+
get_connection_datafile_cloud: Callable[..., str]
|
|
53
|
+
get_connection_datafile_local: Callable[..., str]
|
|
48
54
|
dangerously_skip_permissions: bool
|
|
49
55
|
token: str
|
|
50
56
|
user_token: str
|
tinybird/tb/modules/common.py
CHANGED
|
@@ -124,6 +124,23 @@ def echo_safe_humanfriendly_tables_format_smart_table(data: Iterable[Any], colum
|
|
|
124
124
|
raise exc
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
def echo_safe_humanfriendly_tables_format_pretty_table(data: Iterable[Any], column_names: List[str]) -> None:
|
|
128
|
+
"""
|
|
129
|
+
There is a bug in the humanfriendly library: it breaks to render the small table for small terminals
|
|
130
|
+
(`format_robust_table`) if we call format_smart_table with an empty dataset. This catches the error and prints
|
|
131
|
+
what we would call an empty "robust_table".
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
click.echo(humanfriendly.tables.format_pretty_table(data, column_names=column_names))
|
|
135
|
+
except ValueError as exc:
|
|
136
|
+
if str(exc) == "max() arg is an empty sequence":
|
|
137
|
+
click.echo("------------")
|
|
138
|
+
click.echo("Empty")
|
|
139
|
+
click.echo("------------")
|
|
140
|
+
else:
|
|
141
|
+
raise exc
|
|
142
|
+
|
|
143
|
+
|
|
127
144
|
def echo_safe_format_table(data: Iterable[Any], columns) -> None:
|
|
128
145
|
"""
|
|
129
146
|
There is a bug in the humanfriendly library: it breaks to render the small table for small terminals
|
|
@@ -3,7 +3,7 @@ tinybird/context.py,sha256=FfqYfrGX_I7PKGTQo93utaKPDNVYWelg4Hsp3evX5wM,1291
|
|
|
3
3
|
tinybird/datatypes.py,sha256=r4WCvspmrXTJHiPjjyOTiZyZl31FO3Ynkwq4LQsYm6E,11059
|
|
4
4
|
tinybird/feedback_manager.py,sha256=1INQFfRfuMCb9lfB8KNf4r6qC2khW568hoHjtk-wshI,69305
|
|
5
5
|
tinybird/git_settings.py,sha256=Sw_8rGmribEFJ4Z_6idrVytxpFYk7ez8ei0qHULzs3E,3934
|
|
6
|
-
tinybird/prompts.py,sha256=
|
|
6
|
+
tinybird/prompts.py,sha256=9zXOYI8TzFAyp_bolqtxhtaqg0c1u4AJolFM0o3aYiY,45393
|
|
7
7
|
tinybird/sql.py,sha256=BufnOgclQokDyihtuXesOwHBsebN6wRXIxO5wKRkOwE,48299
|
|
8
8
|
tinybird/sql_template.py,sha256=AezE1o6_BzbHFi0J9OIqTrXQ5WvoX5eNVq4QCbFjGcs,100338
|
|
9
9
|
tinybird/sql_template_fmt.py,sha256=KUHdj5rYCYm_rKKdXYSJAE9vIyXUQLB0YSZnUXHeBlY,10196
|
|
@@ -17,7 +17,7 @@ tinybird/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1w
|
|
|
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=
|
|
20
|
+
tinybird/tb/__cli__.py,sha256=BW9MQs4o1rm0FvIF2XLehXnuzpfC7A9JS2Fu2hJCJ2Y,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
|
|
@@ -26,7 +26,7 @@ tinybird/tb/modules/build.py,sha256=efD-vamK1NPaDo9R86Hn8be2DYoW0Hh5bZiH7knK5dk,
|
|
|
26
26
|
tinybird/tb/modules/build_common.py,sha256=rWhemU8bk0ZE2eiwZDaTmV9cPabDGGlyc2WnRxfhT0M,12859
|
|
27
27
|
tinybird/tb/modules/cicd.py,sha256=0KLKccha9IP749QvlXBmzdWv1On3mFwMY4DUcJlBxiE,7326
|
|
28
28
|
tinybird/tb/modules/cli.py,sha256=1kErLFhxgMWldbE7P4-bkPUcug8ymRyXDHRG9-vGb_4,16755
|
|
29
|
-
tinybird/tb/modules/common.py,sha256=
|
|
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
|
|
@@ -68,28 +68,29 @@ tinybird/tb/modules/watch.py,sha256=No0bK1M1_3CYuMaIgylxf7vYFJ72lTJe3brz6xQ-mJo,
|
|
|
68
68
|
tinybird/tb/modules/workspace.py,sha256=Q_8HcxMsNg8QG9aBlwcWS2umrDP5IkTIHqqz3sfmGuc,11341
|
|
69
69
|
tinybird/tb/modules/workspace_members.py,sha256=5JdkJgfuEwbq-t6vxkBhYwgsiTDxF790wsa6Xfif9nk,8608
|
|
70
70
|
tinybird/tb/modules/agent/__init__.py,sha256=i3oe3vDIWWPaicdCM0zs7D7BJ1W0k7th93ooskHAV00,54
|
|
71
|
-
tinybird/tb/modules/agent/agent.py,sha256=
|
|
71
|
+
tinybird/tb/modules/agent/agent.py,sha256=mCQRrrheH7mEgRg6BF19lSLt2XzaD_mw6mhyugTQLyw,26412
|
|
72
72
|
tinybird/tb/modules/agent/animations.py,sha256=4WOC5_2BracttmMCrV0H91tXfWcUzQHBUaIJc5FA7tE,3490
|
|
73
73
|
tinybird/tb/modules/agent/banner.py,sha256=KX_e467uiy1gWOZ4ofTZt0GCFGQqHQ_8Ob27XLQqda0,3053
|
|
74
|
-
tinybird/tb/modules/agent/memory.py,sha256=
|
|
74
|
+
tinybird/tb/modules/agent/memory.py,sha256=O6Kumn9AyKxcTkhI45yjAUZ3ZIAibLOcNWoiEuLYeqY,3245
|
|
75
75
|
tinybird/tb/modules/agent/models.py,sha256=LW1D27gjcd_jwFmghEzteCgToDfodX2B6B5S8BYbysw,735
|
|
76
|
-
tinybird/tb/modules/agent/prompts.py,sha256
|
|
77
|
-
tinybird/tb/modules/agent/utils.py,sha256=
|
|
76
|
+
tinybird/tb/modules/agent/prompts.py,sha256=qyAv3H1x9qctlYQSel0DHxLlRJM2_8HTg7M-foSoR0k,17567
|
|
77
|
+
tinybird/tb/modules/agent/utils.py,sha256=5mUnc4LZATHLzQZThotNErzZdHQDwK3eur6W4NZULWA,26561
|
|
78
78
|
tinybird/tb/modules/agent/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
79
|
tinybird/tb/modules/agent/tools/analyze.py,sha256=7oxJ3waCS24Qja_k5GUB59_XiHTG9pCewZogOXhH0cA,3495
|
|
80
|
-
tinybird/tb/modules/agent/tools/append.py,sha256=
|
|
80
|
+
tinybird/tb/modules/agent/tools/append.py,sha256=ekG7OxOzPjjjAjYUONAfp_PWlzyLT1GDfQhECgO-DYY,5065
|
|
81
81
|
tinybird/tb/modules/agent/tools/build.py,sha256=LhzJMx6tbxC7gogIrxhfKJc-SDgoSR-FC6IunfaCdn8,758
|
|
82
82
|
tinybird/tb/modules/agent/tools/create_datafile.py,sha256=wcPcChACTIFKw0lKFTlhm0sWJKhQkMLPLnGNpKyeETA,2962
|
|
83
|
-
tinybird/tb/modules/agent/tools/deploy.py,sha256=
|
|
84
|
-
tinybird/tb/modules/agent/tools/deploy_check.py,sha256=
|
|
85
|
-
tinybird/tb/modules/agent/tools/
|
|
83
|
+
tinybird/tb/modules/agent/tools/deploy.py,sha256=dxNmzz8vCLIPNrfwyzof74gJK5cF-GfKHUfQ1vEX4hg,1347
|
|
84
|
+
tinybird/tb/modules/agent/tools/deploy_check.py,sha256=9roUI7FOvhNXbvi1iilS1RNYso4-WsNDVR9m7rmeOy4,1170
|
|
85
|
+
tinybird/tb/modules/agent/tools/diff_resource.py,sha256=euKo_mD9FZHC1X6yESv3D_CXMzBMB8EWHLeZjIRqjJg,2637
|
|
86
|
+
tinybird/tb/modules/agent/tools/execute_query.py,sha256=hMRahWZkyP9qa-nMGzY0Z7MjDa9sb5O9V1evmA_V_w8,2702
|
|
86
87
|
tinybird/tb/modules/agent/tools/explore.py,sha256=ihALc_kBcsjrKT3hZyicqyIowB0g_K3AtNNi-5uz9-8,412
|
|
87
|
-
tinybird/tb/modules/agent/tools/get_endpoint_stats.py,sha256=
|
|
88
|
+
tinybird/tb/modules/agent/tools/get_endpoint_stats.py,sha256=E9yPi9LwnpsTyjFd8EaiSNvDGVPkFSNqp_tZxg_pWqs,1705
|
|
88
89
|
tinybird/tb/modules/agent/tools/get_openapi_definition.py,sha256=9cQ-SUeB1NVhPJN1s8aQh9KQxqI9-DEEW1Ot5r2JbOk,1575
|
|
89
|
-
tinybird/tb/modules/agent/tools/mock.py,sha256=
|
|
90
|
+
tinybird/tb/modules/agent/tools/mock.py,sha256=4gEAPZCdTPo1w-fbryWiEx3hPXOK2ZfW0MpXY7smpcI,3408
|
|
90
91
|
tinybird/tb/modules/agent/tools/plan.py,sha256=pr6LnItz6vlOeCG8GE459ExsrBEG0KLx-g02SZGNjXU,1217
|
|
91
|
-
tinybird/tb/modules/agent/tools/preview_datafile.py,sha256=
|
|
92
|
-
tinybird/tb/modules/agent/tools/request_endpoint.py,sha256=
|
|
92
|
+
tinybird/tb/modules/agent/tools/preview_datafile.py,sha256=Gbao_FxhXstnUnngVQxztpizjugyfx1rOXTkw7Yabls,858
|
|
93
|
+
tinybird/tb/modules/agent/tools/request_endpoint.py,sha256=Jl64ln0Jspu_rmp3ycZabj-2IXkmWFSZxoCdcavRpQo,2687
|
|
93
94
|
tinybird/tb/modules/datafile/build.py,sha256=NFKBrusFLU0WJNCXePAFWiEDuTaXpwc0lHlOQWEJ43s,51117
|
|
94
95
|
tinybird/tb/modules/datafile/build_common.py,sha256=2yNdxe49IMA9wNvl25NemY2Iaz8L66snjOdT64dm1is,4511
|
|
95
96
|
tinybird/tb/modules/datafile/build_datasource.py,sha256=Ra8pVQBDafbFRUKlhpgohhTsRyp_ADKZJVG8Gd69idY,17227
|
|
@@ -110,8 +111,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
|
|
|
110
111
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
111
112
|
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
112
113
|
tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
|
|
113
|
-
tinybird-0.0.1.
|
|
114
|
-
tinybird-0.0.1.
|
|
115
|
-
tinybird-0.0.1.
|
|
116
|
-
tinybird-0.0.1.
|
|
117
|
-
tinybird-0.0.1.
|
|
114
|
+
tinybird-0.0.1.dev256.dist-info/METADATA,sha256=QFATRwPa6QPBUcV7rO4TwGvOBOyt5Dd2VmFjlnFUdV0,1733
|
|
115
|
+
tinybird-0.0.1.dev256.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
116
|
+
tinybird-0.0.1.dev256.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
|
|
117
|
+
tinybird-0.0.1.dev256.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
|
|
118
|
+
tinybird-0.0.1.dev256.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|