tinybird 0.0.1.dev14__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 -42
- tinybird/tb/cli.py +1 -0
- tinybird/tb/modules/build.py +17 -93
- tinybird/tb/modules/build_shell.py +133 -0
- tinybird/tb/modules/cli.py +14 -21
- tinybird/tb/modules/create.py +245 -10
- tinybird/tb/modules/datafile/build.py +29 -21
- tinybird/tb/modules/datafile/build_pipe.py +4 -0
- tinybird/tb/modules/datafile/common.py +989 -49
- tinybird/tb/modules/datafile/parse_datasource.py +1 -0
- tinybird/tb/modules/llm.py +57 -35
- tinybird/tb/modules/local.py +2 -47
- tinybird/tb/modules/local_common.py +54 -0
- tinybird/tb/modules/login.py +0 -1
- tinybird/tb/modules/mock.py +6 -5
- tinybird/tb/modules/test.py +104 -73
- {tinybird-0.0.1.dev14.dist-info → tinybird-0.0.1.dev16.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev14.dist-info → tinybird-0.0.1.dev16.dist-info}/RECORD +23 -22
- tinybird/check_pypi.py +0 -25
- {tinybird-0.0.1.dev14.dist-info → tinybird-0.0.1.dev16.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev14.dist-info → tinybird-0.0.1.dev16.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev14.dist-info → tinybird-0.0.1.dev16.dist-info}/top_level.txt +0 -0
tinybird/tb/modules/llm.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import json
|
|
3
|
+
import urllib.parse
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from typing import Awaitable, Callable, List, Optional
|
|
4
6
|
|
|
5
7
|
from openai import OpenAI
|
|
6
8
|
from pydantic import BaseModel
|
|
7
9
|
|
|
8
10
|
from tinybird.client import TinyB
|
|
9
|
-
from tinybird.
|
|
11
|
+
from tinybird.prompts import create_test_calls_prompt
|
|
12
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class DataFile(BaseModel):
|
|
@@ -19,9 +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,
|
|
24
|
-
self.client =
|
|
37
|
+
def __init__(self, client: TinyB, api_key: Optional[str] = None):
|
|
38
|
+
self.client = client
|
|
39
|
+
user_token = CLIConfig.get_project_config().get_user_token()
|
|
40
|
+
user_client = deepcopy(client)
|
|
41
|
+
user_client.token = user_token
|
|
42
|
+
self.user_client = user_client
|
|
43
|
+
self.openai = OpenAI(api_key=api_key) if api_key else None
|
|
25
44
|
|
|
26
45
|
async def _execute(self, action_fn: Callable[[], Awaitable[str]], checker_fn: Callable[[str], bool]):
|
|
27
46
|
is_valid = False
|
|
@@ -38,36 +57,39 @@ class LLM:
|
|
|
38
57
|
return result
|
|
39
58
|
|
|
40
59
|
async def create_project(self, prompt: str) -> DataProject:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
async def generate_sql_sample_data(
|
|
49
|
-
self, tb_client: TinyB, datasource_content: str, row_count: int = 20, context: str = ""
|
|
50
|
-
) -> str:
|
|
51
|
-
async def action_fn():
|
|
52
|
-
response = self.client.chat.completions.create(
|
|
53
|
-
model="gpt-4o-mini",
|
|
54
|
-
messages=[
|
|
55
|
-
{
|
|
56
|
-
"role": "system",
|
|
57
|
-
"content": sample_data_sql_prompt.format(
|
|
58
|
-
current_datetime=datetime.now().isoformat(), row_count=row_count, context=context
|
|
59
|
-
),
|
|
60
|
-
},
|
|
61
|
-
{"role": "user", "content": datasource_content},
|
|
62
|
-
],
|
|
60
|
+
try:
|
|
61
|
+
response = await self.user_client._req(
|
|
62
|
+
"/v0/llm/create",
|
|
63
|
+
method="POST",
|
|
64
|
+
data=f'{{"prompt": {json.dumps(prompt)}}}',
|
|
65
|
+
headers={"Content-Type": "application/json"},
|
|
63
66
|
)
|
|
64
|
-
return response.choices[0].message.content or ""
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
return DataProject.model_validate(response.get("result", {}))
|
|
69
|
+
except Exception:
|
|
70
|
+
return DataProject(datasources=[], pipes=[])
|
|
71
|
+
|
|
72
|
+
async def generate_sql_sample_data(self, schema: str, rows: int = 20, context: str = "") -> str:
|
|
73
|
+
response = await self.user_client._req(
|
|
74
|
+
"/v0/llm/mock",
|
|
75
|
+
method="POST",
|
|
76
|
+
data=f'{{"schema": "{urllib.parse.quote(schema)}", "rows": {rows}, "context": "{urllib.parse.quote(context)}"}}',
|
|
77
|
+
headers={"Content-Type": "application/json"},
|
|
78
|
+
)
|
|
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")
|
|
72
86
|
|
|
73
|
-
|
|
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,40 +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
|
-
config.persist_to_file()
|
|
147
|
-
return config.get_client(host=TB_LOCAL_HOST, token=token)
|
|
148
|
-
|
|
149
|
-
|
|
150
105
|
@cli.command()
|
|
151
106
|
def upgrade():
|
|
152
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/login.py
CHANGED
|
@@ -162,7 +162,6 @@ def login(host: str, workspace: str):
|
|
|
162
162
|
cli_config.set_token_for_host(workspace_token, host)
|
|
163
163
|
cli_config.set_user_token(user_token)
|
|
164
164
|
config.set_host(host)
|
|
165
|
-
|
|
166
165
|
cli_config.persist_to_file()
|
|
167
166
|
click.echo(FeedbackManager.success(message="✓ Authentication successful!"))
|
|
168
167
|
else:
|
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()
|
|
@@ -45,13 +45,14 @@ async def mock(datasource: str, rows: int, context: str, folder: str) -> None:
|
|
|
45
45
|
click.echo(FeedbackManager.gray(message=f"Overriding context for {datasource_name}..."))
|
|
46
46
|
context_path.write_text(context)
|
|
47
47
|
|
|
48
|
-
|
|
49
48
|
click.echo(FeedbackManager.gray(message=f"Creating fixture for {datasource_name}..."))
|
|
50
49
|
datasource_content = datasource_path.read_text()
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
config = CLIConfig.get_project_config()
|
|
51
|
+
user_client = config.get_client()
|
|
52
|
+
user_client.token = config.get_user_token()
|
|
53
|
+
llm = LLM(client=user_client)
|
|
53
54
|
tb_client = await get_tinybird_local_client(os.path.abspath(folder))
|
|
54
|
-
sql = await llm.generate_sql_sample_data(
|
|
55
|
+
sql = await llm.generate_sql_sample_data(datasource_content, rows=rows, context=context)
|
|
55
56
|
result = await tb_client.query(f"{sql} FORMAT JSON")
|
|
56
57
|
data = result.get("data", [])[:rows]
|
|
57
58
|
fixture_name = build_fixture_name(datasource_path.absolute(), datasource_name, datasource_content)
|
tinybird/tb/modules/test.py
CHANGED
|
@@ -3,24 +3,22 @@
|
|
|
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
|
-
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Iterable, List, Optional, Tuple
|
|
8
11
|
|
|
9
12
|
import click
|
|
13
|
+
import yaml
|
|
10
14
|
|
|
11
|
-
from tinybird.client import AuthNoTokenException
|
|
12
15
|
from tinybird.feedback_manager import FeedbackManager
|
|
13
16
|
from tinybird.tb.modules.cli import cli
|
|
14
|
-
from tinybird.tb.modules.common import coro
|
|
17
|
+
from tinybird.tb.modules.common import coro
|
|
15
18
|
from tinybird.tb.modules.config import CLIConfig
|
|
16
19
|
from tinybird.tb.modules.exceptions import CLIException
|
|
17
|
-
from tinybird.tb.modules.
|
|
18
|
-
|
|
19
|
-
generate_file,
|
|
20
|
-
parse_file,
|
|
21
|
-
run_test_file,
|
|
22
|
-
test_run_summary,
|
|
23
|
-
)
|
|
20
|
+
from tinybird.tb.modules.llm import LLM, TestExpectation
|
|
21
|
+
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
@cli.group()
|
|
@@ -30,78 +28,111 @@ def test(ctx: click.Context) -> None:
|
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
@test.command(
|
|
33
|
-
name="
|
|
34
|
-
help="
|
|
31
|
+
name="create",
|
|
32
|
+
help="Create a test for an existing endpoint",
|
|
35
33
|
)
|
|
36
|
-
@click.argument("
|
|
37
|
-
@click.option(
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
@click.argument("pipe", type=str)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--folder",
|
|
37
|
+
default=".",
|
|
38
|
+
type=click.Path(exists=True, file_okay=False),
|
|
39
|
+
help="Folder where datafiles will be placed",
|
|
40
|
+
)
|
|
41
|
+
@click.option("--prompt", type=str, default=None, help="Prompt to be used to create the test")
|
|
40
42
|
@click.pass_context
|
|
41
43
|
@coro
|
|
42
|
-
async def
|
|
43
|
-
|
|
44
|
+
async def test_create(ctx: click.Context, pipe: str, prompt: Optional[str], folder: Optional[str]) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Create a test for an existing endpoint
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def generate_test_file(pipe_name: str, tests: List[TestExpectation]):
|
|
50
|
+
base = Path("tests")
|
|
51
|
+
if folder:
|
|
52
|
+
base = Path(folder) / base
|
|
53
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
|
|
55
|
+
path = base / f"{pipe_name}.yaml"
|
|
56
|
+
with open(path, "w") as f:
|
|
57
|
+
yaml.dump(tests, f)
|
|
44
58
|
|
|
45
59
|
try:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
pipe_path = Path(pipe)
|
|
61
|
+
pipe_name = pipe
|
|
62
|
+
if pipe_path.suffix == ".pipe":
|
|
63
|
+
pipe_name = pipe_path.stem
|
|
64
|
+
else:
|
|
65
|
+
pipe_path = Path("endpoints", f"{pipe}.pipe")
|
|
66
|
+
if not pipe_path.exists():
|
|
67
|
+
pipe_path = Path("pipes", f"{pipe}.pipe")
|
|
68
|
+
|
|
69
|
+
click.echo(FeedbackManager.gray(message=f"\nCreating tests for {pipe_name} endpoint..."))
|
|
70
|
+
pipe_path = Path(folder) / pipe_path
|
|
71
|
+
pipe_content = pipe_path.read_text()
|
|
72
|
+
|
|
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"])
|
|
79
|
+
|
|
80
|
+
test_expectations = await llm.create_test_commands(
|
|
81
|
+
pipe_content=pipe_content, pipe_params=pipe_params, context=prompt
|
|
53
82
|
)
|
|
83
|
+
valid_test_expectations = []
|
|
84
|
+
for test in test_expectations.tests:
|
|
85
|
+
test_params = test.parameters if test.parameters.startswith("?") else f"?{test.parameters}"
|
|
86
|
+
try:
|
|
87
|
+
response = await client._req(f"/v0/pipes/{pipe_name}.ndjson{test_params}")
|
|
88
|
+
except Exception:
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
test.expected_result = response
|
|
92
|
+
valid_test_expectations.append(test.model_dump())
|
|
93
|
+
if valid_test_expectations:
|
|
94
|
+
generate_test_file(pipe_name, valid_test_expectations)
|
|
95
|
+
click.echo(FeedbackManager.info(message=f"✓ /tests/{pipe_name}.yaml"))
|
|
96
|
+
click.echo(FeedbackManager.success(message="✓ Done!\n"))
|
|
54
97
|
except Exception as e:
|
|
55
98
|
raise CLIException(FeedbackManager.error_exception(error=e))
|
|
56
99
|
|
|
57
|
-
file_list: Iterable[str] = file if len(file) > 0 else glob.glob("./tests/**/*.y*ml", recursive=True)
|
|
58
|
-
click.echo(FeedbackManager.info_skipping_resource(resource="regression.yaml"))
|
|
59
|
-
file_list = [f for f in file_list if not f.endswith("regression.yaml")]
|
|
60
|
-
final_file_list = []
|
|
61
|
-
for f in file_list:
|
|
62
|
-
if "skip_in_branch" in f and current_ws and current_ws.get("is_branch"):
|
|
63
|
-
click.echo(FeedbackManager.info_skipping_resource(resource=f))
|
|
64
|
-
else:
|
|
65
|
-
final_file_list.append(f)
|
|
66
|
-
file_list = final_file_list
|
|
67
|
-
|
|
68
|
-
async def run_test(tb_client, test_file, results):
|
|
69
|
-
try:
|
|
70
|
-
test_result = await run_test_file(tb_client, test_file)
|
|
71
|
-
results.append(TestSummaryResults(filename=test_file, results=test_result, semver=tb_client.semver))
|
|
72
|
-
except Exception as e:
|
|
73
|
-
if verbose:
|
|
74
|
-
click.echo(FeedbackManager.error_exception(error=e))
|
|
75
|
-
raise CLIException(FeedbackManager.error_running_test(file=test_file))
|
|
76
|
-
|
|
77
|
-
test_tasks = [run_test(tb_client, test_file, results) for test_file in file_list]
|
|
78
|
-
await gather_with_concurrency(concurrency, *test_tasks)
|
|
79
|
-
|
|
80
|
-
if len(results) <= 0:
|
|
81
|
-
click.echo(FeedbackManager.warning_no_test_results())
|
|
82
|
-
else:
|
|
83
|
-
test_run_summary(results, only_fail=only_fail, verbose_level=int(verbose))
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@test.command(name="init", help="Initialize a file list with a simple test suite.")
|
|
87
|
-
@click.argument("files", nargs=-1)
|
|
88
|
-
@click.option("--force", is_flag=True, default=False, help="Override existing files")
|
|
89
|
-
@click.pass_context
|
|
90
|
-
@coro
|
|
91
|
-
async def test_init(ctx: click.Context, files: Tuple[str, ...], force: bool) -> None:
|
|
92
|
-
if len(files) == 0:
|
|
93
|
-
files = ("tests/default.yaml",)
|
|
94
|
-
|
|
95
|
-
for file in files:
|
|
96
|
-
generate_file(file, overwrite=force)
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
@test.command(
|
|
102
|
+
name="run",
|
|
103
|
+
help="Run the test suite, a file, or a test. To skip test to run in branches and CI put them in a 'skip_in_branch' folder.",
|
|
104
|
+
)
|
|
105
|
+
@click.argument("file", nargs=-1)
|
|
106
|
+
@click.option(
|
|
107
|
+
"--folder",
|
|
108
|
+
default=".",
|
|
109
|
+
type=click.Path(exists=True, file_okay=False),
|
|
110
|
+
help="Folder where tests will be placed",
|
|
111
|
+
)
|
|
101
112
|
@click.pass_context
|
|
102
113
|
@coro
|
|
103
|
-
async def
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
async def test_run(ctx: click.Context, file: Tuple[str, ...], folder: Optional[str]) -> None:
|
|
115
|
+
client = await get_tinybird_local_client(os.path.abspath(folder))
|
|
116
|
+
file_list: Iterable[str] = file if len(file) > 0 else glob.glob("./tests/**/*.y*ml", recursive=True)
|
|
117
|
+
|
|
118
|
+
async def run_test(test_file):
|
|
119
|
+
test_file_path = Path(test_file)
|
|
120
|
+
test_file_content = yaml.safe_load(test_file_path.read_text())
|
|
121
|
+
for test in test_file_content:
|
|
122
|
+
try:
|
|
123
|
+
response = await client._req(f"/v0/pipes/{test_file_path.stem}.ndjson?{test['parameters']}")
|
|
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
|
+
)
|
|
132
|
+
click.echo(FeedbackManager.success(message=f"✓ {test_file_path.name} - {test['name']}"))
|
|
133
|
+
except Exception as e:
|
|
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}"))
|
|
136
|
+
|
|
137
|
+
for test_file in file_list:
|
|
138
|
+
await run_test(test_file)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
tinybird/__cli__.py,sha256=pgYsVLcqL16wtSn6KtKweNZYoYJdEksTgSvQAW7hH64,250
|
|
2
|
-
tinybird/
|
|
3
|
-
tinybird/client.py,sha256=nd97gD2-8Ap8yDonBcVwk9eXDAL43hmIYdo-Pse43RE,50738
|
|
2
|
+
tinybird/client.py,sha256=_voHMgKOwOhmncSaPH6DL94X6YPqKJJzcY-EKp8xLug,50810
|
|
4
3
|
tinybird/config.py,sha256=Z-BX9FrjgsLw1YwcCdF0IztLB97Zpc70VVPplO_pDSY,6089
|
|
5
4
|
tinybird/connectors.py,sha256=lkpVSUmSuViEZBa4QjTK7YmPHUop0a5UFoTrSmlVq6k,15244
|
|
6
5
|
tinybird/context.py,sha256=kutUQ0kCwparowI74_YLXx6wtTzGLRouJ6oGHVBPzBo,1291
|
|
7
6
|
tinybird/datatypes.py,sha256=IHyhZ86ib54Vnd1pbod9y2aS8DDvDKZm1HJGlThdbuQ,10460
|
|
8
|
-
tinybird/feedback_manager.py,sha256
|
|
7
|
+
tinybird/feedback_manager.py,sha256=U_VbVl3RBCPV-qc3g8vz124Mg9XeMmKk055eqiIaBqY,68338
|
|
9
8
|
tinybird/git_settings.py,sha256=XUL9ZUj59-ZVQJDYmMEq4UpnuuOuQOHGlNcX3JgQHjQ,3954
|
|
9
|
+
tinybird/prompts.py,sha256=pCqN1JAHqdA-PgxBnzjIYpCXjpko89Xu4EHl6ol0iNs,6417
|
|
10
10
|
tinybird/sql.py,sha256=gfRKjdqEygcE1WOTeQ1QV2Jal8Jzl4RSX8fftu1KSEs,45825
|
|
11
11
|
tinybird/sql_template.py,sha256=IqYRfUxDYBCoOYjqqvn--_8QXLv9FSRnJ0bInx7q1Xs,93051
|
|
12
12
|
tinybird/sql_template_fmt.py,sha256=1z-PuqSZXtzso8Z_mPqUc-NxIxUrNUcVIPezNieZk-M,10196
|
|
@@ -15,46 +15,47 @@ tinybird/syncasync.py,sha256=fAvq0qkRgqXqXMKwbY2iJNYqLT_r6mDsh1MRpGKrdRU,27763
|
|
|
15
15
|
tinybird/tornado_template.py,sha256=o2HguxrL1Evnt8o3IvrsI8Zm6JtRQ3zhLJKf1XyR3SQ,41965
|
|
16
16
|
tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
|
|
17
17
|
tinybird/ch_utils/engine.py,sha256=OXkBhlzGjZotjD0vaT-rFIbSGV4tpiHxE8qO_ip0SyQ,40454
|
|
18
|
-
tinybird/tb/cli.py,sha256
|
|
18
|
+
tinybird/tb/cli.py,sha256=VS9cy6FfiQkpc1-ps7snwuw7bB_F8Z6FEmucEo3jrzY,849
|
|
19
19
|
tinybird/tb/modules/auth.py,sha256=hynZ-Temot8YBsySUWKSFzZlYadtFPxG3o6lCSu1n6E,9018
|
|
20
20
|
tinybird/tb/modules/branch.py,sha256=R1tTUBGyI0p_dt2IAWbuyNOvemhjCIPwYxEmOxL3zOg,38468
|
|
21
|
-
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
|
|
22
23
|
tinybird/tb/modules/cicd.py,sha256=KCFfywFfvGRh24GZwqrhICiTK_arHelPs_X4EB-pXIw,7331
|
|
23
|
-
tinybird/tb/modules/cli.py,sha256=
|
|
24
|
+
tinybird/tb/modules/cli.py,sha256=UAVZfdQhw-zKokKlEL23Uup55adT1JVwcK9HPDv6TJQ,55759
|
|
24
25
|
tinybird/tb/modules/common.py,sha256=Vubc2AIR8BfEupnT5e1Y8OYGEyvNoIcjo8th-SaUflw,80111
|
|
25
26
|
tinybird/tb/modules/config.py,sha256=ppWvACHrSLkb5hOoQLYNby2w8jR76-8Kx2NBCst7ntQ,11760
|
|
26
27
|
tinybird/tb/modules/connection.py,sha256=ZSqBGoRiJedjHKEyB_fr1ybucOHtaad8d7uqGa2Q92M,28668
|
|
27
|
-
tinybird/tb/modules/create.py,sha256=
|
|
28
|
+
tinybird/tb/modules/create.py,sha256=BiqeuRtDM9dfIB3wwFrO5SNqdafr1hWDwma4oNHYris,42432
|
|
28
29
|
tinybird/tb/modules/datasource.py,sha256=tjcf5o-HYIdTkb_c1ErGUFIE-W6G992vsvCuDGcxb9Q,35818
|
|
29
30
|
tinybird/tb/modules/exceptions.py,sha256=4A2sSjCEqKUMqpP3WI00zouCWW4uLaghXXLZBSw04mY,3363
|
|
30
31
|
tinybird/tb/modules/fmt.py,sha256=UszEQO15fdzQ49QEj7Unhu68IKwSuKPsOrKhk2p2TAg,3547
|
|
31
32
|
tinybird/tb/modules/job.py,sha256=eoBVyA24lYIPonU88Jn7FF9hBKz1kScy9_w_oWreuc4,2952
|
|
32
|
-
tinybird/tb/modules/llm.py,sha256=
|
|
33
|
-
tinybird/tb/modules/local.py,sha256=
|
|
34
|
-
tinybird/tb/modules/
|
|
35
|
-
tinybird/tb/modules/
|
|
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
|
|
36
|
+
tinybird/tb/modules/login.py,sha256=vIeysdttfGDBMkf_i3cqAVNR5s0X0D6exATcRsDdWiA,5849
|
|
37
|
+
tinybird/tb/modules/mock.py,sha256=5xhR_djr1-_JwJPI-Oy1exI3ndz7srmmvlei0DMQCAk,2824
|
|
36
38
|
tinybird/tb/modules/pipe.py,sha256=9wnfKbp2FkmLiJgVk3qbra76ktwsUTXghu6j9cCEahQ,31058
|
|
37
|
-
tinybird/tb/modules/prompts.py,sha256=g0cBW2ePzuftib02wV82VIcAZd59buAAusnirAbzqVE,8662
|
|
38
39
|
tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
39
40
|
tinybird/tb/modules/table.py,sha256=hG-PRDVuFp2uph41WpoLRV1yjp3RI2fi_iGGiI0rdxU,7695
|
|
40
41
|
tinybird/tb/modules/tag.py,sha256=1qQWyk1p3Btv3LzM8VbJG-k7x2-pFuAlYCg3QL6QewI,3480
|
|
41
42
|
tinybird/tb/modules/telemetry.py,sha256=iEGnMuCuNhvF6ln__j6X9MSTwL_0Hm-GgFHHHvhfknk,10466
|
|
42
|
-
tinybird/tb/modules/test.py,sha256=
|
|
43
|
+
tinybird/tb/modules/test.py,sha256=wWOg9FFyAiJkF7qjYapJUne5SVyXwmmhStfpCAZwZ80,5383
|
|
43
44
|
tinybird/tb/modules/token.py,sha256=r0oeG1RpOOzHtqbUaHBiOmhE55HfNIvReAAWyKl9fJg,12695
|
|
44
45
|
tinybird/tb/modules/workspace.py,sha256=FVlh-kbiZp5Gvp6dGFxi0UD8ail77rMamXLhqdVwrZ0,10916
|
|
45
46
|
tinybird/tb/modules/workspace_members.py,sha256=08W0onEYkKLEC5TkAI07cxN9XSquEm7HnL7OkHAVDjo,8715
|
|
46
|
-
tinybird/tb/modules/datafile/build.py,sha256=
|
|
47
|
+
tinybird/tb/modules/datafile/build.py,sha256=rFdK_GerPDgPfyPfZ4EZ0-cQqWfHd6htS0ls-Yy7khk,92491
|
|
47
48
|
tinybird/tb/modules/datafile/build_common.py,sha256=74547h5ja4C66DAwDMabj75FA_BUTJxTJv-24tSFmrs,4551
|
|
48
49
|
tinybird/tb/modules/datafile/build_datasource.py,sha256=fquzEGwk9NL_0K5YYG86Xtvgn4J5YHtRUoKJxbQGO0s,17344
|
|
49
|
-
tinybird/tb/modules/datafile/build_pipe.py,sha256=
|
|
50
|
-
tinybird/tb/modules/datafile/common.py,sha256=
|
|
50
|
+
tinybird/tb/modules/datafile/build_pipe.py,sha256=sSSl1rQMkR4uUbFCxK_aDXZi3JwKV64YZlBdBWgGKjo,27657
|
|
51
|
+
tinybird/tb/modules/datafile/common.py,sha256=NkoCdj4p-Ak3n80DJB5a33Ucw2WTcSYa8iqw4KsRZGs,81082
|
|
51
52
|
tinybird/tb/modules/datafile/diff.py,sha256=-iaP7GvAzZtZSa8jPgVpOFlTRutxgxRBLBcGL1_RFr4,6743
|
|
52
53
|
tinybird/tb/modules/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
|
|
53
54
|
tinybird/tb/modules/datafile/fixture.py,sha256=YHlL4tojmPwm343Y8KO6r7d5Bhsk7U3lKP-oLMeBMsY,1771
|
|
54
55
|
tinybird/tb/modules/datafile/format_common.py,sha256=zNWDXvwSKC9_T5e9R92LLj9ekDflVWwsllhGQilZsnY,2184
|
|
55
56
|
tinybird/tb/modules/datafile/format_datasource.py,sha256=tsnCjONISvhFuucKNbIHkT__UmlUbcswx5mwI9hiDQc,6216
|
|
56
57
|
tinybird/tb/modules/datafile/format_pipe.py,sha256=R5tnlEccLn3KX6ehtC_H2sGQNrthuJUiVSN9z_-KGCY,7474
|
|
57
|
-
tinybird/tb/modules/datafile/parse_datasource.py,sha256=
|
|
58
|
+
tinybird/tb/modules/datafile/parse_datasource.py,sha256=YKt4Sy830p2jqXW5jN1Bf60AM5o4gZ3QcZjxnyWIeBg,1190
|
|
58
59
|
tinybird/tb/modules/datafile/parse_pipe.py,sha256=STgA12LOLUnnb_cvVvZeEE4ka-nfk0jsNzxJhWj94cY,2599
|
|
59
60
|
tinybird/tb/modules/datafile/pipe_checker.py,sha256=cp80Bru41GlyMRvyERpdJNXns2MjmtIAWFnBLF4cPXs,24667
|
|
60
61
|
tinybird/tb/modules/datafile/pull.py,sha256=wBXBAZIruIyCRQZvfYxMc7h1q35NlKF-hFIF-bUm4iY,5956
|
|
@@ -66,8 +67,8 @@ tinybird/tb_cli_modules/config.py,sha256=6NTgIdwf0X132A1j6G_YrdPep87ymZ9b5pABabK
|
|
|
66
67
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
67
68
|
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
68
69
|
tinybird/tb_cli_modules/telemetry.py,sha256=iEGnMuCuNhvF6ln__j6X9MSTwL_0Hm-GgFHHHvhfknk,10466
|
|
69
|
-
tinybird-0.0.1.
|
|
70
|
-
tinybird-0.0.1.
|
|
71
|
-
tinybird-0.0.1.
|
|
72
|
-
tinybird-0.0.1.
|
|
73
|
-
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,,
|
tinybird/check_pypi.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
import requests
|
|
4
|
-
|
|
5
|
-
from tinybird.feedback_manager import FeedbackManager
|
|
6
|
-
from tinybird.syncasync import sync_to_async
|
|
7
|
-
from tinybird.tb_cli_modules.common import CLIException, getenv_bool
|
|
8
|
-
|
|
9
|
-
PYPY_URL = "https://pypi.org/pypi/tinybird/json"
|
|
10
|
-
requests_get = sync_to_async(requests.get, thread_sensitive=False)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class CheckPypi:
|
|
14
|
-
async def get_latest_version(self) -> Optional[str]:
|
|
15
|
-
version: Optional[str] = None
|
|
16
|
-
try:
|
|
17
|
-
disable_ssl: bool = getenv_bool("TB_DISABLE_SSL_CHECKS", False)
|
|
18
|
-
response: requests.Response = await requests_get(PYPY_URL, verify=not disable_ssl)
|
|
19
|
-
if response.status_code != 200:
|
|
20
|
-
raise CLIException(FeedbackManager.error_exception(error=response.content.decode("utf-8")))
|
|
21
|
-
version = response.json()["info"]["version"]
|
|
22
|
-
except Exception as e:
|
|
23
|
-
raise CLIException(FeedbackManager.error_exception(error=str(e)))
|
|
24
|
-
|
|
25
|
-
return version
|
|
File without changes
|
|
File without changes
|
|
File without changes
|