tinybird 0.0.1.dev28__py3-none-any.whl → 0.0.1.dev30__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/client.py +1 -1
- tinybird/prompts.py +218 -325
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +2 -1
- tinybird/tb/modules/build.py +61 -201
- tinybird/tb/modules/build_client.py +219 -0
- tinybird/tb/modules/cli.py +52 -16
- tinybird/tb/modules/common.py +1 -26
- tinybird/tb/modules/config.py +0 -8
- tinybird/tb/modules/create.py +20 -1
- 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/deploy.py +33 -33
- tinybird/tb/modules/llm.py +19 -4
- tinybird/tb/modules/local.py +1 -1
- tinybird/tb/modules/login.py +7 -2
- tinybird/tb/modules/shell.py +2 -2
- tinybird/tb/modules/test.py +41 -22
- tinybird/tb/modules/update.py +182 -0
- {tinybird-0.0.1.dev28.dist-info → tinybird-0.0.1.dev30.dist-info}/METADATA +2 -1
- {tinybird-0.0.1.dev28.dist-info → tinybird-0.0.1.dev30.dist-info}/RECORD +25 -24
- tinybird/tb/modules/build_server.py +0 -75
- {tinybird-0.0.1.dev28.dist-info → tinybird-0.0.1.dev30.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev28.dist-info → tinybird-0.0.1.dev30.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev28.dist-info → tinybird-0.0.1.dev30.dist-info}/top_level.txt +0 -0
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
|
|
@@ -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/deploy.py
CHANGED
|
@@ -3,7 +3,7 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
import time
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import List
|
|
6
|
+
from typing import List, Optional
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
9
|
import requests
|
|
@@ -24,8 +24,8 @@ def project_files(project_path: Path) -> List[str]:
|
|
|
24
24
|
return project_files
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def promote_deployment(host: str, headers: dict) -> None:
|
|
28
|
-
TINYBIRD_API_URL = host
|
|
27
|
+
def promote_deployment(host: Optional[str], headers: dict) -> None:
|
|
28
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments"
|
|
29
29
|
r = requests.get(TINYBIRD_API_URL, headers=headers)
|
|
30
30
|
result = r.json()
|
|
31
31
|
logging.debug(json.dumps(result, indent=2))
|
|
@@ -50,14 +50,14 @@ def promote_deployment(host: str, headers: dict) -> None:
|
|
|
50
50
|
else:
|
|
51
51
|
click.echo(FeedbackManager.success(message="Promoting deployment"))
|
|
52
52
|
|
|
53
|
-
TINYBIRD_API_URL =
|
|
53
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments/{candidate_deployment.get('id')}/set-live"
|
|
54
54
|
r = requests.post(TINYBIRD_API_URL, headers=headers)
|
|
55
55
|
result = r.json()
|
|
56
56
|
logging.debug(json.dumps(result, indent=2))
|
|
57
57
|
|
|
58
58
|
click.echo(FeedbackManager.success(message="Removing old deployment"))
|
|
59
59
|
|
|
60
|
-
TINYBIRD_API_URL =
|
|
60
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments/{last_deployment.get('id')}"
|
|
61
61
|
r = requests.delete(TINYBIRD_API_URL, headers=headers)
|
|
62
62
|
result = r.json()
|
|
63
63
|
logging.debug(json.dumps(result, indent=2))
|
|
@@ -65,8 +65,8 @@ def promote_deployment(host: str, headers: dict) -> None:
|
|
|
65
65
|
click.echo(FeedbackManager.success(message="Deployment promoted successfully"))
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def rollback_deployment(host: str, headers: dict) -> None:
|
|
69
|
-
TINYBIRD_API_URL = host
|
|
68
|
+
def rollback_deployment(host: Optional[str], headers: dict) -> None:
|
|
69
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments"
|
|
70
70
|
r = requests.get(TINYBIRD_API_URL, headers=headers)
|
|
71
71
|
result = r.json()
|
|
72
72
|
logging.debug(json.dumps(result, indent=2))
|
|
@@ -91,14 +91,14 @@ def rollback_deployment(host: str, headers: dict) -> None:
|
|
|
91
91
|
else:
|
|
92
92
|
click.echo(FeedbackManager.success(message="Promoting previous deployment"))
|
|
93
93
|
|
|
94
|
-
TINYBIRD_API_URL =
|
|
94
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments/{previous_deployment.get('id')}/set-live"
|
|
95
95
|
r = requests.post(TINYBIRD_API_URL, headers=headers)
|
|
96
96
|
result = r.json()
|
|
97
97
|
logging.debug(json.dumps(result, indent=2))
|
|
98
98
|
|
|
99
99
|
click.echo(FeedbackManager.success(message="Removing current deployment"))
|
|
100
100
|
|
|
101
|
-
TINYBIRD_API_URL =
|
|
101
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments/{current_deployment.get('id')}"
|
|
102
102
|
r = requests.delete(TINYBIRD_API_URL, headers=headers)
|
|
103
103
|
result = r.json()
|
|
104
104
|
logging.debug(json.dumps(result, indent=2))
|
|
@@ -106,7 +106,15 @@ def rollback_deployment(host: str, headers: dict) -> None:
|
|
|
106
106
|
click.echo(FeedbackManager.success(message="Deployment rolled back successfully"))
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
@cli.
|
|
109
|
+
@cli.group(name="deploy")
|
|
110
|
+
def deploy_group() -> None:
|
|
111
|
+
"""
|
|
112
|
+
Deploy commands.
|
|
113
|
+
"""
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@deploy_group.command(name="create")
|
|
110
118
|
@click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
|
|
111
119
|
@click.option(
|
|
112
120
|
"--wait/--no-wait",
|
|
@@ -120,7 +128,7 @@ def rollback_deployment(host: str, headers: dict) -> None:
|
|
|
120
128
|
default=False,
|
|
121
129
|
help="Auto-promote the deployment. Only works if --wait is enabled. Disabled by default.",
|
|
122
130
|
)
|
|
123
|
-
def
|
|
131
|
+
def create(project_path: Path, wait: bool, auto: bool) -> None:
|
|
124
132
|
"""
|
|
125
133
|
Validate and deploy the project server side.
|
|
126
134
|
"""
|
|
@@ -133,7 +141,7 @@ def deploy(project_path: Path, wait: bool, auto: bool) -> None:
|
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
config = CLIConfig.get_project_config(str(project_path))
|
|
136
|
-
TINYBIRD_API_URL =
|
|
144
|
+
TINYBIRD_API_URL = f"{config.get_host()}/v1/deploy"
|
|
137
145
|
TINYBIRD_API_KEY = config.get_token()
|
|
138
146
|
|
|
139
147
|
files = [
|
|
@@ -178,7 +186,7 @@ def deploy(project_path: Path, wait: bool, auto: bool) -> None:
|
|
|
178
186
|
if deployment and wait:
|
|
179
187
|
while deployment.get("status") != "data_ready":
|
|
180
188
|
time.sleep(5)
|
|
181
|
-
TINYBIRD_API_URL =
|
|
189
|
+
TINYBIRD_API_URL = f"{config.get_host()}/v1/deployments/{deployment.get('id')}"
|
|
182
190
|
r = requests.get(TINYBIRD_API_URL, headers=HEADERS)
|
|
183
191
|
result = r.json()
|
|
184
192
|
deployment = result.get("deployment")
|
|
@@ -189,28 +197,20 @@ def deploy(project_path: Path, wait: bool, auto: bool) -> None:
|
|
|
189
197
|
click.echo(FeedbackManager.success(message="Deployment is ready"))
|
|
190
198
|
|
|
191
199
|
if auto:
|
|
192
|
-
promote_deployment(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
@cli.group(name="releases")
|
|
196
|
-
def releases_group() -> None:
|
|
197
|
-
"""
|
|
198
|
-
Release commands.
|
|
199
|
-
"""
|
|
200
|
-
pass
|
|
200
|
+
promote_deployment(config.get_host(), HEADERS)
|
|
201
201
|
|
|
202
202
|
|
|
203
|
-
@
|
|
203
|
+
@deploy_group.command(name="list")
|
|
204
204
|
@click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
|
|
205
|
-
def
|
|
205
|
+
def deploy_list(project_path: Path) -> None:
|
|
206
206
|
"""
|
|
207
|
-
List all the
|
|
207
|
+
List all the deployments you have in the project.
|
|
208
208
|
"""
|
|
209
209
|
config = CLIConfig.get_project_config(str(project_path))
|
|
210
210
|
|
|
211
211
|
TINYBIRD_API_KEY = config.get_token()
|
|
212
212
|
HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
|
|
213
|
-
TINYBIRD_API_URL =
|
|
213
|
+
TINYBIRD_API_URL = f"{config.get_host()}/v1/deployments"
|
|
214
214
|
|
|
215
215
|
r = requests.get(TINYBIRD_API_URL, headers=HEADERS)
|
|
216
216
|
result = r.json()
|
|
@@ -226,9 +226,9 @@ def release_list(project_path: Path) -> None:
|
|
|
226
226
|
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
227
227
|
|
|
228
228
|
|
|
229
|
-
@
|
|
229
|
+
@deploy_group.command(name="promote")
|
|
230
230
|
@click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
|
|
231
|
-
def
|
|
231
|
+
def deploy_promote(project_path: Path) -> None:
|
|
232
232
|
"""
|
|
233
233
|
Promote last deploy to ready and remove old one.
|
|
234
234
|
"""
|
|
@@ -237,18 +237,18 @@ def release_promote(project_path: Path) -> None:
|
|
|
237
237
|
TINYBIRD_API_KEY = config.get_token()
|
|
238
238
|
HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
|
|
239
239
|
|
|
240
|
-
promote_deployment(
|
|
240
|
+
promote_deployment(config.get_host(), HEADERS)
|
|
241
241
|
|
|
242
242
|
|
|
243
|
-
@
|
|
243
|
+
@deploy_group.command(name="rollback")
|
|
244
244
|
@click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
|
|
245
|
-
def
|
|
245
|
+
def deploy_rollback(project_path: Path) -> None:
|
|
246
246
|
"""
|
|
247
|
-
Rollback to the previous
|
|
247
|
+
Rollback to the previous deployment.
|
|
248
248
|
"""
|
|
249
249
|
config = CLIConfig.get_project_config(str(project_path))
|
|
250
250
|
|
|
251
251
|
TINYBIRD_API_KEY = config.get_token()
|
|
252
252
|
HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
|
|
253
253
|
|
|
254
|
-
rollback_deployment(
|
|
254
|
+
rollback_deployment(config.get_host(), HEADERS)
|
tinybird/tb/modules/llm.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import os
|
|
2
3
|
import urllib.parse
|
|
3
4
|
from copy import deepcopy
|
|
4
|
-
from typing import List
|
|
5
|
+
from typing import Any, List
|
|
5
6
|
|
|
7
|
+
from anthropic import AnthropicVertex
|
|
6
8
|
from pydantic import BaseModel
|
|
7
9
|
|
|
8
10
|
from tinybird.client import TinyB
|
|
@@ -37,7 +39,7 @@ class LLM:
|
|
|
37
39
|
self.user_client = deepcopy(client)
|
|
38
40
|
self.user_client.token = user_token
|
|
39
41
|
|
|
40
|
-
async def ask(self, prompt: str, system_prompt: str = ""
|
|
42
|
+
async def ask(self, prompt: str, system_prompt: str = "") -> str:
|
|
41
43
|
"""
|
|
42
44
|
Calls the model with the given prompt and returns the response.
|
|
43
45
|
|
|
@@ -47,7 +49,7 @@ class LLM:
|
|
|
47
49
|
Returns:
|
|
48
50
|
str: The response from the language model.
|
|
49
51
|
"""
|
|
50
|
-
messages = []
|
|
52
|
+
messages: List[Any] = []
|
|
51
53
|
|
|
52
54
|
if system_prompt:
|
|
53
55
|
messages.append({"role": "user", "content": system_prompt})
|
|
@@ -55,8 +57,21 @@ class LLM:
|
|
|
55
57
|
if prompt:
|
|
56
58
|
messages.append({"role": "user", "content": prompt})
|
|
57
59
|
|
|
60
|
+
if gcloud_access_token := os.getenv("GCLOUD_ACCESS_TOKEN"):
|
|
61
|
+
client = AnthropicVertex(
|
|
62
|
+
region="europe-west1",
|
|
63
|
+
project_id="gen-lang-client-0705305160",
|
|
64
|
+
access_token=gcloud_access_token,
|
|
65
|
+
)
|
|
66
|
+
message = client.messages.create(
|
|
67
|
+
max_tokens=8000,
|
|
68
|
+
messages=messages,
|
|
69
|
+
model="claude-3-5-sonnet-v2@20241022",
|
|
70
|
+
)
|
|
71
|
+
return message.content[0].text or "" # type: ignore
|
|
72
|
+
|
|
58
73
|
data = {
|
|
59
|
-
"model":
|
|
74
|
+
"model": "o1-mini",
|
|
60
75
|
"messages": messages,
|
|
61
76
|
}
|
|
62
77
|
response = await self.user_client._req(
|
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):
|
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/shell.py
CHANGED
|
@@ -274,7 +274,7 @@ class Shell:
|
|
|
274
274
|
elif arg.startswith("mock"):
|
|
275
275
|
self.handle_mock(arg)
|
|
276
276
|
else:
|
|
277
|
-
subprocess.run(f"tb
|
|
277
|
+
subprocess.run(f"tb {arg}", shell=True, text=True)
|
|
278
278
|
|
|
279
279
|
def default(self, argline):
|
|
280
280
|
click.echo("")
|
|
@@ -286,7 +286,7 @@ class Shell:
|
|
|
286
286
|
elif len(arg.split()) == 1 and arg in self.endpoints + self.pipes + self.datasources + self.shared_datasources:
|
|
287
287
|
self.run_sql(f"select * from {arg}")
|
|
288
288
|
else:
|
|
289
|
-
subprocess.run(f"tb
|
|
289
|
+
subprocess.run(f"tb {arg}", shell=True, text=True)
|
|
290
290
|
|
|
291
291
|
def run_sql(self, query, rows_limit=20):
|
|
292
292
|
try:
|
tinybird/tb/modules/test.py
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
import difflib
|
|
7
7
|
import glob
|
|
8
8
|
import os
|
|
9
|
+
import urllib.parse
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import Any, Dict,
|
|
11
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
11
12
|
|
|
12
13
|
import click
|
|
13
14
|
import yaml
|
|
15
|
+
from requests import Response
|
|
14
16
|
|
|
15
17
|
from tinybird.prompts import test_create_prompt
|
|
16
18
|
from tinybird.tb.modules.cli import cli
|
|
@@ -66,9 +68,9 @@ def test(ctx: click.Context) -> None:
|
|
|
66
68
|
type=click.Path(exists=True, file_okay=False),
|
|
67
69
|
help="Folder where datafiles will be placed",
|
|
68
70
|
)
|
|
69
|
-
@click.option("--prompt", type=str, default=
|
|
71
|
+
@click.option("--prompt", type=str, default="", help="Prompt to be used to create the test")
|
|
70
72
|
@coro
|
|
71
|
-
async def test_create(name_or_filename: str, prompt:
|
|
73
|
+
async def test_create(name_or_filename: str, prompt: str, folder: str) -> None:
|
|
72
74
|
"""
|
|
73
75
|
Create a test for an existing pipe
|
|
74
76
|
"""
|
|
@@ -80,15 +82,13 @@ async def test_create(name_or_filename: str, prompt: Optional[str], folder: str)
|
|
|
80
82
|
raise CLIException(FeedbackManager.error(message=f"Pipe {name_or_filename} not found"))
|
|
81
83
|
else:
|
|
82
84
|
pipe_folders = ("endpoints", "copies", "materializations", "sinks", "pipes")
|
|
83
|
-
|
|
84
|
-
(
|
|
85
|
+
try:
|
|
86
|
+
pipe_path = next(
|
|
85
87
|
root_path / folder / f"{name_or_filename}.pipe"
|
|
86
88
|
for folder in pipe_folders
|
|
87
89
|
if (root_path / folder / f"{name_or_filename}.pipe").exists()
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
if not pipe_path:
|
|
90
|
+
)
|
|
91
|
+
except Exception:
|
|
92
92
|
raise CLIException(FeedbackManager.error(message=f"Pipe {name_or_filename} not found"))
|
|
93
93
|
|
|
94
94
|
pipe_name = pipe_path.stem
|
|
@@ -97,8 +97,8 @@ async def test_create(name_or_filename: str, prompt: Optional[str], folder: str)
|
|
|
97
97
|
pipe_content = pipe_path.read_text()
|
|
98
98
|
|
|
99
99
|
client = await get_tinybird_local_client(os.path.abspath(folder))
|
|
100
|
-
|
|
101
|
-
parameters = set([param["name"] for node in
|
|
100
|
+
pipe = await client._req(f"/v0/pipes/{pipe_name}")
|
|
101
|
+
parameters = set([param["name"] for node in pipe["nodes"] for param in node["params"]])
|
|
102
102
|
|
|
103
103
|
system_prompt = test_create_prompt.format(
|
|
104
104
|
name=pipe_name,
|
|
@@ -107,24 +107,26 @@ async def test_create(name_or_filename: str, prompt: Optional[str], folder: str)
|
|
|
107
107
|
)
|
|
108
108
|
config = CLIConfig.get_project_config(folder)
|
|
109
109
|
user_token = config.get_user_token()
|
|
110
|
+
if not user_token:
|
|
111
|
+
raise CLIException(FeedbackManager.error(message="No user token found"))
|
|
110
112
|
llm = LLM(user_token=user_token, client=config.get_client())
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
tests_content = parse_xml(
|
|
114
|
+
response_llm = await llm.ask(prompt, system_prompt=system_prompt)
|
|
115
|
+
response_xml = extract_xml(response_llm, "response")
|
|
116
|
+
tests_content = parse_xml(response_xml, "test")
|
|
115
117
|
|
|
116
118
|
tests: List[Dict[str, Any]] = []
|
|
117
119
|
for test_content in tests_content:
|
|
118
|
-
test = {}
|
|
120
|
+
test: Dict[str, Any] = {}
|
|
119
121
|
test["name"] = extract_xml(test_content, "name")
|
|
120
122
|
test["description"] = extract_xml(test_content, "description")
|
|
121
|
-
|
|
122
|
-
test["parameters"] =
|
|
123
|
+
parameters_api = extract_xml(test_content, "parameters")
|
|
124
|
+
test["parameters"] = parameters_api.split("?")[1] if "?" in parameters_api else parameters_api
|
|
123
125
|
test["expected_result"] = ""
|
|
124
126
|
|
|
125
127
|
response = None
|
|
126
128
|
try:
|
|
127
|
-
response = await client
|
|
129
|
+
response = await get_pipe_data(client, pipe_name=pipe_name, test_params=test["parameters"])
|
|
128
130
|
except Exception:
|
|
129
131
|
pass
|
|
130
132
|
|
|
@@ -181,7 +183,7 @@ async def test_update(pipe: str, folder: str) -> None:
|
|
|
181
183
|
test_params = test["parameters"].split("?")[1] if "?" in test["parameters"] else test["parameters"]
|
|
182
184
|
response = None
|
|
183
185
|
try:
|
|
184
|
-
response = await client
|
|
186
|
+
response = await get_pipe_data(client, pipe_name=pipe_name, test_params=test_params)
|
|
185
187
|
except Exception:
|
|
186
188
|
continue
|
|
187
189
|
|
|
@@ -221,20 +223,21 @@ async def run_tests(name: Tuple[str, ...], folder: str) -> None:
|
|
|
221
223
|
client = await get_tinybird_local_client(os.path.abspath(folder))
|
|
222
224
|
paths = [Path(n) for n in name]
|
|
223
225
|
endpoints = [f"./tests/{p.stem}.yaml" for p in paths]
|
|
224
|
-
test_files:
|
|
226
|
+
test_files: List[str] = endpoints if len(endpoints) > 0 else glob.glob("./tests/**/*.y*ml", recursive=True)
|
|
225
227
|
|
|
226
228
|
async def run_test(test_file):
|
|
227
229
|
test_file_path = Path(test_file)
|
|
228
230
|
click.echo(FeedbackManager.info(message=f"\n* {test_file_path.stem}{test_file_path.suffix}"))
|
|
229
231
|
test_file_content = yaml.safe_load(test_file_path.read_text())
|
|
232
|
+
|
|
230
233
|
for test in test_file_content:
|
|
231
234
|
try:
|
|
232
235
|
test_params = test["parameters"].split("?")[1] if "?" in test["parameters"] else test["parameters"]
|
|
233
236
|
response = None
|
|
234
237
|
try:
|
|
235
|
-
response = await client
|
|
238
|
+
response = await get_pipe_data(client, pipe_name=test_file_path.stem, test_params=test_params)
|
|
236
239
|
except Exception:
|
|
237
|
-
|
|
240
|
+
continue
|
|
238
241
|
|
|
239
242
|
expected_result = response.text
|
|
240
243
|
if response.status_code >= 400:
|
|
@@ -271,3 +274,19 @@ async def run_tests(name: Tuple[str, ...], folder: str) -> None:
|
|
|
271
274
|
exit(1)
|
|
272
275
|
else:
|
|
273
276
|
click.echo(FeedbackManager.success(message=f"\n✓ {test_count}/{test_count} passed"))
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
async def get_pipe_data(client, pipe_name: str, test_params: str) -> Response:
|
|
280
|
+
pipe = await client._req(f"/v0/pipes/{pipe_name}")
|
|
281
|
+
output_node = next(
|
|
282
|
+
(node for node in pipe["nodes"] if node["node_type"] != "default" and node["node_type"] != "standard"),
|
|
283
|
+
{"name": "not_found"},
|
|
284
|
+
)
|
|
285
|
+
if output_node["node_type"] == "endpoint":
|
|
286
|
+
return await client._req_raw(f"/v0/pipes/{pipe_name}.ndjson?{test_params}")
|
|
287
|
+
|
|
288
|
+
params = {
|
|
289
|
+
"q": output_node["sql"],
|
|
290
|
+
"pipeline": pipe_name,
|
|
291
|
+
}
|
|
292
|
+
return await client._req_raw(f"""/v0/sql?{urllib.parse.urlencode(params)}&{test_params}""")
|