tinybird 0.0.1.dev3__tar.gz → 0.0.1.dev5__tar.gz
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-0.0.1.dev3 → tinybird-0.0.1.dev5}/PKG-INFO +1 -1
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/__cli__.py +2 -2
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/check_pypi.py +1 -1
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/datafile.py +1 -1
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/feedback_manager.py +11 -6
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli.py +1 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/build.py +50 -51
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/cli.py +2 -69
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/common.py +24 -7
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/config.py +8 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/create.py +21 -45
- tinybird-0.0.1.dev5/tinybird/tb_cli_modules/llm.py +73 -0
- tinybird-0.0.1.dev5/tinybird/tb_cli_modules/local.py +168 -0
- tinybird-0.0.1.dev5/tinybird/tb_cli_modules/mock.py +53 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/prompts.py +3 -0
- tinybird-0.0.1.dev5/tinybird/tb_cli_modules/table.py +185 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird.egg-info/PKG-INFO +1 -1
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird.egg-info/SOURCES.txt +2 -0
- tinybird-0.0.1.dev3/tinybird/tb_cli_modules/llm.py +0 -31
- tinybird-0.0.1.dev3/tinybird/tb_cli_modules/local.py +0 -108
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/setup.cfg +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/client.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/config.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/connectors.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/context.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/datatypes.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/git_settings.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/sql.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/sql_template.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/sql_toolset.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/syncasync.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/datasource.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/token.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird/tornado_template.py +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird.egg-info/dependency_links.txt +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird.egg-info/entry_points.txt +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird.egg-info/requires.txt +0 -0
- {tinybird-0.0.1.dev3 → tinybird-0.0.1.dev5}/tinybird.egg-info/top_level.txt +0 -0
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '0.0.1.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '0.0.1.dev5'
|
|
8
|
+
__revision__ = 'b69dd20'
|
|
@@ -6,7 +6,7 @@ from tinybird.feedback_manager import FeedbackManager
|
|
|
6
6
|
from tinybird.syncasync import sync_to_async
|
|
7
7
|
from tinybird.tb_cli_modules.common import CLIException, getenv_bool
|
|
8
8
|
|
|
9
|
-
PYPY_URL = "https://pypi.org/pypi/tinybird
|
|
9
|
+
PYPY_URL = "https://pypi.org/pypi/tinybird/json"
|
|
10
10
|
requests_get = sync_to_async(requests.get, thread_sensitive=False)
|
|
11
11
|
|
|
12
12
|
|
|
@@ -3125,7 +3125,7 @@ async def new_pipe(
|
|
|
3125
3125
|
except Exception as e:
|
|
3126
3126
|
raise click.ClickException(FeedbackManager.error_creating_pipe(error=e))
|
|
3127
3127
|
|
|
3128
|
-
if data.get("type") == "endpoint":
|
|
3128
|
+
if data.get("type") == "endpoint" and t:
|
|
3129
3129
|
click.echo(FeedbackManager.success_test_endpoint(host=host, pipe=p["name"], token=t["token"]))
|
|
3130
3130
|
|
|
3131
3131
|
|
|
@@ -51,6 +51,10 @@ def prompt_message(message: str) -> Callable[..., str]:
|
|
|
51
51
|
return print_message(message, bcolors.HEADER)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def gray_message(message: str) -> Callable[..., str]:
|
|
55
|
+
return print_message(message, bcolors.CGREY)
|
|
56
|
+
|
|
57
|
+
|
|
54
58
|
class FeedbackManager:
|
|
55
59
|
error_exception = error_message("{error}")
|
|
56
60
|
simple_error_exception = simple_error_message("{error}")
|
|
@@ -561,9 +565,9 @@ Ready? """
|
|
|
561
565
|
)
|
|
562
566
|
warning_fixture_not_found = warning_message("** Warning: No fixture found for the datasource {datasource_name}")
|
|
563
567
|
warning_update_version = warning_message(
|
|
564
|
-
'** UPDATE AVAILABLE: please run "pip install tinybird
|
|
568
|
+
'** UPDATE AVAILABLE: please run "pip install tinybird=={latest_version}" to update or `export TB_VERSION_WARNING=0` to skip the check.'
|
|
565
569
|
)
|
|
566
|
-
warning_current_version = warning_message("** current: tinybird
|
|
570
|
+
warning_current_version = warning_message("** current: tinybird {current_version}\n")
|
|
567
571
|
warning_confirm_truncate_datasource = prompt_message(
|
|
568
572
|
"Do you want to truncate {datasource}? Once truncated, your data can't be recovered"
|
|
569
573
|
)
|
|
@@ -712,9 +716,9 @@ Ready? """
|
|
|
712
716
|
info_removing_pipe = info_message("** Removing pipe {pipe}")
|
|
713
717
|
info_removing_pipe_not_found = info_message("** {pipe} not found")
|
|
714
718
|
info_dry_removing_pipe = info_message("** [DRY RUN] Removing pipe {pipe}")
|
|
715
|
-
info_path_created = info_message("
|
|
716
|
-
info_file_created = info_message("
|
|
717
|
-
info_path_already_exists = info_message("
|
|
719
|
+
info_path_created = info_message("✓ /{path}")
|
|
720
|
+
info_file_created = info_message("✓ /{file}")
|
|
721
|
+
info_path_already_exists = info_message("✓ /{path} already exists, skipping")
|
|
718
722
|
info_dottinyb_already_ignored = info_message("** - '.tinyb' already in .gitignore, skipping")
|
|
719
723
|
info_dotdifftemp_already_ignored = info_message("** - '.diff_tmp' not found or already in .gitignore, skipping")
|
|
720
724
|
info_dottinyenv_already_exists = info_message("** - '.tinyenv' already exists, skipping")
|
|
@@ -935,7 +939,7 @@ Ready? """
|
|
|
935
939
|
success_create = success_message("** '{name}' created")
|
|
936
940
|
success_delete = success_message("** '{name}' deleted")
|
|
937
941
|
success_dynamodb_initial_load = success_message("** Initial load of DynamoDB table started: {job_url}")
|
|
938
|
-
success_progress_blocks = success_message("
|
|
942
|
+
success_progress_blocks = success_message("✓ Done!")
|
|
939
943
|
success_now_using_config = success_message("** Now using {name} ({id})")
|
|
940
944
|
success_connector_config = success_message(
|
|
941
945
|
"** {connector} configuration written to {file_name} file, consider adding it to .gitignore"
|
|
@@ -1026,3 +1030,4 @@ Ready? """
|
|
|
1026
1030
|
info = info_message("{message}")
|
|
1027
1031
|
highlight = info_highlight_message("{message}")
|
|
1028
1032
|
error = error_message("{message}")
|
|
1033
|
+
gray = gray_message("{message}")
|
|
@@ -14,6 +14,7 @@ import tinybird.tb_cli_modules.create
|
|
|
14
14
|
import tinybird.tb_cli_modules.datasource
|
|
15
15
|
import tinybird.tb_cli_modules.fmt
|
|
16
16
|
import tinybird.tb_cli_modules.job
|
|
17
|
+
import tinybird.tb_cli_modules.mock
|
|
17
18
|
import tinybird.tb_cli_modules.pipe
|
|
18
19
|
import tinybird.tb_cli_modules.tag
|
|
19
20
|
import tinybird.tb_cli_modules.test
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import json
|
|
3
2
|
import os
|
|
3
|
+
import random
|
|
4
4
|
import time
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, Awaitable, Callable, Dict, List, Union
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
|
+
import humanfriendly
|
|
9
10
|
from watchdog.events import FileSystemEventHandler
|
|
10
11
|
from watchdog.observers import Observer
|
|
11
12
|
|
|
@@ -20,20 +21,15 @@ from tinybird.datafile import (
|
|
|
20
21
|
parse_datasource,
|
|
21
22
|
parse_pipe,
|
|
22
23
|
)
|
|
23
|
-
from tinybird.feedback_manager import FeedbackManager,
|
|
24
|
+
from tinybird.feedback_manager import FeedbackManager, bcolors
|
|
24
25
|
from tinybird.tb_cli_modules.cli import cli
|
|
25
26
|
from tinybird.tb_cli_modules.common import (
|
|
26
27
|
coro,
|
|
27
|
-
echo_safe_humanfriendly_tables_format_smart_table,
|
|
28
28
|
)
|
|
29
|
-
from tinybird.tb_cli_modules.create import generate_sample_data_from_columns
|
|
30
29
|
from tinybird.tb_cli_modules.local import (
|
|
31
|
-
get_docker_client,
|
|
32
30
|
get_tinybird_local_client,
|
|
33
|
-
remove_tinybird_local,
|
|
34
|
-
start_tinybird_local,
|
|
35
|
-
stop_tinybird_local,
|
|
36
31
|
)
|
|
32
|
+
from tinybird.tb_cli_modules.table import format_table
|
|
37
33
|
|
|
38
34
|
|
|
39
35
|
class FileChangeHandler(FileSystemEventHandler):
|
|
@@ -44,7 +40,7 @@ class FileChangeHandler(FileSystemEventHandler):
|
|
|
44
40
|
def on_modified(self, event: Any) -> None:
|
|
45
41
|
if not event.is_directory and any(event.src_path.endswith(ext) for ext in [".datasource", ".pipe"]):
|
|
46
42
|
filename = event.src_path.split("/")[-1]
|
|
47
|
-
click.echo(
|
|
43
|
+
click.echo(FeedbackManager.highlight(message=f"\n⟲ Changes detected in {filename}\n"))
|
|
48
44
|
try:
|
|
49
45
|
self.process([event.src_path])
|
|
50
46
|
except Exception as e:
|
|
@@ -65,7 +61,10 @@ def watch_files(
|
|
|
65
61
|
process(files, watch=True)
|
|
66
62
|
time_end = time.time()
|
|
67
63
|
elapsed_time = time_end - time_start
|
|
68
|
-
click.echo(
|
|
64
|
+
click.echo(
|
|
65
|
+
FeedbackManager.success(message="\n✓ ")
|
|
66
|
+
+ FeedbackManager.gray(message=f"Rebuild completed in {elapsed_time:.1f}s")
|
|
67
|
+
)
|
|
69
68
|
|
|
70
69
|
event_handler = FileChangeHandler(filenames, lambda f: asyncio.run(process_wrapper(f)))
|
|
71
70
|
observer = Observer()
|
|
@@ -100,23 +99,19 @@ def watch_files(
|
|
|
100
99
|
help="Watch for changes in the files and re-check them.",
|
|
101
100
|
)
|
|
102
101
|
@click.option(
|
|
103
|
-
"--
|
|
102
|
+
"--skip-datasources",
|
|
104
103
|
is_flag=True,
|
|
105
|
-
help="
|
|
104
|
+
help="Skip rebuilding datasources.",
|
|
106
105
|
)
|
|
107
106
|
@coro
|
|
108
107
|
async def build(
|
|
109
108
|
folder: str,
|
|
110
109
|
watch: bool,
|
|
111
|
-
|
|
110
|
+
skip_datasources: bool,
|
|
112
111
|
) -> None:
|
|
113
112
|
"""
|
|
114
113
|
Watch for changes in the files and re-check them.
|
|
115
114
|
"""
|
|
116
|
-
docker_client = get_docker_client()
|
|
117
|
-
if restart:
|
|
118
|
-
remove_tinybird_local(docker_client)
|
|
119
|
-
start_tinybird_local(docker_client)
|
|
120
115
|
ignore_sql_errors = FeatureFlags.ignore_sql_errors()
|
|
121
116
|
context.disable_template_security_validation.set(True)
|
|
122
117
|
is_internal = has_internal_datafiles(folder)
|
|
@@ -156,15 +151,6 @@ async def build(
|
|
|
156
151
|
only_pipes=only_pipes,
|
|
157
152
|
)
|
|
158
153
|
|
|
159
|
-
for filename in filenames:
|
|
160
|
-
if filename.endswith(".datasource"):
|
|
161
|
-
ds_path = Path(filename)
|
|
162
|
-
ds_name = ds_path.stem
|
|
163
|
-
datasource_content = ds_path.read_text()
|
|
164
|
-
sample_data = await generate_sample_data_from_columns(tb_client, datasource_content)
|
|
165
|
-
ndjson_data = "\n".join([json.dumps(row) for row in sample_data])
|
|
166
|
-
await tb_client.datasource_events(ds_name, ndjson_data)
|
|
167
|
-
|
|
168
154
|
if watch:
|
|
169
155
|
filename = filenames[0]
|
|
170
156
|
if filename.endswith(".pipe"):
|
|
@@ -176,7 +162,7 @@ async def build(
|
|
|
176
162
|
try:
|
|
177
163
|
click.echo("⚡ Building project...")
|
|
178
164
|
time_start = time.time()
|
|
179
|
-
await process(filenames=filenames, watch=False)
|
|
165
|
+
await process(filenames=filenames, watch=False, only_pipes=skip_datasources)
|
|
180
166
|
time_end = time.time()
|
|
181
167
|
elapsed_time = time_end - time_start
|
|
182
168
|
click.echo(FeedbackManager.success(message=f"\n✓ Build completed in {elapsed_time:.1f}s\n"))
|
|
@@ -191,31 +177,44 @@ async def build(
|
|
|
191
177
|
|
|
192
178
|
|
|
193
179
|
async def build_and_print_pipe(tb_client: TinyB, filename: str):
|
|
194
|
-
|
|
195
|
-
|
|
180
|
+
rebuild_colors = [bcolors.FAIL, bcolors.OKBLUE, bcolors.WARNING, bcolors.OKGREEN, bcolors.HEADER]
|
|
181
|
+
rebuild_index = random.randint(0, len(rebuild_colors) - 1)
|
|
182
|
+
rebuild_color = rebuild_colors[rebuild_index % len(rebuild_colors)]
|
|
183
|
+
pipe_name = Path(filename).stem
|
|
184
|
+
res = await tb_client.query(f"SELECT * FROM {pipe_name} FORMAT JSON", pipeline=pipe_name)
|
|
196
185
|
data = []
|
|
197
|
-
|
|
186
|
+
limit = 5
|
|
187
|
+
for d in res["data"][:5]:
|
|
198
188
|
data.append(d.values())
|
|
199
189
|
meta = res["meta"]
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
async def stop() -> None:
|
|
207
|
-
"""Stop Tinybird development environment"""
|
|
208
|
-
click.echo(FeedbackManager.info(message="Shutting down Tinybird development environment..."))
|
|
209
|
-
docker_client = get_docker_client()
|
|
210
|
-
stop_tinybird_local(docker_client)
|
|
211
|
-
click.echo(FeedbackManager.success(message="Tinybird development environment stopped"))
|
|
212
|
-
|
|
190
|
+
row_count = res.get("rows", 0)
|
|
191
|
+
stats = res.get("statistics", {})
|
|
192
|
+
elapsed = stats.get("elapsed", 0)
|
|
193
|
+
node_name = "endpoint"
|
|
194
|
+
cols = len(meta)
|
|
195
|
+
try:
|
|
213
196
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
197
|
+
def print_message(message: str, color=bcolors.CGREY):
|
|
198
|
+
return f"{color}{message}{bcolors.ENDC}"
|
|
199
|
+
|
|
200
|
+
table = format_table(data, meta)
|
|
201
|
+
colored_char = print_message("│", rebuild_color)
|
|
202
|
+
table_with_marker = "\n".join(f"{colored_char} {line}" for line in table.split("\n"))
|
|
203
|
+
click.echo(f"\n{colored_char} {print_message('⚡', rebuild_color)} Running {pipe_name} → {node_name}")
|
|
204
|
+
click.echo(colored_char)
|
|
205
|
+
click.echo(table_with_marker)
|
|
206
|
+
click.echo(colored_char)
|
|
207
|
+
rows_read = humanfriendly.format_number(stats.get("rows_read", 0))
|
|
208
|
+
bytes_read = humanfriendly.format_size(stats.get("bytes_read", 0))
|
|
209
|
+
elapsed = humanfriendly.format_timespan(elapsed) if elapsed >= 1 else f"{elapsed * 1000:.2f}ms"
|
|
210
|
+
stats_message = f"» {bytes_read} ({rows_read} rows x {cols} cols) in {elapsed}"
|
|
211
|
+
rows_message = f"» Showing {limit} first rows" if row_count > limit else "» Showing all rows"
|
|
212
|
+
click.echo(f"{colored_char} {print_message(stats_message, bcolors.OKGREEN)}")
|
|
213
|
+
click.echo(f"{colored_char} {print_message(rows_message, bcolors.CGREY)}")
|
|
214
|
+
except ValueError as exc:
|
|
215
|
+
if str(exc) == "max() arg is an empty sequence":
|
|
216
|
+
click.echo("------------")
|
|
217
|
+
click.echo("Empty")
|
|
218
|
+
click.echo("------------")
|
|
219
|
+
else:
|
|
220
|
+
raise exc
|
|
@@ -10,14 +10,12 @@ import pprint
|
|
|
10
10
|
import re
|
|
11
11
|
import shutil
|
|
12
12
|
import sys
|
|
13
|
-
from datetime import datetime
|
|
14
13
|
from os import environ, getcwd
|
|
15
14
|
from pathlib import Path
|
|
16
15
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
|
17
16
|
|
|
18
17
|
import click
|
|
19
18
|
import humanfriendly
|
|
20
|
-
import requests
|
|
21
19
|
from click import Context
|
|
22
20
|
from packaging import version
|
|
23
21
|
|
|
@@ -58,7 +56,7 @@ from tinybird.tb_cli_modules.common import (
|
|
|
58
56
|
_get_tb_client,
|
|
59
57
|
coro,
|
|
60
58
|
create_tb_client,
|
|
61
|
-
|
|
59
|
+
echo_safe_format_table,
|
|
62
60
|
folder_init,
|
|
63
61
|
get_current_main_workspace,
|
|
64
62
|
getenv_bool,
|
|
@@ -69,7 +67,6 @@ from tinybird.tb_cli_modules.common import (
|
|
|
69
67
|
try_update_config_with_remote,
|
|
70
68
|
)
|
|
71
69
|
from tinybird.tb_cli_modules.config import CLIConfig
|
|
72
|
-
from tinybird.tb_cli_modules.prompts import sample_data_prompt
|
|
73
70
|
from tinybird.tb_cli_modules.telemetry import add_telemetry_event
|
|
74
71
|
|
|
75
72
|
__old_click_echo = click.echo
|
|
@@ -927,7 +924,7 @@ async def sql(
|
|
|
927
924
|
dd = []
|
|
928
925
|
for d in res["data"]:
|
|
929
926
|
dd.append(d.values())
|
|
930
|
-
|
|
927
|
+
echo_safe_format_table(dd, columns=res["meta"])
|
|
931
928
|
else:
|
|
932
929
|
click.echo(FeedbackManager.info_no_rows())
|
|
933
930
|
|
|
@@ -1572,67 +1569,3 @@ async def deploy(
|
|
|
1572
1569
|
raise
|
|
1573
1570
|
except Exception as e:
|
|
1574
1571
|
raise CLIException(str(e))
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
@cli.command()
|
|
1578
|
-
@click.argument("datasource_file", type=click.Path(exists=True))
|
|
1579
|
-
@click.option("--count", type=int, default=10, help="Number of events to send")
|
|
1580
|
-
@click.option("--model", type=str, default=None, help="Model to use for data generation")
|
|
1581
|
-
@click.option("--print-data", is_flag=True, default=False, help="Print the data being sent")
|
|
1582
|
-
@click.pass_context
|
|
1583
|
-
def load_sample_data(ctx: Context, datasource_file: str, count: int, model: Optional[str], print_data: bool) -> None:
|
|
1584
|
-
"""Load sample data into a datasource.
|
|
1585
|
-
|
|
1586
|
-
Args:
|
|
1587
|
-
ctx: Click context object
|
|
1588
|
-
datasource_file: Path to the datasource file to load sample data into
|
|
1589
|
-
"""
|
|
1590
|
-
import llm
|
|
1591
|
-
|
|
1592
|
-
try:
|
|
1593
|
-
# TODO(eclbg): allow passing a datasource name instead of a file
|
|
1594
|
-
datasource_path = Path(datasource_file)
|
|
1595
|
-
if datasource_path.suffix != ".datasource":
|
|
1596
|
-
raise CLIException(FeedbackManager.error_file_extension(filename=datasource_file))
|
|
1597
|
-
|
|
1598
|
-
datasource_name = datasource_path.stem
|
|
1599
|
-
|
|
1600
|
-
response = requests.get("http://localhost:80/tokens")
|
|
1601
|
-
token = response.json()["workspace_admin_token"]
|
|
1602
|
-
|
|
1603
|
-
with open(datasource_file) as f:
|
|
1604
|
-
content = f.read()
|
|
1605
|
-
schema_start = next(i for i, line in enumerate(content.splitlines()) if line.strip().startswith("SCHEMA >"))
|
|
1606
|
-
schema_end = next(
|
|
1607
|
-
i
|
|
1608
|
-
for i, line in enumerate(content.splitlines()[schema_start + 1 :], schema_start + 1)
|
|
1609
|
-
if not line.strip()
|
|
1610
|
-
)
|
|
1611
|
-
schema = "\n".join(content.splitlines()[schema_start:schema_end])
|
|
1612
|
-
llm_model = llm.get_model(model)
|
|
1613
|
-
click.echo(f"Using model: {model}")
|
|
1614
|
-
prompt = sample_data_prompt.format(current_datetime=datetime.now().isoformat(), row_count=count)
|
|
1615
|
-
# prompt = sample_data_with_errors_prompt.format(current_datetime=datetime.now().isoformat()) # This prompt will generate data with errors
|
|
1616
|
-
full_prompt = prompt + "\n\n" + schema
|
|
1617
|
-
sent_events = 0
|
|
1618
|
-
while sent_events < count:
|
|
1619
|
-
click.echo(f"Generating data for '{datasource_name}'")
|
|
1620
|
-
data = llm_model.prompt(full_prompt)
|
|
1621
|
-
|
|
1622
|
-
click.echo(f"Sending data to '{datasource_name}'")
|
|
1623
|
-
headers = {"Authorization": f"Bearer {token}"}
|
|
1624
|
-
if print_data:
|
|
1625
|
-
click.echo(f"Data: {data}")
|
|
1626
|
-
response = requests.post(
|
|
1627
|
-
f"http://localhost:80/v0/events?name={datasource_name}",
|
|
1628
|
-
data=data,
|
|
1629
|
-
headers=headers,
|
|
1630
|
-
)
|
|
1631
|
-
if response.status_code not in (200, 202):
|
|
1632
|
-
raise CLIException(f"Failed to send data: {response.text}")
|
|
1633
|
-
click.echo(f"Response: {response.text}")
|
|
1634
|
-
sent_events += 10
|
|
1635
|
-
click.echo(f"Sent 10 events to datasource '{datasource_name}'")
|
|
1636
|
-
|
|
1637
|
-
except Exception as e:
|
|
1638
|
-
raise CLIException(FeedbackManager.error_exception(error=str(e)))
|
|
@@ -53,6 +53,7 @@ from tinybird.config import (
|
|
|
53
53
|
get_display_host,
|
|
54
54
|
write_config,
|
|
55
55
|
)
|
|
56
|
+
from tinybird.tb_cli_modules.table import format_table
|
|
56
57
|
|
|
57
58
|
if TYPE_CHECKING:
|
|
58
59
|
from tinybird.connectors import Connector
|
|
@@ -134,6 +135,23 @@ def echo_safe_humanfriendly_tables_format_smart_table(data: Iterable[Any], colum
|
|
|
134
135
|
raise exc
|
|
135
136
|
|
|
136
137
|
|
|
138
|
+
def echo_safe_format_table(data: Iterable[Any], columns) -> None:
|
|
139
|
+
"""
|
|
140
|
+
There is a bug in the humanfriendly library: it breaks to render the small table for small terminals
|
|
141
|
+
(`format_robust_table`) if we call format_smart_table with an empty dataset. This catches the error and prints
|
|
142
|
+
what we would call an empty "robust_table".
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
click.echo(format_table(data, columns))
|
|
146
|
+
except ValueError as exc:
|
|
147
|
+
if str(exc) == "max() arg is an empty sequence":
|
|
148
|
+
click.echo("------------")
|
|
149
|
+
click.echo("Empty")
|
|
150
|
+
click.echo("------------")
|
|
151
|
+
else:
|
|
152
|
+
raise exc
|
|
153
|
+
|
|
154
|
+
|
|
137
155
|
def normalize_datasource_name(s: str) -> str:
|
|
138
156
|
s = re.sub(r"[^0-9a-zA-Z_]", "_", s)
|
|
139
157
|
if s[0] in "0123456789":
|
|
@@ -153,7 +171,7 @@ def generate_datafile(
|
|
|
153
171
|
if not f.exists() or force:
|
|
154
172
|
with open(f"{f}", "w") as ds_file:
|
|
155
173
|
ds_file.write(datafile)
|
|
156
|
-
click.echo(FeedbackManager.
|
|
174
|
+
click.echo(FeedbackManager.info_file_created(file=f))
|
|
157
175
|
|
|
158
176
|
if data and (base / "fixtures").exists():
|
|
159
177
|
# Generating a fixture for Parquet files is not so trivial, since Parquet format
|
|
@@ -1090,7 +1108,7 @@ async def push_data(
|
|
|
1090
1108
|
cb.First = True # type: ignore[attr-defined]
|
|
1091
1109
|
cb.prev_done = 0 # type: ignore[attr-defined]
|
|
1092
1110
|
|
|
1093
|
-
click.echo(FeedbackManager.
|
|
1111
|
+
click.echo(FeedbackManager.gray(message=f"\nImporting data to {datasource_name} Data Source..."))
|
|
1094
1112
|
|
|
1095
1113
|
if isinstance(url, list):
|
|
1096
1114
|
urls = url
|
|
@@ -1161,17 +1179,16 @@ async def push_data(
|
|
|
1161
1179
|
except Exception as e:
|
|
1162
1180
|
raise CLIException(FeedbackManager.error_exception(error=e))
|
|
1163
1181
|
else:
|
|
1164
|
-
click.echo(FeedbackManager.success_progress_blocks())
|
|
1165
1182
|
if mode == "append" and parser and parser != "clickhouse":
|
|
1166
1183
|
click.echo(FeedbackManager.success_appended_rows(appended_rows=appended_rows))
|
|
1167
1184
|
|
|
1168
|
-
click.echo(FeedbackManager.success_total_rows(datasource=datasource_name, total_rows=total_rows))
|
|
1169
|
-
|
|
1170
1185
|
if mode == "replace":
|
|
1171
1186
|
click.echo(FeedbackManager.success_replaced_datasource(datasource=datasource_name))
|
|
1172
1187
|
else:
|
|
1173
|
-
click.echo(FeedbackManager.
|
|
1174
|
-
|
|
1188
|
+
click.echo(FeedbackManager.highlight(message="» 2.57m rows x 9 cols in 852.04ms"))
|
|
1189
|
+
|
|
1190
|
+
click.echo(FeedbackManager.success_progress_blocks())
|
|
1191
|
+
|
|
1175
1192
|
finally:
|
|
1176
1193
|
try:
|
|
1177
1194
|
for url in urls:
|
|
@@ -338,6 +338,14 @@ class CLIConfig:
|
|
|
338
338
|
CLIConfig._projects[working_dir] = result
|
|
339
339
|
return result
|
|
340
340
|
|
|
341
|
+
@staticmethod
|
|
342
|
+
def get_llm_config(working_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
343
|
+
return (
|
|
344
|
+
CLIConfig.get_project_config(working_dir)
|
|
345
|
+
.get("llms", {})
|
|
346
|
+
.get("openai", {"model": "gpt-4o-mini", "api_key": None})
|
|
347
|
+
)
|
|
348
|
+
|
|
341
349
|
@staticmethod
|
|
342
350
|
def reset() -> None:
|
|
343
351
|
CLIConfig._global = None
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import os
|
|
3
2
|
from os import getcwd
|
|
4
3
|
from pathlib import Path
|
|
@@ -6,7 +5,6 @@ from typing import Any, Dict, List, Optional
|
|
|
6
5
|
|
|
7
6
|
import click
|
|
8
7
|
from click import Context
|
|
9
|
-
from openai import OpenAI
|
|
10
8
|
|
|
11
9
|
from tinybird.client import TinyB
|
|
12
10
|
from tinybird.datafile import folder_build
|
|
@@ -16,8 +14,9 @@ from tinybird.tb_cli_modules.common import _generate_datafile, coro, generate_da
|
|
|
16
14
|
from tinybird.tb_cli_modules.config import CLIConfig
|
|
17
15
|
from tinybird.tb_cli_modules.exceptions import CLIDatasourceException
|
|
18
16
|
from tinybird.tb_cli_modules.llm import LLM
|
|
19
|
-
from tinybird.tb_cli_modules.local import
|
|
20
|
-
|
|
17
|
+
from tinybird.tb_cli_modules.local import (
|
|
18
|
+
get_tinybird_local_client,
|
|
19
|
+
)
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
@cli.command()
|
|
@@ -48,12 +47,13 @@ async def create(
|
|
|
48
47
|
folder: Optional[str],
|
|
49
48
|
) -> None:
|
|
50
49
|
"""Initialize a new project."""
|
|
51
|
-
click.echo(FeedbackManager.
|
|
50
|
+
click.echo(FeedbackManager.gray(message="Setting up Tinybird Local"))
|
|
52
51
|
folder = folder or getcwd()
|
|
53
52
|
try:
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
tb_client = get_tinybird_local_client()
|
|
54
|
+
click.echo(FeedbackManager.gray(message="Creating new project structure..."))
|
|
56
55
|
await project_create(tb_client, data, prompt, folder)
|
|
56
|
+
click.echo(FeedbackManager.success(message="✓ Scaffolding completed!\n"))
|
|
57
57
|
workspaces: List[Dict[str, Any]] = (await tb_client.user_workspaces()).get("workspaces", [])
|
|
58
58
|
datasources = await tb_client.datasources()
|
|
59
59
|
pipes = await tb_client.pipes(dependencies=True)
|
|
@@ -69,11 +69,15 @@ async def create(
|
|
|
69
69
|
elif prompt:
|
|
70
70
|
datasource_files = [f for f in os.listdir(Path(folder) / "datasources") if f.endswith(".datasource")]
|
|
71
71
|
for datasource_file in datasource_files:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
datasource_path = Path(folder) / "datasources" / datasource_file
|
|
73
|
+
llm_config = CLIConfig.get_llm_config()
|
|
74
|
+
llm = LLM(key=llm_config["api_key"])
|
|
75
|
+
datasource_name = datasource_path.stem
|
|
76
|
+
datasource_content = datasource_path.read_text()
|
|
77
|
+
has_json_path = "`json:" in datasource_content
|
|
78
|
+
if has_json_path:
|
|
79
|
+
await llm.generate_sql_sample_data(tb_client, datasource_name, datasource_content)
|
|
80
|
+
click.echo(FeedbackManager.success(message="\n✓ Tinybird Local is ready!"))
|
|
77
81
|
except Exception as e:
|
|
78
82
|
click.echo(FeedbackManager.error(message=f"Error: {str(e)}"))
|
|
79
83
|
|
|
@@ -84,7 +88,7 @@ async def project_create(
|
|
|
84
88
|
prompt: Optional[str],
|
|
85
89
|
folder: str,
|
|
86
90
|
):
|
|
87
|
-
project_paths = ["datasources", "endpoints", "
|
|
91
|
+
project_paths = ["datasources", "endpoints", "materializations", "copies", "sinks"]
|
|
88
92
|
force = True
|
|
89
93
|
for x in project_paths:
|
|
90
94
|
try:
|
|
@@ -92,7 +96,7 @@ async def project_create(
|
|
|
92
96
|
f.mkdir()
|
|
93
97
|
click.echo(FeedbackManager.info_path_created(path=x))
|
|
94
98
|
except FileExistsError:
|
|
95
|
-
|
|
99
|
+
click.echo(FeedbackManager.info_path_created(path=x))
|
|
96
100
|
|
|
97
101
|
def generate_pipe_file(name: str, content: str):
|
|
98
102
|
base = Path("endpoints")
|
|
@@ -101,7 +105,7 @@ async def project_create(
|
|
|
101
105
|
f = base / (f"{name}.pipe")
|
|
102
106
|
with open(f"{f}", "w") as file:
|
|
103
107
|
file.write(content)
|
|
104
|
-
click.echo(FeedbackManager.
|
|
108
|
+
click.echo(FeedbackManager.info_file_created(file=f))
|
|
105
109
|
|
|
106
110
|
if data:
|
|
107
111
|
path = Path(folder) / data
|
|
@@ -119,10 +123,8 @@ TYPE ENDPOINT
|
|
|
119
123
|
)
|
|
120
124
|
elif prompt:
|
|
121
125
|
try:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
api_key = config.get("llms", {}).get("openai", {}).get("api_key", None)
|
|
125
|
-
llm = LLM(model=model, key=api_key)
|
|
126
|
+
llm_config = CLIConfig.get_llm_config()
|
|
127
|
+
llm = LLM(key=llm_config["api_key"])
|
|
126
128
|
result = await llm.create_project(prompt)
|
|
127
129
|
for ds in result.datasources:
|
|
128
130
|
content = ds.content.replace("```", "")
|
|
@@ -198,29 +200,3 @@ async def append_datasource(
|
|
|
198
200
|
ignore_empty=ignore_empty,
|
|
199
201
|
concurrency=concurrency,
|
|
200
202
|
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def generate_sql_sample_data(datasource_content: str, row_count: int, model: str, api_key: str) -> str:
|
|
204
|
-
client = OpenAI(api_key=api_key)
|
|
205
|
-
|
|
206
|
-
response = client.chat.completions.create(
|
|
207
|
-
model=model,
|
|
208
|
-
messages=[
|
|
209
|
-
{"role": "system", "content": sample_data_sql_prompt.format(row_count=row_count)},
|
|
210
|
-
{"role": "user", "content": datasource_content},
|
|
211
|
-
],
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
return response.choices[0].message.content or ""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
async def generate_sample_data_from_columns(
|
|
218
|
-
tb_client: TinyB, datasource_content: str, row_count: int = 20
|
|
219
|
-
) -> List[Dict[str, Any]]:
|
|
220
|
-
config = CLIConfig.get_project_config()
|
|
221
|
-
model = config.get("llms", {}).get("openai", {}).get("model", "gpt-4o-mini")
|
|
222
|
-
api_key = config.get("llms", {}).get("openai", {}).get("api_key", None)
|
|
223
|
-
sql = generate_sql_sample_data(datasource_content, row_count, model, api_key)
|
|
224
|
-
result = await tb_client.query(f"{sql} FORMAT JSON")
|
|
225
|
-
data = result.get("data", [])
|
|
226
|
-
return data
|