tinybird 0.0.1.dev48__py3-none-any.whl → 0.0.1.dev49__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tinybird might be problematic. Click here for more details.
- tinybird/prompts.py +0 -59
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +0 -1
- tinybird/tb/modules/build.py +22 -9
- tinybird/tb/modules/cli.py +17 -62
- tinybird/tb/modules/create.py +17 -12
- tinybird/tb/modules/datafile/build.py +1 -2
- tinybird/tb/modules/datafile/common.py +8 -10
- tinybird/tb/modules/deployment.py +65 -14
- tinybird/tb/modules/feedback_manager.py +1 -3
- tinybird/tb/modules/local_common.py +6 -4
- tinybird/tb/modules/login.py +1 -1
- tinybird/tb/modules/mock.py +2 -2
- tinybird/tb/modules/project.py +2 -2
- tinybird/tb/modules/shell.py +50 -17
- tinybird/tb/modules/test.py +9 -8
- {tinybird-0.0.1.dev48.dist-info → tinybird-0.0.1.dev49.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev48.dist-info → tinybird-0.0.1.dev49.dist-info}/RECORD +21 -22
- {tinybird-0.0.1.dev48.dist-info → tinybird-0.0.1.dev49.dist-info}/top_level.txt +1 -0
- tinybird/tb/modules/update.py +0 -182
- {tinybird-0.0.1.dev48.dist-info → tinybird-0.0.1.dev49.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev48.dist-info → tinybird-0.0.1.dev49.dist-info}/entry_points.txt +0 -0
tinybird/prompts.py
CHANGED
|
@@ -651,32 +651,6 @@ DATASOURCE sales_by_hour
|
|
|
651
651
|
"""
|
|
652
652
|
|
|
653
653
|
|
|
654
|
-
def ask_prompt(existing_resources: str) -> str:
|
|
655
|
-
return """
|
|
656
|
-
You are a Tinybird expert. You will be given a prompt to ask questions about Tinybird resources.
|
|
657
|
-
<existing_resources>{existing_resources}</existing_resources>
|
|
658
|
-
{datasource_instructions}
|
|
659
|
-
{pipe_instructions}
|
|
660
|
-
{sql_instructions}
|
|
661
|
-
{datasource_example}
|
|
662
|
-
{pipe_example}
|
|
663
|
-
{copy_pipe_instructions}
|
|
664
|
-
{materialized_pipe_instructions}
|
|
665
|
-
|
|
666
|
-
The previous instructions are explanations of how things work in Tinybird. Answer in natural language.
|
|
667
|
-
|
|
668
|
-
""".format(
|
|
669
|
-
existing_resources=existing_resources,
|
|
670
|
-
datasource_instructions=datasource_instructions,
|
|
671
|
-
datasource_example=datasource_example,
|
|
672
|
-
pipe_instructions=pipe_instructions,
|
|
673
|
-
pipe_example=pipe_example,
|
|
674
|
-
sql_instructions=sql_instructions,
|
|
675
|
-
copy_pipe_instructions=copy_pipe_instructions,
|
|
676
|
-
materialized_pipe_instructions=materialized_pipe_instructions,
|
|
677
|
-
)
|
|
678
|
-
|
|
679
|
-
|
|
680
654
|
datasource_instructions = """
|
|
681
655
|
<datasource_file_instructions>
|
|
682
656
|
- Content cannot be empty.
|
|
@@ -790,39 +764,6 @@ sql_instructions = """
|
|
|
790
764
|
)
|
|
791
765
|
|
|
792
766
|
|
|
793
|
-
def update_prompt(existing_resources: str) -> str:
|
|
794
|
-
return """
|
|
795
|
-
You are a Tinybird expert. You will be given a prompt to update the existing Tinybird resources: datasources and/or pipes.
|
|
796
|
-
You will return the resources that need to be updated.
|
|
797
|
-
<existing_resources>{existing_resources}</existing_resources>
|
|
798
|
-
{datasource_instructions}
|
|
799
|
-
{pipe_instructions}
|
|
800
|
-
{sql_instructions}
|
|
801
|
-
{datasource_example}
|
|
802
|
-
{pipe_example}
|
|
803
|
-
{copy_pipe_instructions}
|
|
804
|
-
{materialized_pipe_instructions}
|
|
805
|
-
Use the following format to generate the response and do not wrap it in any other text, including the <response> tag.
|
|
806
|
-
<response>
|
|
807
|
-
<resource>
|
|
808
|
-
<type>[datasource or pipe]</type>
|
|
809
|
-
<name>[resource name here]</name>
|
|
810
|
-
<content>[resource content here]</content>
|
|
811
|
-
</resource>
|
|
812
|
-
</response>
|
|
813
|
-
|
|
814
|
-
""".format(
|
|
815
|
-
existing_resources=existing_resources,
|
|
816
|
-
datasource_instructions=datasource_instructions,
|
|
817
|
-
pipe_instructions=pipe_instructions,
|
|
818
|
-
sql_instructions=sql_instructions,
|
|
819
|
-
datasource_example=datasource_example,
|
|
820
|
-
pipe_example=pipe_example,
|
|
821
|
-
copy_pipe_instructions=copy_pipe_instructions,
|
|
822
|
-
materialized_pipe_instructions=materialized_pipe_instructions,
|
|
823
|
-
)
|
|
824
|
-
|
|
825
|
-
|
|
826
767
|
def rules_prompt(source: Optional[str] = None) -> str:
|
|
827
768
|
base_command = source or "tb"
|
|
828
769
|
return """
|
tinybird/tb/__cli__.py
CHANGED
|
@@ -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.dev49'
|
|
8
|
+
__revision__ = 'a0a2378'
|
tinybird/tb/cli.py
CHANGED
|
@@ -23,7 +23,6 @@ import tinybird.tb.modules.pipe
|
|
|
23
23
|
import tinybird.tb.modules.tag
|
|
24
24
|
import tinybird.tb.modules.test
|
|
25
25
|
import tinybird.tb.modules.token
|
|
26
|
-
import tinybird.tb.modules.update
|
|
27
26
|
import tinybird.tb.modules.workspace
|
|
28
27
|
import tinybird.tb.modules.workspace_members
|
|
29
28
|
|
tinybird/tb/modules/build.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
+
import sys
|
|
4
5
|
import threading
|
|
5
6
|
import time
|
|
6
7
|
from pathlib import Path
|
|
@@ -15,7 +16,6 @@ from tinybird.tb.modules.common import push_data
|
|
|
15
16
|
from tinybird.tb.modules.datafile.build import folder_build
|
|
16
17
|
from tinybird.tb.modules.datafile.fixture import build_fixture_name, get_fixture_dir
|
|
17
18
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
18
|
-
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
19
19
|
from tinybird.tb.modules.project import Project
|
|
20
20
|
from tinybird.tb.modules.shell import Shell, print_table_formatted
|
|
21
21
|
from tinybird.tb.modules.watch import watch_project
|
|
@@ -29,7 +29,7 @@ def build(ctx: click.Context, watch: bool) -> None:
|
|
|
29
29
|
Validate and build the project server side.
|
|
30
30
|
"""
|
|
31
31
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
32
|
-
tb_client =
|
|
32
|
+
tb_client: TinyB = ctx.ensure_object(dict)["client"]
|
|
33
33
|
click.echo(FeedbackManager.highlight_building_project())
|
|
34
34
|
time_start = time.time()
|
|
35
35
|
|
|
@@ -40,15 +40,21 @@ def build(ctx: click.Context, watch: bool) -> None:
|
|
|
40
40
|
build_project(project, tb_client, file_changed)
|
|
41
41
|
try:
|
|
42
42
|
if file_changed:
|
|
43
|
-
asyncio.run(folder_build(project, filenames=[file_changed]))
|
|
44
|
-
|
|
43
|
+
asyncio.run(folder_build(project, tb_client, filenames=[file_changed]))
|
|
44
|
+
show_data(tb_client, file_changed, diff)
|
|
45
45
|
except Exception:
|
|
46
46
|
pass
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
try:
|
|
49
|
+
process()
|
|
50
|
+
except click.ClickException as e:
|
|
51
|
+
click.echo(e)
|
|
52
|
+
if not watch:
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
else:
|
|
55
|
+
time_end = time.time()
|
|
56
|
+
elapsed_time = time_end - time_start
|
|
57
|
+
click.echo(FeedbackManager.success(message=f"\n✓ Build completed in {elapsed_time:.1f}s"))
|
|
52
58
|
|
|
53
59
|
if watch:
|
|
54
60
|
shell = Shell(project=project, tb_client=tb_client)
|
|
@@ -137,9 +143,16 @@ def build_project(project: Project, tb_client: TinyB, file_changed: Optional[str
|
|
|
137
143
|
|
|
138
144
|
if fixture_path.exists():
|
|
139
145
|
append_fixture(tb_client, ds_name, str(fixture_path))
|
|
146
|
+
|
|
140
147
|
except Exception:
|
|
141
148
|
pass
|
|
142
149
|
|
|
150
|
+
feedback = result.get("feedback", [])
|
|
151
|
+
for f in feedback:
|
|
152
|
+
click.echo(
|
|
153
|
+
FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}")
|
|
154
|
+
)
|
|
155
|
+
|
|
143
156
|
elif build_result == "failed":
|
|
144
157
|
build_errors = result.get("errors")
|
|
145
158
|
error = True
|
|
@@ -198,7 +211,7 @@ def rebuild_fixture(project: Project, tb_client: TinyB, fixture: str) -> None:
|
|
|
198
211
|
click.echo(FeedbackManager.error_exception(error=e))
|
|
199
212
|
|
|
200
213
|
|
|
201
|
-
def
|
|
214
|
+
def show_data(tb_client: TinyB, filename: str, diff: Optional[str] = None):
|
|
202
215
|
table_name = diff
|
|
203
216
|
resource_path = Path(filename)
|
|
204
217
|
resource_name = resource_path.stem
|
tinybird/tb/modules/cli.py
CHANGED
|
@@ -7,7 +7,6 @@ import json
|
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
9
|
from os import getcwd
|
|
10
|
-
from pathlib import Path
|
|
11
10
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
12
11
|
|
|
13
12
|
import click
|
|
@@ -20,7 +19,6 @@ from tinybird.client import (
|
|
|
20
19
|
TinyB,
|
|
21
20
|
)
|
|
22
21
|
from tinybird.config import get_config
|
|
23
|
-
from tinybird.prompts import ask_prompt
|
|
24
22
|
from tinybird.tb import __cli__
|
|
25
23
|
from tinybird.tb.modules.common import (
|
|
26
24
|
CatchAuthExceptions,
|
|
@@ -37,7 +35,6 @@ from tinybird.tb.modules.datafile.build import build_graph
|
|
|
37
35
|
from tinybird.tb.modules.datafile.diff import diff_command
|
|
38
36
|
from tinybird.tb.modules.datafile.pull import folder_pull
|
|
39
37
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
40
|
-
from tinybird.tb.modules.llm import LLM
|
|
41
38
|
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
42
39
|
from tinybird.tb.modules.project import Project
|
|
43
40
|
|
|
@@ -124,13 +121,9 @@ async def cli(
|
|
|
124
121
|
|
|
125
122
|
logging.debug("debug enabled")
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
client = await create_ctx_client(config, prod, build, skip_client, project)
|
|
124
|
+
client = await create_ctx_client(ctx, config, prod, build, project)
|
|
129
125
|
|
|
130
126
|
if client:
|
|
131
|
-
if not build:
|
|
132
|
-
target = config.get("name", "production") if prod else "Tinybird local"
|
|
133
|
-
click.echo(FeedbackManager.gray(message=f"Running against {target}\n"))
|
|
134
127
|
ctx.ensure_object(dict)["client"] = client
|
|
135
128
|
|
|
136
129
|
ctx.ensure_object(dict)["project"] = project
|
|
@@ -358,56 +351,6 @@ async def sql(
|
|
|
358
351
|
click.echo(FeedbackManager.info_no_rows())
|
|
359
352
|
|
|
360
353
|
|
|
361
|
-
@cli.command(hidden=True)
|
|
362
|
-
@click.argument("prompt")
|
|
363
|
-
@click.option("--folder", default=os.getcwd(), help="The folder to use for the project")
|
|
364
|
-
@coro
|
|
365
|
-
async def ask(prompt: str, folder: str) -> None:
|
|
366
|
-
"""Ask things about your data project."""
|
|
367
|
-
try:
|
|
368
|
-
config = CLIConfig.get_project_config(folder)
|
|
369
|
-
user_token = config.get_user_token()
|
|
370
|
-
|
|
371
|
-
if not user_token:
|
|
372
|
-
raise CLIException("This action requires authentication. Run 'tb login' first.")
|
|
373
|
-
|
|
374
|
-
datasource_paths = [
|
|
375
|
-
Path(folder) / "datasources" / f
|
|
376
|
-
for f in os.listdir(Path(folder) / "datasources")
|
|
377
|
-
if f.endswith(".datasource")
|
|
378
|
-
]
|
|
379
|
-
pipe_folders = ["endpoints", "pipes", "copies", "materializations", "sinks"]
|
|
380
|
-
pipe_paths = [
|
|
381
|
-
Path(folder) / pipe_folder / f
|
|
382
|
-
for pipe_folder in pipe_folders
|
|
383
|
-
if (Path(folder) / pipe_folder).exists()
|
|
384
|
-
for f in os.listdir(Path(folder) / pipe_folder)
|
|
385
|
-
if f.endswith(".pipe")
|
|
386
|
-
]
|
|
387
|
-
resources_xml = "\n".join(
|
|
388
|
-
[
|
|
389
|
-
f"<resource><type>{resource_type}</type><name>{resource_name}</name><content>{resource_content}</content></resource>"
|
|
390
|
-
for resource_type, resource_name, resource_content in [
|
|
391
|
-
("datasource", ds.stem, ds.read_text()) for ds in datasource_paths
|
|
392
|
-
]
|
|
393
|
-
+ [
|
|
394
|
-
(
|
|
395
|
-
"pipe",
|
|
396
|
-
pipe.stem,
|
|
397
|
-
pipe.read_text(),
|
|
398
|
-
)
|
|
399
|
-
for pipe in pipe_paths
|
|
400
|
-
]
|
|
401
|
-
]
|
|
402
|
-
)
|
|
403
|
-
|
|
404
|
-
client = config.get_client()
|
|
405
|
-
llm = LLM(user_token=user_token, host=client.host)
|
|
406
|
-
click.echo(llm.ask(system_prompt=ask_prompt(resources_xml), prompt=prompt))
|
|
407
|
-
except Exception as e:
|
|
408
|
-
raise CLIException(FeedbackManager.error_exception(error=e))
|
|
409
|
-
|
|
410
|
-
|
|
411
354
|
def __patch_click_output():
|
|
412
355
|
import re
|
|
413
356
|
|
|
@@ -445,11 +388,23 @@ def __unpatch_click_output():
|
|
|
445
388
|
click.secho = __old_click_secho
|
|
446
389
|
|
|
447
390
|
|
|
448
|
-
async def create_ctx_client(config: Dict[str, Any], prod: bool, build: bool,
|
|
449
|
-
|
|
391
|
+
async def create_ctx_client(ctx: Context, config: Dict[str, Any], prod: bool, build: bool, project: Project):
|
|
392
|
+
commands_without_ctx_client = ["auth", "check", "login", "local"]
|
|
393
|
+
command = ctx.invoked_subcommand
|
|
394
|
+
if command in commands_without_ctx_client:
|
|
450
395
|
return None
|
|
451
396
|
|
|
452
|
-
|
|
397
|
+
commands_always_prod = ["pull"]
|
|
398
|
+
commands_always_build = ["build", "test"]
|
|
399
|
+
commands_always_local = ["create", "mock"]
|
|
400
|
+
if (
|
|
401
|
+
(prod or command in commands_always_prod)
|
|
402
|
+
and command not in commands_always_build
|
|
403
|
+
and command not in commands_always_local
|
|
404
|
+
):
|
|
405
|
+
click.echo(FeedbackManager.gray(message=f"Running against {config.get('name') or 'production'}"))
|
|
453
406
|
return _get_tb_client(config.get("token", None), config["host"])
|
|
454
|
-
|
|
407
|
+
build = command in commands_always_build or build
|
|
408
|
+
if not build and command not in commands_always_local and command not in commands_always_build:
|
|
409
|
+
click.echo(FeedbackManager.gray(message="Running against Tinybird local\n"))
|
|
455
410
|
return await get_tinybird_local_client(str(project.path), build=build)
|
tinybird/tb/modules/create.py
CHANGED
|
@@ -17,7 +17,6 @@ from tinybird.tb.modules.exceptions import CLIException
|
|
|
17
17
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
18
18
|
from tinybird.tb.modules.llm import LLM
|
|
19
19
|
from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
|
|
20
|
-
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
21
20
|
from tinybird.tb.modules.project import Project
|
|
22
21
|
|
|
23
22
|
|
|
@@ -41,8 +40,8 @@ from tinybird.tb.modules.project import Project
|
|
|
41
40
|
help="Folder where datafiles will be placed",
|
|
42
41
|
)
|
|
43
42
|
@click.option("--rows", type=int, default=10, help="Number of events to send")
|
|
44
|
-
@click.option("--cursor", default=False, is_flag=True, help="Create .cursorrules file with Tinybird rules")
|
|
45
43
|
@click.option("--source", type=str, default="tb", help="Source of the command")
|
|
44
|
+
@click.option("--skip", is_flag=True, default=False, help="Skip following up on the generated data")
|
|
46
45
|
@click.pass_context
|
|
47
46
|
@coro
|
|
48
47
|
async def create(
|
|
@@ -51,11 +50,12 @@ async def create(
|
|
|
51
50
|
prompt: Optional[str],
|
|
52
51
|
folder: Optional[str],
|
|
53
52
|
rows: int,
|
|
54
|
-
cursor: bool,
|
|
55
53
|
source: str,
|
|
54
|
+
skip: bool,
|
|
56
55
|
) -> None:
|
|
57
56
|
"""Initialize a new project."""
|
|
58
57
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
58
|
+
local_client: TinyB = ctx.ensure_object(dict)["client"]
|
|
59
59
|
folder = folder or getcwd()
|
|
60
60
|
folder_path = Path(folder)
|
|
61
61
|
if not folder_path.exists():
|
|
@@ -78,7 +78,6 @@ async def create(
|
|
|
78
78
|
)
|
|
79
79
|
)
|
|
80
80
|
return
|
|
81
|
-
local_client = await get_tinybird_local_client(folder)
|
|
82
81
|
|
|
83
82
|
if not validate_project_structure(folder):
|
|
84
83
|
click.echo(FeedbackManager.highlight(message="\n» Creating new project structure..."))
|
|
@@ -88,7 +87,7 @@ async def create(
|
|
|
88
87
|
result = ""
|
|
89
88
|
if data or prompt:
|
|
90
89
|
click.echo(FeedbackManager.highlight(message="\n» Creating resources..."))
|
|
91
|
-
result = await create_resources(local_client, tb_client, user_token, data, prompt, folder)
|
|
90
|
+
result = await create_resources(local_client, tb_client, user_token, data, prompt, folder, skip)
|
|
92
91
|
click.echo(FeedbackManager.success(message="✓ Done!\n"))
|
|
93
92
|
|
|
94
93
|
if not already_has_cicd(folder):
|
|
@@ -97,6 +96,11 @@ async def create(
|
|
|
97
96
|
await init_cicd(data_project_dir=os.path.relpath(folder))
|
|
98
97
|
click.echo(FeedbackManager.success(message="✓ Done!\n"))
|
|
99
98
|
|
|
99
|
+
if not already_has_cursor_rules(folder):
|
|
100
|
+
click.echo(FeedbackManager.highlight(message="\n» Creating .cursorrules..."))
|
|
101
|
+
create_rules(folder, source, "cursor")
|
|
102
|
+
click.echo(FeedbackManager.success(message="✓ Done!\n"))
|
|
103
|
+
|
|
100
104
|
if should_generate_fixtures(result):
|
|
101
105
|
click.echo(FeedbackManager.highlight(message="\n» Generating fixtures..."))
|
|
102
106
|
|
|
@@ -130,12 +134,6 @@ async def create(
|
|
|
130
134
|
if data:
|
|
131
135
|
persist_fixture(fixture_name, data, folder)
|
|
132
136
|
click.echo(FeedbackManager.info(message=f"✓ /fixtures/{datasource_name}"))
|
|
133
|
-
|
|
134
|
-
if cursor:
|
|
135
|
-
click.echo(FeedbackManager.highlight(message="\n» Creating .cursorrules..."))
|
|
136
|
-
create_rules(folder, source, "cursor")
|
|
137
|
-
click.echo(FeedbackManager.success(message="✓ Done!\n"))
|
|
138
|
-
|
|
139
137
|
except Exception as e:
|
|
140
138
|
click.echo(FeedbackManager.error(message=f"Error: {str(e)}"))
|
|
141
139
|
|
|
@@ -158,6 +156,11 @@ def already_has_cicd(folder: str) -> bool:
|
|
|
158
156
|
return any((Path(folder) / path).exists() for path in ci_cd_paths)
|
|
159
157
|
|
|
160
158
|
|
|
159
|
+
def already_has_cursor_rules(folder: str) -> bool:
|
|
160
|
+
cursor_rules_paths = (".cursorrules", ".windsurfrules")
|
|
161
|
+
return any((Path(folder) / path).exists() for path in cursor_rules_paths)
|
|
162
|
+
|
|
163
|
+
|
|
161
164
|
def create_project_structure(folder: str):
|
|
162
165
|
folder_path = Path(folder)
|
|
163
166
|
for x in PROJECT_PATHS:
|
|
@@ -176,6 +179,7 @@ async def create_resources(
|
|
|
176
179
|
data: Optional[str],
|
|
177
180
|
prompt: Optional[str],
|
|
178
181
|
folder: str,
|
|
182
|
+
skip: bool,
|
|
179
183
|
):
|
|
180
184
|
result = ""
|
|
181
185
|
folder_path = Path(folder)
|
|
@@ -285,7 +289,8 @@ TYPE ENDPOINT
|
|
|
285
289
|
content = pipe["content"].replace("```", "")
|
|
286
290
|
pipe_path = generate_pipe_file(pipe["name"], content, folder)
|
|
287
291
|
generated_paths.append(pipe_path)
|
|
288
|
-
|
|
292
|
+
if skip:
|
|
293
|
+
break
|
|
289
294
|
iterations += 1
|
|
290
295
|
|
|
291
296
|
if iterations == 10:
|
|
@@ -42,12 +42,12 @@ from tinybird.tb.modules.datafile.exceptions import AlreadyExistsException, Incl
|
|
|
42
42
|
from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
|
|
43
43
|
from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
|
|
44
44
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
45
|
-
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
46
45
|
from tinybird.tb.modules.project import Project
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
async def folder_build(
|
|
50
49
|
project: Project,
|
|
50
|
+
tb_client: TinyB,
|
|
51
51
|
filenames: Optional[List[str]] = None,
|
|
52
52
|
is_internal: bool = False,
|
|
53
53
|
is_vendor: bool = False,
|
|
@@ -67,7 +67,6 @@ async def folder_build(
|
|
|
67
67
|
fork = False
|
|
68
68
|
release_created = False
|
|
69
69
|
folder = str(project.path)
|
|
70
|
-
tb_client = await get_tinybird_local_client(folder)
|
|
71
70
|
datasources: List[Dict[str, Any]] = await tb_client.datasources()
|
|
72
71
|
pipes: List[Dict[str, Any]] = await tb_client.pipes(dependencies=True)
|
|
73
72
|
|
|
@@ -207,7 +207,7 @@ class Datafile:
|
|
|
207
207
|
# [x] SQL in all nodes
|
|
208
208
|
# [x] Materialized nodes have target datasource
|
|
209
209
|
# [x] Only one materialized node
|
|
210
|
-
# [
|
|
210
|
+
# [x] Only one node of any specific type
|
|
211
211
|
# [ ] ...
|
|
212
212
|
repeated_node_names = [
|
|
213
213
|
name for name, count in filter(lambda x: x[1] > 1, Counter(n["name"] for n in self.nodes).items())
|
|
@@ -219,16 +219,14 @@ class Datafile:
|
|
|
219
219
|
for node in self.nodes:
|
|
220
220
|
if "sql" not in node:
|
|
221
221
|
raise DatafileValidationError(f"SQL missing for node {repr(node['name'])}")
|
|
222
|
-
|
|
222
|
+
non_standard_nodes_count = 0
|
|
223
223
|
for node in self.nodes:
|
|
224
|
-
if node.get("type", "").lower()
|
|
225
|
-
|
|
226
|
-
if
|
|
227
|
-
raise DatafileValidationError("Multiple
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
f"Materialized node {repr(node['name'])} missing target datasource"
|
|
231
|
-
)
|
|
224
|
+
if node.get("type", "").lower() not in {PipeNodeTypes.STANDARD, ""}:
|
|
225
|
+
non_standard_nodes_count += 1
|
|
226
|
+
if non_standard_nodes_count > 1:
|
|
227
|
+
raise DatafileValidationError("Multiple non-standard nodes in pipe. There can only be one")
|
|
228
|
+
if node.get("type", "").lower() == PipeNodeTypes.MATERIALIZED and "datasource" not in node:
|
|
229
|
+
raise DatafileValidationError(f"Materialized node {repr(node['name'])} missing target datasource")
|
|
232
230
|
elif self.kind == DatafileKind.datasource:
|
|
233
231
|
# TODO(eclbg):
|
|
234
232
|
# [x] Just one node
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
import sys
|
|
3
4
|
import time
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from pathlib import Path
|
|
@@ -14,7 +15,7 @@ from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
|
14
15
|
from tinybird.tb.modules.project import Project
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
def promote_deployment(host: Optional[str], headers: dict) -> None:
|
|
18
|
+
def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
18
19
|
TINYBIRD_API_URL = f"{host}/v1/deployments"
|
|
19
20
|
r = requests.get(TINYBIRD_API_URL, headers=headers)
|
|
20
21
|
result = r.json()
|
|
@@ -23,11 +24,11 @@ def promote_deployment(host: Optional[str], headers: dict) -> None:
|
|
|
23
24
|
deployments = result.get("deployments")
|
|
24
25
|
if not deployments:
|
|
25
26
|
click.echo(FeedbackManager.error(message="No deployments found"))
|
|
26
|
-
|
|
27
|
+
sys.exit(1)
|
|
27
28
|
|
|
28
29
|
if len(deployments) < 2:
|
|
29
30
|
click.echo(FeedbackManager.error(message="Only one deployment found"))
|
|
30
|
-
|
|
31
|
+
sys.exit(1)
|
|
31
32
|
|
|
32
33
|
last_deployment, candidate_deployment = deployments[0], deployments[1]
|
|
33
34
|
|
|
@@ -36,7 +37,7 @@ def promote_deployment(host: Optional[str], headers: dict) -> None:
|
|
|
36
37
|
deploy_errors = candidate_deployment.get("errors", [])
|
|
37
38
|
for deploy_error in deploy_errors:
|
|
38
39
|
click.echo(FeedbackManager.error(message=f"* {deploy_error}"))
|
|
39
|
-
|
|
40
|
+
sys.exit(1)
|
|
40
41
|
|
|
41
42
|
if candidate_deployment.get("live"):
|
|
42
43
|
click.echo(FeedbackManager.error(message="Candidate deployment is already live"))
|
|
@@ -55,10 +56,23 @@ def promote_deployment(host: Optional[str], headers: dict) -> None:
|
|
|
55
56
|
result = r.json()
|
|
56
57
|
logging.debug(json.dumps(result, indent=2))
|
|
57
58
|
|
|
59
|
+
if wait:
|
|
60
|
+
while True:
|
|
61
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments/{last_deployment.get('id')}"
|
|
62
|
+
r = requests.get(TINYBIRD_API_URL, headers=headers)
|
|
63
|
+
result = r.json()
|
|
64
|
+
logging.debug(json.dumps(result, indent=2))
|
|
65
|
+
|
|
66
|
+
last_deployment = result.get("deployment")
|
|
67
|
+
if last_deployment.get("status") == "deleted":
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
time.sleep(5)
|
|
71
|
+
|
|
58
72
|
click.echo(FeedbackManager.success(message="Deployment promoted successfully"))
|
|
59
73
|
|
|
60
74
|
|
|
61
|
-
def rollback_deployment(host: Optional[str], headers: dict) -> None:
|
|
75
|
+
def rollback_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
62
76
|
TINYBIRD_API_URL = f"{host}/v1/deployments"
|
|
63
77
|
r = requests.get(TINYBIRD_API_URL, headers=headers)
|
|
64
78
|
result = r.json()
|
|
@@ -99,6 +113,18 @@ def rollback_deployment(host: Optional[str], headers: dict) -> None:
|
|
|
99
113
|
result = r.json()
|
|
100
114
|
logging.debug(json.dumps(result, indent=2))
|
|
101
115
|
|
|
116
|
+
if wait:
|
|
117
|
+
while True:
|
|
118
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments/{current_deployment.get('id')}"
|
|
119
|
+
r = requests.get(TINYBIRD_API_URL, headers=headers)
|
|
120
|
+
result = r.json()
|
|
121
|
+
logging.debug(json.dumps(result, indent=2))
|
|
122
|
+
|
|
123
|
+
current_deployment = result.get("deployment")
|
|
124
|
+
if current_deployment.get("status") == "deleted":
|
|
125
|
+
break
|
|
126
|
+
time.sleep(5)
|
|
127
|
+
|
|
102
128
|
click.echo(FeedbackManager.success(message="Deployment rolled back successfully"))
|
|
103
129
|
|
|
104
130
|
|
|
@@ -165,7 +191,13 @@ def deployment_ls(ctx: click.Context) -> None:
|
|
|
165
191
|
|
|
166
192
|
@deployment_group.command(name="promote")
|
|
167
193
|
@click.pass_context
|
|
168
|
-
|
|
194
|
+
@click.option(
|
|
195
|
+
"--wait/--no-wait",
|
|
196
|
+
is_flag=True,
|
|
197
|
+
default=False,
|
|
198
|
+
help="Wait for deploy to finish. Disabled by default.",
|
|
199
|
+
)
|
|
200
|
+
def deployment_promote(ctx: click.Context, wait: bool) -> None:
|
|
169
201
|
"""
|
|
170
202
|
Promote last deploy to ready and remove old one.
|
|
171
203
|
"""
|
|
@@ -174,12 +206,18 @@ def deployment_promote(ctx: click.Context) -> None:
|
|
|
174
206
|
TINYBIRD_API_KEY = client.token
|
|
175
207
|
HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
|
|
176
208
|
|
|
177
|
-
promote_deployment(client.host, HEADERS)
|
|
209
|
+
promote_deployment(client.host, HEADERS, wait=wait)
|
|
178
210
|
|
|
179
211
|
|
|
180
212
|
@deployment_group.command(name="rollback")
|
|
181
213
|
@click.pass_context
|
|
182
|
-
|
|
214
|
+
@click.option(
|
|
215
|
+
"--wait/--no-wait",
|
|
216
|
+
is_flag=True,
|
|
217
|
+
default=False,
|
|
218
|
+
help="Wait for deploy to finish. Disabled by default.",
|
|
219
|
+
)
|
|
220
|
+
def deployment_rollback(ctx: click.Context, wait: bool) -> None:
|
|
183
221
|
"""
|
|
184
222
|
Rollback to the previous deployment.
|
|
185
223
|
"""
|
|
@@ -188,7 +226,7 @@ def deployment_rollback(ctx: click.Context) -> None:
|
|
|
188
226
|
TINYBIRD_API_KEY = client.token
|
|
189
227
|
HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
|
|
190
228
|
|
|
191
|
-
rollback_deployment(client.host, HEADERS)
|
|
229
|
+
rollback_deployment(client.host, HEADERS, wait=wait)
|
|
192
230
|
|
|
193
231
|
|
|
194
232
|
@cli.command(name="deploy", hidden=True)
|
|
@@ -248,6 +286,12 @@ def create_deployment(ctx: click.Context, wait: bool, auto: bool) -> None:
|
|
|
248
286
|
if deploy_result == "success":
|
|
249
287
|
click.echo(FeedbackManager.success(message="Deployment submitted successfully"))
|
|
250
288
|
deployment = result.get("deployment")
|
|
289
|
+
feedback = deployment.get("feedback", [])
|
|
290
|
+
for f in feedback:
|
|
291
|
+
click.echo(
|
|
292
|
+
FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}")
|
|
293
|
+
)
|
|
294
|
+
|
|
251
295
|
elif deploy_result == "failed":
|
|
252
296
|
click.echo(FeedbackManager.error(message="Deployment failed"))
|
|
253
297
|
deploy_errors = result.get("errors")
|
|
@@ -264,9 +308,11 @@ def create_deployment(ctx: click.Context, wait: bool, auto: bool) -> None:
|
|
|
264
308
|
for fd in fds:
|
|
265
309
|
fd.close()
|
|
266
310
|
|
|
311
|
+
if not deployment:
|
|
312
|
+
sys.exit(1)
|
|
313
|
+
|
|
267
314
|
if deployment and wait:
|
|
268
|
-
while
|
|
269
|
-
time.sleep(5)
|
|
315
|
+
while True:
|
|
270
316
|
TINYBIRD_API_URL = f"{client.host}/v1/deployments/{deployment.get('id')}"
|
|
271
317
|
r = requests.get(TINYBIRD_API_URL, headers=HEADERS)
|
|
272
318
|
result = r.json()
|
|
@@ -281,10 +327,15 @@ def create_deployment(ctx: click.Context, wait: bool, auto: bool) -> None:
|
|
|
281
327
|
|
|
282
328
|
if auto:
|
|
283
329
|
click.echo(FeedbackManager.error(message="Rolling back deployment"))
|
|
284
|
-
rollback_deployment(client.host, HEADERS)
|
|
285
|
-
|
|
330
|
+
rollback_deployment(client.host, HEADERS, wait=wait)
|
|
331
|
+
sys.exit(1)
|
|
332
|
+
|
|
333
|
+
if deployment.get("status") == "data_ready":
|
|
334
|
+
break
|
|
335
|
+
|
|
336
|
+
time.sleep(5)
|
|
286
337
|
|
|
287
338
|
click.echo(FeedbackManager.success(message="Deployment is ready"))
|
|
288
339
|
|
|
289
340
|
if auto:
|
|
290
|
-
promote_deployment(client.host, HEADERS)
|
|
341
|
+
promote_deployment(client.host, HEADERS, wait=wait)
|
|
@@ -73,9 +73,7 @@ class FeedbackManager:
|
|
|
73
73
|
error_exception = error_exception("{error}")
|
|
74
74
|
simple_error_exception = simple_error_message("{error}")
|
|
75
75
|
error_exception_trace = error_message("{error}\n** Trace:\n{trace}")
|
|
76
|
-
error_notoken = error_message(
|
|
77
|
-
"No auth token provided. Run 'tb auth' to configure them or re-run the command passing the --token param (example: tb --token <the_token> datasource ls)."
|
|
78
|
-
)
|
|
76
|
+
error_notoken = error_message("This action requires authentication. Run 'tb login' first")
|
|
79
77
|
error_auth_config = error_message("{config_file} does not exist")
|
|
80
78
|
error_file_config = error_message("{config_file} can't be written, check write permissions on this folder")
|
|
81
79
|
error_load_file_config = error_message("{config_file} can't be loaded, remove it and run the command again")
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
|
|
5
5
|
import requests
|
|
6
6
|
|
|
7
|
-
from tinybird.client import TinyB
|
|
7
|
+
from tinybird.client import AuthNoTokenException, TinyB
|
|
8
8
|
from tinybird.tb.modules.config import CLIConfig
|
|
9
9
|
from tinybird.tb.modules.exceptions import CLIException
|
|
10
10
|
|
|
@@ -40,8 +40,10 @@ async def get_tinybird_local_config(path: str, build: bool = False) -> CLIConfig
|
|
|
40
40
|
if path:
|
|
41
41
|
folder_hash = hashlib.sha256(path.encode()).hexdigest()
|
|
42
42
|
user_client = config.get_client(host=TB_LOCAL_HOST, token=user_token)
|
|
43
|
-
ws_name = config.get("name")
|
|
44
|
-
|
|
43
|
+
ws_name = f"Tinybird_Local_Build_{folder_hash}" if build else config.get("name")
|
|
44
|
+
if not ws_name:
|
|
45
|
+
raise AuthNoTokenException()
|
|
46
|
+
|
|
45
47
|
logging.debug(f"Workspace used for build: {ws_name}")
|
|
46
48
|
|
|
47
49
|
user_workspaces = requests.get(f"{TB_LOCAL_HOST}/v0/user/workspaces?token={user_token}").json()
|
|
@@ -54,7 +56,7 @@ async def get_tinybird_local_config(path: str, build: bool = False) -> CLIConfig
|
|
|
54
56
|
user_workspaces = requests.get(f"{TB_LOCAL_HOST}/v0/user/workspaces?token={user_token}").json()
|
|
55
57
|
ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
|
|
56
58
|
if not ws:
|
|
57
|
-
raise
|
|
59
|
+
raise AuthNoTokenException()
|
|
58
60
|
|
|
59
61
|
ws_token = ws["token"]
|
|
60
62
|
|
tinybird/tb/modules/login.py
CHANGED
|
@@ -121,7 +121,7 @@ async def login(host: str, workspace: str):
|
|
|
121
121
|
"""Authenticate using the browser."""
|
|
122
122
|
auth_event = threading.Event()
|
|
123
123
|
auth_code = [None] # Using a list to store the code, as it's mutable
|
|
124
|
-
host = host or "https://api.tinybird.co"
|
|
124
|
+
host = host or "https://api.wadus2.gcp.tinybird.co"
|
|
125
125
|
|
|
126
126
|
def auth_callback(code):
|
|
127
127
|
auth_code[0] = code
|
tinybird/tb/modules/mock.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Optional
|
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
7
|
|
|
8
|
+
from tinybird.client import TinyB
|
|
8
9
|
from tinybird.prompts import mock_prompt
|
|
9
10
|
from tinybird.tb.modules.cli import cli
|
|
10
11
|
from tinybird.tb.modules.common import CLIException, check_user_token_with_client, coro
|
|
@@ -13,7 +14,6 @@ from tinybird.tb.modules.datafile.fixture import build_fixture_name, persist_fix
|
|
|
13
14
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
14
15
|
from tinybird.tb.modules.llm import LLM
|
|
15
16
|
from tinybird.tb.modules.llm_utils import extract_xml
|
|
16
|
-
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
17
17
|
from tinybird.tb.modules.project import Project
|
|
18
18
|
|
|
19
19
|
|
|
@@ -40,6 +40,7 @@ async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str, skip
|
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
try:
|
|
43
|
+
tb_client: TinyB = ctx.ensure_object(dict)["client"]
|
|
43
44
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
44
45
|
datasource_path = Path(datasource)
|
|
45
46
|
datasource_name = datasource
|
|
@@ -76,7 +77,6 @@ async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str, skip
|
|
|
76
77
|
click.echo(FeedbackManager.error(message="This action requires authentication. Run 'tb login' first."))
|
|
77
78
|
return
|
|
78
79
|
llm = LLM(user_token=user_token, host=user_client.host)
|
|
79
|
-
tb_client = await get_tinybird_local_client(folder)
|
|
80
80
|
prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
|
|
81
81
|
iterations = 0
|
|
82
82
|
history = ""
|
tinybird/tb/modules/project.py
CHANGED
|
@@ -40,11 +40,11 @@ class Project:
|
|
|
40
40
|
|
|
41
41
|
@property
|
|
42
42
|
def datasources(self) -> List[str]:
|
|
43
|
-
return [Path(f).stem for f in glob.glob(f"{self.path}/**/*.datasource", recursive=True)]
|
|
43
|
+
return sorted([Path(f).stem for f in glob.glob(f"{self.path}/**/*.datasource", recursive=True)])
|
|
44
44
|
|
|
45
45
|
@property
|
|
46
46
|
def pipes(self) -> List[str]:
|
|
47
|
-
return [Path(f).stem for f in glob.glob(f"{self.path}/**/*.pipe", recursive=True)]
|
|
47
|
+
return sorted([Path(f).stem for f in glob.glob(f"{self.path}/**/*.pipe", recursive=True)])
|
|
48
48
|
|
|
49
49
|
def get_pipe_datafile(self, filename: str) -> Optional[Datafile]:
|
|
50
50
|
try:
|
tinybird/tb/modules/shell.py
CHANGED
|
@@ -69,14 +69,27 @@ class DynamicCompleter(Completer):
|
|
|
69
69
|
if not words:
|
|
70
70
|
return
|
|
71
71
|
|
|
72
|
-
# If we
|
|
73
|
-
if
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
# If we're after FROM, suggest filtered datasources and pipes
|
|
73
|
+
if "from" in words:
|
|
74
|
+
from_index = words.index("from")
|
|
75
|
+
# Check if we're typing after FROM
|
|
76
|
+
if len(words) > from_index + 1:
|
|
77
|
+
current_word = words[-1].lower()
|
|
78
|
+
# Suggest filtered datasources and pipes based on current word
|
|
79
|
+
for x in self.project.datasources:
|
|
80
|
+
if current_word in x.lower():
|
|
81
|
+
yield Completion(
|
|
82
|
+
x, start_position=-len(current_word), display=x, style="class:completion.datasource"
|
|
83
|
+
)
|
|
84
|
+
for x in self.project.pipes:
|
|
85
|
+
if current_word in x.lower():
|
|
86
|
+
yield Completion(x, start_position=-len(current_word), display=x, style="class:completion.pipe")
|
|
87
|
+
else:
|
|
88
|
+
# Just typed FROM, show all datasources and pipes
|
|
89
|
+
for x in self.project.datasources:
|
|
90
|
+
yield Completion(x, start_position=0, display=x, style="class:completion.datasource")
|
|
91
|
+
for x in self.project.pipes:
|
|
92
|
+
yield Completion(x, start_position=0, display=x, style="class:completion.pipe")
|
|
80
93
|
return
|
|
81
94
|
|
|
82
95
|
# If we're starting a query, suggest SQL keywords
|
|
@@ -88,13 +101,20 @@ class DynamicCompleter(Completer):
|
|
|
88
101
|
)
|
|
89
102
|
|
|
90
103
|
def _handle_mock_completions(self, words: List[str]):
|
|
91
|
-
if len(words) == 1:
|
|
104
|
+
if len(words) == 1 or len(words) == 2:
|
|
92
105
|
# After 'mock', show datasources
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
current_word = words[-1]
|
|
107
|
+
for cmd in self.project.datasources:
|
|
108
|
+
if current_word in cmd.lower():
|
|
109
|
+
yield Completion(
|
|
110
|
+
cmd,
|
|
111
|
+
start_position=-len(current_word) if current_word else 0,
|
|
112
|
+
display=cmd,
|
|
113
|
+
style="class:completion.datasource",
|
|
114
|
+
)
|
|
95
115
|
return
|
|
96
116
|
|
|
97
|
-
if len(words) ==
|
|
117
|
+
if len(words) == 3 or len(words) == 4:
|
|
98
118
|
# After datasource or after a flag value, show available flags
|
|
99
119
|
available_flags = [f for f in self.mock_flags if f not in words]
|
|
100
120
|
for flag in available_flags:
|
|
@@ -113,9 +133,16 @@ class DynamicCompleter(Completer):
|
|
|
113
133
|
for cmd in self.test_commands:
|
|
114
134
|
yield Completion(cmd, start_position=0, display=cmd, style="class:completion.cmd")
|
|
115
135
|
return
|
|
116
|
-
elif len(words) == 2:
|
|
136
|
+
elif len(words) == 2 or len(words) == 3:
|
|
137
|
+
current_word = words[-1]
|
|
117
138
|
for cmd in self.project.pipes:
|
|
118
|
-
|
|
139
|
+
if current_word in cmd.lower():
|
|
140
|
+
yield Completion(
|
|
141
|
+
cmd,
|
|
142
|
+
start_position=-len(current_word) if current_word else 0,
|
|
143
|
+
display=cmd,
|
|
144
|
+
style="class:completion.pipe",
|
|
145
|
+
)
|
|
119
146
|
return
|
|
120
147
|
|
|
121
148
|
def _yield_static_commands(self, current_word: str):
|
|
@@ -129,7 +156,7 @@ class DynamicCompleter(Completer):
|
|
|
129
156
|
)
|
|
130
157
|
|
|
131
158
|
for cmd in self.project.datasources:
|
|
132
|
-
if cmd.
|
|
159
|
+
if current_word in cmd.lower():
|
|
133
160
|
yield Completion(
|
|
134
161
|
cmd,
|
|
135
162
|
start_position=-len(current_word) if current_word else 0,
|
|
@@ -138,7 +165,7 @@ class DynamicCompleter(Completer):
|
|
|
138
165
|
)
|
|
139
166
|
|
|
140
167
|
for cmd in self.project.pipes:
|
|
141
|
-
if cmd.
|
|
168
|
+
if current_word in cmd.lower():
|
|
142
169
|
yield Completion(
|
|
143
170
|
cmd,
|
|
144
171
|
start_position=-len(current_word) if current_word else 0,
|
|
@@ -245,7 +272,7 @@ class Shell:
|
|
|
245
272
|
click.echo(FeedbackManager.error(message=f"'tb {arg}' command is not available in watch mode"))
|
|
246
273
|
|
|
247
274
|
def handle_mock(self, arg):
|
|
248
|
-
subprocess.run(f"tb --build --folder {self.project.folder} mock {arg}", shell=True, text=True)
|
|
275
|
+
subprocess.run(f"tb --build --folder {self.project.folder} mock {arg} --skip", shell=True, text=True)
|
|
249
276
|
|
|
250
277
|
def handle_tb(self, arg):
|
|
251
278
|
click.echo("")
|
|
@@ -259,6 +286,9 @@ class Shell:
|
|
|
259
286
|
elif arg.startswith("mock"):
|
|
260
287
|
self.handle_mock(arg)
|
|
261
288
|
else:
|
|
289
|
+
need_skip = ("mock", "test create", "create")
|
|
290
|
+
if any(arg.startswith(cmd) for cmd in need_skip):
|
|
291
|
+
arg = f"{arg} --skip"
|
|
262
292
|
subprocess.run(f"tb --build --folder {self.project.folder} {arg}", shell=True, text=True)
|
|
263
293
|
|
|
264
294
|
def default(self, argline):
|
|
@@ -271,6 +301,9 @@ class Shell:
|
|
|
271
301
|
elif len(arg.split()) == 1 and arg in self.project.pipes + self.project.datasources:
|
|
272
302
|
self.run_sql(f"select * from {arg}")
|
|
273
303
|
else:
|
|
304
|
+
need_skip = ("mock", "test create", "create")
|
|
305
|
+
if any(arg.startswith(cmd) for cmd in need_skip):
|
|
306
|
+
arg = f"{arg} --skip"
|
|
274
307
|
subprocess.run(f"tb --build --folder {self.project.folder} {arg}", shell=True, text=True)
|
|
275
308
|
|
|
276
309
|
def run_sql(self, query, rows_limit=20):
|
tinybird/tb/modules/test.py
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import difflib
|
|
7
7
|
import glob
|
|
8
|
-
import os
|
|
9
8
|
import urllib.parse
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
from typing import Any, Dict, List, Optional, Tuple
|
|
@@ -14,6 +13,7 @@ import click
|
|
|
14
13
|
import yaml
|
|
15
14
|
from requests import Response
|
|
16
15
|
|
|
16
|
+
from tinybird.client import TinyB
|
|
17
17
|
from tinybird.prompts import test_create_prompt
|
|
18
18
|
from tinybird.tb.modules.cli import cli
|
|
19
19
|
from tinybird.tb.modules.common import coro
|
|
@@ -22,7 +22,6 @@ from tinybird.tb.modules.exceptions import CLIException
|
|
|
22
22
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
23
23
|
from tinybird.tb.modules.llm import LLM
|
|
24
24
|
from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
|
|
25
|
-
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
26
25
|
from tinybird.tb.modules.project import Project
|
|
27
26
|
|
|
28
27
|
yaml.SafeDumper.org_represent_str = yaml.SafeDumper.represent_str # type: ignore[attr-defined]
|
|
@@ -79,6 +78,7 @@ async def test_create(ctx: click.Context, name_or_filename: str, prompt: str, sk
|
|
|
79
78
|
Create a test for an existing pipe
|
|
80
79
|
"""
|
|
81
80
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
81
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
82
82
|
root_path = Path(project.folder)
|
|
83
83
|
folder = project.folder
|
|
84
84
|
try:
|
|
@@ -102,7 +102,6 @@ async def test_create(ctx: click.Context, name_or_filename: str, prompt: str, sk
|
|
|
102
102
|
pipe_path = root_path / pipe_path
|
|
103
103
|
pipe_content = pipe_path.read_text()
|
|
104
104
|
|
|
105
|
-
client = await get_tinybird_local_client(folder, build=True)
|
|
106
105
|
pipe = await client._req(f"/v0/pipes/{pipe_name}")
|
|
107
106
|
parameters = set([param["name"] for node in pipe["nodes"] for param in node["params"]])
|
|
108
107
|
|
|
@@ -200,10 +199,11 @@ async def test_create(ctx: click.Context, name_or_filename: str, prompt: str, sk
|
|
|
200
199
|
type=click.Path(exists=True, file_okay=False),
|
|
201
200
|
help="Folder where datafiles will be placed",
|
|
202
201
|
)
|
|
202
|
+
@click.pass_context
|
|
203
203
|
@coro
|
|
204
|
-
async def test_update(pipe: str, folder: str) -> None:
|
|
204
|
+
async def test_update(ctx: click.Context, pipe: str, folder: str) -> None:
|
|
205
205
|
try:
|
|
206
|
-
client =
|
|
206
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
207
207
|
pipe_tests_path = Path(pipe)
|
|
208
208
|
pipe_name = pipe
|
|
209
209
|
if pipe_tests_path.suffix == ".yaml":
|
|
@@ -252,10 +252,11 @@ async def test_update(pipe: str, folder: str) -> None:
|
|
|
252
252
|
type=click.Path(exists=True, file_okay=False),
|
|
253
253
|
help="Folder where tests will be placed",
|
|
254
254
|
)
|
|
255
|
+
@click.pass_context
|
|
255
256
|
@coro
|
|
256
|
-
async def run_tests(name: Tuple[str, ...], folder: str) -> None:
|
|
257
|
+
async def run_tests(ctx: click.Context, name: Tuple[str, ...], folder: str) -> None:
|
|
257
258
|
click.echo(FeedbackManager.highlight(message="\n» Running tests"))
|
|
258
|
-
client =
|
|
259
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
259
260
|
paths = [Path(n) for n in name]
|
|
260
261
|
endpoints = [f"./tests/{p.stem}.yaml" for p in paths]
|
|
261
262
|
test_files: List[str] = endpoints if len(endpoints) > 0 else glob.glob("./tests/**/*.y*ml", recursive=True)
|
|
@@ -311,7 +312,7 @@ async def run_tests(name: Tuple[str, ...], folder: str) -> None:
|
|
|
311
312
|
click.echo(FeedbackManager.success(message=f"\n✓ {test_count}/{test_count} passed"))
|
|
312
313
|
|
|
313
314
|
|
|
314
|
-
async def get_pipe_data(client, pipe_name: str, test_params: str) -> Response:
|
|
315
|
+
async def get_pipe_data(client: TinyB, pipe_name: str, test_params: str) -> Response:
|
|
315
316
|
pipe = await client._req(f"/v0/pipes/{pipe_name}")
|
|
316
317
|
output_node = next(
|
|
317
318
|
(node for node in pipe["nodes"] if node["node_type"] != "default" and node["node_type"] != "standard"),
|
|
@@ -6,7 +6,7 @@ tinybird/context.py,sha256=A3GBApac9xO6hrAMJ1s9dMrI_ou9aKF84CdEjtPddMk,1417
|
|
|
6
6
|
tinybird/datatypes.py,sha256=XNypumfqNjsvLJ5iNXnbVHRvAJe0aQwI3lS6Cxox-e0,10979
|
|
7
7
|
tinybird/feedback_manager.py,sha256=g1r9NcFfKXdk_13soaiTZLvdoUGleVfawl6Yfj3zmRw,67823
|
|
8
8
|
tinybird/git_settings.py,sha256=Sw_8rGmribEFJ4Z_6idrVytxpFYk7ez8ei0qHULzs3E,3934
|
|
9
|
-
tinybird/prompts.py,sha256=
|
|
9
|
+
tinybird/prompts.py,sha256=unaqHksayhVtxFaTTxuL6Dftb1IM9vQncXtpTI3pxuY,30545
|
|
10
10
|
tinybird/sql.py,sha256=LBi74GxhNAYTb6m2-KNGpAkguSKh7rcvBbERbE7nalA,46195
|
|
11
11
|
tinybird/sql_template.py,sha256=GmMLAI10MTqjQo9qztuQHLRWs67teozsWDxUBdvkAn4,93668
|
|
12
12
|
tinybird/sql_template_fmt.py,sha256=KUHdj5rYCYm_rKKdXYSJAE9vIyXUQLB0YSZnUXHeBlY,10196
|
|
@@ -15,48 +15,47 @@ tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
|
|
|
15
15
|
tinybird/tornado_template.py,sha256=FL85SMPq2dH4JqKovmSbaolGdEzwOO91NqOzqXo2Qr0,41863
|
|
16
16
|
tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
|
|
17
17
|
tinybird/ch_utils/engine.py,sha256=OXkBhlzGjZotjD0vaT-rFIbSGV4tpiHxE8qO_ip0SyQ,40454
|
|
18
|
-
tinybird/tb/__cli__.py,sha256=
|
|
19
|
-
tinybird/tb/cli.py,sha256=
|
|
18
|
+
tinybird/tb/__cli__.py,sha256=Yocwg57d8BTpRpM9BAwbi73DJuatECKd9hhtsD5-Y3U,251
|
|
19
|
+
tinybird/tb/cli.py,sha256=FD1pfbzu9YHJHEG6Vtn_EwPLTYhwqw-I6AxXeTaRHU8,926
|
|
20
20
|
tinybird/tb/modules/auth.py,sha256=EzRWFmwRkXNhUmRaruEVFLdkbUg8xMSix0cAWl5D4Jg,9029
|
|
21
|
-
tinybird/tb/modules/build.py,sha256=
|
|
21
|
+
tinybird/tb/modules/build.py,sha256=LvopBeGSuTey7UDMOABl7xpLRyyVQS2E2wiUHxxWPnc,8340
|
|
22
22
|
tinybird/tb/modules/cicd.py,sha256=xxXwy-QekJcG14kkJeGNl7LkHduhZXfvBZE8WrU6-t4,5351
|
|
23
|
-
tinybird/tb/modules/cli.py,sha256=
|
|
23
|
+
tinybird/tb/modules/cli.py,sha256=882TimWnFyq1v0sfLDTtmPA_lFuVyL-GwmFpufB5Rxg,16075
|
|
24
24
|
tinybird/tb/modules/common.py,sha256=TWcGJUgzJCQvzI1oMKbNdx-KTRmMGvB25BawHpsaV8Q,70610
|
|
25
25
|
tinybird/tb/modules/config.py,sha256=mie3oMVTf5YOUFEiLs88P16U4LkJafJjSpjwyAkFHog,10979
|
|
26
26
|
tinybird/tb/modules/copy.py,sha256=wxyxZg8BPiWDgbW5HXJKYQp7_EumBXmAilo3McbCQOo,5916
|
|
27
|
-
tinybird/tb/modules/create.py,sha256=
|
|
27
|
+
tinybird/tb/modules/create.py,sha256=_eAMjntZ85uub1HqHoDekWC7ks79FQ2Zbc_6grjhT_g,14472
|
|
28
28
|
tinybird/tb/modules/datasource.py,sha256=TQ4wSag3CCw34d54FEXPJFGLQNYyNqv2nQbU6QT9uAE,14725
|
|
29
|
-
tinybird/tb/modules/deployment.py,sha256=
|
|
29
|
+
tinybird/tb/modules/deployment.py,sha256=Ezb_7ZK6ym967GNPzuSUH3K6ukAMWXaHb1gFXNet2pA,11859
|
|
30
30
|
tinybird/tb/modules/endpoint.py,sha256=9arqN1JQCMb0Nd3-EJ7lukOYkGHHCpQmiiZpp5FqPhc,9432
|
|
31
31
|
tinybird/tb/modules/exceptions.py,sha256=4A2sSjCEqKUMqpP3WI00zouCWW4uLaghXXLZBSw04mY,3363
|
|
32
|
-
tinybird/tb/modules/feedback_manager.py,sha256=
|
|
32
|
+
tinybird/tb/modules/feedback_manager.py,sha256=mrw5tdYycfvg6WLXlM0KIjfJardm_aNpnJkUg2vH0cA,68463
|
|
33
33
|
tinybird/tb/modules/fmt.py,sha256=poh6_cwVGSf-sBu6LKWuO2TANL_J8Sgm25sPpwxa3Aw,3558
|
|
34
34
|
tinybird/tb/modules/job.py,sha256=956Pj8BEEsiD2GZsV9RKKVM3I_CveOLgS82lykO5ukk,2963
|
|
35
35
|
tinybird/tb/modules/llm.py,sha256=AC0VSphTOM2t-v1_3NLvNN_FIbgMo4dTyMqIv5nniPo,835
|
|
36
36
|
tinybird/tb/modules/llm_utils.py,sha256=nS9r4FAElJw8yXtmdYrx-rtI2zXR8qXfi1QqUDCfxvg,3469
|
|
37
37
|
tinybird/tb/modules/local.py,sha256=x4xuCGVkoa8KLYGZEJnFUP8HUkKX05Frp_djRVjVjTs,5669
|
|
38
|
-
tinybird/tb/modules/local_common.py,sha256=
|
|
39
|
-
tinybird/tb/modules/login.py,sha256=
|
|
38
|
+
tinybird/tb/modules/local_common.py,sha256=UW6tNexC98aGRC6ca2w8qDDzyTTn-GDfr3uRy7zRHBM,2767
|
|
39
|
+
tinybird/tb/modules/login.py,sha256=cnB2Vwjc94FpG9C-7Y-l7Lb0Xilp2oUFDzqbQW0tpkU,6519
|
|
40
40
|
tinybird/tb/modules/materialization.py,sha256=HQKRTH6lkcYiDQJihbFqF_in58ezXG4ggZ_7Ywp_nUM,5738
|
|
41
|
-
tinybird/tb/modules/mock.py,sha256=
|
|
41
|
+
tinybird/tb/modules/mock.py,sha256=PzFtZL-6bZAZ3EiCC2nYJo058I4m50fD7FcBHISn3cI,5235
|
|
42
42
|
tinybird/tb/modules/pipe.py,sha256=pH2KwgH6Xbvl3kT8vMelpKvT6bcyB4EKFDvGfOsxXbg,2418
|
|
43
|
-
tinybird/tb/modules/project.py,sha256=
|
|
43
|
+
tinybird/tb/modules/project.py,sha256=rucAh_6K1EsW2dxjmqHPJ7bt6IliOWR0dNtEYG454fw,2620
|
|
44
44
|
tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
45
|
-
tinybird/tb/modules/shell.py,sha256=
|
|
45
|
+
tinybird/tb/modules/shell.py,sha256=LShGXwP_kzYAOUiTcvHMmcDugVr0crIfdNb5En6cfxo,14604
|
|
46
46
|
tinybird/tb/modules/table.py,sha256=4XrtjM-N0zfNtxVkbvLDQQazno1EPXnxTyo7llivfXk,11035
|
|
47
47
|
tinybird/tb/modules/tag.py,sha256=anPmMUBc-TbFovlpFi8GPkKA18y7Y0GczMsMms5TZsU,3502
|
|
48
48
|
tinybird/tb/modules/telemetry.py,sha256=iEGnMuCuNhvF6ln__j6X9MSTwL_0Hm-GgFHHHvhfknk,10466
|
|
49
|
-
tinybird/tb/modules/test.py,sha256=
|
|
49
|
+
tinybird/tb/modules/test.py,sha256=UoYGwTSY6_0ijS2sClMq-1OdOBbg-IXMDD8oLfZXvUg,13093
|
|
50
50
|
tinybird/tb/modules/token.py,sha256=sPdJoBE-6dd3Sd6W-prst7VOoJ0NbvP0uTaB6dXHs5s,12711
|
|
51
|
-
tinybird/tb/modules/update.py,sha256=GWYfbKnB0yf4ywAUp8jItaZY0ltBnaIMcLSiPpwtbMc,6832
|
|
52
51
|
tinybird/tb/modules/watch.py,sha256=Kredt5C7OOiI6YOivuR5QBdiDY4J_xLiwqHOROnfcsU,8591
|
|
53
52
|
tinybird/tb/modules/workspace.py,sha256=sfT9QkoeFlN7ndUXxyImp4a7EFEHjY9MlGlldOViz0Y,6404
|
|
54
53
|
tinybird/tb/modules/workspace_members.py,sha256=Ai6iCOzXX1zQ8q9iXIFSFHsBJlT-8Q28DaG5Ie-UweY,8726
|
|
55
|
-
tinybird/tb/modules/datafile/build.py,sha256=
|
|
54
|
+
tinybird/tb/modules/datafile/build.py,sha256=seGFSvmgyRrAM1-icsKBkuog3WccfGUYFTPT-xoA5W8,50940
|
|
56
55
|
tinybird/tb/modules/datafile/build_common.py,sha256=IXl-Z51zUi1dypV7meNenX0iu2UmowNeqgG6WHyMHlk,4562
|
|
57
56
|
tinybird/tb/modules/datafile/build_datasource.py,sha256=4aP8_DYCRGghXntZSeWDNJxjps1QRVa7WHoYCzQwQts,17355
|
|
58
57
|
tinybird/tb/modules/datafile/build_pipe.py,sha256=Jgv3YKIvMfjPiSIdw1k2mpaoDdAWMiMRaSHwRgyI97E,28258
|
|
59
|
-
tinybird/tb/modules/datafile/common.py,sha256=
|
|
58
|
+
tinybird/tb/modules/datafile/common.py,sha256=E_JDdlYqsPq3TVEt8FMcMpuF5xbKLtl8uLpj99RXSgE,78848
|
|
60
59
|
tinybird/tb/modules/datafile/diff.py,sha256=-0J7PsBO64T7LOZSkZ4ZFHHCPvT7cKItnJkbz2PkndU,6754
|
|
61
60
|
tinybird/tb/modules/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
|
|
62
61
|
tinybird/tb/modules/datafile/fixture.py,sha256=bdZndItV6ibOegPCrN3OgKdkpjDFCFvoSoiZVsCV_XQ,1852
|
|
@@ -75,8 +74,8 @@ tinybird/tb_cli_modules/config.py,sha256=6u6B5QCdiQLbJkCkwtnKGs9H3nP-KXXhC75mF7B
|
|
|
75
74
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
76
75
|
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
77
76
|
tinybird/tb_cli_modules/telemetry.py,sha256=iEGnMuCuNhvF6ln__j6X9MSTwL_0Hm-GgFHHHvhfknk,10466
|
|
78
|
-
tinybird-0.0.1.
|
|
79
|
-
tinybird-0.0.1.
|
|
80
|
-
tinybird-0.0.1.
|
|
81
|
-
tinybird-0.0.1.
|
|
82
|
-
tinybird-0.0.1.
|
|
77
|
+
tinybird-0.0.1.dev49.dist-info/METADATA,sha256=xwzvzDJ7ucjTfGvI0bPguc6fKa-C06ub0NSPwUmqC3g,2482
|
|
78
|
+
tinybird-0.0.1.dev49.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
79
|
+
tinybird-0.0.1.dev49.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
|
|
80
|
+
tinybird-0.0.1.dev49.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
|
|
81
|
+
tinybird-0.0.1.dev49.dist-info/RECORD,,
|
tinybird/tb/modules/update.py
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import re
|
|
3
|
-
from os import getcwd
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
import click
|
|
8
|
-
|
|
9
|
-
from tinybird.client import TinyB
|
|
10
|
-
from tinybird.prompts import mock_prompt, update_prompt
|
|
11
|
-
from tinybird.tb.modules.cli import cli
|
|
12
|
-
from tinybird.tb.modules.common import check_user_token_with_client, coro, generate_datafile
|
|
13
|
-
from tinybird.tb.modules.config import CLIConfig
|
|
14
|
-
from tinybird.tb.modules.datafile.fixture import build_fixture_name, persist_fixture
|
|
15
|
-
from tinybird.tb.modules.exceptions import CLIException
|
|
16
|
-
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
17
|
-
from tinybird.tb.modules.llm import LLM
|
|
18
|
-
from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
|
|
19
|
-
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@cli.command(hidden=True)
|
|
23
|
-
@click.argument("prompt")
|
|
24
|
-
@click.option(
|
|
25
|
-
"--folder",
|
|
26
|
-
default=".",
|
|
27
|
-
type=click.Path(exists=False, file_okay=False),
|
|
28
|
-
help="Folder where project files will be placed",
|
|
29
|
-
)
|
|
30
|
-
@coro
|
|
31
|
-
async def update(
|
|
32
|
-
prompt: str,
|
|
33
|
-
folder: str,
|
|
34
|
-
) -> None:
|
|
35
|
-
"""Update resources in the project."""
|
|
36
|
-
folder = folder or getcwd()
|
|
37
|
-
folder_path = Path(folder)
|
|
38
|
-
if not folder_path.exists():
|
|
39
|
-
folder_path.mkdir()
|
|
40
|
-
|
|
41
|
-
try:
|
|
42
|
-
config = CLIConfig.get_project_config(folder)
|
|
43
|
-
tb_client = config.get_client()
|
|
44
|
-
user_token: Optional[str] = None
|
|
45
|
-
try:
|
|
46
|
-
user_token = config.get_user_token()
|
|
47
|
-
if not user_token:
|
|
48
|
-
raise CLIException("No user token found")
|
|
49
|
-
await check_user_token_with_client(tb_client, token=user_token)
|
|
50
|
-
except Exception as e:
|
|
51
|
-
click.echo(
|
|
52
|
-
FeedbackManager.error(message=f"This action requires authentication. Run 'tb login' first. Error: {e}")
|
|
53
|
-
)
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
local_client = await get_tinybird_local_client(folder)
|
|
57
|
-
|
|
58
|
-
click.echo(FeedbackManager.highlight(message="\n» Updating resources..."))
|
|
59
|
-
datasources_updated = await update_resources(tb_client, user_token, prompt, folder)
|
|
60
|
-
click.echo(FeedbackManager.success(message="✓ Done!\n"))
|
|
61
|
-
|
|
62
|
-
if datasources_updated and user_token:
|
|
63
|
-
click.echo(FeedbackManager.highlight(message="\n» Generating fixtures..."))
|
|
64
|
-
|
|
65
|
-
datasource_files = [f for f in os.listdir(Path(folder) / "datasources") if f.endswith(".datasource")]
|
|
66
|
-
for datasource_file in datasource_files:
|
|
67
|
-
datasource_path = Path(folder) / "datasources" / datasource_file
|
|
68
|
-
llm = LLM(user_token=user_token, host=tb_client.host)
|
|
69
|
-
datasource_name = datasource_path.stem
|
|
70
|
-
datasource_content = datasource_path.read_text()
|
|
71
|
-
has_json_path = "`json:" in datasource_content
|
|
72
|
-
if has_json_path:
|
|
73
|
-
prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
|
|
74
|
-
response = llm.ask(system_prompt=mock_prompt(rows=20), prompt=prompt)
|
|
75
|
-
sql = extract_xml(response, "sql")
|
|
76
|
-
sql = sql.split("FORMAT")[0]
|
|
77
|
-
result = await local_client.query(f"{sql} FORMAT JSON")
|
|
78
|
-
data = result.get("data", [])
|
|
79
|
-
fixture_name = build_fixture_name(
|
|
80
|
-
datasource_path.absolute().as_posix(), datasource_name, datasource_content
|
|
81
|
-
)
|
|
82
|
-
if data:
|
|
83
|
-
persist_fixture(fixture_name, data, folder)
|
|
84
|
-
click.echo(FeedbackManager.info(message=f"✓ /fixtures/{datasource_name}"))
|
|
85
|
-
|
|
86
|
-
click.echo(FeedbackManager.success(message="✓ Done!\n"))
|
|
87
|
-
except Exception as e:
|
|
88
|
-
click.echo(FeedbackManager.error(message=f"Error: {str(e)}"))
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
async def update_resources(
|
|
92
|
-
tb_client: TinyB,
|
|
93
|
-
user_token: str,
|
|
94
|
-
prompt: str,
|
|
95
|
-
folder: str,
|
|
96
|
-
):
|
|
97
|
-
datasource_paths = [
|
|
98
|
-
Path(folder) / "datasources" / f for f in os.listdir(Path(folder) / "datasources") if f.endswith(".datasource")
|
|
99
|
-
]
|
|
100
|
-
pipes_paths = [
|
|
101
|
-
Path(folder) / "endpoints" / f for f in os.listdir(Path(folder) / "endpoints") if f.endswith(".pipe")
|
|
102
|
-
]
|
|
103
|
-
resources_xml = "\n".join(
|
|
104
|
-
[
|
|
105
|
-
f"<resource><type>{resource_type}</type><name>{resource_name}</name><content>{resource_content}</content></resource>"
|
|
106
|
-
for resource_type, resource_name, resource_content in [
|
|
107
|
-
("datasource", ds.stem, ds.read_text()) for ds in datasource_paths
|
|
108
|
-
]
|
|
109
|
-
+ [
|
|
110
|
-
(
|
|
111
|
-
"pipe",
|
|
112
|
-
pipe.stem,
|
|
113
|
-
pipe.read_text(),
|
|
114
|
-
)
|
|
115
|
-
for pipe in pipes_paths
|
|
116
|
-
]
|
|
117
|
-
]
|
|
118
|
-
)
|
|
119
|
-
llm = LLM(user_token=user_token, host=tb_client.host)
|
|
120
|
-
result = llm.ask(system_prompt=update_prompt(resources_xml), prompt=prompt)
|
|
121
|
-
result = extract_xml(result, "response")
|
|
122
|
-
resources = parse_xml(result, "resource")
|
|
123
|
-
datasources = []
|
|
124
|
-
pipes = []
|
|
125
|
-
for resource_xml in resources:
|
|
126
|
-
resource_type = extract_xml(resource_xml, "type")
|
|
127
|
-
name = extract_xml(resource_xml, "name")
|
|
128
|
-
content = extract_xml(resource_xml, "content")
|
|
129
|
-
resource = {
|
|
130
|
-
"name": name,
|
|
131
|
-
"content": content,
|
|
132
|
-
}
|
|
133
|
-
if resource_type.lower() == "datasource":
|
|
134
|
-
datasources.append(resource)
|
|
135
|
-
elif resource_type.lower() == "pipe":
|
|
136
|
-
pipes.append(resource)
|
|
137
|
-
|
|
138
|
-
for ds in datasources:
|
|
139
|
-
content = ds["content"].replace("```", "")
|
|
140
|
-
filename = f"{ds['name']}.datasource"
|
|
141
|
-
generate_datafile(
|
|
142
|
-
content,
|
|
143
|
-
filename=filename,
|
|
144
|
-
data=None,
|
|
145
|
-
_format="ndjson",
|
|
146
|
-
force=True,
|
|
147
|
-
folder=folder,
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
for pipe in pipes:
|
|
151
|
-
content = pipe["content"].replace("```", "")
|
|
152
|
-
generate_pipe_file(pipe["name"], content, folder)
|
|
153
|
-
|
|
154
|
-
return len(datasources) > 0
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def generate_pipe_file(name: str, content: str, folder: str):
|
|
158
|
-
def is_copy(content: str) -> bool:
|
|
159
|
-
return re.search(r"TYPE copy", content, re.IGNORECASE) is not None
|
|
160
|
-
|
|
161
|
-
def is_materialization(content: str) -> bool:
|
|
162
|
-
return re.search(r"TYPE materialized", content, re.IGNORECASE) is not None
|
|
163
|
-
|
|
164
|
-
def is_sink(content: str) -> bool:
|
|
165
|
-
return re.search(r"TYPE sink", content, re.IGNORECASE) is not None
|
|
166
|
-
|
|
167
|
-
if is_copy(content):
|
|
168
|
-
pathname = "copies"
|
|
169
|
-
elif is_materialization(content):
|
|
170
|
-
pathname = "materializations"
|
|
171
|
-
elif is_sink(content):
|
|
172
|
-
pathname = "sinks"
|
|
173
|
-
else:
|
|
174
|
-
pathname = "endpoints"
|
|
175
|
-
|
|
176
|
-
base = Path(folder) / pathname
|
|
177
|
-
if not base.exists():
|
|
178
|
-
base = Path()
|
|
179
|
-
f = base / (f"{name}.pipe")
|
|
180
|
-
with open(f"{f}", "w") as file:
|
|
181
|
-
file.write(content)
|
|
182
|
-
click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
|
|
File without changes
|
|
File without changes
|