tinybird 0.0.1.dev29__py3-none-any.whl → 0.0.1.dev31__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/__cli__.py +2 -2
- tinybird/client.py +1 -1
- tinybird/config.py +1 -0
- tinybird/context.py +1 -0
- tinybird/prompts.py +218 -325
- tinybird/sql_template.py +21 -2
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +2 -1
- tinybird/tb/modules/build.py +75 -188
- tinybird/tb/modules/build_client.py +219 -0
- tinybird/tb/modules/cli.py +53 -16
- tinybird/tb/modules/common.py +1 -26
- tinybird/tb/modules/config.py +0 -8
- tinybird/tb/modules/create.py +22 -3
- tinybird/tb/modules/datafile/build.py +2 -2
- tinybird/tb/modules/datafile/build_pipe.py +13 -1
- tinybird/tb/modules/datasource.py +1 -1
- tinybird/tb/modules/llm.py +6 -73
- tinybird/tb/modules/local.py +1 -1
- tinybird/tb/modules/local_common.py +1 -1
- tinybird/tb/modules/login.py +7 -2
- tinybird/tb/modules/mock.py +1 -1
- tinybird/tb/modules/shell.py +30 -35
- tinybird/tb/modules/test.py +41 -22
- tinybird/tb/modules/update.py +182 -0
- tinybird/tb/modules/watch.py +76 -1
- {tinybird-0.0.1.dev29.dist-info → tinybird-0.0.1.dev31.dist-info}/METADATA +2 -1
- {tinybird-0.0.1.dev29.dist-info → tinybird-0.0.1.dev31.dist-info}/RECORD +31 -30
- tinybird/tb/modules/build_server.py +0 -75
- {tinybird-0.0.1.dev29.dist-info → tinybird-0.0.1.dev31.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev29.dist-info → tinybird-0.0.1.dev31.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev29.dist-info → tinybird-0.0.1.dev31.dist-info}/top_level.txt +0 -0
tinybird/tb/modules/cli.py
CHANGED
|
@@ -9,7 +9,7 @@ import os
|
|
|
9
9
|
import pprint
|
|
10
10
|
from os import getcwd
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
|
|
12
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
|
13
13
|
|
|
14
14
|
import click
|
|
15
15
|
import humanfriendly
|
|
@@ -20,7 +20,8 @@ from tinybird.client import (
|
|
|
20
20
|
AuthNoTokenException,
|
|
21
21
|
TinyB,
|
|
22
22
|
)
|
|
23
|
-
from tinybird.config import
|
|
23
|
+
from tinybird.config import get_config
|
|
24
|
+
from tinybird.prompts import ask_prompt
|
|
24
25
|
from tinybird.tb import __cli__
|
|
25
26
|
from tinybird.tb.modules.common import (
|
|
26
27
|
CatchAuthExceptions,
|
|
@@ -30,7 +31,6 @@ from tinybird.tb.modules.common import (
|
|
|
30
31
|
echo_safe_format_table,
|
|
31
32
|
get_current_main_workspace,
|
|
32
33
|
getenv_bool,
|
|
33
|
-
load_connector_config,
|
|
34
34
|
try_update_config_with_remote,
|
|
35
35
|
)
|
|
36
36
|
from tinybird.tb.modules.config import CLIConfig
|
|
@@ -94,7 +94,7 @@ VERSION = f"{__cli__.__version__} (rev {__cli__.__revision__})"
|
|
|
94
94
|
"--with-headers", help="Flag to enable connector to export with headers", is_flag=True, default=False, hidden=True
|
|
95
95
|
)
|
|
96
96
|
@click.option("--show-tokens", is_flag=True, default=False, help="Enable the output of tokens")
|
|
97
|
-
@click.option("--
|
|
97
|
+
@click.option("--prod", is_flag=True, default=False, help="Run against production")
|
|
98
98
|
@click.version_option(version=VERSION)
|
|
99
99
|
@click.pass_context
|
|
100
100
|
@coro
|
|
@@ -117,14 +117,13 @@ async def cli(
|
|
|
117
117
|
sf_stage,
|
|
118
118
|
with_headers: bool,
|
|
119
119
|
show_tokens: bool,
|
|
120
|
-
|
|
120
|
+
prod: bool,
|
|
121
121
|
) -> None:
|
|
122
122
|
"""
|
|
123
123
|
Use `OBFUSCATE_REGEX_PATTERN` and `OBFUSCATE_PATTERN_SEPARATOR` environment variables to define a regex pattern and a separator (in case of a single string with multiple regex) to obfuscate secrets in the CLI output.
|
|
124
124
|
"""
|
|
125
|
-
|
|
126
125
|
# We need to unpatch for our tests not to break
|
|
127
|
-
if show_tokens or
|
|
126
|
+
if show_tokens or not prod or ctx.invoked_subcommand == "build":
|
|
128
127
|
__unpatch_click_output()
|
|
129
128
|
else:
|
|
130
129
|
__patch_click_output()
|
|
@@ -211,12 +210,11 @@ async def cli(
|
|
|
211
210
|
|
|
212
211
|
logging.debug("debug enabled")
|
|
213
212
|
|
|
214
|
-
ctx.
|
|
215
|
-
|
|
216
|
-
)
|
|
213
|
+
skip_client = ctx.invoked_subcommand in ["login", "workspace"]
|
|
214
|
+
client = await create_ctx_client(config, prod, skip_client)
|
|
217
215
|
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
if client:
|
|
217
|
+
ctx.ensure_object(dict)["client"] = client
|
|
220
218
|
|
|
221
219
|
|
|
222
220
|
@cli.command(hidden=True)
|
|
@@ -489,11 +487,10 @@ async def sql(
|
|
|
489
487
|
|
|
490
488
|
@cli.command(hidden=True)
|
|
491
489
|
@click.argument("prompt")
|
|
492
|
-
@click.option("--model", default="gpt-4o", help="The model to use for the LLM")
|
|
493
490
|
@click.option("--folder", default=".", help="The folder to use for the project")
|
|
494
491
|
@coro
|
|
495
|
-
async def
|
|
496
|
-
"""
|
|
492
|
+
async def ask(prompt: str, folder: str) -> None:
|
|
493
|
+
"""Ask things about your data project."""
|
|
497
494
|
try:
|
|
498
495
|
config = CLIConfig.get_project_config(folder)
|
|
499
496
|
user_token = config.get_user_token()
|
|
@@ -501,9 +498,39 @@ async def llm(prompt: str, model: str, folder: str) -> None:
|
|
|
501
498
|
if not user_token:
|
|
502
499
|
raise CLIException("This action requires authentication. Run 'tb login' first.")
|
|
503
500
|
|
|
501
|
+
datasource_paths = [
|
|
502
|
+
Path(folder) / "datasources" / f
|
|
503
|
+
for f in os.listdir(Path(folder) / "datasources")
|
|
504
|
+
if f.endswith(".datasource")
|
|
505
|
+
]
|
|
506
|
+
pipe_folders = ["endpoints", "pipes", "copies", "materializations", "sinks"]
|
|
507
|
+
pipe_paths = [
|
|
508
|
+
Path(folder) / pipe_folder / f
|
|
509
|
+
for pipe_folder in pipe_folders
|
|
510
|
+
if (Path(folder) / pipe_folder).exists()
|
|
511
|
+
for f in os.listdir(Path(folder) / pipe_folder)
|
|
512
|
+
if f.endswith(".pipe")
|
|
513
|
+
]
|
|
514
|
+
resources_xml = "\n".join(
|
|
515
|
+
[
|
|
516
|
+
f"<resource><type>{resource_type}</type><name>{resource_name}</name><content>{resource_content}</content></resource>"
|
|
517
|
+
for resource_type, resource_name, resource_content in [
|
|
518
|
+
("datasource", ds.stem, ds.read_text()) for ds in datasource_paths
|
|
519
|
+
]
|
|
520
|
+
+ [
|
|
521
|
+
(
|
|
522
|
+
"pipe",
|
|
523
|
+
pipe.stem,
|
|
524
|
+
pipe.read_text(),
|
|
525
|
+
)
|
|
526
|
+
for pipe in pipe_paths
|
|
527
|
+
]
|
|
528
|
+
]
|
|
529
|
+
)
|
|
530
|
+
|
|
504
531
|
client = config.get_client()
|
|
505
532
|
llm = LLM(user_token=user_token, client=client)
|
|
506
|
-
click.echo(await llm.ask(
|
|
533
|
+
click.echo(await llm.ask(system_prompt=ask_prompt(resources_xml), prompt=prompt))
|
|
507
534
|
except Exception as e:
|
|
508
535
|
raise CLIException(FeedbackManager.error_exception(error=e))
|
|
509
536
|
|
|
@@ -543,3 +570,13 @@ def __patch_click_output():
|
|
|
543
570
|
def __unpatch_click_output():
|
|
544
571
|
click.echo = __old_click_echo
|
|
545
572
|
click.secho = __old_click_secho
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
async def create_ctx_client(config: Dict[str, Any], prod: bool, skip_client: bool):
|
|
576
|
+
if skip_client:
|
|
577
|
+
return None
|
|
578
|
+
|
|
579
|
+
if prod:
|
|
580
|
+
return _get_tb_client(config.get("token", None), config["host"])
|
|
581
|
+
|
|
582
|
+
return await get_tinybird_local_client()
|
tinybird/tb/modules/common.py
CHANGED
|
@@ -175,7 +175,7 @@ def generate_datafile(
|
|
|
175
175
|
if not f.exists() or force:
|
|
176
176
|
with open(f"{f}", "w") as ds_file:
|
|
177
177
|
ds_file.write(datafile)
|
|
178
|
-
click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
|
|
178
|
+
click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder or ".")))
|
|
179
179
|
|
|
180
180
|
if data and (base / "fixtures").exists():
|
|
181
181
|
# Generating a fixture for Parquet files is not so trivial, since Parquet format
|
|
@@ -1177,31 +1177,6 @@ async def print_current_workspace(config: CLIConfig) -> None:
|
|
|
1177
1177
|
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
1178
1178
|
|
|
1179
1179
|
|
|
1180
|
-
async def print_current_branch(config: CLIConfig) -> None:
|
|
1181
|
-
_ = await try_update_config_with_remote(config, only_if_needed=True)
|
|
1182
|
-
|
|
1183
|
-
response = await config.get_client().user_workspaces_and_branches()
|
|
1184
|
-
|
|
1185
|
-
columns = ["name", "id", "workspace"]
|
|
1186
|
-
table = []
|
|
1187
|
-
|
|
1188
|
-
for workspace in response["workspaces"]:
|
|
1189
|
-
if config["id"] == workspace["id"]:
|
|
1190
|
-
click.echo(FeedbackManager.info_current_branch())
|
|
1191
|
-
if workspace.get("is_branch"):
|
|
1192
|
-
name = workspace["name"]
|
|
1193
|
-
main_workspace = await get_current_main_workspace(config)
|
|
1194
|
-
assert isinstance(main_workspace, dict)
|
|
1195
|
-
main_name = main_workspace["name"]
|
|
1196
|
-
else:
|
|
1197
|
-
name = MAIN_BRANCH
|
|
1198
|
-
main_name = workspace["name"]
|
|
1199
|
-
table.append([name, workspace["id"], main_name])
|
|
1200
|
-
break
|
|
1201
|
-
|
|
1202
|
-
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
1180
|
class ConnectionReplacements:
|
|
1206
1181
|
_PARAMS_REPLACEMENTS: Dict[str, Dict[str, str]] = {
|
|
1207
1182
|
"s3": {
|
tinybird/tb/modules/config.py
CHANGED
|
@@ -323,14 +323,6 @@ class CLIConfig:
|
|
|
323
323
|
path: str = os.path.join(working_dir, ".tinyb")
|
|
324
324
|
return CLIConfig(path, parent=CLIConfig.get_global_config())
|
|
325
325
|
|
|
326
|
-
@staticmethod
|
|
327
|
-
def get_llm_config(working_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
328
|
-
return (
|
|
329
|
-
CLIConfig.get_project_config(working_dir)
|
|
330
|
-
.get("llms", {})
|
|
331
|
-
.get("openai", {"model": "gpt-4o-mini", "api_key": None})
|
|
332
|
-
)
|
|
333
|
-
|
|
334
326
|
@staticmethod
|
|
335
327
|
def reset() -> None:
|
|
336
328
|
CLIConfig._global = None
|
tinybird/tb/modules/create.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
from os import getcwd
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Optional
|
|
@@ -108,7 +109,7 @@ async def create(
|
|
|
108
109
|
has_json_path = "`json:" in datasource_content
|
|
109
110
|
if has_json_path:
|
|
110
111
|
prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
|
|
111
|
-
response = await llm.ask(
|
|
112
|
+
response = await llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
|
|
112
113
|
sql = extract_xml(response, "sql")
|
|
113
114
|
sql = sql.split("FORMAT")[0]
|
|
114
115
|
result = await local_client.query(f"{sql} FORMAT JSON")
|
|
@@ -205,7 +206,7 @@ TYPE ENDPOINT
|
|
|
205
206
|
]
|
|
206
207
|
)
|
|
207
208
|
llm = LLM(user_token=user_token, client=tb_client)
|
|
208
|
-
result = await llm.ask(
|
|
209
|
+
result = await llm.ask(system_prompt=create_prompt(resources_xml), prompt=prompt)
|
|
209
210
|
result = extract_xml(result, "response")
|
|
210
211
|
resources = parse_xml(result, "resource")
|
|
211
212
|
datasources = []
|
|
@@ -260,7 +261,25 @@ def init_git(folder: str):
|
|
|
260
261
|
|
|
261
262
|
|
|
262
263
|
def generate_pipe_file(name: str, content: str, folder: str):
|
|
263
|
-
|
|
264
|
+
def is_copy(content: str) -> bool:
|
|
265
|
+
return re.search(r"TYPE copy", content, re.IGNORECASE) is not None
|
|
266
|
+
|
|
267
|
+
def is_materialization(content: str) -> bool:
|
|
268
|
+
return re.search(r"TYPE materialized", content, re.IGNORECASE) is not None
|
|
269
|
+
|
|
270
|
+
def is_sink(content: str) -> bool:
|
|
271
|
+
return re.search(r"TYPE sink", content, re.IGNORECASE) is not None
|
|
272
|
+
|
|
273
|
+
if is_copy(content):
|
|
274
|
+
pathname = "copies"
|
|
275
|
+
elif is_materialization(content):
|
|
276
|
+
pathname = "materializations"
|
|
277
|
+
elif is_sink(content):
|
|
278
|
+
pathname = "sinks"
|
|
279
|
+
else:
|
|
280
|
+
pathname = "endpoints"
|
|
281
|
+
|
|
282
|
+
base = Path(folder) / pathname
|
|
264
283
|
if not base.exists():
|
|
265
284
|
base = Path()
|
|
266
285
|
f = base / (f"{name}.pipe")
|
|
@@ -768,13 +768,13 @@ async def process(
|
|
|
768
768
|
raise click.ClickException(FeedbackManager.error_forkdownstream_pipes_with_engine(pipe=resource_name))
|
|
769
769
|
|
|
770
770
|
to_run[resource_name] = r
|
|
771
|
-
file_deps = r.get("deps", [])
|
|
771
|
+
file_deps: List[str] = r.get("deps", [])
|
|
772
772
|
deps += file_deps
|
|
773
773
|
# calculate and look for deps
|
|
774
774
|
dep_list = []
|
|
775
775
|
for x in file_deps:
|
|
776
776
|
if x not in INTERNAL_TABLES or is_internal:
|
|
777
|
-
f, ds = find_file_by_name(dir_path, x, verbose, vendor_paths=vendor_paths, resource=r)
|
|
777
|
+
f, ds = find_file_by_name(dir_path or ".", x, verbose, vendor_paths=vendor_paths, resource=r)
|
|
778
778
|
if f:
|
|
779
779
|
dep_list.append(f.rsplit(".", 1)[0])
|
|
780
780
|
if ds:
|
|
@@ -266,7 +266,19 @@ async def new_pipe(
|
|
|
266
266
|
|
|
267
267
|
if data.get("type") == "endpoint":
|
|
268
268
|
token = tb_client.token
|
|
269
|
-
|
|
269
|
+
try:
|
|
270
|
+
example_params = {
|
|
271
|
+
"format": "json",
|
|
272
|
+
"pipe": p["name"],
|
|
273
|
+
"q": "",
|
|
274
|
+
"token": token,
|
|
275
|
+
}
|
|
276
|
+
endpoint_url = await tb_client._req(f"/examples/query.http?{urlencode(example_params)}")
|
|
277
|
+
if endpoint_url:
|
|
278
|
+
endpoint_url = endpoint_url.replace("http://localhost:8001", host)
|
|
279
|
+
click.echo(f"""** => Test endpoint with:\n** $ curl {endpoint_url}""")
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
270
282
|
|
|
271
283
|
|
|
272
284
|
async def get_token_from_main_branch(branch_tb_client: TinyB) -> Optional[str]:
|
|
@@ -542,7 +542,7 @@ async def datasource_share(ctx: Context, datasource_name: str, workspace_name_or
|
|
|
542
542
|
"""Share a datasource"""
|
|
543
543
|
|
|
544
544
|
config = CLIConfig.get_project_config()
|
|
545
|
-
client =
|
|
545
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
546
546
|
host = config.get_host() or CLIConfig.DEFAULTS["host"]
|
|
547
547
|
ui_host = get_display_host(host)
|
|
548
548
|
|
tinybird/tb/modules/llm.py
CHANGED
|
@@ -1,33 +1,9 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import urllib.parse
|
|
3
1
|
from copy import deepcopy
|
|
4
|
-
from typing import
|
|
5
|
-
|
|
6
|
-
from pydantic import BaseModel
|
|
2
|
+
from typing import Optional
|
|
7
3
|
|
|
8
4
|
from tinybird.client import TinyB
|
|
9
5
|
|
|
10
6
|
|
|
11
|
-
class DataFile(BaseModel):
|
|
12
|
-
name: str
|
|
13
|
-
content: str
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class DataProject(BaseModel):
|
|
17
|
-
datasources: List[DataFile]
|
|
18
|
-
pipes: List[DataFile]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class TestExpectation(BaseModel):
|
|
22
|
-
name: str
|
|
23
|
-
description: str
|
|
24
|
-
parameters: str
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class TestExpectations(BaseModel):
|
|
28
|
-
tests: List[TestExpectation]
|
|
29
|
-
|
|
30
|
-
|
|
31
7
|
class LLM:
|
|
32
8
|
def __init__(
|
|
33
9
|
self,
|
|
@@ -37,69 +13,26 @@ class LLM:
|
|
|
37
13
|
self.user_client = deepcopy(client)
|
|
38
14
|
self.user_client.token = user_token
|
|
39
15
|
|
|
40
|
-
async def ask(self,
|
|
16
|
+
async def ask(self, system_prompt: str, prompt: Optional[str] = None) -> str:
|
|
41
17
|
"""
|
|
42
18
|
Calls the model with the given prompt and returns the response.
|
|
43
19
|
|
|
44
20
|
Args:
|
|
21
|
+
system_prompt (str): The system prompt to send to the model.
|
|
45
22
|
prompt (str): The user prompt to send to the model.
|
|
46
23
|
|
|
47
24
|
Returns:
|
|
48
25
|
str: The response from the language model.
|
|
49
26
|
"""
|
|
50
|
-
messages = []
|
|
51
27
|
|
|
52
|
-
|
|
53
|
-
messages.append({"role": "user", "content": system_prompt})
|
|
28
|
+
data = {"system": system_prompt}
|
|
54
29
|
|
|
55
30
|
if prompt:
|
|
56
|
-
|
|
31
|
+
data["prompt"] = prompt
|
|
57
32
|
|
|
58
|
-
data = {
|
|
59
|
-
"model": model,
|
|
60
|
-
"messages": messages,
|
|
61
|
-
}
|
|
62
33
|
response = await self.user_client._req(
|
|
63
34
|
"/v0/llm",
|
|
64
35
|
method="POST",
|
|
65
|
-
data=
|
|
66
|
-
headers={"Content-Type": "application/json"},
|
|
36
|
+
data=data,
|
|
67
37
|
)
|
|
68
38
|
return response.get("result", "")
|
|
69
|
-
|
|
70
|
-
async def create_project(self, prompt: str) -> DataProject:
|
|
71
|
-
try:
|
|
72
|
-
prompt = (
|
|
73
|
-
prompt
|
|
74
|
-
+ "\n#More extra context\n- If you add some array data type remember that the json path should be like this: `json:$.array_field[:]`"
|
|
75
|
-
)
|
|
76
|
-
response = await self.user_client._req(
|
|
77
|
-
"/v0/llm/create",
|
|
78
|
-
method="POST",
|
|
79
|
-
data=f'{{"prompt": {json.dumps(prompt)}}}',
|
|
80
|
-
headers={"Content-Type": "application/json"},
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
return DataProject.model_validate(response.get("result", {}))
|
|
84
|
-
except Exception:
|
|
85
|
-
return DataProject(datasources=[], pipes=[])
|
|
86
|
-
|
|
87
|
-
async def generate_sql_sample_data(self, schema: str, rows: int = 20, prompt: str = "") -> str:
|
|
88
|
-
response = await self.user_client._req(
|
|
89
|
-
"/v0/llm/mock",
|
|
90
|
-
method="POST",
|
|
91
|
-
data=f'{{"schema": "{urllib.parse.quote(schema)}", "rows": {rows}, "context": "{prompt}"}}',
|
|
92
|
-
headers={"Content-Type": "application/json"},
|
|
93
|
-
)
|
|
94
|
-
result = response.get("result", "")
|
|
95
|
-
return result.replace("elementAt", "arrayElement")
|
|
96
|
-
|
|
97
|
-
async def create_tests(self, pipe_content: str, pipe_params: set[str], prompt: str = "") -> TestExpectations:
|
|
98
|
-
response = await self.user_client._req(
|
|
99
|
-
"/v0/llm/create/tests",
|
|
100
|
-
method="POST",
|
|
101
|
-
data=json.dumps({"pipe_content": pipe_content, "pipe_params": list(pipe_params), "prompt": prompt}),
|
|
102
|
-
headers={"Content-Type": "application/json"},
|
|
103
|
-
)
|
|
104
|
-
result = response.get("result", "")
|
|
105
|
-
return TestExpectations.model_validate(result)
|
tinybird/tb/modules/local.py
CHANGED
|
@@ -82,7 +82,7 @@ def get_docker_client():
|
|
|
82
82
|
client.ping()
|
|
83
83
|
return client
|
|
84
84
|
except Exception:
|
|
85
|
-
raise CLIException("
|
|
85
|
+
raise CLIException("No container runtime is running. Make sure a Docker-compatible runtime is installed and running.")
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
def stop_tinybird_local(docker_client):
|
|
@@ -8,7 +8,7 @@ from tinybird.client import TinyB
|
|
|
8
8
|
from tinybird.tb.modules.config import CLIConfig
|
|
9
9
|
from tinybird.tb.modules.exceptions import CLIException
|
|
10
10
|
|
|
11
|
-
TB_IMAGE_NAME = "tinybirdco/tinybird-local:
|
|
11
|
+
TB_IMAGE_NAME = "tinybirdco/tinybird-local:beta"
|
|
12
12
|
TB_CONTAINER_NAME = "tinybird-local"
|
|
13
13
|
TB_LOCAL_PORT = int(os.getenv("TB_LOCAL_PORT", 80))
|
|
14
14
|
TB_LOCAL_HOST = f"http://localhost:{TB_LOCAL_PORT}"
|
tinybird/tb/modules/login.py
CHANGED
|
@@ -135,7 +135,12 @@ async def login(host: str, workspace: str):
|
|
|
135
135
|
server_thread.start()
|
|
136
136
|
|
|
137
137
|
# Open the browser to the auth page
|
|
138
|
-
|
|
138
|
+
if "wadus" in host:
|
|
139
|
+
client_id = "Rpl7Uy9aSjqoPCSvHgGl3zNQuZcSOXBe"
|
|
140
|
+
base_auth_url = "https://auth.wadus1.tinybird.co"
|
|
141
|
+
else:
|
|
142
|
+
client_id = "T6excMo8IKguvUw4vFNYfqlt9pe6msCU"
|
|
143
|
+
base_auth_url = "https://auth.tinybird.co"
|
|
139
144
|
callback_url = f"http://localhost:{AUTH_SERVER_PORT}"
|
|
140
145
|
params = {
|
|
141
146
|
"client_id": client_id,
|
|
@@ -143,7 +148,7 @@ async def login(host: str, workspace: str):
|
|
|
143
148
|
"response_type": "token",
|
|
144
149
|
"scope": "openid profile email",
|
|
145
150
|
}
|
|
146
|
-
auth_url = f"
|
|
151
|
+
auth_url = f"{base_auth_url}/authorize?{urlencode(params)}"
|
|
147
152
|
webbrowser.open(auth_url)
|
|
148
153
|
|
|
149
154
|
# Wait for the authentication to complete or timeout
|
tinybird/tb/modules/mock.py
CHANGED
|
@@ -66,7 +66,7 @@ async def mock(datasource: str, rows: int, prompt: str, folder: str) -> None:
|
|
|
66
66
|
llm = LLM(user_token=user_token, client=user_client)
|
|
67
67
|
tb_client = await get_tinybird_local_client(os.path.abspath(folder))
|
|
68
68
|
prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
|
|
69
|
-
response = await llm.ask(
|
|
69
|
+
response = await llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
|
|
70
70
|
sql = extract_xml(response, "sql")
|
|
71
71
|
if os.environ.get("TB_DEBUG", "") != "":
|
|
72
72
|
logging.debug(sql)
|
tinybird/tb/modules/shell.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import concurrent.futures
|
|
3
|
+
import glob
|
|
3
4
|
import os
|
|
4
5
|
import subprocess
|
|
5
6
|
import sys
|
|
7
|
+
from pathlib import Path
|
|
6
8
|
from typing import List
|
|
7
9
|
|
|
8
10
|
import click
|
|
@@ -21,17 +23,22 @@ from tinybird.tb.modules.table import format_table
|
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class DynamicCompleter(Completer):
|
|
24
|
-
def __init__(self,
|
|
25
|
-
self.
|
|
26
|
-
self.shared_datasources = shared_datasources
|
|
27
|
-
self.endpoints = endpoints
|
|
28
|
-
self.pipes = pipes
|
|
26
|
+
def __init__(self, folder: str):
|
|
27
|
+
self.folder = folder
|
|
29
28
|
self.static_commands = ["create", "mock", "test", "select"]
|
|
30
29
|
self.test_commands = ["create", "run", "update"]
|
|
31
30
|
self.mock_flags = ["--prompt", "--rows"]
|
|
32
31
|
self.common_rows = ["10", "50", "100", "500", "1000"]
|
|
33
32
|
self.sql_keywords = ["select", "from", "where", "group by", "order by", "limit"]
|
|
34
33
|
|
|
34
|
+
@property
|
|
35
|
+
def datasources(self):
|
|
36
|
+
return [Path(f).stem for f in glob.glob(f"{self.folder}/**/*.datasource", recursive=True)]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def pipes(self):
|
|
40
|
+
return [Path(f).stem for f in glob.glob(f"{self.folder}/**/*.pipe", recursive=True)]
|
|
41
|
+
|
|
35
42
|
def get_completions(self, document, complete_event):
|
|
36
43
|
text = document.text_before_cursor.strip()
|
|
37
44
|
words = text.split()
|
|
@@ -75,9 +82,9 @@ class DynamicCompleter(Completer):
|
|
|
75
82
|
if words[-1] == "from" or (
|
|
76
83
|
"from" in words and len(words) > words.index("from") + 1 and text_lower.endswith(" ")
|
|
77
84
|
):
|
|
78
|
-
for x in self.datasources
|
|
85
|
+
for x in self.datasources:
|
|
79
86
|
yield Completion(x, start_position=0, display=x, style="class:completion.datasource")
|
|
80
|
-
for x in self.
|
|
87
|
+
for x in self.pipes:
|
|
81
88
|
yield Completion(x, start_position=0, display=x, style="class:completion.pipe")
|
|
82
89
|
return
|
|
83
90
|
|
|
@@ -116,7 +123,7 @@ class DynamicCompleter(Completer):
|
|
|
116
123
|
yield Completion(cmd, start_position=0, display=cmd, style="class:completion.cmd")
|
|
117
124
|
return
|
|
118
125
|
elif len(words) == 2:
|
|
119
|
-
for cmd in self.
|
|
126
|
+
for cmd in self.pipes:
|
|
120
127
|
yield Completion(cmd, start_position=0, display=cmd, style="class:completion.pipe")
|
|
121
128
|
return
|
|
122
129
|
|
|
@@ -130,7 +137,7 @@ class DynamicCompleter(Completer):
|
|
|
130
137
|
style="class:completion.cmd",
|
|
131
138
|
)
|
|
132
139
|
|
|
133
|
-
for cmd in self.datasources
|
|
140
|
+
for cmd in self.datasources:
|
|
134
141
|
if cmd.startswith(current_word):
|
|
135
142
|
yield Completion(
|
|
136
143
|
cmd,
|
|
@@ -139,7 +146,7 @@ class DynamicCompleter(Completer):
|
|
|
139
146
|
style="class:completion.datasource",
|
|
140
147
|
)
|
|
141
148
|
|
|
142
|
-
for cmd in self.
|
|
149
|
+
for cmd in self.pipes:
|
|
143
150
|
if cmd.startswith(current_word):
|
|
144
151
|
yield Completion(
|
|
145
152
|
cmd,
|
|
@@ -176,26 +183,14 @@ def _(event):
|
|
|
176
183
|
|
|
177
184
|
|
|
178
185
|
class Shell:
|
|
179
|
-
def __init__(
|
|
180
|
-
self,
|
|
181
|
-
folder: str,
|
|
182
|
-
client: TinyB,
|
|
183
|
-
datasources: List[str],
|
|
184
|
-
shared_datasources: List[str],
|
|
185
|
-
pipes: List[str],
|
|
186
|
-
endpoints: List[str],
|
|
187
|
-
):
|
|
186
|
+
def __init__(self, folder: str, client: TinyB):
|
|
188
187
|
self.history = self.get_history()
|
|
189
188
|
self.folder = folder
|
|
190
189
|
self.client = client
|
|
191
|
-
self.datasources = datasources
|
|
192
|
-
self.shared_datasources = shared_datasources
|
|
193
|
-
self.pipes = pipes
|
|
194
|
-
self.endpoints = endpoints
|
|
195
190
|
self.prompt_message = "\ntb > "
|
|
196
191
|
self.commands = ["create", "mock", "test", "tb", "select"]
|
|
197
192
|
self.session: PromptSession = PromptSession(
|
|
198
|
-
completer=DynamicCompleter(
|
|
193
|
+
completer=DynamicCompleter(folder),
|
|
199
194
|
complete_style=CompleteStyle.COLUMN,
|
|
200
195
|
complete_while_typing=True,
|
|
201
196
|
history=self.history,
|
|
@@ -274,7 +269,7 @@ class Shell:
|
|
|
274
269
|
elif arg.startswith("mock"):
|
|
275
270
|
self.handle_mock(arg)
|
|
276
271
|
else:
|
|
277
|
-
subprocess.run(f"tb
|
|
272
|
+
subprocess.run(f"tb {arg}", shell=True, text=True)
|
|
278
273
|
|
|
279
274
|
def default(self, argline):
|
|
280
275
|
click.echo("")
|
|
@@ -283,10 +278,10 @@ class Shell:
|
|
|
283
278
|
return
|
|
284
279
|
if arg.startswith("with") or arg.startswith("select"):
|
|
285
280
|
self.run_sql(argline)
|
|
286
|
-
elif len(arg.split()) == 1 and arg in self.
|
|
281
|
+
elif len(arg.split()) == 1 and arg in self.pipes + self.datasources:
|
|
287
282
|
self.run_sql(f"select * from {arg}")
|
|
288
283
|
else:
|
|
289
|
-
subprocess.run(f"tb
|
|
284
|
+
subprocess.run(f"tb {arg}", shell=True, text=True)
|
|
290
285
|
|
|
291
286
|
def run_sql(self, query, rows_limit=20):
|
|
292
287
|
try:
|
|
@@ -310,16 +305,16 @@ class Shell:
|
|
|
310
305
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
311
306
|
res = executor.submit(run_query_in_thread).result()
|
|
312
307
|
|
|
313
|
-
|
|
314
|
-
|
|
308
|
+
if isinstance(res, dict) and "error" in res:
|
|
309
|
+
click.echo(FeedbackManager.error_exception(error=res["error"]))
|
|
315
310
|
|
|
316
|
-
|
|
317
|
-
|
|
311
|
+
if isinstance(res, dict) and "data" in res and res["data"]:
|
|
312
|
+
print_table_formatted(res, "QUERY")
|
|
313
|
+
else:
|
|
314
|
+
click.echo(FeedbackManager.info_no_rows())
|
|
318
315
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
else:
|
|
322
|
-
click.echo(FeedbackManager.info_no_rows())
|
|
316
|
+
except Exception as e:
|
|
317
|
+
click.echo(FeedbackManager.error_exception(error=str(e)))
|
|
323
318
|
|
|
324
319
|
def reprint_prompt(self):
|
|
325
320
|
click.echo(f"{bcolors.OKGREEN}{self.prompt_message}{bcolors.ENDC}", nl=False)
|