tinybird 0.0.1.dev67__py3-none-any.whl → 0.0.1.dev69__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tinybird might be problematic. Click here for more details.
- tinybird/ch_utils/engine.py +2 -4
- tinybird/context.py +0 -1
- tinybird/prompts.py +3 -3
- tinybird/sql_template.py +1 -3
- tinybird/sql_toolset.py +3 -3
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/modules/auth.py +1 -1
- tinybird/tb/modules/cli.py +5 -5
- tinybird/tb/modules/common.py +9 -9
- tinybird/tb/modules/create.py +42 -80
- tinybird/tb/modules/datafile/build_common.py +1 -1
- tinybird/tb/modules/datafile/build_datasource.py +1 -1
- tinybird/tb/modules/datafile/common.py +18 -3
- tinybird/tb/modules/datafile/pipe_checker.py +1 -1
- tinybird/tb/modules/datasource.py +2 -2
- tinybird/tb/modules/deployment.py +60 -11
- tinybird/tb/modules/endpoint.py +1 -1
- tinybird/tb/modules/fmt.py +1 -1
- tinybird/tb/modules/materialization.py +5 -5
- tinybird/tb/modules/mock.py +9 -38
- tinybird/tb/modules/pipe.py +1 -1
- tinybird/tb/modules/project.py +3 -3
- tinybird/tb/modules/shell.py +13 -21
- tinybird/tb/modules/test.py +39 -71
- tinybird/tb/modules/token.py +4 -4
- tinybird/tb/modules/watch.py +25 -12
- tinybird/tb/modules/workspace.py +6 -6
- tinybird/tb/modules/workspace_members.py +6 -6
- tinybird/tb_cli_modules/common.py +2 -2
- tinybird/tornado_template.py +2 -1
- tinybird-0.0.1.dev69.dist-info/METADATA +73 -0
- {tinybird-0.0.1.dev67.dist-info → tinybird-0.0.1.dev69.dist-info}/RECORD +35 -35
- {tinybird-0.0.1.dev67.dist-info → tinybird-0.0.1.dev69.dist-info}/WHEEL +1 -1
- tinybird-0.0.1.dev67.dist-info/METADATA +0 -64
- {tinybird-0.0.1.dev67.dist-info → tinybird-0.0.1.dev69.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev67.dist-info → tinybird-0.0.1.dev69.dist-info}/top_level.txt +0 -0
|
@@ -44,12 +44,15 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
44
44
|
if candidate_deployment.get("live"):
|
|
45
45
|
click.echo(FeedbackManager.error(message="Candidate deployment is already live"))
|
|
46
46
|
else:
|
|
47
|
-
click.echo(FeedbackManager.success(message="
|
|
47
|
+
click.echo(FeedbackManager.success(message="Setting candidate deployment as live"))
|
|
48
48
|
|
|
49
49
|
TINYBIRD_API_URL = f"{host}/v1/deployments/{candidate_deployment.get('id')}/set-live"
|
|
50
50
|
r = requests.post(TINYBIRD_API_URL, headers=headers)
|
|
51
51
|
result = r.json()
|
|
52
52
|
logging.debug(json.dumps(result, indent=2))
|
|
53
|
+
if result.get("error"):
|
|
54
|
+
click.echo(FeedbackManager.error(message=result.get("error")))
|
|
55
|
+
sys.exit(1)
|
|
53
56
|
|
|
54
57
|
click.echo(FeedbackManager.success(message="Removing old deployment"))
|
|
55
58
|
|
|
@@ -57,6 +60,11 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
57
60
|
r = requests.delete(TINYBIRD_API_URL, headers=headers)
|
|
58
61
|
result = r.json()
|
|
59
62
|
logging.debug(json.dumps(result, indent=2))
|
|
63
|
+
if result.get("error"):
|
|
64
|
+
click.echo(FeedbackManager.error(message=result.get("error")))
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
click.echo(FeedbackManager.success(message="Deployment promotion successfully started"))
|
|
60
68
|
|
|
61
69
|
if wait:
|
|
62
70
|
while True:
|
|
@@ -67,12 +75,11 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
67
75
|
|
|
68
76
|
last_deployment = result.get("deployment")
|
|
69
77
|
if last_deployment.get("status") == "deleted":
|
|
78
|
+
click.echo(FeedbackManager.success(message="Deployment promoted successfully"))
|
|
70
79
|
break
|
|
71
80
|
|
|
72
81
|
time.sleep(5)
|
|
73
82
|
|
|
74
|
-
click.echo(FeedbackManager.success(message="Deployment promoted successfully"))
|
|
75
|
-
|
|
76
83
|
|
|
77
84
|
# TODO(eclbg): This logic should be in the server, and there should be a dedicated endpoint for rolling back a
|
|
78
85
|
# deployment
|
|
@@ -109,6 +116,9 @@ def rollback_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
109
116
|
r = requests.post(TINYBIRD_API_URL, headers=headers)
|
|
110
117
|
result = r.json()
|
|
111
118
|
logging.debug(json.dumps(result, indent=2))
|
|
119
|
+
if result.get("error"):
|
|
120
|
+
click.echo(FeedbackManager.error(message=result.get("error")))
|
|
121
|
+
sys.exit(1)
|
|
112
122
|
|
|
113
123
|
click.echo(FeedbackManager.success(message="Removing current deployment"))
|
|
114
124
|
|
|
@@ -116,6 +126,11 @@ def rollback_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
116
126
|
r = requests.delete(TINYBIRD_API_URL, headers=headers)
|
|
117
127
|
result = r.json()
|
|
118
128
|
logging.debug(json.dumps(result, indent=2))
|
|
129
|
+
if result.get("error"):
|
|
130
|
+
click.echo(FeedbackManager.error(message=result.get("error")))
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
|
|
133
|
+
click.echo(FeedbackManager.success(message="Deployment rollback successfully started"))
|
|
119
134
|
|
|
120
135
|
if wait:
|
|
121
136
|
while True:
|
|
@@ -126,11 +141,10 @@ def rollback_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
126
141
|
|
|
127
142
|
current_deployment = result.get("deployment")
|
|
128
143
|
if current_deployment.get("status") == "deleted":
|
|
144
|
+
click.echo(FeedbackManager.success(message="Deployment rolled back successfully"))
|
|
129
145
|
break
|
|
130
146
|
time.sleep(5)
|
|
131
147
|
|
|
132
|
-
click.echo(FeedbackManager.success(message="Deployment rolled back successfully"))
|
|
133
|
-
|
|
134
148
|
|
|
135
149
|
@cli.group(name="deployment")
|
|
136
150
|
def deployment_group() -> None:
|
|
@@ -159,12 +173,18 @@ def deployment_group() -> None:
|
|
|
159
173
|
default=False,
|
|
160
174
|
help="Validate the deployment before creating it. Disabled by default.",
|
|
161
175
|
)
|
|
176
|
+
@click.option(
|
|
177
|
+
"--allow-remove-datasources/--no-allow-remove-datasources",
|
|
178
|
+
is_flag=True,
|
|
179
|
+
default=False,
|
|
180
|
+
help="Allow removing datasources. Disabled by default.",
|
|
181
|
+
)
|
|
162
182
|
@click.pass_context
|
|
163
|
-
def deployment_create(ctx: click.Context, wait: bool, auto: bool, check: bool) -> None:
|
|
183
|
+
def deployment_create(ctx: click.Context, wait: bool, auto: bool, check: bool, allow_remove_datasources: bool) -> None:
|
|
164
184
|
"""
|
|
165
185
|
Validate and deploy the project server side.
|
|
166
186
|
"""
|
|
167
|
-
create_deployment(ctx, wait, auto, check)
|
|
187
|
+
create_deployment(ctx, wait, auto, check, allow_remove_datasources)
|
|
168
188
|
|
|
169
189
|
|
|
170
190
|
@deployment_group.command(name="ls")
|
|
@@ -187,6 +207,9 @@ def deployment_ls(ctx: click.Context) -> None:
|
|
|
187
207
|
columns = ["ID", "Status", "Created at", "Live"]
|
|
188
208
|
table = []
|
|
189
209
|
for deployment in result.get("deployments"):
|
|
210
|
+
if deployment.get("id") == "0":
|
|
211
|
+
continue
|
|
212
|
+
|
|
190
213
|
table.append(
|
|
191
214
|
[
|
|
192
215
|
deployment.get("id"),
|
|
@@ -258,15 +281,27 @@ def deployment_rollback(ctx: click.Context, wait: bool) -> None:
|
|
|
258
281
|
default=False,
|
|
259
282
|
help="Validate the deployment before creating it. Disabled by default.",
|
|
260
283
|
)
|
|
284
|
+
@click.option(
|
|
285
|
+
"--allow-remove-datasources/--no-allow-remove-datasources",
|
|
286
|
+
is_flag=True,
|
|
287
|
+
default=False,
|
|
288
|
+
help="Allow removing datasources. Disabled by default.",
|
|
289
|
+
)
|
|
261
290
|
@click.pass_context
|
|
262
|
-
def deploy(ctx: click.Context, wait: bool, auto: bool, check: bool) -> None:
|
|
291
|
+
def deploy(ctx: click.Context, wait: bool, auto: bool, check: bool, allow_remove_datasources: bool) -> None:
|
|
263
292
|
"""
|
|
264
293
|
Deploy the project.
|
|
265
294
|
"""
|
|
266
|
-
create_deployment(ctx, wait, auto, check)
|
|
295
|
+
create_deployment(ctx, wait, auto, check, allow_remove_datasources)
|
|
267
296
|
|
|
268
297
|
|
|
269
|
-
def create_deployment(
|
|
298
|
+
def create_deployment(
|
|
299
|
+
ctx: click.Context,
|
|
300
|
+
wait: bool,
|
|
301
|
+
auto: bool,
|
|
302
|
+
check: Optional[bool] = None,
|
|
303
|
+
allow_remove_datasources: Optional[bool] = None,
|
|
304
|
+
) -> None:
|
|
270
305
|
# TODO: This code is duplicated in build_server.py
|
|
271
306
|
# Should be refactored to be shared
|
|
272
307
|
MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
|
|
@@ -297,21 +332,31 @@ def create_deployment(ctx: click.Context, wait: bool, auto: bool, check: Optiona
|
|
|
297
332
|
if check:
|
|
298
333
|
click.echo(FeedbackManager.highlight(message="\n» Validating deployment...\n"))
|
|
299
334
|
params["check"] = "true"
|
|
335
|
+
if allow_remove_datasources:
|
|
336
|
+
params["allow_remove_datasources"] = "true"
|
|
300
337
|
r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS, params=params)
|
|
301
338
|
result = r.json()
|
|
302
339
|
logging.debug(json.dumps(result, indent=2))
|
|
303
340
|
|
|
304
341
|
if check:
|
|
305
342
|
print_changes(result, project)
|
|
343
|
+
feedback = result.get("deployment", {}).get("feedback", [])
|
|
344
|
+
for f in feedback:
|
|
345
|
+
click.echo(
|
|
346
|
+
FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}")
|
|
347
|
+
)
|
|
348
|
+
|
|
306
349
|
status = result.get("result")
|
|
307
350
|
if status == "success":
|
|
308
351
|
click.echo(FeedbackManager.success(message="\n✓ Deployment is valid"))
|
|
309
352
|
else:
|
|
310
353
|
click.echo(FeedbackManager.error(message="\n✗ Deployment is not valid"))
|
|
354
|
+
|
|
311
355
|
return
|
|
312
356
|
|
|
313
357
|
deploy_result = result.get("result")
|
|
314
358
|
if deploy_result == "success":
|
|
359
|
+
print_changes(result, project)
|
|
315
360
|
click.echo(FeedbackManager.success(message="Deployment submitted successfully"))
|
|
316
361
|
deployment = result.get("deployment")
|
|
317
362
|
feedback = deployment.get("feedback", [])
|
|
@@ -394,4 +439,8 @@ def print_changes(result: dict, project: Project) -> None:
|
|
|
394
439
|
for p in deployment.get("deleted_pipe_names", []):
|
|
395
440
|
resources.append(["deleted", p, project.get_resource_path(p, "pipe")])
|
|
396
441
|
|
|
397
|
-
|
|
442
|
+
if resources:
|
|
443
|
+
click.echo(FeedbackManager.highlight(message="\n» Changes to be deployed...\n"))
|
|
444
|
+
echo_safe_humanfriendly_tables_format_smart_table(resources, column_names=columns)
|
|
445
|
+
else:
|
|
446
|
+
click.echo(FeedbackManager.highlight(message="\n» No changes to be deployed\n"))
|
tinybird/tb/modules/endpoint.py
CHANGED
|
@@ -29,7 +29,7 @@ def endpoint(ctx):
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
@endpoint.command(name="ls")
|
|
32
|
-
@click.option("--match", default=None, help="Retrieve any
|
|
32
|
+
@click.option("--match", default=None, help="Retrieve any resource matching the pattern. For example, --match _test")
|
|
33
33
|
@click.option(
|
|
34
34
|
"--format",
|
|
35
35
|
"format_",
|
tinybird/tb/modules/fmt.py
CHANGED
|
@@ -58,7 +58,7 @@ async def fmt(
|
|
|
58
58
|
elif (".pipe" in extensions) or (".incl" in extensions):
|
|
59
59
|
result = await format_pipe(filename, line_length, skip_eval=True)
|
|
60
60
|
else:
|
|
61
|
-
click.echo("Unsupported file type. Supported files types are: .pipe
|
|
61
|
+
click.echo("Unsupported file type. Supported files types are: .pipe and .datasource")
|
|
62
62
|
return None
|
|
63
63
|
|
|
64
64
|
if diff:
|
|
@@ -23,7 +23,7 @@ def materialization(ctx):
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@materialization.command(name="ls")
|
|
26
|
-
@click.option("--match", default=None, help="Retrieve any resourcing matching the pattern.
|
|
26
|
+
@click.option("--match", default=None, help="Retrieve any resourcing matching the pattern. For example, --match _test")
|
|
27
27
|
@click.option(
|
|
28
28
|
"--format",
|
|
29
29
|
"format_",
|
|
@@ -79,16 +79,16 @@ async def materialization_ls(ctx: click.Context, match: str, format_: str):
|
|
|
79
79
|
"--sql-condition",
|
|
80
80
|
type=str,
|
|
81
81
|
default=None,
|
|
82
|
-
help="Populate with a SQL condition to be applied to the trigger
|
|
82
|
+
help="Populate with a SQL condition to be applied to the trigger data source of the materialized view. For instance, `--sql-condition='date == toYYYYMM(now())'` it'll populate taking all the rows from the trigger data source which `date` is the current month. Use it together with --populate. --sql-condition is not taken into account if the --subset param is present. Including in the ``sql_condition`` any column present in the data source ``engine_sorting_key`` will make the populate job process less data.",
|
|
83
83
|
)
|
|
84
84
|
@click.option(
|
|
85
|
-
"--truncate", is_flag=True, default=False, help="Truncates the materialized
|
|
85
|
+
"--truncate", is_flag=True, default=False, help="Truncates the materialized data source before populating it."
|
|
86
86
|
)
|
|
87
87
|
@click.option(
|
|
88
88
|
"--unlink-on-populate-error",
|
|
89
89
|
is_flag=True,
|
|
90
90
|
default=False,
|
|
91
|
-
help="If the populate job fails the
|
|
91
|
+
help="If the populate job fails the materialized view is unlinked and new data won't be ingested in the materialized view. First time a populate job fails, the materialized view is always unlinked.",
|
|
92
92
|
)
|
|
93
93
|
@click.option(
|
|
94
94
|
"--wait",
|
|
@@ -107,7 +107,7 @@ async def pipe_populate(
|
|
|
107
107
|
unlink_on_populate_error: bool,
|
|
108
108
|
wait: bool,
|
|
109
109
|
):
|
|
110
|
-
"""Populate the result of a Materialized Node into the target
|
|
110
|
+
"""Populate the result of a Materialized Node into the target materialized view"""
|
|
111
111
|
cl = create_tb_client(ctx)
|
|
112
112
|
|
|
113
113
|
pipe = await cl.pipe(pipe_name)
|
tinybird/tb/modules/mock.py
CHANGED
|
@@ -2,7 +2,6 @@ import glob
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
5
|
|
|
7
6
|
import click
|
|
8
7
|
|
|
@@ -27,10 +26,9 @@ from tinybird.tb.modules.project import Project
|
|
|
27
26
|
default="",
|
|
28
27
|
help="Extra context to use for data generation",
|
|
29
28
|
)
|
|
30
|
-
@click.option("--skip", is_flag=True, default=False, help="Skip following up on the generated data")
|
|
31
29
|
@click.pass_context
|
|
32
30
|
@coro
|
|
33
|
-
async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str
|
|
31
|
+
async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str) -> None:
|
|
34
32
|
"""Generate sample data for a datasource.
|
|
35
33
|
|
|
36
34
|
Args:
|
|
@@ -80,44 +78,17 @@ async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str, skip
|
|
|
80
78
|
return
|
|
81
79
|
llm = LLM(user_token=user_token, host=user_client.host)
|
|
82
80
|
prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
|
|
83
|
-
iterations = 0
|
|
84
|
-
history = ""
|
|
85
|
-
fixture_path: Optional[Path] = None
|
|
86
81
|
sql = ""
|
|
87
|
-
while iterations < 10:
|
|
88
|
-
feedback = ""
|
|
89
|
-
if iterations > 0:
|
|
90
|
-
feedback = click.prompt("\nFollow-up instructions or continue", default="continue")
|
|
91
|
-
if iterations > 0 and (not feedback or feedback in ("continue", "ok", "exit", "quit", "q")):
|
|
92
|
-
break
|
|
93
|
-
else:
|
|
94
|
-
if iterations > 0:
|
|
95
|
-
if fixture_path:
|
|
96
|
-
fixture_path.unlink()
|
|
97
|
-
fixture_path = None
|
|
98
|
-
click.echo(FeedbackManager.highlight(message=f"\n» Creating fixture for {datasource_name}..."))
|
|
99
82
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
83
|
+
response = llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
|
|
84
|
+
sql = extract_xml(response, "sql")
|
|
85
|
+
result = await tb_client.query(f"{sql} FORMAT JSON")
|
|
86
|
+
data = result.get("data", [])[:rows]
|
|
87
|
+
persist_fixture(datasource_name, data, folder)
|
|
88
|
+
click.echo(FeedbackManager.info(message=f"✓ /fixtures/{datasource_name}.ndjson created"))
|
|
106
89
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
history = (
|
|
111
|
-
history
|
|
112
|
-
+ f"""
|
|
113
|
-
<result_iteration_{iterations}>
|
|
114
|
-
{response}
|
|
115
|
-
</result_iteration_{iterations}>
|
|
116
|
-
"""
|
|
117
|
-
)
|
|
118
|
-
if skip:
|
|
119
|
-
break
|
|
120
|
-
iterations += 1
|
|
90
|
+
if os.environ.get("TB_DEBUG", "") != "":
|
|
91
|
+
logging.debug(sql)
|
|
121
92
|
|
|
122
93
|
click.echo(FeedbackManager.success(message=f"✓ Sample data for {datasource_name} created with {rows} rows"))
|
|
123
94
|
|
tinybird/tb/modules/pipe.py
CHANGED
|
@@ -27,7 +27,7 @@ def pipe(ctx):
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@pipe.command(name="ls")
|
|
30
|
-
@click.option("--match", default=None, help="Retrieve any resourcing matching the pattern.
|
|
30
|
+
@click.option("--match", default=None, help="Retrieve any resourcing matching the pattern. For example, --match _test")
|
|
31
31
|
@click.option(
|
|
32
32
|
"--format",
|
|
33
33
|
"format_",
|
tinybird/tb/modules/project.py
CHANGED
|
@@ -40,17 +40,17 @@ class Project:
|
|
|
40
40
|
|
|
41
41
|
def get_vendor_files(self) -> List[str]:
|
|
42
42
|
vendor_files: List[str] = []
|
|
43
|
-
for project_file in glob.glob(f"{self.vendor_path}/**/*.datasource", recursive=
|
|
43
|
+
for project_file in glob.glob(f"{self.vendor_path}/**/*.datasource", recursive=False):
|
|
44
44
|
vendor_files.append(project_file)
|
|
45
45
|
return vendor_files
|
|
46
46
|
|
|
47
47
|
@property
|
|
48
48
|
def datasources(self) -> List[str]:
|
|
49
|
-
return sorted([Path(f).stem for f in glob.glob(f"{self.path}/**/*.datasource", recursive=
|
|
49
|
+
return sorted([Path(f).stem for f in glob.glob(f"{self.path}/**/*.datasource", recursive=False)])
|
|
50
50
|
|
|
51
51
|
@property
|
|
52
52
|
def pipes(self) -> List[str]:
|
|
53
|
-
return sorted([Path(f).stem for f in glob.glob(f"{self.path}/**/*.pipe", recursive=
|
|
53
|
+
return sorted([Path(f).stem for f in glob.glob(f"{self.path}/**/*.pipe", recursive=False)])
|
|
54
54
|
|
|
55
55
|
def get_pipe_datafile(self, filename: str) -> Optional[Datafile]:
|
|
56
56
|
try:
|
tinybird/tb/modules/shell.py
CHANGED
|
@@ -24,10 +24,17 @@ from tinybird.tb.modules.table import format_table
|
|
|
24
24
|
class DynamicCompleter(Completer):
|
|
25
25
|
def __init__(self, project: Project):
|
|
26
26
|
self.project = project
|
|
27
|
-
self.static_commands = [
|
|
27
|
+
self.static_commands = [
|
|
28
|
+
"create",
|
|
29
|
+
"mock",
|
|
30
|
+
"test",
|
|
31
|
+
"select",
|
|
32
|
+
"datasource",
|
|
33
|
+
"pipe",
|
|
34
|
+
"endpoint",
|
|
35
|
+
"copy",
|
|
36
|
+
]
|
|
28
37
|
self.test_commands = ["create", "run", "update"]
|
|
29
|
-
self.mock_flags = ["--prompt", "--rows"]
|
|
30
|
-
self.common_rows = ["10", "50", "100", "500", "1000"]
|
|
31
38
|
self.sql_keywords = ["select", "from", "where", "group by", "order by", "limit"]
|
|
32
39
|
|
|
33
40
|
def get_completions(self, document, complete_event):
|
|
@@ -114,20 +121,6 @@ class DynamicCompleter(Completer):
|
|
|
114
121
|
)
|
|
115
122
|
return
|
|
116
123
|
|
|
117
|
-
if len(words) == 3 or len(words) == 4:
|
|
118
|
-
# After datasource or after a flag value, show available flags
|
|
119
|
-
available_flags = [f for f in self.mock_flags if f not in words]
|
|
120
|
-
for flag in available_flags:
|
|
121
|
-
yield Completion(flag, start_position=0, display=flag, style="class:completion.cmd")
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
last_word = words[-1]
|
|
125
|
-
if last_word == "--prompt":
|
|
126
|
-
yield Completion('""', start_position=0, display='"Enter your prompt..."', style="class:completion.cmd")
|
|
127
|
-
elif last_word == "--rows":
|
|
128
|
-
for rows in self.common_rows:
|
|
129
|
-
yield Completion(rows, start_position=0, display=rows, style="class:completion.cmd")
|
|
130
|
-
|
|
131
124
|
def _handle_test_completions(self, words: List[str]):
|
|
132
125
|
if len(words) == 1:
|
|
133
126
|
for cmd in self.test_commands:
|
|
@@ -206,7 +199,6 @@ class Shell:
|
|
|
206
199
|
self.project = project
|
|
207
200
|
self.tb_client = tb_client
|
|
208
201
|
self.prompt_message = "\ntb > "
|
|
209
|
-
self.commands = ["create", "mock", "test", "tb", "select"]
|
|
210
202
|
self.session: PromptSession = PromptSession(
|
|
211
203
|
completer=DynamicCompleter(project),
|
|
212
204
|
complete_style=CompleteStyle.COLUMN,
|
|
@@ -274,7 +266,7 @@ class Shell:
|
|
|
274
266
|
def handle_mock(self, arg):
|
|
275
267
|
if "mock" in arg.strip().lower():
|
|
276
268
|
arg = arg.replace("mock", "")
|
|
277
|
-
subprocess.run(f"tb --build mock {arg}
|
|
269
|
+
subprocess.run(f"tb --build mock {arg}", shell=True, text=True)
|
|
278
270
|
|
|
279
271
|
def handle_tb(self, argline):
|
|
280
272
|
click.echo("")
|
|
@@ -290,7 +282,7 @@ class Shell:
|
|
|
290
282
|
else:
|
|
291
283
|
need_skip = ("mock", "test create", "create")
|
|
292
284
|
if any(arg.startswith(cmd) for cmd in need_skip):
|
|
293
|
-
argline = f"{argline}
|
|
285
|
+
argline = f"{argline}"
|
|
294
286
|
subprocess.run(f"tb --build {argline}", shell=True, text=True)
|
|
295
287
|
|
|
296
288
|
def default(self, argline):
|
|
@@ -305,7 +297,7 @@ class Shell:
|
|
|
305
297
|
else:
|
|
306
298
|
need_skip = ("mock", "test create", "create")
|
|
307
299
|
if any(arg.startswith(cmd) for cmd in need_skip):
|
|
308
|
-
argline = f"{argline}
|
|
300
|
+
argline = f"{argline}"
|
|
309
301
|
subprocess.run(f"tb --build {argline}", shell=True, text=True)
|
|
310
302
|
|
|
311
303
|
def run_sql(self, query, rows_limit=20):
|
tinybird/tb/modules/test.py
CHANGED
|
@@ -66,14 +66,9 @@ def test(ctx: click.Context) -> None:
|
|
|
66
66
|
@click.option(
|
|
67
67
|
"--prompt", type=str, default="Create a test for the selected pipe", help="Prompt to be used to create the test"
|
|
68
68
|
)
|
|
69
|
-
@click.option(
|
|
70
|
-
"--skip",
|
|
71
|
-
is_flag=True,
|
|
72
|
-
help="Skip the test creation process and only generate the test file",
|
|
73
|
-
)
|
|
74
69
|
@click.pass_context
|
|
75
70
|
@coro
|
|
76
|
-
async def test_create(ctx: click.Context, name_or_filename: str, prompt: str
|
|
71
|
+
async def test_create(ctx: click.Context, name_or_filename: str, prompt: str) -> None:
|
|
77
72
|
"""
|
|
78
73
|
Create a test for an existing pipe
|
|
79
74
|
"""
|
|
@@ -114,74 +109,47 @@ async def test_create(ctx: click.Context, name_or_filename: str, prompt: str, sk
|
|
|
114
109
|
user_token = config.get_user_token()
|
|
115
110
|
if not user_token:
|
|
116
111
|
raise CLIException(FeedbackManager.error(message="No user token found"))
|
|
112
|
+
|
|
117
113
|
llm = LLM(user_token=user_token, host=config.get_client().host)
|
|
114
|
+
click.echo(FeedbackManager.highlight(message=f"\n» Creating test for {pipe_name} endpoint..."))
|
|
115
|
+
response_llm = llm.ask(system_prompt=system_prompt, prompt=prompt)
|
|
116
|
+
response_xml = extract_xml(response_llm, "response")
|
|
117
|
+
tests_content = parse_xml(response_xml, "test")
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
history = ""
|
|
121
|
-
test_path: Optional[Path] = None
|
|
119
|
+
tests: List[Dict[str, Any]] = []
|
|
122
120
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
for test_content in tests_content:
|
|
142
|
-
test: Dict[str, Any] = {}
|
|
143
|
-
test["name"] = extract_xml(test_content, "name")
|
|
144
|
-
test["description"] = extract_xml(test_content, "description")
|
|
145
|
-
parameters_api = extract_xml(test_content, "parameters")
|
|
146
|
-
test["parameters"] = parameters_api.split("?")[1] if "?" in parameters_api else parameters_api
|
|
147
|
-
test["expected_result"] = ""
|
|
148
|
-
|
|
149
|
-
response = None
|
|
150
|
-
try:
|
|
151
|
-
response = await get_pipe_data(client, pipe_name=pipe_name, test_params=test["parameters"])
|
|
152
|
-
except Exception:
|
|
153
|
-
pass
|
|
154
|
-
|
|
155
|
-
if response:
|
|
156
|
-
if response.status_code >= 400:
|
|
157
|
-
test["expected_http_status"] = response.status_code
|
|
158
|
-
test["expected_result"] = response.json()["error"]
|
|
159
|
-
else:
|
|
160
|
-
if "expected_http_status" in test:
|
|
161
|
-
del test["expected_http_status"]
|
|
162
|
-
test["expected_result"] = response.text or ""
|
|
163
|
-
|
|
164
|
-
tests.append(test)
|
|
165
|
-
|
|
166
|
-
if len(tests) > 0:
|
|
167
|
-
test_path = generate_test_file(pipe_name, tests, folder, mode="a")
|
|
168
|
-
for test in tests:
|
|
169
|
-
test_name = test["name"]
|
|
170
|
-
click.echo(FeedbackManager.info(message=f"✓ {test_name} created"))
|
|
171
|
-
|
|
172
|
-
history = (
|
|
173
|
-
history
|
|
174
|
-
+ f"""
|
|
175
|
-
<result_iteration_{iterations}>
|
|
176
|
-
{response_xml}
|
|
177
|
-
</result_iteration_{iterations}>
|
|
178
|
-
"""
|
|
179
|
-
)
|
|
180
|
-
if skip:
|
|
181
|
-
break
|
|
182
|
-
iterations += 1
|
|
121
|
+
for test_content in tests_content:
|
|
122
|
+
test: Dict[str, Any] = {}
|
|
123
|
+
test["name"] = extract_xml(test_content, "name")
|
|
124
|
+
test["description"] = extract_xml(test_content, "description")
|
|
125
|
+
parameters_api = extract_xml(test_content, "parameters")
|
|
126
|
+
test["parameters"] = parameters_api.split("?")[1] if "?" in parameters_api else parameters_api
|
|
127
|
+
test["expected_result"] = ""
|
|
128
|
+
|
|
129
|
+
response = None
|
|
130
|
+
try:
|
|
131
|
+
response = await get_pipe_data(client, pipe_name=pipe_name, test_params=test["parameters"])
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
if response:
|
|
136
|
+
if response.status_code >= 400:
|
|
137
|
+
test["expected_http_status"] = response.status_code
|
|
138
|
+
test["expected_result"] = response.json()["error"]
|
|
183
139
|
else:
|
|
184
|
-
|
|
140
|
+
if "expected_http_status" in test:
|
|
141
|
+
del test["expected_http_status"]
|
|
142
|
+
test["expected_result"] = response.text or ""
|
|
143
|
+
|
|
144
|
+
tests.append(test)
|
|
145
|
+
|
|
146
|
+
if len(tests) > 0:
|
|
147
|
+
generate_test_file(pipe_name, tests, folder, mode="a")
|
|
148
|
+
for test in tests:
|
|
149
|
+
test_name = test["name"]
|
|
150
|
+
click.echo(FeedbackManager.info(message=f"✓ {test_name} created"))
|
|
151
|
+
else:
|
|
152
|
+
click.echo(FeedbackManager.info(message="* No tests created"))
|
|
185
153
|
|
|
186
154
|
click.echo(FeedbackManager.success(message="✓ Done!\n"))
|
|
187
155
|
except Exception as e:
|
|
@@ -239,7 +207,7 @@ async def test_update(ctx: click.Context, pipe: str) -> None:
|
|
|
239
207
|
|
|
240
208
|
@test.command(
|
|
241
209
|
name="run",
|
|
242
|
-
help="Run the test suite, a file, or a test
|
|
210
|
+
help="Run the test suite, a file, or a test",
|
|
243
211
|
)
|
|
244
212
|
@click.argument("name", nargs=-1)
|
|
245
213
|
@click.pass_context
|
tinybird/tb/modules/token.py
CHANGED
|
@@ -24,7 +24,7 @@ def token(ctx: Context) -> None:
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@token.command(name="ls")
|
|
27
|
-
@click.option("--match", default=None, help="Retrieve any token matching the pattern.
|
|
27
|
+
@click.option("--match", default=None, help="Retrieve any token matching the pattern. For example, --match _test")
|
|
28
28
|
@click.pass_context
|
|
29
29
|
@coro
|
|
30
30
|
async def token_ls(
|
|
@@ -52,7 +52,7 @@ async def token_ls(
|
|
|
52
52
|
|
|
53
53
|
@token.command(name="rm")
|
|
54
54
|
@click.argument("token_id")
|
|
55
|
-
@click.option("--yes", is_flag=True, default=False, help="
|
|
55
|
+
@click.option("--yes", is_flag=True, default=False, help="Don't ask for confirmation")
|
|
56
56
|
@click.pass_context
|
|
57
57
|
@coro
|
|
58
58
|
async def token_rm(ctx: Context, token_id: str, yes: bool) -> None:
|
|
@@ -74,7 +74,7 @@ async def token_rm(ctx: Context, token_id: str, yes: bool) -> None:
|
|
|
74
74
|
|
|
75
75
|
@token.command(name="refresh")
|
|
76
76
|
@click.argument("token_id")
|
|
77
|
-
@click.option("--yes", is_flag=True, default=False, help="
|
|
77
|
+
@click.option("--yes", is_flag=True, default=False, help="Don't ask for confirmation")
|
|
78
78
|
@click.pass_context
|
|
79
79
|
@coro
|
|
80
80
|
async def token_refresh(ctx: Context, token_id: str, yes: bool) -> None:
|
|
@@ -347,4 +347,4 @@ async def create_static_token(ctx, name: str):
|
|
|
347
347
|
except Exception as e:
|
|
348
348
|
raise CLITokenException(FeedbackManager.error_exception(error=e))
|
|
349
349
|
|
|
350
|
-
click.echo("
|
|
350
|
+
click.echo("Token has been generated successfully.")
|
tinybird/tb/modules/watch.py
CHANGED
|
@@ -7,7 +7,7 @@ import click
|
|
|
7
7
|
from watchdog.events import (
|
|
8
8
|
DirDeletedEvent,
|
|
9
9
|
FileDeletedEvent,
|
|
10
|
-
|
|
10
|
+
FileSystemEventHandler,
|
|
11
11
|
)
|
|
12
12
|
from watchdog.observers import Observer
|
|
13
13
|
|
|
@@ -17,25 +17,23 @@ from tinybird.tb.modules.project import Project
|
|
|
17
17
|
from tinybird.tb.modules.shell import Shell
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class WatchProjectHandler(
|
|
20
|
+
class WatchProjectHandler(FileSystemEventHandler):
|
|
21
21
|
def __init__(self, shell: Shell, project: Project, process: Callable):
|
|
22
22
|
self.shell = shell
|
|
23
23
|
self.project = project
|
|
24
24
|
self.process = process
|
|
25
25
|
self.datafiles = project.get_project_datafiles()
|
|
26
|
-
super().__init__(
|
|
27
|
-
patterns=[
|
|
28
|
-
f"{project.path}/**/*.datasource",
|
|
29
|
-
f"{project.path}/**/*.pipe",
|
|
30
|
-
f"{project.path}/fixtures/*.ndjson",
|
|
31
|
-
],
|
|
32
|
-
ignore_patterns=[f"{project.path}/vendor/"],
|
|
33
|
-
)
|
|
26
|
+
super().__init__()
|
|
34
27
|
|
|
35
28
|
def should_process(self, event: Any) -> Optional[str]:
|
|
36
29
|
if event.is_directory:
|
|
37
30
|
return None
|
|
38
31
|
|
|
32
|
+
valid_extensions = [".datasource", ".pipe", ".ndjson"]
|
|
33
|
+
|
|
34
|
+
if not any(event.src_path.endswith(ext) for ext in valid_extensions):
|
|
35
|
+
return None
|
|
36
|
+
|
|
39
37
|
if os.path.exists(event.src_path):
|
|
40
38
|
return event.src_path
|
|
41
39
|
|
|
@@ -91,13 +89,28 @@ class WatchProjectHandler(PatternMatchingEventHandler):
|
|
|
91
89
|
|
|
92
90
|
return None
|
|
93
91
|
|
|
94
|
-
def
|
|
92
|
+
def on_any_event(self, event):
|
|
93
|
+
if str(event.src_path).endswith("~"):
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
if event.event_type == "modified":
|
|
97
|
+
self.modified(event)
|
|
98
|
+
elif event.event_type == "deleted":
|
|
99
|
+
self.deleted(event)
|
|
100
|
+
|
|
101
|
+
def created(self, event: Any) -> None:
|
|
102
|
+
if path := self.should_process(event):
|
|
103
|
+
filename = Path(path).name
|
|
104
|
+
click.echo(FeedbackManager.highlight(message=f"\n\n⟲ New file detected: {filename}\n"))
|
|
105
|
+
self._process(path)
|
|
106
|
+
|
|
107
|
+
def modified(self, event: Any) -> None:
|
|
95
108
|
if path := self.should_process(event):
|
|
96
109
|
filename = Path(path).name
|
|
97
110
|
click.echo(FeedbackManager.highlight(message=f"\n\n⟲ Changes detected in {filename}\n"))
|
|
98
111
|
self._process(path)
|
|
99
112
|
|
|
100
|
-
def
|
|
113
|
+
def deleted(self, event: Union[DirDeletedEvent, FileDeletedEvent]) -> None:
|
|
101
114
|
filename = Path(str(event.src_path)).name
|
|
102
115
|
if event.is_directory:
|
|
103
116
|
click.echo(FeedbackManager.highlight(message=f"\n\n⟲ Deleted directory: {filename}\n"))
|