tinybird 0.0.1.dev15__py3-none-any.whl → 0.0.1.dev16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tinybird might be problematic. Click here for more details.
- tinybird/client.py +3 -1
- tinybird/feedback_manager.py +12 -1
- tinybird/{tb/modules/prompts.py → prompts.py} +26 -64
- tinybird/tb/cli.py +2 -0
- tinybird/tb/modules/build.py +4 -129
- tinybird/tb/modules/build_shell.py +133 -0
- tinybird/tb/modules/cli.py +8 -1
- tinybird/tb/modules/create.py +241 -2
- tinybird/tb/modules/llm.py +35 -3
- tinybird/tb/modules/local.py +2 -46
- tinybird/tb/modules/local_common.py +54 -0
- tinybird/tb/modules/mock.py +1 -1
- tinybird/tb/modules/test.py +23 -12
- {tinybird-0.0.1.dev15.dist-info → tinybird-0.0.1.dev16.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev15.dist-info → tinybird-0.0.1.dev16.dist-info}/RECORD +18 -16
- {tinybird-0.0.1.dev15.dist-info → tinybird-0.0.1.dev16.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev15.dist-info → tinybird-0.0.1.dev16.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev15.dist-info → tinybird-0.0.1.dev16.dist-info}/top_level.txt +0 -0
tinybird/client.py
CHANGED
|
@@ -188,7 +188,9 @@ class TinyB:
|
|
|
188
188
|
if response.status_code == 599:
|
|
189
189
|
raise TimeoutException("timeout")
|
|
190
190
|
if "Content-Type" in response.headers and (
|
|
191
|
-
response.headers["Content-Type"] == "text/plain"
|
|
191
|
+
response.headers["Content-Type"] == "text/plain"
|
|
192
|
+
or "text/csv" in response.headers["Content-Type"]
|
|
193
|
+
or "ndjson" in response.headers["Content-Type"]
|
|
192
194
|
):
|
|
193
195
|
return response.content.decode("utf-8")
|
|
194
196
|
if response.status_code >= 400 and response.status_code not in [400, 403, 404, 409, 429]:
|
tinybird/feedback_manager.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections import namedtuple
|
|
2
2
|
from typing import Any, Callable
|
|
3
|
+
import os
|
|
3
4
|
|
|
4
5
|
FeedbackMessage = namedtuple("FeedbackMessage", "message")
|
|
5
6
|
|
|
@@ -26,6 +27,16 @@ def print_message(message: str, color: str = bcolors.ENDC) -> Callable[..., str]
|
|
|
26
27
|
def error_message(message: str) -> Callable[..., str]:
|
|
27
28
|
return print_message(f"\n** {message}", bcolors.FAIL)
|
|
28
29
|
|
|
30
|
+
def error_exception(message: Exception) -> Callable[..., str]:
|
|
31
|
+
def formatter(**kwargs: Any) -> str:
|
|
32
|
+
color = bcolors.FAIL
|
|
33
|
+
if isinstance(kwargs.get('error', ''), Exception) and os.environ.get('TB_DEBUG', '') != '':
|
|
34
|
+
import traceback
|
|
35
|
+
stack_trace = f"Traceback:\n{''.join(traceback.format_tb(kwargs['error'].__traceback__))}"
|
|
36
|
+
return f"{color}{message.format(**kwargs)}{stack_trace}{bcolors.ENDC}"
|
|
37
|
+
return f"{color}{message.format(**kwargs)}{bcolors.ENDC}"
|
|
38
|
+
return formatter
|
|
39
|
+
|
|
29
40
|
|
|
30
41
|
def simple_error_message(message: str) -> Callable[..., str]:
|
|
31
42
|
return print_message(f"{message}", bcolors.FAIL)
|
|
@@ -56,7 +67,7 @@ def gray_message(message: str) -> Callable[..., str]:
|
|
|
56
67
|
|
|
57
68
|
|
|
58
69
|
class FeedbackManager:
|
|
59
|
-
error_exception =
|
|
70
|
+
error_exception = error_exception("{error}")
|
|
60
71
|
simple_error_exception = simple_error_message("{error}")
|
|
61
72
|
error_exception_trace = error_message("{error}\n** Trace:\n{trace}")
|
|
62
73
|
error_notoken = error_message(
|
|
@@ -76,68 +76,7 @@ SQL >
|
|
|
76
76
|
</instructions>
|
|
77
77
|
"""
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
You are a Tinybird expert. You will be given a pipe endpoint containing different nodes with SQL and Tinybird templating syntax. You will generate URLs to test it with different parameters combinations.
|
|
81
|
-
|
|
82
|
-
<test>
|
|
83
|
-
<test_1>:
|
|
84
|
-
name: <test_name_1>
|
|
85
|
-
description: <description_1>
|
|
86
|
-
parameters: <url_encoded_parameters_1>
|
|
87
|
-
<test_2>:
|
|
88
|
-
name: <test_name_2>
|
|
89
|
-
description: <description_2>
|
|
90
|
-
parameters: <url_encoded_parameters_2>
|
|
91
|
-
</test>
|
|
92
|
-
<instructions>
|
|
93
|
-
- The test name must be unique.
|
|
94
|
-
- The test command must be a valid Tinybird command that can be run in the terminal.
|
|
95
|
-
- The test command can have as many parameters as are needed to test the pipe.
|
|
96
|
-
- The parameter within Tinybird templating syntax looks like this one {{String(my_param_name, default_value)}}.
|
|
97
|
-
- If there are no parameters in the , you can omit parametrs and generate a single test command.
|
|
98
|
-
- Extra context: {context}
|
|
99
|
-
</instructions>
|
|
100
|
-
"""
|
|
101
|
-
|
|
102
|
-
sample_data_prompt = """Your role is to generate sample data for a given data source. A data source is a table in Tinybird. You will be given a schema for the data source, and you will need to generate a set of rows that fit that schema.
|
|
103
|
-
|
|
104
|
-
The schema will be provided to you at the end of this prompt in the following format:
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
SCHEMA >
|
|
108
|
-
`span_id` String `json:$.span_id`,
|
|
109
|
-
`operation_id` String `json:$.operation_name`,
|
|
110
|
-
`duration` Nullable(Float32) `json:$.duration`,
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
In the example above, `span_id` are column names, String and Float32 are column types, and `json:$.duration` is a JSON path expression that points to the duration value in the JSON data. Use this information to generate valid data.
|
|
114
|
-
|
|
115
|
-
You will generate {row_count} rows of data in JSON format, newlines separated. Your reponse will only contain the {row_count} rows of data, without any other text. Do not format the data in any way, and do not place it inside any markers for a code block of any kind. Most importantly, do not use backticks on the first or last line or anywhere else in the response. This is extremely important, ensure that your response doesn't include any backticks.
|
|
116
|
-
|
|
117
|
-
Remember that for a JSON to be valid, there can't be a trailing comma after the last value in an object.
|
|
118
|
-
|
|
119
|
-
Make sure to include all columns in the schema, and use valid JSON. You may leave some columns empty only if the column type allows it (e.g. Nullable(String)). Use realistic values for the data, and make sure to include a variety of values in each column.
|
|
120
|
-
|
|
121
|
-
For columns that represent timestamps, use values that are close to the current date and time. The current date and time is {current_datetime}"""
|
|
122
|
-
|
|
123
|
-
sample_data_with_errors_prompt = """Your role is to generate incorrect sample data for a given data source. A data source is a table in Tinybird. You will be given a schema for the data source, and you will need to generate a set of rows that mostly fit that schema, but with some errors.
|
|
124
|
-
|
|
125
|
-
The schema will be provided to you at the end of this prompt in the following format:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
SCHEMA >
|
|
129
|
-
`span_id` String `json:$.span_id`,
|
|
130
|
-
`operation_id` String `json:$.operation_name`,
|
|
131
|
-
`duration` Nullable(Float32) `json:$.duration`,
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
In the example above, `span_id` are column names, String and Float32 are column types, and `json:$.duration` is a JSON path expression that points to the duration value in the JSON data. Use this information to generate valid data.
|
|
135
|
-
|
|
136
|
-
You will generate {row_count} rows of data in JSON format, newlines separated. Your reponse will be a single string containing all {row_count} rows, without any other text. Make sure to include all columns in the schema, and use valid JSON. Use realistic values for the data, and make sure to include a variety of values in each column. In every set of {row_count} rows, there should be at least one row that has an error. The error could be in any column, and it could be a type error (e.g. a string where a number is expected), or it could be an error in the structure of the JSON (e.g. a value in the wrong place in the document).
|
|
137
|
-
|
|
138
|
-
For columns that represent timestamps, use values that are close to the current date and time. The current date and time is {current_datetime}"""
|
|
139
|
-
|
|
140
|
-
sample_data_sql_prompt = """
|
|
79
|
+
generate_sql_mock_data_prompt = """
|
|
141
80
|
Given the schema for a Tinybird datasource, return a can you create a clickhouse sql query to generate some random data that matches that schema.
|
|
142
81
|
|
|
143
82
|
Response format MUST be just a valid clickhouse sql query.
|
|
@@ -164,13 +103,13 @@ SELECT
|
|
|
164
103
|
rand() % 50 AS pvp_kills, -- Random PvP kills between 0 and 49
|
|
165
104
|
rand() % 200 AS quest_completions, -- Random quest completions between 0 and 199
|
|
166
105
|
now() - rand() % 86400 AS timestamp -- Random timestamp within the last day
|
|
167
|
-
FROM numbers({
|
|
106
|
+
FROM numbers({rows})
|
|
168
107
|
|
|
169
108
|
# Instructions:
|
|
170
109
|
|
|
171
110
|
- The query MUST return a random sample of data that matches the schema.
|
|
172
111
|
- The query MUST return a valid clickhouse sql query.
|
|
173
|
-
- The query MUST return a sample of EXACTLY {
|
|
112
|
+
- The query MUST return a sample of EXACTLY {rows} rows.
|
|
174
113
|
- The query MUST be valid for clickhouse and Tinybird.
|
|
175
114
|
- Return JUST the sql query, without any other text or symbols.
|
|
176
115
|
- Do NOT include ```clickhouse or ```sql or any other wrapping text.
|
|
@@ -182,3 +121,26 @@ FROM numbers({row_count})
|
|
|
182
121
|
{context}
|
|
183
122
|
|
|
184
123
|
"""
|
|
124
|
+
|
|
125
|
+
create_test_calls_prompt = """
|
|
126
|
+
You are a Tinybird expert. You will be given a pipe endpoint containing different nodes with SQL and Tinybird templating syntax. You will generate URLs to test it with different parameters combinations.
|
|
127
|
+
|
|
128
|
+
<test>
|
|
129
|
+
<test_1>:
|
|
130
|
+
name: <test_name_1>
|
|
131
|
+
description: <description_1>
|
|
132
|
+
parameters: <url_encoded_parameters_1>
|
|
133
|
+
<test_2>:
|
|
134
|
+
name: <test_name_2>
|
|
135
|
+
description: <description_2>
|
|
136
|
+
parameters: <url_encoded_parameters_2>
|
|
137
|
+
</test>
|
|
138
|
+
<instructions>
|
|
139
|
+
- The test name must be unique.
|
|
140
|
+
- The test command must be a valid Tinybird command that can be run in the terminal.
|
|
141
|
+
- The test command can have as many parameters as are needed to test the pipe.
|
|
142
|
+
- The parameter within Tinybird templating syntax looks like this one {{String(my_param_name, default_value)}}.
|
|
143
|
+
- If there are no parameters in the , you can omit parametrs and generate a single test command.
|
|
144
|
+
- Extra context: {context}
|
|
145
|
+
</instructions>
|
|
146
|
+
"""
|
tinybird/tb/cli.py
CHANGED
|
@@ -14,10 +14,12 @@ import tinybird.tb.modules.create
|
|
|
14
14
|
import tinybird.tb.modules.datasource
|
|
15
15
|
import tinybird.tb.modules.fmt
|
|
16
16
|
import tinybird.tb.modules.job
|
|
17
|
+
import tinybird.tb.modules.local
|
|
17
18
|
import tinybird.tb.modules.login
|
|
18
19
|
import tinybird.tb.modules.mock
|
|
19
20
|
import tinybird.tb.modules.pipe
|
|
20
21
|
import tinybird.tb.modules.tag
|
|
22
|
+
import tinybird.tb.modules.test
|
|
21
23
|
import tinybird.tb.modules.token
|
|
22
24
|
import tinybird.tb.modules.workspace
|
|
23
25
|
import tinybird.tb.modules.workspace_members
|
tinybird/tb/modules/build.py
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import cmd
|
|
3
2
|
import os
|
|
4
|
-
import random
|
|
5
|
-
import subprocess
|
|
6
|
-
import sys
|
|
7
3
|
import threading
|
|
8
4
|
import time
|
|
9
5
|
from pathlib import Path
|
|
10
6
|
from typing import Any, Awaitable, Callable, List, Union
|
|
11
7
|
|
|
12
8
|
import click
|
|
13
|
-
import humanfriendly
|
|
14
9
|
from click import Context
|
|
15
10
|
from watchdog.events import FileSystemEventHandler
|
|
16
11
|
from watchdog.observers import Observer
|
|
17
12
|
|
|
18
13
|
import tinybird.context as context
|
|
19
|
-
from tinybird.
|
|
20
|
-
from tinybird.client import TinyB, AuthNoTokenException
|
|
21
|
-
from tinybird.tb.modules.common import CLIException
|
|
14
|
+
from tinybird.client import TinyB
|
|
22
15
|
from tinybird.config import FeatureFlags
|
|
23
|
-
from tinybird.feedback_manager import FeedbackManager
|
|
16
|
+
from tinybird.feedback_manager import FeedbackManager
|
|
17
|
+
from tinybird.tb.modules.build_shell import BuildShell, print_table_formatted
|
|
24
18
|
from tinybird.tb.modules.cli import cli
|
|
25
19
|
from tinybird.tb.modules.common import coro, push_data
|
|
26
20
|
from tinybird.tb.modules.datafile.build import folder_build
|
|
@@ -29,48 +23,7 @@ from tinybird.tb.modules.datafile.exceptions import ParseException
|
|
|
29
23
|
from tinybird.tb.modules.datafile.fixture import build_fixture_name, get_fixture_dir
|
|
30
24
|
from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
|
|
31
25
|
from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
|
|
32
|
-
from tinybird.tb.modules.
|
|
33
|
-
from tinybird.tb.modules.table import format_table
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class BuildShell(cmd.Cmd):
|
|
37
|
-
prompt = "\n\001\033[1;32m\002TB > \001\033[0m\002"
|
|
38
|
-
|
|
39
|
-
def __init__(self, folder: str, client: TinyB):
|
|
40
|
-
super().__init__()
|
|
41
|
-
self.folder = folder
|
|
42
|
-
self.client = client
|
|
43
|
-
def do_exit(self, arg):
|
|
44
|
-
sys.exit(0)
|
|
45
|
-
|
|
46
|
-
def do_quit(self, arg):
|
|
47
|
-
sys.exit(0)
|
|
48
|
-
|
|
49
|
-
def default(self, argline):
|
|
50
|
-
click.echo("")
|
|
51
|
-
if argline.startswith("tb build"):
|
|
52
|
-
click.echo(FeedbackManager.error(message="Build command is already running"))
|
|
53
|
-
else:
|
|
54
|
-
arg_stripped = argline.strip().lower()
|
|
55
|
-
if not arg_stripped:
|
|
56
|
-
return
|
|
57
|
-
if arg_stripped.startswith("tb"):
|
|
58
|
-
extra_args = f" --folder {self.folder}" if arg_stripped.startswith("tb mock") else ""
|
|
59
|
-
subprocess.run(arg_stripped + extra_args, shell=True, text=True)
|
|
60
|
-
elif arg_stripped.startswith("with") or arg_stripped.startswith("select"):
|
|
61
|
-
try:
|
|
62
|
-
run_sql(self.client, argline)
|
|
63
|
-
except Exception as e:
|
|
64
|
-
click.echo(FeedbackManager.error(message=str(e)))
|
|
65
|
-
|
|
66
|
-
elif arg_stripped.startswith("mock "):
|
|
67
|
-
subprocess.run(f"tb {arg_stripped} --folder {self.folder}", shell=True, text=True)
|
|
68
|
-
else:
|
|
69
|
-
click.echo(FeedbackManager.error(message="Invalid command"))
|
|
70
|
-
|
|
71
|
-
def reprint_prompt(self):
|
|
72
|
-
self.stdout.write(self.prompt)
|
|
73
|
-
self.stdout.flush()
|
|
26
|
+
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
74
27
|
|
|
75
28
|
|
|
76
29
|
class FileChangeHandler(FileSystemEventHandler):
|
|
@@ -131,44 +84,6 @@ def watch_files(
|
|
|
131
84
|
observer.join()
|
|
132
85
|
|
|
133
86
|
|
|
134
|
-
|
|
135
|
-
def run_sql(client: TinyB, query, rows_limit=20):
|
|
136
|
-
try:
|
|
137
|
-
q = query.strip()
|
|
138
|
-
if q.startswith("insert"):
|
|
139
|
-
click.echo(FeedbackManager.info_append_data())
|
|
140
|
-
raise CLIException(FeedbackManager.error_invalid_query())
|
|
141
|
-
if q.startswith("delete"):
|
|
142
|
-
raise CLIException(FeedbackManager.error_invalid_query())
|
|
143
|
-
|
|
144
|
-
# fuck my life
|
|
145
|
-
def run_query_in_thread():
|
|
146
|
-
loop = asyncio.new_event_loop()
|
|
147
|
-
asyncio.set_event_loop(loop)
|
|
148
|
-
try:
|
|
149
|
-
return loop.run_until_complete(client.query(f"SELECT * FROM ({query}) LIMIT {rows_limit} FORMAT JSON"))
|
|
150
|
-
finally:
|
|
151
|
-
loop.close()
|
|
152
|
-
|
|
153
|
-
# Run the query in a separate thread
|
|
154
|
-
import concurrent.futures
|
|
155
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
156
|
-
res = executor.submit(run_query_in_thread).result()
|
|
157
|
-
|
|
158
|
-
except AuthNoTokenException:
|
|
159
|
-
raise
|
|
160
|
-
except Exception as e:
|
|
161
|
-
raise CLIException(FeedbackManager.error_exception(error=str(e)))
|
|
162
|
-
|
|
163
|
-
if isinstance(res, dict) and "error" in res:
|
|
164
|
-
raise CLIException(FeedbackManager.error_exception(error=res["error"]))
|
|
165
|
-
|
|
166
|
-
if isinstance(res, dict) and "data" in res and res["data"]:
|
|
167
|
-
print_table_formatted(res, 'QUERY')
|
|
168
|
-
else:
|
|
169
|
-
click.echo(FeedbackManager.info_no_rows())
|
|
170
|
-
|
|
171
|
-
|
|
172
87
|
@cli.command()
|
|
173
88
|
@click.option(
|
|
174
89
|
"--folder",
|
|
@@ -286,46 +201,6 @@ async def build_and_print_resource(tb_client: TinyB, filename: str):
|
|
|
286
201
|
res = await tb_client.query(f"SELECT * FROM {name} FORMAT JSON", pipeline=pipeline)
|
|
287
202
|
print_table_formatted(res, name)
|
|
288
203
|
|
|
289
|
-
def print_table_formatted(res: dict, name: str):
|
|
290
|
-
rebuild_colors = [bcolors.FAIL, bcolors.OKBLUE, bcolors.WARNING, bcolors.OKGREEN, bcolors.HEADER]
|
|
291
|
-
rebuild_index = random.randint(0, len(rebuild_colors) - 1)
|
|
292
|
-
rebuild_color = rebuild_colors[rebuild_index % len(rebuild_colors)]
|
|
293
|
-
data = []
|
|
294
|
-
limit = 5
|
|
295
|
-
for d in res["data"][:5]:
|
|
296
|
-
data.append(d.values())
|
|
297
|
-
meta = res["meta"]
|
|
298
|
-
row_count = res.get("rows", 0)
|
|
299
|
-
stats = res.get("statistics", {})
|
|
300
|
-
elapsed = stats.get("elapsed", 0)
|
|
301
|
-
cols = len(meta)
|
|
302
|
-
try:
|
|
303
|
-
|
|
304
|
-
def print_message(message: str, color=bcolors.CGREY):
|
|
305
|
-
return f"{color}{message}{bcolors.ENDC}"
|
|
306
|
-
|
|
307
|
-
table = format_table(data, meta)
|
|
308
|
-
colored_char = print_message("│", rebuild_color)
|
|
309
|
-
table_with_marker = "\n".join(f"{colored_char} {line}" for line in table.split("\n"))
|
|
310
|
-
click.echo(f"\n{colored_char} {print_message('⚡', rebuild_color)} Running {name}")
|
|
311
|
-
click.echo(colored_char)
|
|
312
|
-
click.echo(table_with_marker)
|
|
313
|
-
click.echo(colored_char)
|
|
314
|
-
rows_read = humanfriendly.format_number(stats.get("rows_read", 0))
|
|
315
|
-
bytes_read = humanfriendly.format_size(stats.get("bytes_read", 0))
|
|
316
|
-
elapsed = humanfriendly.format_timespan(elapsed) if elapsed >= 1 else f"{elapsed * 1000:.2f}ms"
|
|
317
|
-
stats_message = f"» {bytes_read} ({rows_read} rows x {cols} cols) in {elapsed}"
|
|
318
|
-
rows_message = f"» Showing {limit} first rows" if row_count > limit else "» Showing all rows"
|
|
319
|
-
click.echo(f"{colored_char} {print_message(stats_message, bcolors.OKGREEN)}")
|
|
320
|
-
click.echo(f"{colored_char} {print_message(rows_message, bcolors.CGREY)}")
|
|
321
|
-
except ValueError as exc:
|
|
322
|
-
if str(exc) == "max() arg is an empty sequence":
|
|
323
|
-
click.echo("------------")
|
|
324
|
-
click.echo("Empty")
|
|
325
|
-
click.echo("------------")
|
|
326
|
-
else:
|
|
327
|
-
raise exc
|
|
328
|
-
|
|
329
204
|
|
|
330
205
|
async def append_datasource(
|
|
331
206
|
ctx: click.Context,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import cmd
|
|
3
|
+
import random
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import humanfriendly
|
|
9
|
+
|
|
10
|
+
from tinybird.client import TinyB
|
|
11
|
+
from tinybird.feedback_manager import FeedbackManager, bcolors
|
|
12
|
+
from tinybird.tb.modules.exceptions import CLIException
|
|
13
|
+
from tinybird.tb.modules.table import format_table
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BuildShell(cmd.Cmd):
|
|
17
|
+
prompt = "\n\001\033[1;32m\002TB > \001\033[0m\002"
|
|
18
|
+
|
|
19
|
+
def __init__(self, folder: str, client: TinyB):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.folder = folder
|
|
22
|
+
self.client = client
|
|
23
|
+
|
|
24
|
+
def do_exit(self, arg):
|
|
25
|
+
sys.exit(0)
|
|
26
|
+
|
|
27
|
+
def do_quit(self, arg):
|
|
28
|
+
sys.exit(0)
|
|
29
|
+
|
|
30
|
+
def default(self, argline):
|
|
31
|
+
click.echo("")
|
|
32
|
+
if argline.startswith("tb build"):
|
|
33
|
+
click.echo(FeedbackManager.error(message="Build command is already running"))
|
|
34
|
+
else:
|
|
35
|
+
arg_stripped = argline.strip().lower()
|
|
36
|
+
if not arg_stripped:
|
|
37
|
+
return
|
|
38
|
+
if arg_stripped.startswith("tb"):
|
|
39
|
+
arg_stripped = arg_stripped.replace("tb", "tb --local")
|
|
40
|
+
extra_args = f" --folder {self.folder}" if arg_stripped.startswith("tb mock") else ""
|
|
41
|
+
subprocess.run(arg_stripped + extra_args, shell=True, text=True)
|
|
42
|
+
elif arg_stripped.startswith("with") or arg_stripped.startswith("select"):
|
|
43
|
+
try:
|
|
44
|
+
self.run_sql(self.client, argline)
|
|
45
|
+
except Exception as e:
|
|
46
|
+
click.echo(FeedbackManager.error(message=str(e)))
|
|
47
|
+
|
|
48
|
+
elif arg_stripped.startswith("mock "):
|
|
49
|
+
subprocess.run(f"tb {arg_stripped} --folder {self.folder}", shell=True, text=True)
|
|
50
|
+
else:
|
|
51
|
+
click.echo(FeedbackManager.error(message="Invalid command"))
|
|
52
|
+
|
|
53
|
+
def reprint_prompt(self):
|
|
54
|
+
self.stdout.write(self.prompt)
|
|
55
|
+
self.stdout.flush()
|
|
56
|
+
|
|
57
|
+
def run_sql(self, query, rows_limit=20):
|
|
58
|
+
try:
|
|
59
|
+
q = query.strip()
|
|
60
|
+
if q.startswith("insert"):
|
|
61
|
+
click.echo(FeedbackManager.info_append_data())
|
|
62
|
+
raise CLIException(FeedbackManager.error_invalid_query())
|
|
63
|
+
if q.startswith("delete"):
|
|
64
|
+
raise CLIException(FeedbackManager.error_invalid_query())
|
|
65
|
+
|
|
66
|
+
# fuck my life
|
|
67
|
+
def run_query_in_thread():
|
|
68
|
+
loop = asyncio.new_event_loop()
|
|
69
|
+
asyncio.set_event_loop(loop)
|
|
70
|
+
try:
|
|
71
|
+
return loop.run_until_complete(
|
|
72
|
+
self.client.query(f"SELECT * FROM ({query}) LIMIT {rows_limit} FORMAT JSON")
|
|
73
|
+
)
|
|
74
|
+
finally:
|
|
75
|
+
loop.close()
|
|
76
|
+
|
|
77
|
+
# Run the query in a separate thread
|
|
78
|
+
import concurrent.futures
|
|
79
|
+
|
|
80
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
81
|
+
res = executor.submit(run_query_in_thread).result()
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise CLIException(FeedbackManager.error_exception(error=str(e)))
|
|
85
|
+
|
|
86
|
+
if isinstance(res, dict) and "error" in res:
|
|
87
|
+
raise CLIException(FeedbackManager.error_exception(error=res["error"]))
|
|
88
|
+
|
|
89
|
+
if isinstance(res, dict) and "data" in res and res["data"]:
|
|
90
|
+
print_table_formatted(res, "QUERY")
|
|
91
|
+
else:
|
|
92
|
+
click.echo(FeedbackManager.info_no_rows())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def print_table_formatted(res: dict, name: str):
|
|
96
|
+
rebuild_colors = [bcolors.FAIL, bcolors.OKBLUE, bcolors.WARNING, bcolors.OKGREEN, bcolors.HEADER]
|
|
97
|
+
rebuild_index = random.randint(0, len(rebuild_colors) - 1)
|
|
98
|
+
rebuild_color = rebuild_colors[rebuild_index % len(rebuild_colors)]
|
|
99
|
+
data = []
|
|
100
|
+
limit = 5
|
|
101
|
+
for d in res["data"][:5]:
|
|
102
|
+
data.append(d.values())
|
|
103
|
+
meta = res["meta"]
|
|
104
|
+
row_count = res.get("rows", 0)
|
|
105
|
+
stats = res.get("statistics", {})
|
|
106
|
+
elapsed = stats.get("elapsed", 0)
|
|
107
|
+
cols = len(meta)
|
|
108
|
+
try:
|
|
109
|
+
|
|
110
|
+
def print_message(message: str, color=bcolors.CGREY):
|
|
111
|
+
return f"{color}{message}{bcolors.ENDC}"
|
|
112
|
+
|
|
113
|
+
table = format_table(data, meta)
|
|
114
|
+
colored_char = print_message("│", rebuild_color)
|
|
115
|
+
table_with_marker = "\n".join(f"{colored_char} {line}" for line in table.split("\n"))
|
|
116
|
+
click.echo(f"\n{colored_char} {print_message('⚡', rebuild_color)} Running {name}")
|
|
117
|
+
click.echo(colored_char)
|
|
118
|
+
click.echo(table_with_marker)
|
|
119
|
+
click.echo(colored_char)
|
|
120
|
+
rows_read = humanfriendly.format_number(stats.get("rows_read", 0))
|
|
121
|
+
bytes_read = humanfriendly.format_size(stats.get("bytes_read", 0))
|
|
122
|
+
elapsed = humanfriendly.format_timespan(elapsed) if elapsed >= 1 else f"{elapsed * 1000:.2f}ms"
|
|
123
|
+
stats_message = f"» {bytes_read} ({rows_read} rows x {cols} cols) in {elapsed}"
|
|
124
|
+
rows_message = f"» Showing {limit} first rows" if row_count > limit else "» Showing all rows"
|
|
125
|
+
click.echo(f"{colored_char} {print_message(stats_message, bcolors.OKGREEN)}")
|
|
126
|
+
click.echo(f"{colored_char} {print_message(rows_message, bcolors.CGREY)}")
|
|
127
|
+
except ValueError as exc:
|
|
128
|
+
if str(exc) == "max() arg is an empty sequence":
|
|
129
|
+
click.echo("------------")
|
|
130
|
+
click.echo("Empty")
|
|
131
|
+
click.echo("------------")
|
|
132
|
+
else:
|
|
133
|
+
raise exc
|
tinybird/tb/modules/cli.py
CHANGED
|
@@ -63,6 +63,7 @@ from tinybird.tb.modules.datafile.exceptions import (
|
|
|
63
63
|
from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
|
|
64
64
|
from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
|
|
65
65
|
from tinybird.tb.modules.datafile.pull import folder_pull
|
|
66
|
+
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
66
67
|
from tinybird.tb.modules.telemetry import add_telemetry_event
|
|
67
68
|
|
|
68
69
|
__old_click_echo = click.echo
|
|
@@ -108,6 +109,7 @@ DEFAULT_PATTERNS: List[Tuple[str, Union[str, Callable[[str], str]]]] = [
|
|
|
108
109
|
"--with-headers", help="Flag to enable connector to export with headers", is_flag=True, default=False, hidden=True
|
|
109
110
|
)
|
|
110
111
|
@click.option("--show-tokens", is_flag=True, default=False, help="Enable the output of tokens")
|
|
112
|
+
@click.option("--local", is_flag=True, default=False, help="Run in local mode")
|
|
111
113
|
@click.version_option(version=VERSION)
|
|
112
114
|
@click.pass_context
|
|
113
115
|
@coro
|
|
@@ -131,6 +133,7 @@ async def cli(
|
|
|
131
133
|
sf_stage,
|
|
132
134
|
with_headers: bool,
|
|
133
135
|
show_tokens: bool,
|
|
136
|
+
local: bool,
|
|
134
137
|
) -> None:
|
|
135
138
|
"""
|
|
136
139
|
Use `OBFUSCATE_REGEX_PATTERN` and `OBFUSCATE_PATTERN_SEPARATOR` environment variables to define a regex pattern and a separator (in case of a single string with multiple regex) to obfuscate secrets in the CLI output.
|
|
@@ -228,7 +231,11 @@ async def cli(
|
|
|
228
231
|
|
|
229
232
|
logging.debug("debug enabled")
|
|
230
233
|
|
|
231
|
-
ctx.ensure_object(dict)["client"] =
|
|
234
|
+
ctx.ensure_object(dict)["client"] = (
|
|
235
|
+
await get_tinybird_local_client()
|
|
236
|
+
if local
|
|
237
|
+
else _get_tb_client(config.get("token", None), config["host"], semver)
|
|
238
|
+
)
|
|
232
239
|
|
|
233
240
|
for connector in SUPPORTED_CONNECTORS:
|
|
234
241
|
load_connector_config(ctx, connector, debug, check_uninstalled=True)
|
tinybird/tb/modules/create.py
CHANGED
|
@@ -19,6 +19,11 @@ from tinybird.tb.modules.llm import LLM
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@cli.command()
|
|
22
|
+
@click.option(
|
|
23
|
+
"--demo",
|
|
24
|
+
is_flag=True,
|
|
25
|
+
help="Demo data and files to get started",
|
|
26
|
+
)
|
|
22
27
|
@click.option(
|
|
23
28
|
"--data",
|
|
24
29
|
type=click.Path(exists=True),
|
|
@@ -42,6 +47,7 @@ from tinybird.tb.modules.llm import LLM
|
|
|
42
47
|
@coro
|
|
43
48
|
async def create(
|
|
44
49
|
ctx: Context,
|
|
50
|
+
demo: bool,
|
|
45
51
|
data: Optional[str],
|
|
46
52
|
prompt: Optional[str],
|
|
47
53
|
folder: Optional[str],
|
|
@@ -63,7 +69,240 @@ async def create(
|
|
|
63
69
|
|
|
64
70
|
click.echo(FeedbackManager.gray(message="Building fixtures..."))
|
|
65
71
|
|
|
66
|
-
if
|
|
72
|
+
if demo:
|
|
73
|
+
# Users datasource
|
|
74
|
+
ds_name = "users"
|
|
75
|
+
datasource_path = Path(folder) / "datasources" / f"{ds_name}.datasource"
|
|
76
|
+
datasource_content = """SCHEMA >
|
|
77
|
+
`id` String `json:$.id`,
|
|
78
|
+
`organization_id` String `json:$.organization_id`,
|
|
79
|
+
`tier` String `json:$.tier`
|
|
80
|
+
|
|
81
|
+
ENGINE "MergeTree"
|
|
82
|
+
ENGINE_SORTING_KEY "id, organization_id"
|
|
83
|
+
"""
|
|
84
|
+
datasource_path.write_text(datasource_content)
|
|
85
|
+
click.echo(FeedbackManager.info(message=f"✓ /datasources/{ds_name}.datasource"))
|
|
86
|
+
|
|
87
|
+
# Events datasource
|
|
88
|
+
ds_name = "events"
|
|
89
|
+
datasource_path = Path(folder) / "datasources" / f"{ds_name}.datasource"
|
|
90
|
+
datasource_content = """SCHEMA >
|
|
91
|
+
`request_id` String `json:$.request_id`,
|
|
92
|
+
`timestamp` DateTime64(3) `json:$.timestamp`,
|
|
93
|
+
`user_id` String `json:$.user_id`,
|
|
94
|
+
`type` String `json:$.request.type`,
|
|
95
|
+
`endpoint` String `json:$.request.endpoint`,
|
|
96
|
+
`model` String `json:$.request.model`,
|
|
97
|
+
`request_options_temperature` String `json:$.request.options.temperature`,
|
|
98
|
+
`request_options_max_tokens` Int32 `json:$.request.options.max_tokens`,
|
|
99
|
+
`request_options_stream` Boolean `json:$.request.options.stream`,
|
|
100
|
+
`usage_prompt_tokens` Int32 `json:$.usage.prompt_tokens`,
|
|
101
|
+
`usage_completion_tokens` Int32 `json:$.usage.completion_tokens`,
|
|
102
|
+
`usage_total_tokens` Int32 `json:$.usage.total_tokens`
|
|
103
|
+
|
|
104
|
+
ENGINE "MergeTree"
|
|
105
|
+
ENGINE_SORTING_KEY "user_id, timestamp"
|
|
106
|
+
"""
|
|
107
|
+
datasource_path.write_text(datasource_content)
|
|
108
|
+
click.echo(FeedbackManager.info(message=f"✓ /datasources/{ds_name}.datasource"))
|
|
109
|
+
|
|
110
|
+
ds_name = "users"
|
|
111
|
+
ds_content = """
|
|
112
|
+
{"id":"usr_1","organization_id":"org_202","tier":"pro"}
|
|
113
|
+
{"id":"usr_2","organization_id":"org_49","tier":"enterprise"}
|
|
114
|
+
{"id":"usr_3","organization_id":"org_181","tier":"pro"}
|
|
115
|
+
{"id":"usr_4","organization_id":"org_194","tier":"pro"}
|
|
116
|
+
{"id":"usr_5","organization_id":"org_180","tier":"enterprise"}
|
|
117
|
+
{"id":"usr_6","organization_id":"org_112","tier":"enterprise"}
|
|
118
|
+
{"id":"usr_7","organization_id":"org_291","tier":"pro"}
|
|
119
|
+
{"id":"usr_8","organization_id":"org_150","tier":"free"}
|
|
120
|
+
{"id":"usr_9","organization_id":"org_127","tier":"enterprise"}
|
|
121
|
+
{"id":"usr_10","organization_id":"org_279","tier":"enterprise"}
|
|
122
|
+
{"id":"usr_11","organization_id":"org_58","tier":"pro"}
|
|
123
|
+
{"id":"usr_12","organization_id":"org_183","tier":"pro"}
|
|
124
|
+
{"id":"usr_13","organization_id":"org_2","tier":"pro"}
|
|
125
|
+
{"id":"usr_14","organization_id":"org_15","tier":"free"}
|
|
126
|
+
{"id":"usr_15","organization_id":"org_54","tier":"pro"}
|
|
127
|
+
{"id":"usr_16","organization_id":"org_24","tier":"pro"}
|
|
128
|
+
{"id":"usr_17","organization_id":"org_135","tier":"free"}
|
|
129
|
+
{"id":"usr_18","organization_id":"org_76","tier":"enterprise"}
|
|
130
|
+
{"id":"usr_19","organization_id":"org_166","tier":"enterprise"}
|
|
131
|
+
{"id":"usr_20","organization_id":"org_203","tier":"pro"}
|
|
132
|
+
{"id":"usr_21","organization_id":"org_103","tier":"pro"}
|
|
133
|
+
{"id":"usr_22","organization_id":"org_178","tier":"pro"}
|
|
134
|
+
{"id":"usr_23","organization_id":"org_41","tier":"pro"}
|
|
135
|
+
{"id":"usr_24","organization_id":"org_197","tier":"enterprise"}
|
|
136
|
+
{"id":"usr_25","organization_id":"org_21","tier":"pro"}
|
|
137
|
+
{"id":"usr_26","organization_id":"org_180","tier":"enterprise"}
|
|
138
|
+
{"id":"usr_27","organization_id":"org_216","tier":"enterprise"}
|
|
139
|
+
{"id":"usr_28","organization_id":"org_117","tier":"enterprise"}
|
|
140
|
+
{"id":"usr_29","organization_id":"org_73","tier":"pro"}
|
|
141
|
+
{"id":"usr_30","organization_id":"org_92","tier":"enterprise"}
|
|
142
|
+
{"id":"usr_31","organization_id":"org_272","tier":"enterprise"}
|
|
143
|
+
{"id":"usr_32","organization_id":"org_58","tier":"pro"}
|
|
144
|
+
{"id":"usr_33","organization_id":"org_158","tier":"pro"}
|
|
145
|
+
{"id":"usr_34","organization_id":"org_12","tier":"free"}
|
|
146
|
+
{"id":"usr_35","organization_id":"org_119","tier":"free"}
|
|
147
|
+
{"id":"usr_36","organization_id":"org_272","tier":"pro"}
|
|
148
|
+
{"id":"usr_37","organization_id":"org_1","tier":"enterprise"}
|
|
149
|
+
{"id":"usr_38","organization_id":"org_262","tier":"enterprise"}
|
|
150
|
+
{"id":"usr_39","organization_id":"org_189","tier":"pro"}
|
|
151
|
+
{"id":"usr_40","organization_id":"org_126","tier":"free"}
|
|
152
|
+
{"id":"usr_41","organization_id":"org_94","tier":"free"}
|
|
153
|
+
{"id":"usr_42","organization_id":"org_156","tier":"free"}
|
|
154
|
+
{"id":"usr_43","organization_id":"org_155","tier":"enterprise"}
|
|
155
|
+
{"id":"usr_44","organization_id":"org_151","tier":"pro"}
|
|
156
|
+
{"id":"usr_45","organization_id":"org_217","tier":"pro"}
|
|
157
|
+
{"id":"usr_46","organization_id":"org_111","tier":"enterprise"}
|
|
158
|
+
{"id":"usr_47","organization_id":"org_274","tier":"enterprise"}
|
|
159
|
+
{"id":"usr_48","organization_id":"org_117","tier":"enterprise"}
|
|
160
|
+
{"id":"usr_49","organization_id":"org_125","tier":"pro"}
|
|
161
|
+
{"id":"usr_50","organization_id":"org_42","tier":"pro"}
|
|
162
|
+
"""
|
|
163
|
+
persist_fixture(f"{ds_name}", ds_content)
|
|
164
|
+
click.echo(FeedbackManager.info(message=f"✓ /fixtures/{ds_name}"))
|
|
165
|
+
|
|
166
|
+
ds_name = "events"
|
|
167
|
+
ds_content = """
|
|
168
|
+
{"request_id":"req_1","timestamp":"2024-11-14T12:11:14.896Z","user_id":"usr_42","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-4","options":{"temperature":"0.8","max_tokens":1582,"stream":false}},"usage":{"prompt_tokens":499,"completion_tokens":123,"total_tokens":622}}
|
|
169
|
+
{"request_id":"req_2","timestamp":"2024-10-06T10:32:09.164Z","user_id":"usr_20","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"0.9","max_tokens":650,"stream":true}},"usage":{"prompt_tokens":384,"completion_tokens":103,"total_tokens":487}}
|
|
170
|
+
{"request_id":"req_3","timestamp":"2024-11-19T03:49:26.569Z","user_id":"usr_27","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"0.3","max_tokens":764,"stream":true}},"usage":{"prompt_tokens":63,"completion_tokens":284,"total_tokens":347}}
|
|
171
|
+
{"request_id":"req_4","timestamp":"2024-11-09T09:13:05.359Z","user_id":"usr_8","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.8","max_tokens":1790,"stream":true}},"usage":{"prompt_tokens":405,"completion_tokens":231,"total_tokens":636}}
|
|
172
|
+
{"request_id":"req_5","timestamp":"2024-11-24T17:48:03.640Z","user_id":"usr_45","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"1.0","max_tokens":1701,"stream":false}},"usage":{"prompt_tokens":342,"completion_tokens":258,"total_tokens":600}}
|
|
173
|
+
{"request_id":"req_6","timestamp":"2024-11-10T02:07:53.241Z","user_id":"usr_49","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.4","max_tokens":1049,"stream":false}},"usage":{"prompt_tokens":120,"completion_tokens":165,"total_tokens":285}}
|
|
174
|
+
{"request_id":"req_7","timestamp":"2024-10-14T06:43:14.759Z","user_id":"usr_48","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-4","options":{"temperature":"0.9","max_tokens":1548,"stream":true}},"usage":{"prompt_tokens":61,"completion_tokens":104,"total_tokens":165}}
|
|
175
|
+
{"request_id":"req_8","timestamp":"2024-11-21T20:52:03.378Z","user_id":"usr_30","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.5","max_tokens":1742,"stream":true}},"usage":{"prompt_tokens":326,"completion_tokens":236,"total_tokens":562}}
|
|
176
|
+
{"request_id":"req_9","timestamp":"2024-10-24T22:23:11.463Z","user_id":"usr_34","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"0.5","max_tokens":748,"stream":true}},"usage":{"prompt_tokens":488,"completion_tokens":148,"total_tokens":636}}
|
|
177
|
+
{"request_id":"req_10","timestamp":"2024-11-13T05:42:38.971Z","user_id":"usr_24","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.3","max_tokens":1729,"stream":true}},"usage":{"prompt_tokens":262,"completion_tokens":162,"total_tokens":424}}
|
|
178
|
+
{"request_id":"req_11","timestamp":"2024-10-21T07:20:51.693Z","user_id":"usr_1","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-opus","options":{"temperature":"0.3","max_tokens":1811,"stream":false}},"usage":{"prompt_tokens":342,"completion_tokens":274,"total_tokens":616}}
|
|
179
|
+
{"request_id":"req_12","timestamp":"2024-11-15T06:03:07.775Z","user_id":"usr_21","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-4","options":{"temperature":"0.3","max_tokens":1484,"stream":true}},"usage":{"prompt_tokens":273,"completion_tokens":266,"total_tokens":539}}
|
|
180
|
+
{"request_id":"req_13","timestamp":"2024-11-29T18:31:23.125Z","user_id":"usr_41","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"0.8","max_tokens":1812,"stream":true}},"usage":{"prompt_tokens":265,"completion_tokens":102,"total_tokens":367}}
|
|
181
|
+
{"request_id":"req_14","timestamp":"2024-11-13T23:50:03.888Z","user_id":"usr_13","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.9","max_tokens":1694,"stream":false}},"usage":{"prompt_tokens":76,"completion_tokens":290,"total_tokens":366}}
|
|
182
|
+
{"request_id":"req_15","timestamp":"2024-11-01T14:27:22.401Z","user_id":"usr_32","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-sonnet","options":{"temperature":"0.4","max_tokens":949,"stream":true}},"usage":{"prompt_tokens":105,"completion_tokens":197,"total_tokens":302}}
|
|
183
|
+
{"request_id":"req_16","timestamp":"2024-11-12T09:13:06.447Z","user_id":"usr_2","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"0.1","max_tokens":651,"stream":false}},"usage":{"prompt_tokens":348,"completion_tokens":80,"total_tokens":428}}
|
|
184
|
+
{"request_id":"req_17","timestamp":"2024-11-09T21:09:51.044Z","user_id":"usr_24","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.4","max_tokens":692,"stream":false}},"usage":{"prompt_tokens":195,"completion_tokens":178,"total_tokens":373}}
|
|
185
|
+
{"request_id":"req_18","timestamp":"2024-11-06T13:38:08.873Z","user_id":"usr_49","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-opus","options":{"temperature":"0.4","max_tokens":1303,"stream":true}},"usage":{"prompt_tokens":304,"completion_tokens":73,"total_tokens":377}}
|
|
186
|
+
{"request_id":"req_19","timestamp":"2024-11-06T20:47:35.718Z","user_id":"usr_33","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"0.8","max_tokens":1359,"stream":true}},"usage":{"prompt_tokens":55,"completion_tokens":258,"total_tokens":313}}
|
|
187
|
+
{"request_id":"req_20","timestamp":"2024-11-15T09:59:27.956Z","user_id":"usr_9","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-opus","options":{"temperature":"0.6","max_tokens":1768,"stream":true}},"usage":{"prompt_tokens":310,"completion_tokens":62,"total_tokens":372}}
|
|
188
|
+
{"request_id":"req_21","timestamp":"2024-11-17T03:46:10.219Z","user_id":"usr_6","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"0.8","max_tokens":1967,"stream":true}},"usage":{"prompt_tokens":87,"completion_tokens":275,"total_tokens":362}}
|
|
189
|
+
{"request_id":"req_22","timestamp":"2024-10-03T06:52:21.902Z","user_id":"usr_13","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.8","max_tokens":1338,"stream":true}},"usage":{"prompt_tokens":332,"completion_tokens":137,"total_tokens":469}}
|
|
190
|
+
{"request_id":"req_23","timestamp":"2024-11-10T02:53:06.630Z","user_id":"usr_16","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-opus","options":{"temperature":"0.1","max_tokens":1958,"stream":true}},"usage":{"prompt_tokens":444,"completion_tokens":294,"total_tokens":738}}
|
|
191
|
+
{"request_id":"req_24","timestamp":"2024-10-10T04:11:45.410Z","user_id":"usr_44","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"1.0","max_tokens":1241,"stream":false}},"usage":{"prompt_tokens":414,"completion_tokens":227,"total_tokens":641}}
|
|
192
|
+
{"request_id":"req_25","timestamp":"2024-10-15T09:53:09.379Z","user_id":"usr_24","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"0.4","max_tokens":1015,"stream":true}},"usage":{"prompt_tokens":433,"completion_tokens":119,"total_tokens":552}}
|
|
193
|
+
{"request_id":"req_26","timestamp":"2024-11-16T16:40:38.042Z","user_id":"usr_8","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"0.3","max_tokens":514,"stream":true}},"usage":{"prompt_tokens":86,"completion_tokens":241,"total_tokens":327}}
|
|
194
|
+
{"request_id":"req_27","timestamp":"2024-10-06T11:34:58.073Z","user_id":"usr_40","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.7","max_tokens":1752,"stream":true}},"usage":{"prompt_tokens":312,"completion_tokens":262,"total_tokens":574}}
|
|
195
|
+
{"request_id":"req_28","timestamp":"2024-10-21T05:35:11.046Z","user_id":"usr_22","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.5","max_tokens":1425,"stream":true}},"usage":{"prompt_tokens":50,"completion_tokens":182,"total_tokens":232}}
|
|
196
|
+
{"request_id":"req_29","timestamp":"2024-10-11T06:14:52.973Z","user_id":"usr_4","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-sonnet","options":{"temperature":"0.4","max_tokens":1660,"stream":true}},"usage":{"prompt_tokens":384,"completion_tokens":124,"total_tokens":508}}
|
|
197
|
+
{"request_id":"req_30","timestamp":"2024-11-13T10:48:53.729Z","user_id":"usr_22","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-4","options":{"temperature":"0.4","max_tokens":1425,"stream":true}},"usage":{"prompt_tokens":299,"completion_tokens":57,"total_tokens":356}}
|
|
198
|
+
{"request_id":"req_31","timestamp":"2024-10-01T03:26:07.133Z","user_id":"usr_41","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"0.8","max_tokens":1975,"stream":true}},"usage":{"prompt_tokens":471,"completion_tokens":245,"total_tokens":716}}
|
|
199
|
+
{"request_id":"req_32","timestamp":"2024-11-26T12:13:53.221Z","user_id":"usr_33","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-opus","options":{"temperature":"0.5","max_tokens":557,"stream":false}},"usage":{"prompt_tokens":75,"completion_tokens":92,"total_tokens":167}}
|
|
200
|
+
{"request_id":"req_33","timestamp":"2024-10-14T07:41:11.783Z","user_id":"usr_18","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-opus","options":{"temperature":"1.0","max_tokens":843,"stream":false}},"usage":{"prompt_tokens":321,"completion_tokens":258,"total_tokens":579}}
|
|
201
|
+
{"request_id":"req_34","timestamp":"2024-10-21T11:20:47.679Z","user_id":"usr_37","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.7","max_tokens":1148,"stream":false}},"usage":{"prompt_tokens":425,"completion_tokens":245,"total_tokens":670}}
|
|
202
|
+
{"request_id":"req_35","timestamp":"2024-11-18T12:02:12.268Z","user_id":"usr_38","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"0.2","max_tokens":1987,"stream":false}},"usage":{"prompt_tokens":50,"completion_tokens":223,"total_tokens":273}}
|
|
203
|
+
{"request_id":"req_36","timestamp":"2024-10-03T02:17:56.042Z","user_id":"usr_38","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.2","max_tokens":1339,"stream":false}},"usage":{"prompt_tokens":102,"completion_tokens":112,"total_tokens":214}}
|
|
204
|
+
{"request_id":"req_37","timestamp":"2024-11-02T19:52:17.904Z","user_id":"usr_34","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"0.3","max_tokens":1050,"stream":false}},"usage":{"prompt_tokens":485,"completion_tokens":189,"total_tokens":674}}
|
|
205
|
+
{"request_id":"req_38","timestamp":"2024-11-17T22:26:34.434Z","user_id":"usr_5","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"0.6","max_tokens":857,"stream":false}},"usage":{"prompt_tokens":442,"completion_tokens":224,"total_tokens":666}}
|
|
206
|
+
{"request_id":"req_39","timestamp":"2024-11-17T01:20:40.223Z","user_id":"usr_36","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.9","max_tokens":698,"stream":true}},"usage":{"prompt_tokens":430,"completion_tokens":185,"total_tokens":615}}
|
|
207
|
+
{"request_id":"req_40","timestamp":"2024-11-20T21:38:04.662Z","user_id":"usr_31","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.3","max_tokens":1226,"stream":true}},"usage":{"prompt_tokens":201,"completion_tokens":65,"total_tokens":266}}
|
|
208
|
+
{"request_id":"req_41","timestamp":"2024-11-07T11:20:55.217Z","user_id":"usr_34","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"0.1","max_tokens":1893,"stream":false}},"usage":{"prompt_tokens":307,"completion_tokens":224,"total_tokens":531}}
|
|
209
|
+
{"request_id":"req_42","timestamp":"2024-10-30T23:13:57.704Z","user_id":"usr_44","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"0.3","max_tokens":888,"stream":false}},"usage":{"prompt_tokens":212,"completion_tokens":53,"total_tokens":265}}
|
|
210
|
+
{"request_id":"req_43","timestamp":"2024-11-09T02:04:23.904Z","user_id":"usr_28","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"0.3","max_tokens":1574,"stream":true}},"usage":{"prompt_tokens":161,"completion_tokens":190,"total_tokens":351}}
|
|
211
|
+
{"request_id":"req_44","timestamp":"2024-11-20T21:28:23.931Z","user_id":"usr_22","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-opus","options":{"temperature":"0.4","max_tokens":578,"stream":true}},"usage":{"prompt_tokens":465,"completion_tokens":294,"total_tokens":759}}
|
|
212
|
+
{"request_id":"req_45","timestamp":"2024-11-15T14:02:11.552Z","user_id":"usr_2","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-opus","options":{"temperature":"0.4","max_tokens":502,"stream":true}},"usage":{"prompt_tokens":489,"completion_tokens":288,"total_tokens":777}}
|
|
213
|
+
{"request_id":"req_46","timestamp":"2024-10-22T23:05:29.902Z","user_id":"usr_15","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-4","options":{"temperature":"0.5","max_tokens":1280,"stream":true}},"usage":{"prompt_tokens":462,"completion_tokens":283,"total_tokens":745}}
|
|
214
|
+
{"request_id":"req_47","timestamp":"2024-10-28T01:16:36.936Z","user_id":"usr_28","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"0.4","max_tokens":1513,"stream":true}},"usage":{"prompt_tokens":421,"completion_tokens":204,"total_tokens":625}}
|
|
215
|
+
{"request_id":"req_48","timestamp":"2024-11-20T18:18:38.744Z","user_id":"usr_4","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.8","max_tokens":1194,"stream":true}},"usage":{"prompt_tokens":251,"completion_tokens":109,"total_tokens":360}}
|
|
216
|
+
{"request_id":"req_49","timestamp":"2024-10-12T17:21:20.482Z","user_id":"usr_11","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.3","max_tokens":621,"stream":false}},"usage":{"prompt_tokens":195,"completion_tokens":253,"total_tokens":448}}
|
|
217
|
+
{"request_id":"req_50","timestamp":"2024-11-27T14:14:23.737Z","user_id":"usr_8","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-4","options":{"temperature":"0.0","max_tokens":1468,"stream":true}},"usage":{"prompt_tokens":461,"completion_tokens":100,"total_tokens":561}}
|
|
218
|
+
{"request_id":"req_51","timestamp":"2024-10-15T14:58:09.623Z","user_id":"usr_48","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"1.0","max_tokens":1879,"stream":false}},"usage":{"prompt_tokens":51,"completion_tokens":215,"total_tokens":266}}
|
|
219
|
+
{"request_id":"req_52","timestamp":"2024-11-18T00:55:43.893Z","user_id":"usr_48","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"0.2","max_tokens":549,"stream":false}},"usage":{"prompt_tokens":481,"completion_tokens":142,"total_tokens":623}}
|
|
220
|
+
{"request_id":"req_53","timestamp":"2024-11-01T03:22:01.890Z","user_id":"usr_14","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.8","max_tokens":1771,"stream":false}},"usage":{"prompt_tokens":459,"completion_tokens":79,"total_tokens":538}}
|
|
221
|
+
{"request_id":"req_54","timestamp":"2024-10-26T08:57:25.867Z","user_id":"usr_17","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"0.4","max_tokens":1888,"stream":false}},"usage":{"prompt_tokens":219,"completion_tokens":226,"total_tokens":445}}
|
|
222
|
+
{"request_id":"req_55","timestamp":"2024-11-17T16:12:47.424Z","user_id":"usr_43","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"0.0","max_tokens":1281,"stream":false}},"usage":{"prompt_tokens":426,"completion_tokens":244,"total_tokens":670}}
|
|
223
|
+
{"request_id":"req_56","timestamp":"2024-10-10T01:03:29.669Z","user_id":"usr_6","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.3","max_tokens":1004,"stream":false}},"usage":{"prompt_tokens":420,"completion_tokens":69,"total_tokens":489}}
|
|
224
|
+
{"request_id":"req_57","timestamp":"2024-10-10T19:18:37.937Z","user_id":"usr_30","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"0.7","max_tokens":1209,"stream":true}},"usage":{"prompt_tokens":59,"completion_tokens":66,"total_tokens":125}}
|
|
225
|
+
{"request_id":"req_58","timestamp":"2024-10-07T19:20:21.754Z","user_id":"usr_4","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.5","max_tokens":1215,"stream":true}},"usage":{"prompt_tokens":197,"completion_tokens":201,"total_tokens":398}}
|
|
226
|
+
{"request_id":"req_59","timestamp":"2024-10-12T07:54:12.535Z","user_id":"usr_35","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-4","options":{"temperature":"0.1","max_tokens":1445,"stream":false}},"usage":{"prompt_tokens":318,"completion_tokens":111,"total_tokens":429}}
|
|
227
|
+
{"request_id":"req_60","timestamp":"2024-11-04T00:11:56.488Z","user_id":"usr_3","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"1.0","max_tokens":1127,"stream":true}},"usage":{"prompt_tokens":286,"completion_tokens":293,"total_tokens":579}}
|
|
228
|
+
{"request_id":"req_61","timestamp":"2024-10-15T10:29:51.122Z","user_id":"usr_36","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.9","max_tokens":1810,"stream":false}},"usage":{"prompt_tokens":308,"completion_tokens":238,"total_tokens":546}}
|
|
229
|
+
{"request_id":"req_62","timestamp":"2024-10-15T07:46:39.995Z","user_id":"usr_36","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.8","max_tokens":736,"stream":true}},"usage":{"prompt_tokens":102,"completion_tokens":194,"total_tokens":296}}
|
|
230
|
+
{"request_id":"req_63","timestamp":"2024-10-25T11:39:09.912Z","user_id":"usr_37","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.5","max_tokens":1112,"stream":true}},"usage":{"prompt_tokens":211,"completion_tokens":139,"total_tokens":350}}
|
|
231
|
+
{"request_id":"req_64","timestamp":"2024-10-20T16:35:00.170Z","user_id":"usr_32","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-4","options":{"temperature":"0.2","max_tokens":535,"stream":true}},"usage":{"prompt_tokens":355,"completion_tokens":116,"total_tokens":471}}
|
|
232
|
+
{"request_id":"req_65","timestamp":"2024-11-27T04:31:51.172Z","user_id":"usr_44","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"0.3","max_tokens":838,"stream":true}},"usage":{"prompt_tokens":468,"completion_tokens":298,"total_tokens":766}}
|
|
233
|
+
{"request_id":"req_66","timestamp":"2024-10-26T17:30:31.696Z","user_id":"usr_12","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-sonnet","options":{"temperature":"0.7","max_tokens":505,"stream":true}},"usage":{"prompt_tokens":190,"completion_tokens":207,"total_tokens":397}}
|
|
234
|
+
{"request_id":"req_67","timestamp":"2024-10-04T02:09:21.280Z","user_id":"usr_31","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"1.0","max_tokens":886,"stream":false}},"usage":{"prompt_tokens":264,"completion_tokens":60,"total_tokens":324}}
|
|
235
|
+
{"request_id":"req_68","timestamp":"2024-11-14T09:34:25.291Z","user_id":"usr_17","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"0.5","max_tokens":1794,"stream":true}},"usage":{"prompt_tokens":166,"completion_tokens":133,"total_tokens":299}}
|
|
236
|
+
{"request_id":"req_69","timestamp":"2024-10-13T20:06:12.700Z","user_id":"usr_18","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.6","max_tokens":555,"stream":false}},"usage":{"prompt_tokens":247,"completion_tokens":149,"total_tokens":396}}
|
|
237
|
+
{"request_id":"req_70","timestamp":"2024-10-05T01:45:08.193Z","user_id":"usr_31","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.9","max_tokens":1428,"stream":true}},"usage":{"prompt_tokens":54,"completion_tokens":132,"total_tokens":186}}
|
|
238
|
+
{"request_id":"req_71","timestamp":"2024-11-15T22:14:55.479Z","user_id":"usr_18","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"0.5","max_tokens":650,"stream":false}},"usage":{"prompt_tokens":65,"completion_tokens":206,"total_tokens":271}}
|
|
239
|
+
{"request_id":"req_72","timestamp":"2024-11-09T06:44:07.284Z","user_id":"usr_43","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"0.2","max_tokens":1363,"stream":false}},"usage":{"prompt_tokens":443,"completion_tokens":284,"total_tokens":727}}
|
|
240
|
+
{"request_id":"req_73","timestamp":"2024-11-06T01:10:44.917Z","user_id":"usr_24","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-4","options":{"temperature":"0.4","max_tokens":1927,"stream":true}},"usage":{"prompt_tokens":495,"completion_tokens":216,"total_tokens":711}}
|
|
241
|
+
{"request_id":"req_74","timestamp":"2024-10-19T14:43:49.854Z","user_id":"usr_26","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.4","max_tokens":679,"stream":false}},"usage":{"prompt_tokens":221,"completion_tokens":286,"total_tokens":507}}
|
|
242
|
+
{"request_id":"req_75","timestamp":"2024-10-05T06:19:28.703Z","user_id":"usr_25","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-opus","options":{"temperature":"0.8","max_tokens":1614,"stream":false}},"usage":{"prompt_tokens":59,"completion_tokens":93,"total_tokens":152}}
|
|
243
|
+
{"request_id":"req_76","timestamp":"2024-11-04T22:29:57.675Z","user_id":"usr_33","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.9","max_tokens":1491,"stream":false}},"usage":{"prompt_tokens":90,"completion_tokens":120,"total_tokens":210}}
|
|
244
|
+
{"request_id":"req_77","timestamp":"2024-11-08T02:17:28.909Z","user_id":"usr_4","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"0.0","max_tokens":1324,"stream":true}},"usage":{"prompt_tokens":355,"completion_tokens":102,"total_tokens":457}}
|
|
245
|
+
{"request_id":"req_78","timestamp":"2024-10-24T15:25:14.046Z","user_id":"usr_39","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"0.8","max_tokens":1529,"stream":false}},"usage":{"prompt_tokens":208,"completion_tokens":141,"total_tokens":349}}
|
|
246
|
+
{"request_id":"req_79","timestamp":"2024-10-27T13:01:16.190Z","user_id":"usr_40","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-opus","options":{"temperature":"0.0","max_tokens":1638,"stream":false}},"usage":{"prompt_tokens":243,"completion_tokens":274,"total_tokens":517}}
|
|
247
|
+
{"request_id":"req_80","timestamp":"2024-11-22T13:07:16.699Z","user_id":"usr_24","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.4","max_tokens":1397,"stream":true}},"usage":{"prompt_tokens":263,"completion_tokens":185,"total_tokens":448}}
|
|
248
|
+
{"request_id":"req_81","timestamp":"2024-11-29T03:35:54.264Z","user_id":"usr_26","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-4","options":{"temperature":"0.1","max_tokens":1521,"stream":false}},"usage":{"prompt_tokens":273,"completion_tokens":199,"total_tokens":472}}
|
|
249
|
+
{"request_id":"req_82","timestamp":"2024-11-20T08:27:02.022Z","user_id":"usr_38","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-opus","options":{"temperature":"0.6","max_tokens":1463,"stream":true}},"usage":{"prompt_tokens":192,"completion_tokens":120,"total_tokens":312}}
|
|
250
|
+
{"request_id":"req_83","timestamp":"2024-11-09T10:38:51.640Z","user_id":"usr_24","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-3.5-turbo","options":{"temperature":"1.0","max_tokens":1574,"stream":false}},"usage":{"prompt_tokens":406,"completion_tokens":58,"total_tokens":464}}
|
|
251
|
+
{"request_id":"req_84","timestamp":"2024-10-29T18:37:34.187Z","user_id":"usr_16","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-opus","options":{"temperature":"0.1","max_tokens":1924,"stream":true}},"usage":{"prompt_tokens":237,"completion_tokens":284,"total_tokens":521}}
|
|
252
|
+
{"request_id":"req_85","timestamp":"2024-10-17T21:47:26.419Z","user_id":"usr_49","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-opus","options":{"temperature":"0.7","max_tokens":1714,"stream":false}},"usage":{"prompt_tokens":114,"completion_tokens":194,"total_tokens":308}}
|
|
253
|
+
{"request_id":"req_86","timestamp":"2024-10-13T16:59:19.264Z","user_id":"usr_40","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.8","max_tokens":520,"stream":true}},"usage":{"prompt_tokens":227,"completion_tokens":190,"total_tokens":417}}
|
|
254
|
+
{"request_id":"req_87","timestamp":"2024-10-25T06:47:20.855Z","user_id":"usr_48","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.3","max_tokens":1904,"stream":false}},"usage":{"prompt_tokens":476,"completion_tokens":96,"total_tokens":572}}
|
|
255
|
+
{"request_id":"req_88","timestamp":"2024-11-05T07:30:25.734Z","user_id":"usr_4","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.9","max_tokens":535,"stream":false}},"usage":{"prompt_tokens":399,"completion_tokens":217,"total_tokens":616}}
|
|
256
|
+
{"request_id":"req_89","timestamp":"2024-11-13T01:46:36.632Z","user_id":"usr_20","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-4","options":{"temperature":"0.4","max_tokens":1260,"stream":true}},"usage":{"prompt_tokens":189,"completion_tokens":159,"total_tokens":348}}
|
|
257
|
+
{"request_id":"req_90","timestamp":"2024-11-29T06:17:41.475Z","user_id":"usr_7","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"claude-3-sonnet","options":{"temperature":"0.5","max_tokens":1105,"stream":false}},"usage":{"prompt_tokens":164,"completion_tokens":223,"total_tokens":387}}
|
|
258
|
+
{"request_id":"req_91","timestamp":"2024-11-21T21:18:42.618Z","user_id":"usr_31","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.1","max_tokens":1899,"stream":false}},"usage":{"prompt_tokens":424,"completion_tokens":121,"total_tokens":545}}
|
|
259
|
+
{"request_id":"req_92","timestamp":"2024-10-13T04:50:06.616Z","user_id":"usr_20","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-4","options":{"temperature":"0.6","max_tokens":752,"stream":true}},"usage":{"prompt_tokens":400,"completion_tokens":160,"total_tokens":560}}
|
|
260
|
+
{"request_id":"req_93","timestamp":"2024-10-06T12:44:26.578Z","user_id":"usr_18","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"0.4","max_tokens":1916,"stream":false}},"usage":{"prompt_tokens":193,"completion_tokens":299,"total_tokens":492}}
|
|
261
|
+
{"request_id":"req_94","timestamp":"2024-11-16T12:40:44.907Z","user_id":"usr_46","request":{"type":"chat","endpoint":"/v1/completions","model":"claude-3-sonnet","options":{"temperature":"0.9","max_tokens":1612,"stream":false}},"usage":{"prompt_tokens":149,"completion_tokens":181,"total_tokens":330}}
|
|
262
|
+
{"request_id":"req_95","timestamp":"2024-10-28T14:02:47.893Z","user_id":"usr_21","request":{"type":"chat","endpoint":"/v1/chat/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.6","max_tokens":1378,"stream":true}},"usage":{"prompt_tokens":363,"completion_tokens":292,"total_tokens":655}}
|
|
263
|
+
{"request_id":"req_96","timestamp":"2024-10-24T10:13:34.492Z","user_id":"usr_9","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-opus","options":{"temperature":"0.2","max_tokens":1055,"stream":true}},"usage":{"prompt_tokens":463,"completion_tokens":252,"total_tokens":715}}
|
|
264
|
+
{"request_id":"req_97","timestamp":"2024-10-25T05:35:39.972Z","user_id":"usr_32","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-3.5-turbo","options":{"temperature":"0.1","max_tokens":877,"stream":false}},"usage":{"prompt_tokens":451,"completion_tokens":211,"total_tokens":662}}
|
|
265
|
+
{"request_id":"req_98","timestamp":"2024-10-30T12:55:53.621Z","user_id":"usr_21","request":{"type":"chat","endpoint":"/v1/images/generations","model":"claude-3-sonnet","options":{"temperature":"0.8","max_tokens":1966,"stream":true}},"usage":{"prompt_tokens":497,"completion_tokens":93,"total_tokens":590}}
|
|
266
|
+
{"request_id":"req_99","timestamp":"2024-11-14T11:31:10.758Z","user_id":"usr_11","request":{"type":"chat","endpoint":"/v1/completions","model":"gpt-4","options":{"temperature":"0.4","max_tokens":564,"stream":true}},"usage":{"prompt_tokens":244,"completion_tokens":154,"total_tokens":398}}
|
|
267
|
+
{"request_id":"req_100","timestamp":"2024-11-22T13:43:33.802Z","user_id":"usr_25","request":{"type":"chat","endpoint":"/v1/images/generations","model":"gpt-4","options":{"temperature":"0.9","max_tokens":1096,"stream":true}},"usage":{"prompt_tokens":58,"completion_tokens":274,"total_tokens":332}}
|
|
268
|
+
"""
|
|
269
|
+
persist_fixture(f"{ds_name}", ds_content)
|
|
270
|
+
click.echo(FeedbackManager.info(message=f"✓ /fixtures/{ds_name}"))
|
|
271
|
+
|
|
272
|
+
# Create sample endpoint
|
|
273
|
+
pipe_name = "api_token_usage"
|
|
274
|
+
pipe_path = Path(folder) / "endpoints" / f"{pipe_name}.pipe"
|
|
275
|
+
pipe_content = """DESCRIPTION >
|
|
276
|
+
Pipe to generate a token usage timeline.
|
|
277
|
+
|
|
278
|
+
NODE api_token_usage_node_1
|
|
279
|
+
SQL >
|
|
280
|
+
%
|
|
281
|
+
SELECT
|
|
282
|
+
toDate(timestamp) as date,
|
|
283
|
+
user_id,
|
|
284
|
+
sum(usage_total_tokens) as total_tokens
|
|
285
|
+
FROM
|
|
286
|
+
events
|
|
287
|
+
WHERE
|
|
288
|
+
user_id IN (SELECT id FROM users WHERE organization_id = {{String(organization_id)}})
|
|
289
|
+
{%if defined(start_date) and defined(end_date)%}
|
|
290
|
+
AND timestamp BETWEEN {{DateTime(start_date)}} AND {{DateTime(end_date)}}
|
|
291
|
+
{%else%}
|
|
292
|
+
AND timestamp BETWEEN now() - interval 30 day AND now()
|
|
293
|
+
{%end%}
|
|
294
|
+
GROUP BY
|
|
295
|
+
date, user_id
|
|
296
|
+
ORDER BY
|
|
297
|
+
date
|
|
298
|
+
|
|
299
|
+
TYPE Endpoint
|
|
300
|
+
|
|
301
|
+
"""
|
|
302
|
+
pipe_path.write_text(pipe_content)
|
|
303
|
+
click.echo(FeedbackManager.info(message=f"✓ /endpoints/{pipe_name}.pipe"))
|
|
304
|
+
|
|
305
|
+
elif data:
|
|
67
306
|
ds_name = os.path.basename(data.split(".")[0])
|
|
68
307
|
data_content = Path(data).read_text()
|
|
69
308
|
datasource_path = Path(folder) / "datasources" / f"{ds_name}.datasource"
|
|
@@ -79,7 +318,7 @@ async def create(
|
|
|
79
318
|
datasource_content = datasource_path.read_text()
|
|
80
319
|
has_json_path = "`json:" in datasource_content
|
|
81
320
|
if has_json_path:
|
|
82
|
-
sql = await llm.generate_sql_sample_data(schema=datasource_content, rows=rows)
|
|
321
|
+
sql = await llm.generate_sql_sample_data(schema=datasource_content, rows=rows, context=prompt)
|
|
83
322
|
result = await tb_client.query(f"{sql} FORMAT JSON")
|
|
84
323
|
data = result.get("data", [])
|
|
85
324
|
fixture_name = build_fixture_name(datasource_path.absolute(), datasource_name, datasource_content)
|
tinybird/tb/modules/llm.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import json
|
|
2
3
|
import urllib.parse
|
|
3
4
|
from copy import deepcopy
|
|
4
|
-
from typing import Awaitable, Callable, List
|
|
5
|
+
from typing import Awaitable, Callable, List, Optional
|
|
5
6
|
|
|
7
|
+
from openai import OpenAI
|
|
6
8
|
from pydantic import BaseModel
|
|
7
9
|
|
|
8
10
|
from tinybird.client import TinyB
|
|
11
|
+
from tinybird.prompts import create_test_calls_prompt
|
|
9
12
|
from tinybird.tb.modules.config import CLIConfig
|
|
10
13
|
|
|
11
14
|
|
|
@@ -19,13 +22,25 @@ class DataProject(BaseModel):
|
|
|
19
22
|
pipes: List[DataFile]
|
|
20
23
|
|
|
21
24
|
|
|
25
|
+
class TestExpectation(BaseModel):
|
|
26
|
+
name: str
|
|
27
|
+
description: str
|
|
28
|
+
parameters: str
|
|
29
|
+
expected_result: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestExpectations(BaseModel):
|
|
33
|
+
tests: List[TestExpectation]
|
|
34
|
+
|
|
35
|
+
|
|
22
36
|
class LLM:
|
|
23
|
-
def __init__(self, client: TinyB):
|
|
37
|
+
def __init__(self, client: TinyB, api_key: Optional[str] = None):
|
|
24
38
|
self.client = client
|
|
25
39
|
user_token = CLIConfig.get_project_config().get_user_token()
|
|
26
40
|
user_client = deepcopy(client)
|
|
27
41
|
user_client.token = user_token
|
|
28
42
|
self.user_client = user_client
|
|
43
|
+
self.openai = OpenAI(api_key=api_key) if api_key else None
|
|
29
44
|
|
|
30
45
|
async def _execute(self, action_fn: Callable[[], Awaitable[str]], checker_fn: Callable[[str], bool]):
|
|
31
46
|
is_valid = False
|
|
@@ -46,9 +61,10 @@ class LLM:
|
|
|
46
61
|
response = await self.user_client._req(
|
|
47
62
|
"/v0/llm/create",
|
|
48
63
|
method="POST",
|
|
49
|
-
data=f'{{"prompt":
|
|
64
|
+
data=f'{{"prompt": {json.dumps(prompt)}}}',
|
|
50
65
|
headers={"Content-Type": "application/json"},
|
|
51
66
|
)
|
|
67
|
+
|
|
52
68
|
return DataProject.model_validate(response.get("result", {}))
|
|
53
69
|
except Exception:
|
|
54
70
|
return DataProject(datasources=[], pipes=[])
|
|
@@ -61,3 +77,19 @@ class LLM:
|
|
|
61
77
|
headers={"Content-Type": "application/json"},
|
|
62
78
|
)
|
|
63
79
|
return response.get("result", "")
|
|
80
|
+
|
|
81
|
+
async def create_test_commands(
|
|
82
|
+
self, pipe_content: str, pipe_params: set[str], context: str = ""
|
|
83
|
+
) -> TestExpectations:
|
|
84
|
+
if not self.openai:
|
|
85
|
+
raise ValueError("OpenAI API key is not set")
|
|
86
|
+
|
|
87
|
+
completion = self.openai.beta.chat.completions.parse(
|
|
88
|
+
model="gpt-4o",
|
|
89
|
+
messages=[
|
|
90
|
+
{"role": "system", "content": create_test_calls_prompt.format(context=context)},
|
|
91
|
+
{"role": "user", "content": f"Pipe content: {pipe_content}\nPipe params: {pipe_params}"},
|
|
92
|
+
],
|
|
93
|
+
response_format=TestExpectations,
|
|
94
|
+
)
|
|
95
|
+
return completion.choices[0].message.parsed or TestExpectations(tests=[])
|
tinybird/tb/modules/local.py
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
|
-
import hashlib
|
|
2
1
|
import os
|
|
3
2
|
import time
|
|
4
|
-
from typing import Optional
|
|
5
3
|
|
|
6
4
|
import click
|
|
7
|
-
import requests
|
|
8
5
|
|
|
9
6
|
import docker
|
|
10
7
|
from tinybird.feedback_manager import FeedbackManager
|
|
11
8
|
from tinybird.tb.modules.cli import cli
|
|
12
|
-
from tinybird.tb.modules.common import
|
|
13
|
-
coro,
|
|
14
|
-
)
|
|
15
|
-
from tinybird.tb.modules.config import CLIConfig
|
|
9
|
+
from tinybird.tb.modules.common import coro
|
|
16
10
|
from tinybird.tb.modules.exceptions import CLIException
|
|
17
|
-
|
|
18
|
-
# TODO: Use the official Tinybird image once it's available 'tinybirdco/tinybird-local:latest'
|
|
19
|
-
TB_IMAGE_NAME = "registry.gitlab.com/tinybird/analytics/tinybird-local-jammy-3.11:latest"
|
|
20
|
-
TB_CONTAINER_NAME = "tinybird-local"
|
|
21
|
-
TB_LOCAL_PORT = int(os.getenv("TB_LOCAL_PORT", 80))
|
|
22
|
-
TB_LOCAL_HOST = f"http://localhost:{TB_LOCAL_PORT}"
|
|
11
|
+
from tinybird.tb.modules.local_common import TB_CONTAINER_NAME, TB_IMAGE_NAME, TB_LOCAL_PORT
|
|
23
12
|
|
|
24
13
|
|
|
25
14
|
def start_tinybird_local(
|
|
@@ -113,39 +102,6 @@ def remove_tinybird_local(docker_client):
|
|
|
113
102
|
pass
|
|
114
103
|
|
|
115
104
|
|
|
116
|
-
async def get_tinybird_local_client(path: Optional[str] = None):
|
|
117
|
-
"""Get a Tinybird client connected to the local environment."""
|
|
118
|
-
config = CLIConfig.get_project_config()
|
|
119
|
-
try:
|
|
120
|
-
# ruff: noqa: ASYNC210
|
|
121
|
-
tokens = requests.get(f"{TB_LOCAL_HOST}/tokens").json()
|
|
122
|
-
except Exception:
|
|
123
|
-
raise CLIException("Tinybird local is not running. Please run `tb local start` first.")
|
|
124
|
-
|
|
125
|
-
user_token = tokens["user_token"]
|
|
126
|
-
token = tokens["workspace_admin_token"]
|
|
127
|
-
# Create a new workspace if path is provided. This is used to isolate the build in a different workspace.
|
|
128
|
-
if path:
|
|
129
|
-
folder_hash = hashlib.sha256(path.encode()).hexdigest()
|
|
130
|
-
user_client = config.get_client(host=TB_LOCAL_HOST, token=user_token)
|
|
131
|
-
|
|
132
|
-
ws_name = f"Tinybird_Local_Build_{folder_hash}"
|
|
133
|
-
|
|
134
|
-
user_workspaces = await user_client.user_workspaces()
|
|
135
|
-
ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
|
|
136
|
-
if not ws:
|
|
137
|
-
await user_client.create_workspace(ws_name, template=None)
|
|
138
|
-
user_workspaces = await user_client.user_workspaces()
|
|
139
|
-
ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
|
|
140
|
-
|
|
141
|
-
token = ws["token"]
|
|
142
|
-
|
|
143
|
-
config.set_token(token)
|
|
144
|
-
config.set_host(TB_LOCAL_HOST)
|
|
145
|
-
config.set_user_token(user_token)
|
|
146
|
-
return config.get_client(host=TB_LOCAL_HOST, token=token)
|
|
147
|
-
|
|
148
|
-
|
|
149
105
|
@cli.command()
|
|
150
106
|
def upgrade():
|
|
151
107
|
"""Upgrade Tinybird CLI to the latest version"""
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import os
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from tinybird.client import TinyB
|
|
8
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
9
|
+
from tinybird.tb.modules.exceptions import CLIException
|
|
10
|
+
|
|
11
|
+
# TODO: Use the official Tinybird image once it's available 'tinybirdco/tinybird-local:latest'
|
|
12
|
+
TB_IMAGE_NAME = "registry.gitlab.com/tinybird/analytics/tinybird-local-jammy-3.11:latest"
|
|
13
|
+
TB_CONTAINER_NAME = "tinybird-local"
|
|
14
|
+
TB_LOCAL_PORT = int(os.getenv("TB_LOCAL_PORT", 80))
|
|
15
|
+
TB_LOCAL_HOST = f"http://localhost:{TB_LOCAL_PORT}"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def get_tinybird_local_client(path: Optional[str] = None) -> TinyB:
|
|
19
|
+
"""Get a Tinybird client connected to the local environment."""
|
|
20
|
+
config = CLIConfig.get_project_config()
|
|
21
|
+
try:
|
|
22
|
+
# ruff: noqa: ASYNC210
|
|
23
|
+
tokens = requests.get(f"{TB_LOCAL_HOST}/tokens").json()
|
|
24
|
+
except Exception:
|
|
25
|
+
raise CLIException("Tinybird local is not running. Please run `tb local start` first.")
|
|
26
|
+
|
|
27
|
+
user_token = tokens["user_token"]
|
|
28
|
+
default_token = tokens["workspace_admin_token"]
|
|
29
|
+
# Create a new workspace if path is provided. This is used to isolate the build in a different workspace.
|
|
30
|
+
path = path or os.getcwd()
|
|
31
|
+
if path:
|
|
32
|
+
folder_hash = hashlib.sha256(path.encode()).hexdigest()
|
|
33
|
+
user_client = config.get_client(host=TB_LOCAL_HOST, token=user_token)
|
|
34
|
+
|
|
35
|
+
ws_name = f"Tinybird_Local_Build_{folder_hash}"
|
|
36
|
+
|
|
37
|
+
user_workspaces = await user_client.user_workspaces()
|
|
38
|
+
ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
|
|
39
|
+
if not ws:
|
|
40
|
+
await user_client.create_workspace(ws_name, template=None)
|
|
41
|
+
user_workspaces = await user_client.user_workspaces()
|
|
42
|
+
ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
|
|
43
|
+
|
|
44
|
+
ws_token = ws["token"]
|
|
45
|
+
|
|
46
|
+
config.set_token(ws_token)
|
|
47
|
+
config.set_token_for_host(TB_LOCAL_HOST, ws_token)
|
|
48
|
+
config.set_host(TB_LOCAL_HOST)
|
|
49
|
+
else:
|
|
50
|
+
config.set_token(default_token)
|
|
51
|
+
config.set_token_for_host(TB_LOCAL_HOST, default_token)
|
|
52
|
+
|
|
53
|
+
config.set_user_token(user_token)
|
|
54
|
+
return config.get_client(host=TB_LOCAL_HOST)
|
tinybird/tb/modules/mock.py
CHANGED
|
@@ -9,7 +9,7 @@ from tinybird.tb.modules.common import CLIException, coro
|
|
|
9
9
|
from tinybird.tb.modules.config import CLIConfig
|
|
10
10
|
from tinybird.tb.modules.datafile.fixture import build_fixture_name, persist_fixture
|
|
11
11
|
from tinybird.tb.modules.llm import LLM
|
|
12
|
-
from tinybird.tb.modules.
|
|
12
|
+
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@cli.command()
|
tinybird/tb/modules/test.py
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# - If it makes sense and only when strictly necessary, you can create utility functions in this file.
|
|
4
4
|
# - But please, **do not** interleave utility functions and command definitions.
|
|
5
5
|
|
|
6
|
+
import difflib
|
|
6
7
|
import glob
|
|
7
|
-
import json
|
|
8
8
|
import os
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Iterable, List, Optional, Tuple
|
|
@@ -18,7 +18,7 @@ from tinybird.tb.modules.common import coro
|
|
|
18
18
|
from tinybird.tb.modules.config import CLIConfig
|
|
19
19
|
from tinybird.tb.modules.exceptions import CLIException
|
|
20
20
|
from tinybird.tb.modules.llm import LLM, TestExpectation
|
|
21
|
-
from tinybird.tb.modules.
|
|
21
|
+
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@cli.group()
|
|
@@ -70,22 +70,26 @@ async def test_create(ctx: click.Context, pipe: str, prompt: Optional[str], fold
|
|
|
70
70
|
pipe_path = Path(folder) / pipe_path
|
|
71
71
|
pipe_content = pipe_path.read_text()
|
|
72
72
|
|
|
73
|
-
llm_config = CLIConfig.get_llm_config()
|
|
74
|
-
llm = LLM(key=llm_config["api_key"])
|
|
75
|
-
|
|
76
73
|
client = await get_tinybird_local_client(os.path.abspath(folder))
|
|
74
|
+
pipe_nodes = await client._req(f"/v0/pipes/{pipe_name}")
|
|
75
|
+
pipe_params = set([param["name"] for node in pipe_nodes["nodes"] for param in node["params"]])
|
|
76
|
+
|
|
77
|
+
llm_config = CLIConfig.get_llm_config()
|
|
78
|
+
llm = LLM(client=client, api_key=llm_config["api_key"])
|
|
77
79
|
|
|
78
|
-
test_expectations = await llm.create_test_commands(
|
|
80
|
+
test_expectations = await llm.create_test_commands(
|
|
81
|
+
pipe_content=pipe_content, pipe_params=pipe_params, context=prompt
|
|
82
|
+
)
|
|
79
83
|
valid_test_expectations = []
|
|
80
84
|
for test in test_expectations.tests:
|
|
85
|
+
test_params = test.parameters if test.parameters.startswith("?") else f"?{test.parameters}"
|
|
81
86
|
try:
|
|
82
|
-
response = await client._req(f"/v0/pipes/{pipe_name}.ndjson
|
|
87
|
+
response = await client._req(f"/v0/pipes/{pipe_name}.ndjson{test_params}")
|
|
83
88
|
except Exception:
|
|
84
89
|
continue
|
|
85
90
|
|
|
86
|
-
test.expected_result =
|
|
91
|
+
test.expected_result = response
|
|
87
92
|
valid_test_expectations.append(test.model_dump())
|
|
88
|
-
|
|
89
93
|
if valid_test_expectations:
|
|
90
94
|
generate_test_file(pipe_name, valid_test_expectations)
|
|
91
95
|
click.echo(FeedbackManager.info(message=f"✓ /tests/{pipe_name}.yaml"))
|
|
@@ -117,11 +121,18 @@ async def test_run(ctx: click.Context, file: Tuple[str, ...], folder: Optional[s
|
|
|
117
121
|
for test in test_file_content:
|
|
118
122
|
try:
|
|
119
123
|
response = await client._req(f"/v0/pipes/{test_file_path.stem}.ndjson?{test['parameters']}")
|
|
120
|
-
if test["expected_result"] !=
|
|
121
|
-
|
|
124
|
+
if test["expected_result"] != response:
|
|
125
|
+
diff = difflib.ndiff(
|
|
126
|
+
test["expected_result"].splitlines(keepends=True), response.splitlines(keepends=True)
|
|
127
|
+
)
|
|
128
|
+
printable_diff = "".join(diff)
|
|
129
|
+
raise Exception(
|
|
130
|
+
f"\nExpected: \n{test['expected_result']}\nGot: \n{response}\nDiff: \n{printable_diff}"
|
|
131
|
+
)
|
|
122
132
|
click.echo(FeedbackManager.success(message=f"✓ {test_file_path.name} - {test['name']}"))
|
|
123
|
-
except Exception:
|
|
133
|
+
except Exception as e:
|
|
124
134
|
click.echo(FeedbackManager.error(message=f"✗ {test_file_path.name} - {test['name']}"))
|
|
135
|
+
click.echo(FeedbackManager.error(message=f"Output and expected output are different: \n{e}"))
|
|
125
136
|
|
|
126
137
|
for test_file in file_list:
|
|
127
138
|
await run_test(test_file)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
tinybird/__cli__.py,sha256=pgYsVLcqL16wtSn6KtKweNZYoYJdEksTgSvQAW7hH64,250
|
|
2
|
-
tinybird/client.py,sha256=
|
|
2
|
+
tinybird/client.py,sha256=_voHMgKOwOhmncSaPH6DL94X6YPqKJJzcY-EKp8xLug,50810
|
|
3
3
|
tinybird/config.py,sha256=Z-BX9FrjgsLw1YwcCdF0IztLB97Zpc70VVPplO_pDSY,6089
|
|
4
4
|
tinybird/connectors.py,sha256=lkpVSUmSuViEZBa4QjTK7YmPHUop0a5UFoTrSmlVq6k,15244
|
|
5
5
|
tinybird/context.py,sha256=kutUQ0kCwparowI74_YLXx6wtTzGLRouJ6oGHVBPzBo,1291
|
|
6
6
|
tinybird/datatypes.py,sha256=IHyhZ86ib54Vnd1pbod9y2aS8DDvDKZm1HJGlThdbuQ,10460
|
|
7
|
-
tinybird/feedback_manager.py,sha256
|
|
7
|
+
tinybird/feedback_manager.py,sha256=U_VbVl3RBCPV-qc3g8vz124Mg9XeMmKk055eqiIaBqY,68338
|
|
8
8
|
tinybird/git_settings.py,sha256=XUL9ZUj59-ZVQJDYmMEq4UpnuuOuQOHGlNcX3JgQHjQ,3954
|
|
9
|
+
tinybird/prompts.py,sha256=pCqN1JAHqdA-PgxBnzjIYpCXjpko89Xu4EHl6ol0iNs,6417
|
|
9
10
|
tinybird/sql.py,sha256=gfRKjdqEygcE1WOTeQ1QV2Jal8Jzl4RSX8fftu1KSEs,45825
|
|
10
11
|
tinybird/sql_template.py,sha256=IqYRfUxDYBCoOYjqqvn--_8QXLv9FSRnJ0bInx7q1Xs,93051
|
|
11
12
|
tinybird/sql_template_fmt.py,sha256=1z-PuqSZXtzso8Z_mPqUc-NxIxUrNUcVIPezNieZk-M,10196
|
|
@@ -14,31 +15,32 @@ tinybird/syncasync.py,sha256=fAvq0qkRgqXqXMKwbY2iJNYqLT_r6mDsh1MRpGKrdRU,27763
|
|
|
14
15
|
tinybird/tornado_template.py,sha256=o2HguxrL1Evnt8o3IvrsI8Zm6JtRQ3zhLJKf1XyR3SQ,41965
|
|
15
16
|
tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
|
|
16
17
|
tinybird/ch_utils/engine.py,sha256=OXkBhlzGjZotjD0vaT-rFIbSGV4tpiHxE8qO_ip0SyQ,40454
|
|
17
|
-
tinybird/tb/cli.py,sha256=
|
|
18
|
+
tinybird/tb/cli.py,sha256=VS9cy6FfiQkpc1-ps7snwuw7bB_F8Z6FEmucEo3jrzY,849
|
|
18
19
|
tinybird/tb/modules/auth.py,sha256=hynZ-Temot8YBsySUWKSFzZlYadtFPxG3o6lCSu1n6E,9018
|
|
19
20
|
tinybird/tb/modules/branch.py,sha256=R1tTUBGyI0p_dt2IAWbuyNOvemhjCIPwYxEmOxL3zOg,38468
|
|
20
|
-
tinybird/tb/modules/build.py,sha256=
|
|
21
|
+
tinybird/tb/modules/build.py,sha256=c9fC9qBSw7__MJreJZIpHGvKD6CgyL0zMtparmJCUgw,8001
|
|
22
|
+
tinybird/tb/modules/build_shell.py,sha256=oqIMZYaQ8cpaE1C09shAsiFUlWb-zwawuf6FLqRotBU,5209
|
|
21
23
|
tinybird/tb/modules/cicd.py,sha256=KCFfywFfvGRh24GZwqrhICiTK_arHelPs_X4EB-pXIw,7331
|
|
22
|
-
tinybird/tb/modules/cli.py,sha256=
|
|
24
|
+
tinybird/tb/modules/cli.py,sha256=UAVZfdQhw-zKokKlEL23Uup55adT1JVwcK9HPDv6TJQ,55759
|
|
23
25
|
tinybird/tb/modules/common.py,sha256=Vubc2AIR8BfEupnT5e1Y8OYGEyvNoIcjo8th-SaUflw,80111
|
|
24
26
|
tinybird/tb/modules/config.py,sha256=ppWvACHrSLkb5hOoQLYNby2w8jR76-8Kx2NBCst7ntQ,11760
|
|
25
27
|
tinybird/tb/modules/connection.py,sha256=ZSqBGoRiJedjHKEyB_fr1ybucOHtaad8d7uqGa2Q92M,28668
|
|
26
|
-
tinybird/tb/modules/create.py,sha256=
|
|
28
|
+
tinybird/tb/modules/create.py,sha256=BiqeuRtDM9dfIB3wwFrO5SNqdafr1hWDwma4oNHYris,42432
|
|
27
29
|
tinybird/tb/modules/datasource.py,sha256=tjcf5o-HYIdTkb_c1ErGUFIE-W6G992vsvCuDGcxb9Q,35818
|
|
28
30
|
tinybird/tb/modules/exceptions.py,sha256=4A2sSjCEqKUMqpP3WI00zouCWW4uLaghXXLZBSw04mY,3363
|
|
29
31
|
tinybird/tb/modules/fmt.py,sha256=UszEQO15fdzQ49QEj7Unhu68IKwSuKPsOrKhk2p2TAg,3547
|
|
30
32
|
tinybird/tb/modules/job.py,sha256=eoBVyA24lYIPonU88Jn7FF9hBKz1kScy9_w_oWreuc4,2952
|
|
31
|
-
tinybird/tb/modules/llm.py,sha256=
|
|
32
|
-
tinybird/tb/modules/local.py,sha256=
|
|
33
|
+
tinybird/tb/modules/llm.py,sha256=duwf3r1caLaKzuOJC_stI_Hx54ncr-5uWaWeZ3HN65o,3099
|
|
34
|
+
tinybird/tb/modules/local.py,sha256=hV2fvHPaVHVzKwVoVDFAIbJZslOX1_COx96DZrR-dW8,5151
|
|
35
|
+
tinybird/tb/modules/local_common.py,sha256=Z2JYIE5HIJVNuzdIv1_NFxSEs8DOsLoCzbmT1iy0-HU,2172
|
|
33
36
|
tinybird/tb/modules/login.py,sha256=vIeysdttfGDBMkf_i3cqAVNR5s0X0D6exATcRsDdWiA,5849
|
|
34
|
-
tinybird/tb/modules/mock.py,sha256=
|
|
37
|
+
tinybird/tb/modules/mock.py,sha256=5xhR_djr1-_JwJPI-Oy1exI3ndz7srmmvlei0DMQCAk,2824
|
|
35
38
|
tinybird/tb/modules/pipe.py,sha256=9wnfKbp2FkmLiJgVk3qbra76ktwsUTXghu6j9cCEahQ,31058
|
|
36
|
-
tinybird/tb/modules/prompts.py,sha256=KIDoAC0FvBSSsTft_mhpY6vxliNRBYOCsqikr_Hx0HA,9655
|
|
37
39
|
tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
38
40
|
tinybird/tb/modules/table.py,sha256=hG-PRDVuFp2uph41WpoLRV1yjp3RI2fi_iGGiI0rdxU,7695
|
|
39
41
|
tinybird/tb/modules/tag.py,sha256=1qQWyk1p3Btv3LzM8VbJG-k7x2-pFuAlYCg3QL6QewI,3480
|
|
40
42
|
tinybird/tb/modules/telemetry.py,sha256=iEGnMuCuNhvF6ln__j6X9MSTwL_0Hm-GgFHHHvhfknk,10466
|
|
41
|
-
tinybird/tb/modules/test.py,sha256=
|
|
43
|
+
tinybird/tb/modules/test.py,sha256=wWOg9FFyAiJkF7qjYapJUne5SVyXwmmhStfpCAZwZ80,5383
|
|
42
44
|
tinybird/tb/modules/token.py,sha256=r0oeG1RpOOzHtqbUaHBiOmhE55HfNIvReAAWyKl9fJg,12695
|
|
43
45
|
tinybird/tb/modules/workspace.py,sha256=FVlh-kbiZp5Gvp6dGFxi0UD8ail77rMamXLhqdVwrZ0,10916
|
|
44
46
|
tinybird/tb/modules/workspace_members.py,sha256=08W0onEYkKLEC5TkAI07cxN9XSquEm7HnL7OkHAVDjo,8715
|
|
@@ -65,8 +67,8 @@ tinybird/tb_cli_modules/config.py,sha256=6NTgIdwf0X132A1j6G_YrdPep87ymZ9b5pABabK
|
|
|
65
67
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
66
68
|
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
67
69
|
tinybird/tb_cli_modules/telemetry.py,sha256=iEGnMuCuNhvF6ln__j6X9MSTwL_0Hm-GgFHHHvhfknk,10466
|
|
68
|
-
tinybird-0.0.1.
|
|
69
|
-
tinybird-0.0.1.
|
|
70
|
-
tinybird-0.0.1.
|
|
71
|
-
tinybird-0.0.1.
|
|
72
|
-
tinybird-0.0.1.
|
|
70
|
+
tinybird-0.0.1.dev16.dist-info/METADATA,sha256=Mo5LrkfYVPxIL2oqfKIjc2uUG41aYEzeVyTvWYI_abY,2405
|
|
71
|
+
tinybird-0.0.1.dev16.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
72
|
+
tinybird-0.0.1.dev16.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
|
|
73
|
+
tinybird-0.0.1.dev16.dist-info/top_level.txt,sha256=pgw6AzERHBcW3YTi2PW4arjxLkulk2msOz_SomfOEuc,45
|
|
74
|
+
tinybird-0.0.1.dev16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|