tinybird 0.0.1.dev241__py3-none-any.whl → 0.0.1.dev244__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/tb/__cli__.py +2 -2
- tinybird/tb/modules/agent/agent.py +46 -12
- tinybird/tb/modules/agent/animations.py +86 -0
- tinybird/tb/modules/agent/prompts.py +36 -0
- tinybird/tb/modules/agent/tools/create_datafile.py +19 -15
- tinybird/tb/modules/agent/tools/plan.py +5 -3
- tinybird/tb/modules/agent/utils.py +3 -0
- tinybird/tb/modules/build.py +3 -307
- tinybird/tb/modules/build_common.py +321 -0
- tinybird/tb/modules/cli.py +1 -1
- tinybird/tb/modules/secret.py +2 -50
- tinybird/tb/modules/secret_common.py +56 -0
- tinybird/tb/modules/test.py +1 -1
- {tinybird-0.0.1.dev241.dist-info → tinybird-0.0.1.dev244.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev241.dist-info → tinybird-0.0.1.dev244.dist-info}/RECORD +18 -15
- {tinybird-0.0.1.dev241.dist-info → tinybird-0.0.1.dev244.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev241.dist-info → tinybird-0.0.1.dev244.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev241.dist-info → tinybird-0.0.1.dev244.dist-info}/top_level.txt +0 -0
tinybird/tb/__cli__.py
CHANGED
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://www.tinybird.co/docs/forward/commands'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '0.0.1.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '0.0.1.dev244'
|
|
8
|
+
__revision__ = '434c7ff'
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import uuid
|
|
2
3
|
from datetime import datetime
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import Any
|
|
3
6
|
|
|
4
7
|
import click
|
|
5
8
|
from prompt_toolkit import prompt
|
|
@@ -22,23 +25,33 @@ from tinybird.prompts import (
|
|
|
22
25
|
sink_pipe_instructions,
|
|
23
26
|
)
|
|
24
27
|
from tinybird.tb.client import TinyB
|
|
28
|
+
from tinybird.tb.modules.agent.animations import ThinkingAnimation
|
|
25
29
|
from tinybird.tb.modules.agent.banner import display_banner
|
|
26
30
|
from tinybird.tb.modules.agent.memory import clear_history, load_history
|
|
27
31
|
from tinybird.tb.modules.agent.models import create_model
|
|
28
|
-
from tinybird.tb.modules.agent.prompts import
|
|
32
|
+
from tinybird.tb.modules.agent.prompts import (
|
|
33
|
+
datafile_instructions,
|
|
34
|
+
plan_instructions,
|
|
35
|
+
resources_prompt,
|
|
36
|
+
sql_instructions,
|
|
37
|
+
)
|
|
29
38
|
from tinybird.tb.modules.agent.tools.create_datafile import create_datafile
|
|
30
39
|
from tinybird.tb.modules.agent.tools.explore import explore_data
|
|
31
40
|
from tinybird.tb.modules.agent.tools.plan import plan
|
|
32
41
|
from tinybird.tb.modules.agent.tools.preview_datafile import preview_datafile
|
|
33
42
|
from tinybird.tb.modules.agent.utils import TinybirdAgentContext
|
|
43
|
+
from tinybird.tb.modules.build_common import process as build_process
|
|
44
|
+
from tinybird.tb.modules.exceptions import CLIBuildException
|
|
34
45
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
46
|
+
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
47
|
+
from tinybird.tb.modules.project import Project
|
|
35
48
|
|
|
36
49
|
|
|
37
50
|
class TinybirdAgent:
|
|
38
|
-
def __init__(self, token: str, host: str,
|
|
51
|
+
def __init__(self, token: str, host: str, project: Project):
|
|
39
52
|
self.token = token
|
|
40
53
|
self.host = host
|
|
41
|
-
self.
|
|
54
|
+
self.project = project
|
|
42
55
|
self.messages: list[ModelMessage] = []
|
|
43
56
|
self.agent = Agent(
|
|
44
57
|
model=create_model(token, host),
|
|
@@ -82,9 +95,10 @@ You have access to the following tools:
|
|
|
82
95
|
4. Without asking, use the `create_datafile` tool to create the datafile, because it will ask for confirmation before creating the file.
|
|
83
96
|
5. Check the result of the `create_datafile` tool to see if the datafile was created successfully.
|
|
84
97
|
6. If the datafile was created successfully, report the result to the user.
|
|
85
|
-
7. If the datafile was not created
|
|
98
|
+
7. If the datafile was not created, finish the process and just wait for a new user prompt.
|
|
99
|
+
8. If the datafile was created successfully, but the built failed, try to fix the error and repeat the process.
|
|
86
100
|
|
|
87
|
-
IMPORTANT: If the user cancels some of the steps or there is an error, DO NOT continue with the plan. Stop the process and wait for the user before using any other tool.
|
|
101
|
+
IMPORTANT: If the user cancels some of the steps or there is an error in file creation, DO NOT continue with the plan. Stop the process and wait for the user before using any other tool.
|
|
88
102
|
|
|
89
103
|
# When planning the creation or update of resources:
|
|
90
104
|
{plan_instructions}
|
|
@@ -125,7 +139,7 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
|
125
139
|
Tool(explore_data, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
126
140
|
Tool(preview_datafile, docstring_format="google", require_parameter_descriptions=True, takes_ctx=False),
|
|
127
141
|
Tool(create_datafile, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
128
|
-
Tool(plan, docstring_format="google", require_parameter_descriptions=True, takes_ctx=
|
|
142
|
+
Tool(plan, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
129
143
|
],
|
|
130
144
|
)
|
|
131
145
|
|
|
@@ -133,29 +147,40 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
|
|
|
133
147
|
"""Keep only the last 5 messages to manage token usage."""
|
|
134
148
|
return self.messages[-5:] if len(self.messages) > 5 else self.messages
|
|
135
149
|
|
|
136
|
-
def run(self, user_prompt: str) -> None:
|
|
150
|
+
def run(self, user_prompt: str, project: Project) -> None:
|
|
151
|
+
user_prompt = f"{user_prompt}\n\n# Existing resources in the project:\n{resources_prompt(project)}"
|
|
137
152
|
client = TinyB(token=self.token, host=self.host)
|
|
153
|
+
folder = self.project.folder
|
|
154
|
+
thinking_animation = ThinkingAnimation(message="Chirping", delay=0.15)
|
|
155
|
+
thinking_animation.start()
|
|
156
|
+
|
|
138
157
|
result = self.agent.run_sync(
|
|
139
158
|
user_prompt,
|
|
140
159
|
deps=TinybirdAgentContext(
|
|
141
160
|
# context does not support the whole client, so we need to pass only the functions we need
|
|
142
161
|
explore_data=client.explore_data,
|
|
143
|
-
folder=
|
|
162
|
+
build_project=partial(build_project, folder=folder),
|
|
163
|
+
get_project_files=project.get_project_files,
|
|
164
|
+
folder=folder,
|
|
165
|
+
thinking_animation=thinking_animation,
|
|
144
166
|
),
|
|
145
167
|
message_history=self.messages,
|
|
146
168
|
)
|
|
147
169
|
new_messages = result.new_messages()
|
|
148
170
|
self.messages.extend(new_messages)
|
|
149
|
-
|
|
171
|
+
thinking_animation.stop()
|
|
172
|
+
|
|
150
173
|
click.echo(result.output)
|
|
151
174
|
click.echo("\n")
|
|
152
175
|
|
|
153
176
|
|
|
154
|
-
def run_agent(
|
|
177
|
+
def run_agent(config: dict[str, Any], project: Project):
|
|
155
178
|
display_banner()
|
|
156
179
|
|
|
157
180
|
try:
|
|
158
|
-
|
|
181
|
+
token = config["token"]
|
|
182
|
+
host = config["host"]
|
|
183
|
+
agent = TinybirdAgent(token, host, project)
|
|
159
184
|
click.echo()
|
|
160
185
|
click.echo(FeedbackManager.success(message="Welcome to Tinybird Code"))
|
|
161
186
|
click.echo(FeedbackManager.info(message="Describe what you want to create and I'll help you build it"))
|
|
@@ -200,7 +225,7 @@ def run_agent(token: str, host: str, folder: str):
|
|
|
200
225
|
elif user_input.strip() == "":
|
|
201
226
|
continue
|
|
202
227
|
else:
|
|
203
|
-
agent.run(user_input)
|
|
228
|
+
agent.run(user_input, project)
|
|
204
229
|
|
|
205
230
|
except KeyboardInterrupt:
|
|
206
231
|
click.echo(FeedbackManager.info(message="Goodbye!"))
|
|
@@ -212,3 +237,12 @@ def run_agent(token: str, host: str, folder: str):
|
|
|
212
237
|
except Exception as e:
|
|
213
238
|
click.echo(FeedbackManager.error(message=f"Error: {e}"))
|
|
214
239
|
sys.exit(1)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def build_project(folder: str) -> None:
|
|
243
|
+
workspace_name = f"tmp_workspace_{uuid.uuid4()}"
|
|
244
|
+
project = Project(folder, workspace_name=workspace_name)
|
|
245
|
+
local_client = get_tinybird_local_client({"path": folder, "name": workspace_name}, test=True, silent=True)
|
|
246
|
+
build_error = build_process(project=project, tb_client=local_client, watch=False, silent=True, exit_on_error=False)
|
|
247
|
+
if build_error:
|
|
248
|
+
raise CLIBuildException(build_error)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
from time import sleep
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ThinkingAnimation:
|
|
11
|
+
"""Thinking animation that shows changing sparkles as a prefix."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
message: str = "Thinking",
|
|
16
|
+
delay: float = 0.15,
|
|
17
|
+
colors: bool = True,
|
|
18
|
+
dots: bool = True,
|
|
19
|
+
):
|
|
20
|
+
self.message = message
|
|
21
|
+
self.delay = delay
|
|
22
|
+
self.running = False
|
|
23
|
+
self.thread: Optional[threading.Thread] = None
|
|
24
|
+
self.colors = colors and sys.stdout.isatty()
|
|
25
|
+
self.dots = dots
|
|
26
|
+
|
|
27
|
+
# Default sparkle characters
|
|
28
|
+
self.sparkle_chars = ["✧", "✦", "⋆", "✳", "✺", "✹", "*", "·"]
|
|
29
|
+
|
|
30
|
+
# ANSI color codes
|
|
31
|
+
self.colors_list = [
|
|
32
|
+
"\033[33m", # Yellow
|
|
33
|
+
"\033[36m", # Cyan
|
|
34
|
+
"\033[35m", # Magenta
|
|
35
|
+
"\033[93m", # Bright Yellow
|
|
36
|
+
"\033[96m", # Bright Cyan
|
|
37
|
+
]
|
|
38
|
+
self.reset_color = "\033[0m"
|
|
39
|
+
|
|
40
|
+
def start(self):
|
|
41
|
+
"""Start the animation in a separate thread."""
|
|
42
|
+
self.running = True
|
|
43
|
+
self.thread = threading.Thread(target=self._run_animation)
|
|
44
|
+
self.thread.daemon = True
|
|
45
|
+
|
|
46
|
+
click.echo("\n")
|
|
47
|
+
self.thread.start()
|
|
48
|
+
|
|
49
|
+
def stop(self):
|
|
50
|
+
"""Stop the animation."""
|
|
51
|
+
self.running = False
|
|
52
|
+
if self.thread:
|
|
53
|
+
self.thread.join()
|
|
54
|
+
# Clear the line and reset cursor position
|
|
55
|
+
sys.stdout.write("\r" + " " * (len(self.message) + 10) + "\r")
|
|
56
|
+
sys.stdout.flush()
|
|
57
|
+
|
|
58
|
+
def _run_animation(self):
|
|
59
|
+
"""Run the animation until stopped."""
|
|
60
|
+
frame_count = 0
|
|
61
|
+
dots_count = 0
|
|
62
|
+
|
|
63
|
+
while self.running:
|
|
64
|
+
# Choose a random sparkle for this frame
|
|
65
|
+
sparkle = random.choice(self.sparkle_chars)
|
|
66
|
+
|
|
67
|
+
if self.colors:
|
|
68
|
+
color = random.choice(self.colors_list)
|
|
69
|
+
colored_sparkle = f"{color}{sparkle}{self.reset_color}"
|
|
70
|
+
else:
|
|
71
|
+
colored_sparkle = sparkle
|
|
72
|
+
|
|
73
|
+
# Handle dots animation if enabled
|
|
74
|
+
if self.dots:
|
|
75
|
+
dots_count = (frame_count // 4) % 4 # Change dots every 4 frames
|
|
76
|
+
dots = "." * dots_count
|
|
77
|
+
display_message = f"{self.message}{dots}"
|
|
78
|
+
else:
|
|
79
|
+
display_message = self.message
|
|
80
|
+
|
|
81
|
+
# Print the message with the prefix sparkle
|
|
82
|
+
sys.stdout.write(f"\r{colored_sparkle} {display_message}")
|
|
83
|
+
sys.stdout.flush()
|
|
84
|
+
|
|
85
|
+
sleep(self.delay)
|
|
86
|
+
frame_count += 1
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from tinybird.tb.modules.project import Project
|
|
4
|
+
|
|
1
5
|
plan_instructions = """
|
|
2
6
|
When asked to create a plan, you MUST respond with this EXACT format and NOTHING ELSE:
|
|
3
7
|
|
|
@@ -81,3 +85,35 @@ datafile_instructions = """
|
|
|
81
85
|
- Datasource files will be created under the `/datasources` folder.
|
|
82
86
|
</datafile_instructions>
|
|
83
87
|
"""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def resources_prompt(project: Project) -> str:
|
|
91
|
+
files = project.get_project_files()
|
|
92
|
+
|
|
93
|
+
if not files:
|
|
94
|
+
return "No resources found"
|
|
95
|
+
|
|
96
|
+
paths = [Path(file_path) for file_path in files]
|
|
97
|
+
|
|
98
|
+
def get_resource_type(path: Path) -> str:
|
|
99
|
+
if path.suffix.lower() == ".pipe":
|
|
100
|
+
return Project.get_pipe_type(str(path))
|
|
101
|
+
elif path.suffix.lower() == ".datasource":
|
|
102
|
+
return "datasource"
|
|
103
|
+
elif path.suffix.lower() == ".connection":
|
|
104
|
+
return "connection"
|
|
105
|
+
return "unknown"
|
|
106
|
+
|
|
107
|
+
return "\n".join(
|
|
108
|
+
[
|
|
109
|
+
f"""
|
|
110
|
+
<resource>
|
|
111
|
+
<path>{file_path.relative_to(project.folder)}</path>
|
|
112
|
+
<type>{get_resource_type(file_path)}</type>
|
|
113
|
+
<name>{file_path.stem}</name>
|
|
114
|
+
<content>{file_path.read_text()}</content>
|
|
115
|
+
</resource>
|
|
116
|
+
"""
|
|
117
|
+
for file_path in paths
|
|
118
|
+
]
|
|
119
|
+
)
|
|
@@ -4,13 +4,16 @@ import click
|
|
|
4
4
|
from pydantic_ai import RunContext
|
|
5
5
|
|
|
6
6
|
from tinybird.tb.modules.agent.utils import Datafile, TinybirdAgentContext, show_options
|
|
7
|
+
from tinybird.tb.modules.exceptions import CLIBuildException
|
|
8
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
def get_resource_confirmation(resource: Datafile) -> bool:
|
|
11
|
+
def get_resource_confirmation(resource: Datafile, exists: bool) -> bool:
|
|
10
12
|
"""Get user confirmation for creating a resource"""
|
|
11
13
|
while True:
|
|
14
|
+
action = "create" if not exists else "update"
|
|
12
15
|
result = show_options(
|
|
13
|
-
options=[f"Yes,
|
|
16
|
+
options=[f"Yes, {action} {resource.type} '{resource.name}'", "No, and tell Tinybird Code what to do"],
|
|
14
17
|
title=f"What would you like to do with {resource.type} '{resource.name}'?",
|
|
15
18
|
)
|
|
16
19
|
|
|
@@ -35,28 +38,29 @@ def create_datafile(ctx: RunContext[TinybirdAgentContext], resource: Datafile) -
|
|
|
35
38
|
str: If the resource was created or not.
|
|
36
39
|
"""
|
|
37
40
|
try:
|
|
38
|
-
|
|
41
|
+
ctx.deps.thinking_animation.stop()
|
|
39
42
|
click.echo(resource.content)
|
|
40
|
-
|
|
43
|
+
resource.pathname = resource.pathname.removeprefix("/")
|
|
44
|
+
path = Path(ctx.deps.folder) / resource.pathname
|
|
45
|
+
exists = str(path) in ctx.deps.get_project_files()
|
|
46
|
+
confirmation = get_resource_confirmation(resource, exists)
|
|
47
|
+
ctx.deps.thinking_animation.start()
|
|
41
48
|
|
|
42
49
|
if not confirmation:
|
|
43
50
|
return f"Resource {resource.pathname} was not created. User cancelled creation."
|
|
44
51
|
|
|
45
|
-
resource.pathname = resource.pathname.removeprefix("/")
|
|
46
|
-
|
|
47
|
-
path = Path(ctx.deps.folder) / resource.pathname
|
|
48
|
-
|
|
49
52
|
folder_path = path.parent
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
folder_path.mkdir()
|
|
53
|
-
|
|
54
|
-
if not path.exists():
|
|
55
|
-
path.touch()
|
|
53
|
+
folder_path.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
path.touch(exist_ok=True)
|
|
56
55
|
|
|
57
56
|
path.write_text(resource.content)
|
|
58
|
-
|
|
57
|
+
ctx.deps.build_project()
|
|
59
58
|
return f"Created {resource.pathname}"
|
|
60
59
|
|
|
60
|
+
except CLIBuildException as e:
|
|
61
|
+
ctx.deps.thinking_animation.stop()
|
|
62
|
+
click.echo(FeedbackManager.error(message=e))
|
|
63
|
+
ctx.deps.thinking_animation.start()
|
|
64
|
+
return f"Error building project: {e}"
|
|
61
65
|
except Exception as e:
|
|
62
66
|
return f"Error creating {resource.pathname}: {e}"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
|
+
from pydantic_ai import RunContext
|
|
2
3
|
|
|
3
|
-
from tinybird.tb.modules.agent.utils import show_options
|
|
4
|
+
from tinybird.tb.modules.agent.utils import TinybirdAgentContext, show_options
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def get_plan_confirmation() -> bool:
|
|
@@ -22,7 +23,7 @@ def get_plan_confirmation() -> bool:
|
|
|
22
23
|
return False
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
def plan(plan: str) -> str:
|
|
26
|
+
def plan(ctx: RunContext[TinybirdAgentContext], plan: str) -> str:
|
|
26
27
|
"""Given a plan, ask the user for confirmation to implement it
|
|
27
28
|
|
|
28
29
|
Args:
|
|
@@ -32,9 +33,10 @@ def plan(plan: str) -> str:
|
|
|
32
33
|
str: If the plan was implemented or not.
|
|
33
34
|
"""
|
|
34
35
|
try:
|
|
35
|
-
|
|
36
|
+
ctx.deps.thinking_animation.stop()
|
|
36
37
|
click.echo(plan)
|
|
37
38
|
confirmation = get_plan_confirmation()
|
|
39
|
+
ctx.deps.thinking_animation.start()
|
|
38
40
|
|
|
39
41
|
if not confirmation:
|
|
40
42
|
return "Plan was not implemented. User cancelled implementation."
|
|
@@ -20,6 +20,9 @@ from pydantic import BaseModel, Field
|
|
|
20
20
|
class TinybirdAgentContext(BaseModel):
|
|
21
21
|
explore_data: Callable[[str], str]
|
|
22
22
|
folder: str
|
|
23
|
+
build_project: Callable[[], None]
|
|
24
|
+
thinking_animation: Any
|
|
25
|
+
get_project_files: Callable[[], List[str]]
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
default_style = Style.from_dict(
|
tinybird/tb/modules/build.py
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
1
|
import threading
|
|
4
2
|
import time
|
|
5
3
|
from copy import deepcopy
|
|
6
4
|
from functools import partial
|
|
7
5
|
from pathlib import Path
|
|
8
|
-
from typing import Callable, List
|
|
6
|
+
from typing import Callable, List
|
|
9
7
|
from urllib.parse import urlencode
|
|
10
8
|
|
|
11
9
|
import click
|
|
12
|
-
import requests
|
|
13
10
|
|
|
14
11
|
import tinybird.context as context
|
|
15
12
|
from tinybird.datafile.exceptions import ParseException
|
|
16
13
|
from tinybird.datafile.parse_datasource import parse_datasource
|
|
17
14
|
from tinybird.datafile.parse_pipe import parse_pipe
|
|
18
15
|
from tinybird.tb.client import TinyB
|
|
16
|
+
from tinybird.tb.modules.build_common import process
|
|
19
17
|
from tinybird.tb.modules.cli import cli
|
|
20
|
-
from tinybird.tb.modules.common import push_data, sys_exit
|
|
21
18
|
from tinybird.tb.modules.config import CLIConfig
|
|
22
|
-
from tinybird.tb.modules.datafile.fixture import FixtureExtension, get_fixture_dir, persist_fixture
|
|
23
19
|
from tinybird.tb.modules.datafile.playground import folder_playground
|
|
24
20
|
from tinybird.tb.modules.dev_server import BuildStatus, start_server
|
|
25
21
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
26
22
|
from tinybird.tb.modules.project import Project
|
|
27
|
-
from tinybird.tb.modules.
|
|
23
|
+
from tinybird.tb.modules.secret_common import load_secrets
|
|
28
24
|
from tinybird.tb.modules.shell import Shell, print_table_formatted
|
|
29
25
|
from tinybird.tb.modules.watch import watch_files, watch_project
|
|
30
26
|
|
|
@@ -87,275 +83,6 @@ def dev(ctx: click.Context, data_origin: str, ui: bool) -> None:
|
|
|
87
83
|
)
|
|
88
84
|
|
|
89
85
|
|
|
90
|
-
def build_project(project: Project, tb_client: TinyB, silent: bool = False) -> Optional[bool]:
|
|
91
|
-
MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
|
|
92
|
-
DATAFILE_TYPE_TO_CONTENT_TYPE = {
|
|
93
|
-
".datasource": "text/plain",
|
|
94
|
-
".pipe": "text/plain",
|
|
95
|
-
".connection": "text/plain",
|
|
96
|
-
}
|
|
97
|
-
TINYBIRD_API_URL = tb_client.host + "/v1/build"
|
|
98
|
-
logging.debug(TINYBIRD_API_URL)
|
|
99
|
-
TINYBIRD_API_KEY = tb_client.token
|
|
100
|
-
error: Optional[str] = None
|
|
101
|
-
try:
|
|
102
|
-
files = [
|
|
103
|
-
("context://", ("cli-version", "1.0.0", "text/plain")),
|
|
104
|
-
]
|
|
105
|
-
project_path = project.path
|
|
106
|
-
project_files = project.get_project_files()
|
|
107
|
-
|
|
108
|
-
if not project_files:
|
|
109
|
-
return False
|
|
110
|
-
|
|
111
|
-
for file_path in project_files:
|
|
112
|
-
relative_path = Path(file_path).relative_to(project_path).as_posix()
|
|
113
|
-
with open(file_path, "rb") as fd:
|
|
114
|
-
content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
|
|
115
|
-
files.append(
|
|
116
|
-
(MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type))
|
|
117
|
-
)
|
|
118
|
-
HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
|
|
119
|
-
|
|
120
|
-
r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS)
|
|
121
|
-
try:
|
|
122
|
-
result = r.json()
|
|
123
|
-
except Exception as e:
|
|
124
|
-
logging.debug(e, exc_info=True)
|
|
125
|
-
click.echo(FeedbackManager.error(message="Couldn't parse response from server"))
|
|
126
|
-
sys_exit("build_error", str(e))
|
|
127
|
-
|
|
128
|
-
logging.debug(json.dumps(result, indent=2))
|
|
129
|
-
|
|
130
|
-
build_result = result.get("result")
|
|
131
|
-
if build_result == "success":
|
|
132
|
-
build = result.get("build")
|
|
133
|
-
new_datasources = build.get("new_datasource_names", [])
|
|
134
|
-
new_pipes = build.get("new_pipe_names", [])
|
|
135
|
-
new_connections = build.get("new_data_connector_names", [])
|
|
136
|
-
changed_datasources = build.get("changed_datasource_names", [])
|
|
137
|
-
changed_pipes = build.get("changed_pipe_names", [])
|
|
138
|
-
changed_connections = build.get("changed_data_connector_names", [])
|
|
139
|
-
deleted_datasources = build.get("deleted_datasource_names", [])
|
|
140
|
-
deleted_pipes = build.get("deleted_pipe_names", [])
|
|
141
|
-
deleted_connections = build.get("deleted_data_connector_names", [])
|
|
142
|
-
|
|
143
|
-
no_changes = (
|
|
144
|
-
not new_datasources
|
|
145
|
-
and not changed_datasources
|
|
146
|
-
and not new_pipes
|
|
147
|
-
and not changed_pipes
|
|
148
|
-
and not new_connections
|
|
149
|
-
and not changed_connections
|
|
150
|
-
and not deleted_datasources
|
|
151
|
-
and not deleted_pipes
|
|
152
|
-
and not deleted_connections
|
|
153
|
-
)
|
|
154
|
-
if no_changes:
|
|
155
|
-
return False
|
|
156
|
-
else:
|
|
157
|
-
if not silent:
|
|
158
|
-
echo_changes(project, new_datasources, ".datasource", "created")
|
|
159
|
-
echo_changes(project, changed_datasources, ".datasource", "changed")
|
|
160
|
-
echo_changes(project, deleted_datasources, ".datasource", "deleted")
|
|
161
|
-
echo_changes(project, new_pipes, ".pipe", "created")
|
|
162
|
-
echo_changes(project, changed_pipes, ".pipe", "changed")
|
|
163
|
-
echo_changes(project, deleted_pipes, ".pipe", "deleted")
|
|
164
|
-
echo_changes(project, new_connections, ".connection", "created")
|
|
165
|
-
echo_changes(project, changed_connections, ".connection", "changed")
|
|
166
|
-
echo_changes(project, deleted_connections, ".connection", "deleted")
|
|
167
|
-
try:
|
|
168
|
-
for filename in project_files:
|
|
169
|
-
if filename.endswith(".datasource"):
|
|
170
|
-
ds_path = Path(filename)
|
|
171
|
-
ds_name = ds_path.stem
|
|
172
|
-
fixture_folder = get_fixture_dir(project.folder)
|
|
173
|
-
fixture_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
|
|
174
|
-
fixture_path = next(
|
|
175
|
-
(
|
|
176
|
-
fixture_folder / f"{ds_name}{ext}"
|
|
177
|
-
for ext in fixture_extensions
|
|
178
|
-
if (fixture_folder / f"{ds_name}{ext}").exists()
|
|
179
|
-
),
|
|
180
|
-
None,
|
|
181
|
-
)
|
|
182
|
-
if not fixture_path:
|
|
183
|
-
sql_path = fixture_folder / f"{ds_name}.sql"
|
|
184
|
-
if sql_path.exists():
|
|
185
|
-
fixture_path = rebuild_fixture_sql(project, tb_client, str(sql_path))
|
|
186
|
-
|
|
187
|
-
if fixture_path:
|
|
188
|
-
append_fixture(tb_client, ds_name, str(fixture_path))
|
|
189
|
-
|
|
190
|
-
except Exception as e:
|
|
191
|
-
click.echo(FeedbackManager.error_exception(error=f"Error appending fixtures for '{ds_name}': {e}"))
|
|
192
|
-
|
|
193
|
-
feedback = build.get("feedback", [])
|
|
194
|
-
for f in feedback:
|
|
195
|
-
click.echo(
|
|
196
|
-
FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}")
|
|
197
|
-
)
|
|
198
|
-
elif build_result == "failed":
|
|
199
|
-
build_errors = result.get("errors")
|
|
200
|
-
full_error_msg = ""
|
|
201
|
-
for build_error in build_errors:
|
|
202
|
-
filename_bit = build_error.get("filename", build_error.get("resource", ""))
|
|
203
|
-
error_bit = build_error.get("error") or build_error.get("message") or ""
|
|
204
|
-
error_msg = ((filename_bit + "\n") if filename_bit else "") + error_bit
|
|
205
|
-
full_error_msg += error_msg + "\n\n"
|
|
206
|
-
error = full_error_msg.strip("\n") or "Unknown build error"
|
|
207
|
-
else:
|
|
208
|
-
error = f"Unknown build result. Error: {result.get('error')}"
|
|
209
|
-
except Exception as e:
|
|
210
|
-
error = str(e)
|
|
211
|
-
|
|
212
|
-
if error:
|
|
213
|
-
raise click.ClickException(error)
|
|
214
|
-
|
|
215
|
-
return build_result
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def append_fixture(
|
|
219
|
-
tb_client: TinyB,
|
|
220
|
-
datasource_name: str,
|
|
221
|
-
url: str,
|
|
222
|
-
):
|
|
223
|
-
# Append fixtures only if the datasource is empty
|
|
224
|
-
data = tb_client._req(f"/v0/datasources/{datasource_name}")
|
|
225
|
-
if data.get("statistics", {}).get("row_count", 0) > 0:
|
|
226
|
-
return
|
|
227
|
-
|
|
228
|
-
push_data(
|
|
229
|
-
tb_client,
|
|
230
|
-
datasource_name,
|
|
231
|
-
url,
|
|
232
|
-
mode="append",
|
|
233
|
-
concurrency=1,
|
|
234
|
-
silent=True,
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def rebuild_fixture(project: Project, tb_client: TinyB, fixture: str) -> None:
|
|
239
|
-
try:
|
|
240
|
-
fixture_path = Path(fixture)
|
|
241
|
-
datasources_path = Path(project.folder) / "datasources"
|
|
242
|
-
ds_name = fixture_path.stem
|
|
243
|
-
|
|
244
|
-
if ds_name not in project.datasources:
|
|
245
|
-
try:
|
|
246
|
-
ds_name = "_".join(fixture_path.stem.split("_")[:-1])
|
|
247
|
-
except Exception:
|
|
248
|
-
pass
|
|
249
|
-
|
|
250
|
-
ds_path = datasources_path / f"{ds_name}.datasource"
|
|
251
|
-
|
|
252
|
-
if ds_path.exists():
|
|
253
|
-
tb_client.datasource_truncate(ds_name)
|
|
254
|
-
append_fixture(tb_client, ds_name, str(fixture_path))
|
|
255
|
-
except Exception as e:
|
|
256
|
-
click.echo(FeedbackManager.error_exception(error=e))
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def show_data(tb_client: TinyB, filename: str, diff: Optional[str] = None):
|
|
260
|
-
table_name = diff
|
|
261
|
-
resource_path = Path(filename)
|
|
262
|
-
resource_name = resource_path.stem
|
|
263
|
-
|
|
264
|
-
pipeline = resource_name if filename.endswith(".pipe") else None
|
|
265
|
-
|
|
266
|
-
if not table_name:
|
|
267
|
-
table_name = resource_name
|
|
268
|
-
|
|
269
|
-
sql = f"SELECT * FROM {table_name} FORMAT JSON"
|
|
270
|
-
|
|
271
|
-
res = tb_client.query(sql, pipeline=pipeline)
|
|
272
|
-
print_table_formatted(res, table_name)
|
|
273
|
-
if Project.get_pipe_type(filename) == "endpoint":
|
|
274
|
-
example_params = {
|
|
275
|
-
"format": "json",
|
|
276
|
-
"pipe": resource_name,
|
|
277
|
-
"q": "",
|
|
278
|
-
"token": tb_client.token,
|
|
279
|
-
}
|
|
280
|
-
endpoint_url = tb_client._req(f"/examples/query.http?{urlencode(example_params)}")
|
|
281
|
-
if endpoint_url:
|
|
282
|
-
endpoint_url = endpoint_url.replace("http://localhost:8001", tb_client.host)
|
|
283
|
-
click.echo(FeedbackManager.gray(message="\nTest endpoint at ") + FeedbackManager.info(message=endpoint_url))
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def process(
|
|
287
|
-
project: Project,
|
|
288
|
-
tb_client: TinyB,
|
|
289
|
-
watch: bool,
|
|
290
|
-
file_changed: Optional[str] = None,
|
|
291
|
-
diff: Optional[str] = None,
|
|
292
|
-
silent: bool = False,
|
|
293
|
-
error: bool = False,
|
|
294
|
-
build_status: Optional[BuildStatus] = None,
|
|
295
|
-
) -> Optional[str]:
|
|
296
|
-
time_start = time.time()
|
|
297
|
-
build_failed = False
|
|
298
|
-
build_error: Optional[str] = None
|
|
299
|
-
build_result: Optional[bool] = None
|
|
300
|
-
if build_status:
|
|
301
|
-
if build_status.building:
|
|
302
|
-
return build_status.error
|
|
303
|
-
else:
|
|
304
|
-
build_status.building = True
|
|
305
|
-
if file_changed and (file_changed.endswith(FixtureExtension.NDJSON) or file_changed.endswith(FixtureExtension.CSV)):
|
|
306
|
-
rebuild_fixture(project, tb_client, file_changed)
|
|
307
|
-
if build_status:
|
|
308
|
-
build_status.building = False
|
|
309
|
-
build_status.error = None
|
|
310
|
-
elif file_changed and file_changed.endswith(".sql"):
|
|
311
|
-
rebuild_fixture_sql(project, tb_client, file_changed)
|
|
312
|
-
if build_status:
|
|
313
|
-
build_status.building = False
|
|
314
|
-
build_status.error = None
|
|
315
|
-
elif file_changed and (file_changed.endswith(".env.local") or file_changed.endswith(".env")):
|
|
316
|
-
load_secrets(project, tb_client)
|
|
317
|
-
if build_status:
|
|
318
|
-
build_status.building = False
|
|
319
|
-
build_status.error = None
|
|
320
|
-
else:
|
|
321
|
-
try:
|
|
322
|
-
build_result = build_project(project, tb_client, silent)
|
|
323
|
-
if build_status:
|
|
324
|
-
build_status.building = False
|
|
325
|
-
build_status.error = None
|
|
326
|
-
except click.ClickException as e:
|
|
327
|
-
click.echo(FeedbackManager.info(message=str(e)))
|
|
328
|
-
build_error = str(e)
|
|
329
|
-
build_failed = True
|
|
330
|
-
try:
|
|
331
|
-
if file_changed and not build_failed and not build_status:
|
|
332
|
-
show_data(tb_client, file_changed, diff)
|
|
333
|
-
except Exception:
|
|
334
|
-
pass
|
|
335
|
-
|
|
336
|
-
time_end = time.time()
|
|
337
|
-
elapsed_time = time_end - time_start
|
|
338
|
-
|
|
339
|
-
rebuild_str = "Rebuild" if watch and file_changed else "Build"
|
|
340
|
-
if build_failed:
|
|
341
|
-
click.echo(FeedbackManager.error(message=f"✗ {rebuild_str} failed"))
|
|
342
|
-
if not watch:
|
|
343
|
-
sys_exit("build_error", build_error or "Unknown error")
|
|
344
|
-
build_error = build_error or "Unknown error"
|
|
345
|
-
if build_status:
|
|
346
|
-
build_status.error = build_error
|
|
347
|
-
build_status.building = False
|
|
348
|
-
return build_error
|
|
349
|
-
else:
|
|
350
|
-
if not silent:
|
|
351
|
-
if build_result == False: # noqa: E712
|
|
352
|
-
click.echo(FeedbackManager.info(message="No changes. Build skipped."))
|
|
353
|
-
else:
|
|
354
|
-
click.echo(FeedbackManager.success(message=f"\n✓ {rebuild_str} completed in {elapsed_time:.1f}s"))
|
|
355
|
-
|
|
356
|
-
return None
|
|
357
|
-
|
|
358
|
-
|
|
359
86
|
def run_watch(project: Project, tb_client: TinyB, process: Callable) -> None:
|
|
360
87
|
shell = Shell(project=project, tb_client=tb_client, playground=False)
|
|
361
88
|
click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
|
|
@@ -368,27 +95,6 @@ def run_watch(project: Project, tb_client: TinyB, process: Callable) -> None:
|
|
|
368
95
|
shell.run()
|
|
369
96
|
|
|
370
97
|
|
|
371
|
-
def rebuild_fixture_sql(project: Project, tb_client: TinyB, sql_file: str) -> Path:
|
|
372
|
-
sql_path = Path(sql_file)
|
|
373
|
-
datasource_name = sql_path.stem
|
|
374
|
-
valid_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
|
|
375
|
-
fixtures_path = get_fixture_dir(project.folder)
|
|
376
|
-
current_fixture_path = next(
|
|
377
|
-
(
|
|
378
|
-
fixtures_path / f"{datasource_name}{extension}"
|
|
379
|
-
for extension in valid_extensions
|
|
380
|
-
if (fixtures_path / f"{datasource_name}{extension}").exists()
|
|
381
|
-
),
|
|
382
|
-
None,
|
|
383
|
-
)
|
|
384
|
-
fixture_format = current_fixture_path.suffix.lstrip(".") if current_fixture_path else "ndjson"
|
|
385
|
-
sql = sql_path.read_text()
|
|
386
|
-
sql_format = "CSV" if fixture_format == "csv" else "JSON"
|
|
387
|
-
result = tb_client.query(f"{sql} FORMAT {sql_format}")
|
|
388
|
-
data = result.get("data", [])
|
|
389
|
-
return persist_fixture(datasource_name, data, project.folder, format=fixture_format)
|
|
390
|
-
|
|
391
|
-
|
|
392
98
|
def is_vendor(f: Path) -> bool:
|
|
393
99
|
return f.parts[0] == "vendor"
|
|
394
100
|
|
|
@@ -497,13 +203,3 @@ def build_and_print_resource(config: CLIConfig, tb_client: TinyB, filename: str)
|
|
|
497
203
|
node_sql = last_node["sql"]
|
|
498
204
|
res = tb_client.query(f"{node_sql} FORMAT JSON", playground=playground_id)
|
|
499
205
|
print_table_formatted(res, name)
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
def echo_changes(project: Project, changes: List[str], extension: str, status: str):
|
|
503
|
-
for resource in changes:
|
|
504
|
-
path_str = next(
|
|
505
|
-
(p for p in project.get_project_files() if p.endswith(resource + extension)), resource + extension
|
|
506
|
-
)
|
|
507
|
-
if path_str:
|
|
508
|
-
path_str = path_str.replace(f"{project.folder}/", "")
|
|
509
|
-
click.echo(FeedbackManager.info(message=f"✓ {path_str} {status}"))
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from urllib.parse import urlencode
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from tinybird.tb.client import TinyB
|
|
12
|
+
from tinybird.tb.modules.common import push_data, sys_exit
|
|
13
|
+
from tinybird.tb.modules.datafile.fixture import FixtureExtension, get_fixture_dir, persist_fixture
|
|
14
|
+
from tinybird.tb.modules.dev_server import BuildStatus
|
|
15
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
16
|
+
from tinybird.tb.modules.project import Project
|
|
17
|
+
from tinybird.tb.modules.secret_common import load_secrets
|
|
18
|
+
from tinybird.tb.modules.shell import print_table_formatted
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def process(
|
|
22
|
+
project: Project,
|
|
23
|
+
tb_client: TinyB,
|
|
24
|
+
watch: bool,
|
|
25
|
+
file_changed: Optional[str] = None,
|
|
26
|
+
diff: Optional[str] = None,
|
|
27
|
+
silent: bool = False,
|
|
28
|
+
error: bool = False,
|
|
29
|
+
build_status: Optional[BuildStatus] = None,
|
|
30
|
+
exit_on_error: bool = True,
|
|
31
|
+
) -> Optional[str]:
|
|
32
|
+
time_start = time.time()
|
|
33
|
+
build_failed = False
|
|
34
|
+
build_error: Optional[str] = None
|
|
35
|
+
build_result: Optional[bool] = None
|
|
36
|
+
if build_status:
|
|
37
|
+
if build_status.building:
|
|
38
|
+
return build_status.error
|
|
39
|
+
else:
|
|
40
|
+
build_status.building = True
|
|
41
|
+
if file_changed and (file_changed.endswith(FixtureExtension.NDJSON) or file_changed.endswith(FixtureExtension.CSV)):
|
|
42
|
+
rebuild_fixture(project, tb_client, file_changed)
|
|
43
|
+
if build_status:
|
|
44
|
+
build_status.building = False
|
|
45
|
+
build_status.error = None
|
|
46
|
+
elif file_changed and file_changed.endswith(".sql"):
|
|
47
|
+
rebuild_fixture_sql(project, tb_client, file_changed)
|
|
48
|
+
if build_status:
|
|
49
|
+
build_status.building = False
|
|
50
|
+
build_status.error = None
|
|
51
|
+
elif file_changed and (file_changed.endswith(".env.local") or file_changed.endswith(".env")):
|
|
52
|
+
load_secrets(project, tb_client)
|
|
53
|
+
if build_status:
|
|
54
|
+
build_status.building = False
|
|
55
|
+
build_status.error = None
|
|
56
|
+
else:
|
|
57
|
+
try:
|
|
58
|
+
build_result = build_project(project, tb_client, silent)
|
|
59
|
+
if build_status:
|
|
60
|
+
build_status.building = False
|
|
61
|
+
build_status.error = None
|
|
62
|
+
except click.ClickException as e:
|
|
63
|
+
if not silent:
|
|
64
|
+
click.echo(FeedbackManager.info(message=str(e)))
|
|
65
|
+
build_error = str(e)
|
|
66
|
+
build_failed = True
|
|
67
|
+
try:
|
|
68
|
+
if file_changed and not build_failed and not build_status:
|
|
69
|
+
show_data(tb_client, file_changed, diff)
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
time_end = time.time()
|
|
74
|
+
elapsed_time = time_end - time_start
|
|
75
|
+
|
|
76
|
+
rebuild_str = "Rebuild" if watch and file_changed else "Build"
|
|
77
|
+
if build_failed:
|
|
78
|
+
if not silent:
|
|
79
|
+
click.echo(FeedbackManager.error(message=f"✗ {rebuild_str} failed"))
|
|
80
|
+
if not watch and exit_on_error:
|
|
81
|
+
sys_exit("build_error", build_error or "Unknown error")
|
|
82
|
+
build_error = build_error or "Unknown error"
|
|
83
|
+
if build_status:
|
|
84
|
+
build_status.error = build_error
|
|
85
|
+
build_status.building = False
|
|
86
|
+
return build_error
|
|
87
|
+
else:
|
|
88
|
+
if not silent:
|
|
89
|
+
if build_result == False: # noqa: E712
|
|
90
|
+
click.echo(FeedbackManager.info(message="No changes. Build skipped."))
|
|
91
|
+
else:
|
|
92
|
+
click.echo(FeedbackManager.success(message=f"\n✓ {rebuild_str} completed in {elapsed_time:.1f}s"))
|
|
93
|
+
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def rebuild_fixture(project: Project, tb_client: TinyB, fixture: str) -> None:
|
|
98
|
+
try:
|
|
99
|
+
fixture_path = Path(fixture)
|
|
100
|
+
datasources_path = Path(project.folder) / "datasources"
|
|
101
|
+
ds_name = fixture_path.stem
|
|
102
|
+
|
|
103
|
+
if ds_name not in project.datasources:
|
|
104
|
+
try:
|
|
105
|
+
ds_name = "_".join(fixture_path.stem.split("_")[:-1])
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
ds_path = datasources_path / f"{ds_name}.datasource"
|
|
110
|
+
|
|
111
|
+
if ds_path.exists():
|
|
112
|
+
tb_client.datasource_truncate(ds_name)
|
|
113
|
+
append_fixture(tb_client, ds_name, str(fixture_path))
|
|
114
|
+
except Exception as e:
|
|
115
|
+
click.echo(FeedbackManager.error_exception(error=e))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def rebuild_fixture_sql(project: Project, tb_client: TinyB, sql_file: str) -> Path:
|
|
119
|
+
sql_path = Path(sql_file)
|
|
120
|
+
datasource_name = sql_path.stem
|
|
121
|
+
valid_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
|
|
122
|
+
fixtures_path = get_fixture_dir(project.folder)
|
|
123
|
+
current_fixture_path = next(
|
|
124
|
+
(
|
|
125
|
+
fixtures_path / f"{datasource_name}{extension}"
|
|
126
|
+
for extension in valid_extensions
|
|
127
|
+
if (fixtures_path / f"{datasource_name}{extension}").exists()
|
|
128
|
+
),
|
|
129
|
+
None,
|
|
130
|
+
)
|
|
131
|
+
fixture_format = current_fixture_path.suffix.lstrip(".") if current_fixture_path else "ndjson"
|
|
132
|
+
sql = sql_path.read_text()
|
|
133
|
+
sql_format = "CSV" if fixture_format == "csv" else "JSON"
|
|
134
|
+
result = tb_client.query(f"{sql} FORMAT {sql_format}")
|
|
135
|
+
data = result.get("data", [])
|
|
136
|
+
return persist_fixture(datasource_name, data, project.folder, format=fixture_format)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def append_fixture(
|
|
140
|
+
tb_client: TinyB,
|
|
141
|
+
datasource_name: str,
|
|
142
|
+
url: str,
|
|
143
|
+
):
|
|
144
|
+
# Append fixtures only if the datasource is empty
|
|
145
|
+
data = tb_client._req(f"/v0/datasources/{datasource_name}")
|
|
146
|
+
if data.get("statistics", {}).get("row_count", 0) > 0:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
push_data(
|
|
150
|
+
tb_client,
|
|
151
|
+
datasource_name,
|
|
152
|
+
url,
|
|
153
|
+
mode="append",
|
|
154
|
+
concurrency=1,
|
|
155
|
+
silent=True,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def show_data(tb_client: TinyB, filename: str, diff: Optional[str] = None):
|
|
160
|
+
table_name = diff
|
|
161
|
+
resource_path = Path(filename)
|
|
162
|
+
resource_name = resource_path.stem
|
|
163
|
+
|
|
164
|
+
pipeline = resource_name if filename.endswith(".pipe") else None
|
|
165
|
+
|
|
166
|
+
if not table_name:
|
|
167
|
+
table_name = resource_name
|
|
168
|
+
|
|
169
|
+
sql = f"SELECT * FROM {table_name} FORMAT JSON"
|
|
170
|
+
|
|
171
|
+
res = tb_client.query(sql, pipeline=pipeline)
|
|
172
|
+
print_table_formatted(res, table_name)
|
|
173
|
+
if Project.get_pipe_type(filename) == "endpoint":
|
|
174
|
+
example_params = {
|
|
175
|
+
"format": "json",
|
|
176
|
+
"pipe": resource_name,
|
|
177
|
+
"q": "",
|
|
178
|
+
"token": tb_client.token,
|
|
179
|
+
}
|
|
180
|
+
endpoint_url = tb_client._req(f"/examples/query.http?{urlencode(example_params)}")
|
|
181
|
+
if endpoint_url:
|
|
182
|
+
endpoint_url = endpoint_url.replace("http://localhost:8001", tb_client.host)
|
|
183
|
+
click.echo(FeedbackManager.gray(message="\nTest endpoint at ") + FeedbackManager.info(message=endpoint_url))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def build_project(project: Project, tb_client: TinyB, silent: bool = False) -> Optional[bool]:
|
|
187
|
+
MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
|
|
188
|
+
DATAFILE_TYPE_TO_CONTENT_TYPE = {
|
|
189
|
+
".datasource": "text/plain",
|
|
190
|
+
".pipe": "text/plain",
|
|
191
|
+
".connection": "text/plain",
|
|
192
|
+
}
|
|
193
|
+
TINYBIRD_API_URL = tb_client.host + "/v1/build"
|
|
194
|
+
logging.debug(TINYBIRD_API_URL)
|
|
195
|
+
TINYBIRD_API_KEY = tb_client.token
|
|
196
|
+
error: Optional[str] = None
|
|
197
|
+
try:
|
|
198
|
+
files = [
|
|
199
|
+
("context://", ("cli-version", "1.0.0", "text/plain")),
|
|
200
|
+
]
|
|
201
|
+
project_path = project.path
|
|
202
|
+
project_files = project.get_project_files()
|
|
203
|
+
|
|
204
|
+
if not project_files:
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
for file_path in project_files:
|
|
208
|
+
relative_path = Path(file_path).relative_to(project_path).as_posix()
|
|
209
|
+
with open(file_path, "rb") as fd:
|
|
210
|
+
content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
|
|
211
|
+
files.append(
|
|
212
|
+
(MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type))
|
|
213
|
+
)
|
|
214
|
+
HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
|
|
215
|
+
|
|
216
|
+
r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS)
|
|
217
|
+
try:
|
|
218
|
+
result = r.json()
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logging.debug(e, exc_info=True)
|
|
221
|
+
click.echo(FeedbackManager.error(message="Couldn't parse response from server"))
|
|
222
|
+
sys_exit("build_error", str(e))
|
|
223
|
+
|
|
224
|
+
logging.debug(json.dumps(result, indent=2))
|
|
225
|
+
|
|
226
|
+
build_result = result.get("result")
|
|
227
|
+
if build_result == "success":
|
|
228
|
+
build = result.get("build")
|
|
229
|
+
new_datasources = build.get("new_datasource_names", [])
|
|
230
|
+
new_pipes = build.get("new_pipe_names", [])
|
|
231
|
+
new_connections = build.get("new_data_connector_names", [])
|
|
232
|
+
changed_datasources = build.get("changed_datasource_names", [])
|
|
233
|
+
changed_pipes = build.get("changed_pipe_names", [])
|
|
234
|
+
changed_connections = build.get("changed_data_connector_names", [])
|
|
235
|
+
deleted_datasources = build.get("deleted_datasource_names", [])
|
|
236
|
+
deleted_pipes = build.get("deleted_pipe_names", [])
|
|
237
|
+
deleted_connections = build.get("deleted_data_connector_names", [])
|
|
238
|
+
|
|
239
|
+
no_changes = (
|
|
240
|
+
not new_datasources
|
|
241
|
+
and not changed_datasources
|
|
242
|
+
and not new_pipes
|
|
243
|
+
and not changed_pipes
|
|
244
|
+
and not new_connections
|
|
245
|
+
and not changed_connections
|
|
246
|
+
and not deleted_datasources
|
|
247
|
+
and not deleted_pipes
|
|
248
|
+
and not deleted_connections
|
|
249
|
+
)
|
|
250
|
+
if no_changes:
|
|
251
|
+
return False
|
|
252
|
+
else:
|
|
253
|
+
if not silent:
|
|
254
|
+
echo_changes(project, new_datasources, ".datasource", "created")
|
|
255
|
+
echo_changes(project, changed_datasources, ".datasource", "changed")
|
|
256
|
+
echo_changes(project, deleted_datasources, ".datasource", "deleted")
|
|
257
|
+
echo_changes(project, new_pipes, ".pipe", "created")
|
|
258
|
+
echo_changes(project, changed_pipes, ".pipe", "changed")
|
|
259
|
+
echo_changes(project, deleted_pipes, ".pipe", "deleted")
|
|
260
|
+
echo_changes(project, new_connections, ".connection", "created")
|
|
261
|
+
echo_changes(project, changed_connections, ".connection", "changed")
|
|
262
|
+
echo_changes(project, deleted_connections, ".connection", "deleted")
|
|
263
|
+
try:
|
|
264
|
+
for filename in project_files:
|
|
265
|
+
if filename.endswith(".datasource"):
|
|
266
|
+
ds_path = Path(filename)
|
|
267
|
+
ds_name = ds_path.stem
|
|
268
|
+
fixture_folder = get_fixture_dir(project.folder)
|
|
269
|
+
fixture_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
|
|
270
|
+
fixture_path = next(
|
|
271
|
+
(
|
|
272
|
+
fixture_folder / f"{ds_name}{ext}"
|
|
273
|
+
for ext in fixture_extensions
|
|
274
|
+
if (fixture_folder / f"{ds_name}{ext}").exists()
|
|
275
|
+
),
|
|
276
|
+
None,
|
|
277
|
+
)
|
|
278
|
+
if not fixture_path:
|
|
279
|
+
sql_path = fixture_folder / f"{ds_name}.sql"
|
|
280
|
+
if sql_path.exists():
|
|
281
|
+
fixture_path = rebuild_fixture_sql(project, tb_client, str(sql_path))
|
|
282
|
+
|
|
283
|
+
if fixture_path:
|
|
284
|
+
append_fixture(tb_client, ds_name, str(fixture_path))
|
|
285
|
+
|
|
286
|
+
except Exception as e:
|
|
287
|
+
click.echo(FeedbackManager.error_exception(error=f"Error appending fixtures for '{ds_name}': {e}"))
|
|
288
|
+
|
|
289
|
+
feedback = build.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
|
+
elif build_result == "failed":
|
|
295
|
+
build_errors = result.get("errors")
|
|
296
|
+
full_error_msg = ""
|
|
297
|
+
for build_error in build_errors:
|
|
298
|
+
filename_bit = build_error.get("filename", build_error.get("resource", ""))
|
|
299
|
+
error_bit = build_error.get("error") or build_error.get("message") or ""
|
|
300
|
+
error_msg = ((filename_bit + "\n") if filename_bit else "") + error_bit
|
|
301
|
+
full_error_msg += error_msg + "\n\n"
|
|
302
|
+
error = full_error_msg.strip("\n") or "Unknown build error"
|
|
303
|
+
else:
|
|
304
|
+
error = f"Unknown build result. Error: {result.get('error')}"
|
|
305
|
+
except Exception as e:
|
|
306
|
+
error = str(e)
|
|
307
|
+
|
|
308
|
+
if error:
|
|
309
|
+
raise click.ClickException(error)
|
|
310
|
+
|
|
311
|
+
return build_result
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def echo_changes(project: Project, changes: list[str], extension: str, status: str):
|
|
315
|
+
for resource in changes:
|
|
316
|
+
path_str = next(
|
|
317
|
+
(p for p in project.get_project_files() if p.endswith(resource + extension)), resource + extension
|
|
318
|
+
)
|
|
319
|
+
if path_str:
|
|
320
|
+
path_str = path_str.replace(f"{project.folder}/", "")
|
|
321
|
+
click.echo(FeedbackManager.info(message=f"✓ {path_str} {status}"))
|
tinybird/tb/modules/cli.py
CHANGED
tinybird/tb/modules/secret.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
|
-
from dotenv import
|
|
5
|
+
from dotenv import set_key
|
|
6
6
|
|
|
7
7
|
from tinybird.tb.client import TinyB
|
|
8
8
|
from tinybird.tb.modules.cli import cli
|
|
@@ -97,54 +97,6 @@ def secret_rm(ctx: click.Context, name: str):
|
|
|
97
97
|
click.echo(FeedbackManager.error(message=f"✗ Error: {e}"))
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
def load_secrets(project: Project, client: TinyB):
|
|
101
|
-
try:
|
|
102
|
-
env_vars: Dict[str, str] = {}
|
|
103
|
-
|
|
104
|
-
# Load secrets from .env file
|
|
105
|
-
env_file = ".env"
|
|
106
|
-
env_path = project.path / env_file
|
|
107
|
-
|
|
108
|
-
if env_path.exists():
|
|
109
|
-
env_values = dotenv_values(env_path)
|
|
110
|
-
if env_values:
|
|
111
|
-
env_vars.update({k: v for k, v in env_values.items() if v is not None})
|
|
112
|
-
|
|
113
|
-
# Load secrets from .env.local file
|
|
114
|
-
env_file = ".env.local"
|
|
115
|
-
env_path = project.path / env_file
|
|
116
|
-
|
|
117
|
-
if env_path.exists():
|
|
118
|
-
env_values = dotenv_values(env_path)
|
|
119
|
-
if env_values:
|
|
120
|
-
env_vars.update({k: v for k, v in env_values.items() if v is not None})
|
|
121
|
-
|
|
122
|
-
if len(env_vars.keys()) == 0:
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
click.echo(FeedbackManager.highlight(message="\n» Loading secrets from .env files..."))
|
|
126
|
-
|
|
127
|
-
for name, value in env_vars.items():
|
|
128
|
-
if not value:
|
|
129
|
-
continue
|
|
130
|
-
|
|
131
|
-
try:
|
|
132
|
-
existing_secret = client.get_secret(name)
|
|
133
|
-
except Exception:
|
|
134
|
-
existing_secret = None
|
|
135
|
-
try:
|
|
136
|
-
if existing_secret:
|
|
137
|
-
client.update_secret(name, value)
|
|
138
|
-
else:
|
|
139
|
-
client.create_secret(name, value)
|
|
140
|
-
except Exception as e:
|
|
141
|
-
click.echo(FeedbackManager.error(message=f"✗ Error setting secret '{name}': {e}"))
|
|
142
|
-
|
|
143
|
-
click.echo(FeedbackManager.success(message="✓ Secrets loaded!"))
|
|
144
|
-
except Exception as e:
|
|
145
|
-
click.echo(FeedbackManager.error(message=f"✗ Error: {e}"))
|
|
146
|
-
|
|
147
|
-
|
|
148
100
|
def save_secret_to_env_file(project: Project, name: str, value: str):
|
|
149
101
|
env_path = project.path / ".env.local"
|
|
150
102
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from dotenv import dotenv_values
|
|
5
|
+
|
|
6
|
+
from tinybird.tb.client import TinyB
|
|
7
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
8
|
+
from tinybird.tb.modules.project import Project
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_secrets(project: Project, client: TinyB):
|
|
12
|
+
try:
|
|
13
|
+
env_vars: Dict[str, str] = {}
|
|
14
|
+
|
|
15
|
+
# Load secrets from .env file
|
|
16
|
+
env_file = ".env"
|
|
17
|
+
env_path = project.path / env_file
|
|
18
|
+
|
|
19
|
+
if env_path.exists():
|
|
20
|
+
env_values = dotenv_values(env_path)
|
|
21
|
+
if env_values:
|
|
22
|
+
env_vars.update({k: v for k, v in env_values.items() if v is not None})
|
|
23
|
+
|
|
24
|
+
# Load secrets from .env.local file
|
|
25
|
+
env_file = ".env.local"
|
|
26
|
+
env_path = project.path / env_file
|
|
27
|
+
|
|
28
|
+
if env_path.exists():
|
|
29
|
+
env_values = dotenv_values(env_path)
|
|
30
|
+
if env_values:
|
|
31
|
+
env_vars.update({k: v for k, v in env_values.items() if v is not None})
|
|
32
|
+
|
|
33
|
+
if len(env_vars.keys()) == 0:
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
click.echo(FeedbackManager.highlight(message="\n» Loading secrets from .env files..."))
|
|
37
|
+
|
|
38
|
+
for name, value in env_vars.items():
|
|
39
|
+
if not value:
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
existing_secret = client.get_secret(name)
|
|
44
|
+
except Exception:
|
|
45
|
+
existing_secret = None
|
|
46
|
+
try:
|
|
47
|
+
if existing_secret:
|
|
48
|
+
client.update_secret(name, value)
|
|
49
|
+
else:
|
|
50
|
+
client.create_secret(name, value)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
click.echo(FeedbackManager.error(message=f"✗ Error setting secret '{name}': {e}"))
|
|
53
|
+
|
|
54
|
+
click.echo(FeedbackManager.success(message="✓ Secrets loaded!"))
|
|
55
|
+
except Exception as e:
|
|
56
|
+
click.echo(FeedbackManager.error(message=f"✗ Error: {e}"))
|
tinybird/tb/modules/test.py
CHANGED
|
@@ -26,7 +26,7 @@ from tinybird.tb.modules.llm import LLM
|
|
|
26
26
|
from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
|
|
27
27
|
from tinybird.tb.modules.local_common import get_local_tokens, get_test_workspace_name
|
|
28
28
|
from tinybird.tb.modules.project import Project
|
|
29
|
-
from tinybird.tb.modules.
|
|
29
|
+
from tinybird.tb.modules.secret_common import load_secrets
|
|
30
30
|
|
|
31
31
|
yaml.SafeDumper.org_represent_str = yaml.SafeDumper.represent_str # type: ignore[attr-defined]
|
|
32
32
|
|
|
@@ -17,14 +17,15 @@ tinybird/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1w
|
|
|
17
17
|
tinybird/datafile/parse_connection.py,sha256=tRyn2Rpr1TeWet5BXmMoQgaotbGdYep1qiTak_OqC5E,1825
|
|
18
18
|
tinybird/datafile/parse_datasource.py,sha256=ssW8QeFSgglVFi3sDZj_HgkJiTJ2069v2JgqnH3CkDE,1825
|
|
19
19
|
tinybird/datafile/parse_pipe.py,sha256=xf4m0Tw44QWJzHzAm7Z7FwUoUUtr7noMYjU1NiWnX0k,3880
|
|
20
|
-
tinybird/tb/__cli__.py,sha256=
|
|
20
|
+
tinybird/tb/__cli__.py,sha256=Ah7n0_bJGUxS5K4MX9UxDaK7Bk7ObK2JPR1kL-xkos0,247
|
|
21
21
|
tinybird/tb/check_pypi.py,sha256=Gp0HkHHDFMSDL6nxKlOY51z7z1Uv-2LRexNTZSHHGmM,552
|
|
22
22
|
tinybird/tb/cli.py,sha256=FdDFEIayjmsZEVsVSSvRiVYn_FHOVg_zWQzchnzfWho,1008
|
|
23
23
|
tinybird/tb/client.py,sha256=pJbdkWMXGAqKseNAvdsRRnl_c7I-DCMB0dWCQnG82nU,54146
|
|
24
24
|
tinybird/tb/config.py,sha256=mhMTGnMB5KcxGoh3dewIr2Jjsa6pHE183gCPAQWyp6o,3973
|
|
25
|
-
tinybird/tb/modules/build.py,sha256=
|
|
25
|
+
tinybird/tb/modules/build.py,sha256=2ijDOQDpb64v7zLRfl38UdJATkehoeunFfi2sozSjog,7609
|
|
26
|
+
tinybird/tb/modules/build_common.py,sha256=3umtAVUir9Xdcu7auY5kpcA_RdQ6UoijwWXHoZEmpeg,12867
|
|
26
27
|
tinybird/tb/modules/cicd.py,sha256=0KLKccha9IP749QvlXBmzdWv1On3mFwMY4DUcJlBxiE,7326
|
|
27
|
-
tinybird/tb/modules/cli.py,sha256=
|
|
28
|
+
tinybird/tb/modules/cli.py,sha256=c1r7Z7NZUIUt9O2c5IRbt4uBKiV8LGOXM4c8kOx_vyY,16344
|
|
28
29
|
tinybird/tb/modules/common.py,sha256=jTTaDDHrZREt--032XhP6GkbfFwC79YJ5aH1Sl7bmbo,81925
|
|
29
30
|
tinybird/tb/modules/config.py,sha256=gK7rgaWTDd4ZKCrNEg_Uemr26EQjqWt6TjyQKujxOws,11462
|
|
30
31
|
tinybird/tb/modules/connection.py,sha256=-MY56NUAai6EMC4-wpi7bT0_nz_SA8QzTmHkV7HB1IQ,17810
|
|
@@ -52,27 +53,29 @@ tinybird/tb/modules/open.py,sha256=LYiuO8Z1I9O_v6pv58qpUCWFD6BT00BdeO21fRa4I4Y,1
|
|
|
52
53
|
tinybird/tb/modules/pipe.py,sha256=xPKtezhnWZ6k_g82r4XpgKslofhuIxb_PvynH4gdUzI,2393
|
|
53
54
|
tinybird/tb/modules/project.py,sha256=pOcvtgsR0ibPi0sNu-6GuAI4WS2DORRALezisjN3xY8,5662
|
|
54
55
|
tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
55
|
-
tinybird/tb/modules/secret.py,sha256=
|
|
56
|
+
tinybird/tb/modules/secret.py,sha256=9BIdh2PZDAbY2wRbf4ZDvkEltygztz1RMxgDmY1D0LI,3521
|
|
57
|
+
tinybird/tb/modules/secret_common.py,sha256=HyCLAI9WniDLwfK6SAb7ZUWorWjtf8j_GghlaTaos_I,1829
|
|
56
58
|
tinybird/tb/modules/shell.py,sha256=_9PaKkkh6ZjkixVtKNAtoCPqXMXMn1aQJM_Xzirn7ZM,13621
|
|
57
59
|
tinybird/tb/modules/sink.py,sha256=dK2s__my0ePIUYrqBzhPSgdWN9rbpvP1G4dT7DJzz80,3865
|
|
58
60
|
tinybird/tb/modules/table.py,sha256=4XrtjM-N0zfNtxVkbvLDQQazno1EPXnxTyo7llivfXk,11035
|
|
59
61
|
tinybird/tb/modules/telemetry.py,sha256=T9gtsQffWqG_4hRBaUJPzOfMkPwz7mH-R6Bn1XRYViA,11482
|
|
60
|
-
tinybird/tb/modules/test.py,sha256=
|
|
62
|
+
tinybird/tb/modules/test.py,sha256=ma6wLSf4PPr1vywSBafU80oR3nJLo3ZSK-_judTiiiE,13145
|
|
61
63
|
tinybird/tb/modules/token.py,sha256=DkXW9FNCLGBisXewfk195jTJ6B1Iz7zq3cEEac48aAs,12731
|
|
62
64
|
tinybird/tb/modules/watch.py,sha256=No0bK1M1_3CYuMaIgylxf7vYFJ72lTJe3brz6xQ-mJo,8819
|
|
63
65
|
tinybird/tb/modules/workspace.py,sha256=Q_8HcxMsNg8QG9aBlwcWS2umrDP5IkTIHqqz3sfmGuc,11341
|
|
64
66
|
tinybird/tb/modules/workspace_members.py,sha256=5JdkJgfuEwbq-t6vxkBhYwgsiTDxF790wsa6Xfif9nk,8608
|
|
65
67
|
tinybird/tb/modules/agent/__init__.py,sha256=i3oe3vDIWWPaicdCM0zs7D7BJ1W0k7th93ooskHAV00,54
|
|
66
|
-
tinybird/tb/modules/agent/agent.py,sha256=
|
|
68
|
+
tinybird/tb/modules/agent/agent.py,sha256=h0va31JtVzwxaQ6kD1V8cwclNhpbzJeJbtKOps5MGIg,11864
|
|
69
|
+
tinybird/tb/modules/agent/animations.py,sha256=z0MNLf8TnUO8qAjgYvth_wc9a9283pNVz1Z4jl15Ggs,2558
|
|
67
70
|
tinybird/tb/modules/agent/banner.py,sha256=KX_e467uiy1gWOZ4ofTZt0GCFGQqHQ_8Ob27XLQqda0,3053
|
|
68
71
|
tinybird/tb/modules/agent/memory.py,sha256=H6SJK--2L5C87B7AJd_jMqsq3sCvFvZwZXmajuT0GBE,1171
|
|
69
72
|
tinybird/tb/modules/agent/models.py,sha256=mf8dRCdof6uEFZWh5xQ_D_FStk7eDds7qWRNSbDklUM,589
|
|
70
|
-
tinybird/tb/modules/agent/prompts.py,sha256=
|
|
71
|
-
tinybird/tb/modules/agent/utils.py,sha256=
|
|
73
|
+
tinybird/tb/modules/agent/prompts.py,sha256=rAbcqkrw7BKVuV2sNRGpbLXxfo95suKWRLPwgNx7fdM,5784
|
|
74
|
+
tinybird/tb/modules/agent/utils.py,sha256=SSfAmPTO-pv4UtRrsAbERHjjeqZ3Mkx6Y_eRD5QXr9M,13154
|
|
72
75
|
tinybird/tb/modules/agent/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
tinybird/tb/modules/agent/tools/create_datafile.py,sha256=
|
|
76
|
+
tinybird/tb/modules/agent/tools/create_datafile.py,sha256=e7xMaPziVw5JPaQ8rmeEnNz6SCkR3MP1ZBvvXb6QOwE,2314
|
|
74
77
|
tinybird/tb/modules/agent/tools/explore.py,sha256=ihALc_kBcsjrKT3hZyicqyIowB0g_K3AtNNi-5uz9-8,412
|
|
75
|
-
tinybird/tb/modules/agent/tools/plan.py,sha256=
|
|
78
|
+
tinybird/tb/modules/agent/tools/plan.py,sha256=CMSGrqjdVyhsJ0U1M5B2eRFLZXE7HqJ4K8tl1Ile0f0,1324
|
|
76
79
|
tinybird/tb/modules/agent/tools/preview_datafile.py,sha256=e9q5fR0afApcrntzFrnuHmd10ex7MG_GM6T0Pwc9bRI,850
|
|
77
80
|
tinybird/tb/modules/datafile/build.py,sha256=NFKBrusFLU0WJNCXePAFWiEDuTaXpwc0lHlOQWEJ43s,51117
|
|
78
81
|
tinybird/tb/modules/datafile/build_common.py,sha256=2yNdxe49IMA9wNvl25NemY2Iaz8L66snjOdT64dm1is,4511
|
|
@@ -94,8 +97,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
|
|
|
94
97
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
95
98
|
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
96
99
|
tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
|
|
97
|
-
tinybird-0.0.1.
|
|
98
|
-
tinybird-0.0.1.
|
|
99
|
-
tinybird-0.0.1.
|
|
100
|
-
tinybird-0.0.1.
|
|
101
|
-
tinybird-0.0.1.
|
|
100
|
+
tinybird-0.0.1.dev244.dist-info/METADATA,sha256=6it3lKYPvmXa3ivNyaVevIU9DDt3vmAaDcHHwHNv6FI,1733
|
|
101
|
+
tinybird-0.0.1.dev244.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
102
|
+
tinybird-0.0.1.dev244.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
|
|
103
|
+
tinybird-0.0.1.dev244.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
|
|
104
|
+
tinybird-0.0.1.dev244.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|