tinybird 0.0.1.dev17__tar.gz → 0.0.1.dev18__tar.gz
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-0.0.1.dev17 → tinybird-0.0.1.dev18}/PKG-INFO +1 -1
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/datafile.py +2 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/build.py +18 -10
- tinybird-0.0.1.dev18/tinybird/tb/modules/build_shell.py +326 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/cicd.py +9 -89
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/common.py +1 -108
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/create.py +2 -6
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/common.py +223 -247
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/parse_datasource.py +8 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/parse_pipe.py +10 -1
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/llm.py +4 -3
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/local_common.py +1 -1
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/login.py +1 -1
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/mock.py +14 -12
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/test.py +90 -17
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird.egg-info/PKG-INFO +1 -1
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird.egg-info/requires.txt +1 -0
- tinybird-0.0.1.dev17/tinybird/tb/modules/build_shell.py +0 -149
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/setup.cfg +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/__cli__.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/check_pypi.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/client.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/config.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/connectors.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/context.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/datatypes.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/feedback_manager.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/git_settings.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/prompts.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/sql.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/sql_template.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/sql_toolset.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/syncasync.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/cli.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/auth.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/cli.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/config.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/connection.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/build.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/build_common.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/diff.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/exceptions.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/fixture.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/format_common.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datafile/pull.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/datasource.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/exceptions.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/fmt.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/job.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/local.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/pipe.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/regions.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/table.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/tag.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/telemetry.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/token.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/workspace.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb/modules/workspace_members.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/common.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/datasource.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird/tornado_template.py +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird.egg-info/SOURCES.txt +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird.egg-info/dependency_links.txt +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird.egg-info/entry_points.txt +0 -0
- {tinybird-0.0.1.dev17 → tinybird-0.0.1.dev18}/tinybird.egg-info/top_level.txt +0 -0
|
@@ -212,6 +212,7 @@ class ExportReplacements:
|
|
|
212
212
|
("export_file_template", "file_template", None),
|
|
213
213
|
("export_format", "format", "csv"),
|
|
214
214
|
("export_compression", "compression", None),
|
|
215
|
+
("export_write_strategy", "write_strategy", None),
|
|
215
216
|
("export_strategy", "strategy", "@new"),
|
|
216
217
|
("export_kafka_topic", "kafka_topic", None),
|
|
217
218
|
("kafka_connection_name", "connection", None),
|
|
@@ -1239,6 +1240,7 @@ def parse(
|
|
|
1239
1240
|
"export_format": assign_var("export_format"),
|
|
1240
1241
|
"export_strategy": assign_var("export_strategy"),
|
|
1241
1242
|
"export_compression": assign_var("export_compression"),
|
|
1243
|
+
"export_write_strategy": assign_var("export_write_strategy"),
|
|
1242
1244
|
"export_kafka_topic": assign_var("export_kafka_topic"),
|
|
1243
1245
|
}
|
|
1244
1246
|
|
|
@@ -6,7 +6,6 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Any, Awaitable, Callable, List, Union
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
|
-
from click import Context
|
|
10
9
|
from watchdog.events import FileSystemEventHandler
|
|
11
10
|
from watchdog.observers import Observer
|
|
12
11
|
|
|
@@ -16,7 +15,7 @@ from tinybird.config import FeatureFlags
|
|
|
16
15
|
from tinybird.feedback_manager import FeedbackManager
|
|
17
16
|
from tinybird.tb.modules.build_shell import BuildShell, print_table_formatted
|
|
18
17
|
from tinybird.tb.modules.cli import cli
|
|
19
|
-
from tinybird.tb.modules.common import
|
|
18
|
+
from tinybird.tb.modules.common import push_data
|
|
20
19
|
from tinybird.tb.modules.datafile.build import folder_build
|
|
21
20
|
from tinybird.tb.modules.datafile.common import get_project_filenames, get_project_fixtures, has_internal_datafiles
|
|
22
21
|
from tinybird.tb.modules.datafile.exceptions import ParseException
|
|
@@ -101,10 +100,7 @@ def watch_files(
|
|
|
101
100
|
is_flag=True,
|
|
102
101
|
help="Watch for changes in the files and re-check them.",
|
|
103
102
|
)
|
|
104
|
-
|
|
105
|
-
@coro
|
|
106
|
-
async def build(
|
|
107
|
-
ctx: Context,
|
|
103
|
+
def build(
|
|
108
104
|
folder: str,
|
|
109
105
|
watch: bool,
|
|
110
106
|
) -> None:
|
|
@@ -115,7 +111,7 @@ async def build(
|
|
|
115
111
|
context.disable_template_security_validation.set(True)
|
|
116
112
|
is_internal = has_internal_datafiles(folder)
|
|
117
113
|
folder_path = os.path.abspath(folder)
|
|
118
|
-
tb_client =
|
|
114
|
+
tb_client = asyncio.run(get_tinybird_local_client(folder_path))
|
|
119
115
|
|
|
120
116
|
def check_filenames(filenames: List[str]):
|
|
121
117
|
parser_matrix = {".pipe": parse_pipe, ".datasource": parse_datasource}
|
|
@@ -191,16 +187,28 @@ async def build(
|
|
|
191
187
|
ok = False
|
|
192
188
|
return ok
|
|
193
189
|
|
|
194
|
-
build_ok =
|
|
190
|
+
build_ok = asyncio.run(build_once(filenames))
|
|
195
191
|
|
|
196
192
|
if watch:
|
|
197
|
-
|
|
193
|
+
paths = [Path(f) for f in get_project_filenames(folder, with_vendor=True)]
|
|
194
|
+
|
|
195
|
+
def is_vendor(f: Path) -> bool:
|
|
196
|
+
return "vendor/" in f.parts
|
|
197
|
+
|
|
198
|
+
def get_vendor_workspace(f: Path) -> str:
|
|
199
|
+
return f.parts[1]
|
|
200
|
+
|
|
201
|
+
datasource_paths = [f for f in paths if f.suffix == ".datasource"]
|
|
202
|
+
datasources = [f.stem for f in datasource_paths if not is_vendor(f)]
|
|
203
|
+
shared_datasources = [f"{get_vendor_workspace(f)}.{f.stem}" for f in datasource_paths if is_vendor(f)]
|
|
204
|
+
pipes = [f.stem for f in paths if f.suffix == ".pipe" and not is_vendor(f)]
|
|
205
|
+
shell = BuildShell(folder=folder, client=tb_client, datasources=datasources + shared_datasources, pipes=pipes)
|
|
198
206
|
click.echo(FeedbackManager.highlight(message="◎ Watching for changes..."))
|
|
199
207
|
watcher_thread = threading.Thread(
|
|
200
208
|
target=watch_files, args=(filenames, process, shell, folder, build_ok), daemon=True
|
|
201
209
|
)
|
|
202
210
|
watcher_thread.start()
|
|
203
|
-
shell.
|
|
211
|
+
shell.run_shell()
|
|
204
212
|
|
|
205
213
|
|
|
206
214
|
async def build_and_print_resource(tb_client: TinyB, filename: str):
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import concurrent.futures
|
|
3
|
+
import os
|
|
4
|
+
import random
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
import humanfriendly
|
|
11
|
+
from prompt_toolkit import PromptSession
|
|
12
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
13
|
+
from prompt_toolkit.history import FileHistory
|
|
14
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
15
|
+
from prompt_toolkit.shortcuts import CompleteStyle
|
|
16
|
+
from prompt_toolkit.styles import Style
|
|
17
|
+
|
|
18
|
+
from tinybird.client import TinyB
|
|
19
|
+
from tinybird.feedback_manager import FeedbackManager, bcolors
|
|
20
|
+
from tinybird.tb.modules.exceptions import CLIException
|
|
21
|
+
from tinybird.tb.modules.table import format_table
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DynamicCompleter(Completer):
|
|
25
|
+
def __init__(self, datasources: List[str], pipes: List[str]):
|
|
26
|
+
self.datasources = datasources
|
|
27
|
+
self.pipes = pipes
|
|
28
|
+
self.static_commands = ["create", "mock", "test", "select"]
|
|
29
|
+
self.mock_flags = ["--prompt", "--rows"]
|
|
30
|
+
self.common_rows = ["10", "50", "100", "500", "1000"]
|
|
31
|
+
self.sql_keywords = ["select", "from", "where", "group by", "order by", "limit"]
|
|
32
|
+
|
|
33
|
+
def get_completions(self, document, complete_event):
|
|
34
|
+
text = document.text_before_cursor.strip()
|
|
35
|
+
words = text.split()
|
|
36
|
+
|
|
37
|
+
# Normalize command by removing 'tb' prefix if present
|
|
38
|
+
if words and words[0] == "tb":
|
|
39
|
+
words = words[1:]
|
|
40
|
+
|
|
41
|
+
if not words:
|
|
42
|
+
# Show all available commands when no input
|
|
43
|
+
yield from self._yield_static_commands("")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
command = words[0].lower()
|
|
47
|
+
|
|
48
|
+
if command == "mock":
|
|
49
|
+
yield from self._handle_mock_completions(words)
|
|
50
|
+
elif command == "select" or self._is_sql_query(text.lower()):
|
|
51
|
+
yield from self._handle_sql_completions(text)
|
|
52
|
+
else:
|
|
53
|
+
# Handle general command completions
|
|
54
|
+
yield from self._yield_static_commands(words[-1])
|
|
55
|
+
|
|
56
|
+
def _is_sql_query(self, text: str) -> bool:
|
|
57
|
+
"""Check if the input looks like a SQL query."""
|
|
58
|
+
sql_starters = ["select", "with"]
|
|
59
|
+
return any(text.startswith(starter) for starter in sql_starters)
|
|
60
|
+
|
|
61
|
+
def _handle_sql_completions(self, text: str):
|
|
62
|
+
"""Handle completions for SQL queries."""
|
|
63
|
+
text_lower = text.lower()
|
|
64
|
+
|
|
65
|
+
# Find the last complete word
|
|
66
|
+
words = text_lower.split()
|
|
67
|
+
if not words:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
# If we just typed 'from' or there's a space after 'from', suggest datasources
|
|
71
|
+
if words[-1] == "from" or (
|
|
72
|
+
"from" in words and len(words) > words.index("from") + 1 and text_lower.endswith(" ")
|
|
73
|
+
):
|
|
74
|
+
for x in self.datasources:
|
|
75
|
+
yield Completion(x, start_position=0, display=x, style="class:completion.datasource")
|
|
76
|
+
for x in self.pipes:
|
|
77
|
+
yield Completion(x, start_position=0, display=x, style="class:completion.pipe")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
# If we're starting a query, suggest SQL keywords
|
|
81
|
+
if len(words) <= 2:
|
|
82
|
+
for keyword in self.sql_keywords:
|
|
83
|
+
if keyword.lower().startswith(words[-1]):
|
|
84
|
+
yield Completion(
|
|
85
|
+
keyword, start_position=-len(words[-1]), display=keyword, style="class:completion.keyword"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def _handle_mock_completions(self, words: List[str]):
|
|
89
|
+
if len(words) == 1:
|
|
90
|
+
# After 'mock', show datasources
|
|
91
|
+
for ds in self.datasources:
|
|
92
|
+
yield Completion(ds, start_position=0, display=ds, style="class:completion.cmd")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
if len(words) == 2 or len(words) == 4:
|
|
96
|
+
# After datasource or after a flag value, show available flags
|
|
97
|
+
available_flags = [f for f in self.mock_flags if f not in words]
|
|
98
|
+
for flag in available_flags:
|
|
99
|
+
yield Completion(flag, start_position=0, display=flag)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
last_word = words[-1]
|
|
103
|
+
if last_word == "--prompt":
|
|
104
|
+
yield Completion('""', start_position=0, display='"Enter your prompt..."')
|
|
105
|
+
elif last_word == "--rows":
|
|
106
|
+
for rows in self.common_rows:
|
|
107
|
+
yield Completion(rows, start_position=0, display=rows)
|
|
108
|
+
|
|
109
|
+
def _yield_static_commands(self, current_word: str):
|
|
110
|
+
for cmd in self.static_commands:
|
|
111
|
+
if cmd.startswith(current_word):
|
|
112
|
+
yield Completion(
|
|
113
|
+
cmd,
|
|
114
|
+
start_position=-len(current_word) if current_word else 0,
|
|
115
|
+
display=cmd,
|
|
116
|
+
style="class:completion.cmd",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
style = Style.from_dict(
|
|
121
|
+
{
|
|
122
|
+
"prompt": "fg:#34D399 bold",
|
|
123
|
+
"completion.cmd": "fg:#34D399 bg:#111111 bold",
|
|
124
|
+
"completion.datasource": "fg:#AB49D0 bg:#111111",
|
|
125
|
+
"completion.pipe": "fg:#FEA827 bg:#111111",
|
|
126
|
+
"completion.keyword": "fg:#34D399 bg:#111111",
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
key_bindings = KeyBindings()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@key_bindings.add("c-d")
|
|
134
|
+
def _(event):
|
|
135
|
+
"""
|
|
136
|
+
Start auto completion. If the menu is showing already, select the next
|
|
137
|
+
completion.
|
|
138
|
+
"""
|
|
139
|
+
b = event.app.current_buffer
|
|
140
|
+
if b.complete_state:
|
|
141
|
+
b.complete_next()
|
|
142
|
+
else:
|
|
143
|
+
b.start_completion(select_first=False)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class BuildShell:
|
|
147
|
+
def __init__(self, folder: str, client: TinyB, datasources: List[str], pipes: List[str]):
|
|
148
|
+
self.history = self.get_history()
|
|
149
|
+
self.folder = folder
|
|
150
|
+
self.client = client
|
|
151
|
+
self.datasources = datasources
|
|
152
|
+
self.pipes = pipes
|
|
153
|
+
self.prompt_message = "\ntb > "
|
|
154
|
+
self.commands = ["create", "mock", "test", "tb", "select"]
|
|
155
|
+
|
|
156
|
+
self.session = PromptSession(
|
|
157
|
+
completer=DynamicCompleter(self.datasources, self.pipes),
|
|
158
|
+
complete_style=CompleteStyle.COLUMN,
|
|
159
|
+
complete_while_typing=True,
|
|
160
|
+
history=self.history,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def get_history(self):
|
|
164
|
+
try:
|
|
165
|
+
history_file = os.path.expanduser("~/.tb_history")
|
|
166
|
+
return FileHistory(history_file)
|
|
167
|
+
except Exception:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
def run_shell(self):
|
|
171
|
+
while True:
|
|
172
|
+
try:
|
|
173
|
+
user_input = self.session.prompt(
|
|
174
|
+
[("class:prompt", self.prompt_message)], style=style, key_bindings=key_bindings
|
|
175
|
+
)
|
|
176
|
+
self.handle_input(user_input)
|
|
177
|
+
except (EOFError, KeyboardInterrupt):
|
|
178
|
+
sys.exit(0)
|
|
179
|
+
except CLIException as e:
|
|
180
|
+
click.echo(str(e))
|
|
181
|
+
except Exception as e:
|
|
182
|
+
# Catch-all for unexpected exceptions
|
|
183
|
+
click.echo(FeedbackManager.error_exception(error=str(e)))
|
|
184
|
+
|
|
185
|
+
def handle_input(self, argline):
|
|
186
|
+
line = argline.strip()
|
|
187
|
+
if not line:
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
# Implement the command logic here
|
|
191
|
+
# Replace do_* methods with equivalent logic:
|
|
192
|
+
command_parts = line.split(maxsplit=1)
|
|
193
|
+
cmd = command_parts[0].lower()
|
|
194
|
+
arg = command_parts[1] if len(command_parts) > 1 else ""
|
|
195
|
+
|
|
196
|
+
if cmd in ["exit", "quit"]:
|
|
197
|
+
sys.exit(0)
|
|
198
|
+
elif cmd == "build":
|
|
199
|
+
self.handle_build(arg)
|
|
200
|
+
elif cmd == "auth":
|
|
201
|
+
self.handle_auth(arg)
|
|
202
|
+
elif cmd == "workspace":
|
|
203
|
+
self.handle_workspace(arg)
|
|
204
|
+
elif cmd == "mock":
|
|
205
|
+
self.handle_mock(arg)
|
|
206
|
+
elif cmd == "tb":
|
|
207
|
+
self.handle_tb(arg)
|
|
208
|
+
else:
|
|
209
|
+
# Check if it looks like a SQL query or run as a tb command
|
|
210
|
+
self.default(line)
|
|
211
|
+
|
|
212
|
+
def handle_build(self, arg):
|
|
213
|
+
click.echo(FeedbackManager.error(message=f"'tb {arg}' command is not available in watch mode"))
|
|
214
|
+
|
|
215
|
+
def handle_auth(self, arg):
|
|
216
|
+
click.echo(FeedbackManager.error(message=f"'tb {arg}' command is not available in watch mode"))
|
|
217
|
+
|
|
218
|
+
def handle_workspace(self, arg):
|
|
219
|
+
click.echo(FeedbackManager.error(message=f"'tb {arg}' command is not available in watch mode"))
|
|
220
|
+
|
|
221
|
+
def handle_mock(self, arg):
|
|
222
|
+
subprocess.run(f"tb mock {arg} --folder {self.folder}", shell=True, text=True)
|
|
223
|
+
|
|
224
|
+
def handle_tb(self, arg):
|
|
225
|
+
click.echo("")
|
|
226
|
+
arg = arg.strip().lower()
|
|
227
|
+
if arg.startswith("build"):
|
|
228
|
+
self.handle_build(arg)
|
|
229
|
+
elif arg.startswith("auth"):
|
|
230
|
+
self.handle_auth(arg)
|
|
231
|
+
elif arg.startswith("workspace"):
|
|
232
|
+
self.handle_workspace(arg)
|
|
233
|
+
elif arg.startswith("mock"):
|
|
234
|
+
self.handle_mock(arg)
|
|
235
|
+
else:
|
|
236
|
+
subprocess.run(f"tb --local {arg}", shell=True, text=True)
|
|
237
|
+
|
|
238
|
+
def default(self, argline):
|
|
239
|
+
click.echo("")
|
|
240
|
+
arg = argline.strip().lower()
|
|
241
|
+
if not arg:
|
|
242
|
+
return
|
|
243
|
+
if arg.startswith("with") or arg.startswith("select"):
|
|
244
|
+
try:
|
|
245
|
+
self.run_sql(argline)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
click.echo(FeedbackManager.error(message=str(e)))
|
|
248
|
+
else:
|
|
249
|
+
subprocess.run(f"tb --local {arg}", shell=True, text=True)
|
|
250
|
+
|
|
251
|
+
def run_sql(self, query, rows_limit=20):
|
|
252
|
+
try:
|
|
253
|
+
q = query.strip()
|
|
254
|
+
if q.lower().startswith("insert"):
|
|
255
|
+
click.echo(FeedbackManager.info_append_data())
|
|
256
|
+
raise CLIException(FeedbackManager.error_invalid_query())
|
|
257
|
+
if q.lower().startswith("delete"):
|
|
258
|
+
raise CLIException(FeedbackManager.error_invalid_query())
|
|
259
|
+
|
|
260
|
+
def run_query_in_thread():
|
|
261
|
+
loop = asyncio.new_event_loop()
|
|
262
|
+
asyncio.set_event_loop(loop)
|
|
263
|
+
try:
|
|
264
|
+
return loop.run_until_complete(
|
|
265
|
+
self.client.query(f"SELECT * FROM ({query}) LIMIT {rows_limit} FORMAT JSON")
|
|
266
|
+
)
|
|
267
|
+
finally:
|
|
268
|
+
loop.close()
|
|
269
|
+
|
|
270
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
271
|
+
res = executor.submit(run_query_in_thread).result()
|
|
272
|
+
|
|
273
|
+
except Exception as e:
|
|
274
|
+
raise CLIException(FeedbackManager.error_exception(error=str(e)))
|
|
275
|
+
|
|
276
|
+
if isinstance(res, dict) and "error" in res:
|
|
277
|
+
raise CLIException(FeedbackManager.error_exception(error=res["error"]))
|
|
278
|
+
|
|
279
|
+
if isinstance(res, dict) and "data" in res and res["data"]:
|
|
280
|
+
print_table_formatted(res, "QUERY")
|
|
281
|
+
else:
|
|
282
|
+
click.echo(FeedbackManager.info_no_rows())
|
|
283
|
+
|
|
284
|
+
def reprint_prompt(self):
|
|
285
|
+
click.echo(f"{bcolors.OKGREEN}{self.prompt_message}{bcolors.ENDC}", nl=False)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def print_table_formatted(res: dict, name: str):
|
|
289
|
+
rebuild_colors = [bcolors.FAIL, bcolors.OKBLUE, bcolors.WARNING, bcolors.OKGREEN, bcolors.HEADER]
|
|
290
|
+
rebuild_index = random.randint(0, len(rebuild_colors) - 1)
|
|
291
|
+
rebuild_color = rebuild_colors[rebuild_index % len(rebuild_colors)]
|
|
292
|
+
data = []
|
|
293
|
+
limit = 5
|
|
294
|
+
for d in res["data"][:5]:
|
|
295
|
+
data.append(d.values())
|
|
296
|
+
meta = res["meta"]
|
|
297
|
+
row_count = res.get("rows", 0)
|
|
298
|
+
stats = res.get("statistics", {})
|
|
299
|
+
elapsed = stats.get("elapsed", 0)
|
|
300
|
+
cols = len(meta)
|
|
301
|
+
try:
|
|
302
|
+
|
|
303
|
+
def print_message(message: str, color=bcolors.CGREY):
|
|
304
|
+
return f"{color}{message}{bcolors.ENDC}"
|
|
305
|
+
|
|
306
|
+
table = format_table(data, meta)
|
|
307
|
+
colored_char = print_message("│", rebuild_color)
|
|
308
|
+
table_with_marker = "\n".join(f"{colored_char} {line}" for line in table.split("\n"))
|
|
309
|
+
click.echo(f"\n{colored_char} {print_message('⚡', rebuild_color)} Running {name}")
|
|
310
|
+
click.echo(colored_char)
|
|
311
|
+
click.echo(table_with_marker)
|
|
312
|
+
click.echo(colored_char)
|
|
313
|
+
rows_read = humanfriendly.format_number(stats.get("rows_read", 0))
|
|
314
|
+
bytes_read = humanfriendly.format_size(stats.get("bytes_read", 0))
|
|
315
|
+
elapsed = humanfriendly.format_timespan(elapsed) if elapsed >= 1 else f"{elapsed * 1000:.2f}ms"
|
|
316
|
+
stats_message = f"» {bytes_read} ({rows_read} rows x {cols} cols) in {elapsed}"
|
|
317
|
+
rows_message = f"» Showing {limit} first rows" if row_count > limit else "» Showing all rows"
|
|
318
|
+
click.echo(f"{colored_char} {print_message(stats_message, bcolors.OKGREEN)}")
|
|
319
|
+
click.echo(f"{colored_char} {print_message(rows_message, bcolors.CGREY)}")
|
|
320
|
+
except ValueError as exc:
|
|
321
|
+
if str(exc) == "max() arg is an empty sequence":
|
|
322
|
+
click.echo("------------")
|
|
323
|
+
click.echo("Empty")
|
|
324
|
+
click.echo("------------")
|
|
325
|
+
else:
|
|
326
|
+
raise exc
|
|
@@ -14,9 +14,7 @@ class Provider(Enum):
|
|
|
14
14
|
GitLab = 1
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
WORKFLOW_VERSION = "
|
|
18
|
-
|
|
19
|
-
DEFAULT_REQUIREMENTS_FILE = "tinybird-cli>=5,<6"
|
|
17
|
+
WORKFLOW_VERSION = "v0.0.1"
|
|
20
18
|
|
|
21
19
|
GITHUB_CI_YML = """
|
|
22
20
|
name: Tinybird - CI Workflow
|
|
@@ -50,6 +48,8 @@ jobs:
|
|
|
50
48
|
run: curl -LsSf https://api.tinybird.co/static/install.sh | sh
|
|
51
49
|
- name: Build project
|
|
52
50
|
run: tb build
|
|
51
|
+
- name: Test project
|
|
52
|
+
run: tb test run
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
55
|
|
|
@@ -64,110 +64,30 @@ stages:
|
|
|
64
64
|
|
|
65
65
|
GITLAB_CI_YML = """
|
|
66
66
|
tinybird_ci_workflow:
|
|
67
|
+
image: ubuntu:latest
|
|
67
68
|
stage: tests
|
|
68
69
|
interruptible: true
|
|
69
70
|
needs: []
|
|
70
71
|
rules:
|
|
71
|
-
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
72
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
72
73
|
changes:
|
|
73
|
-
- .gitlab/tinybird/*
|
|
74
|
+
- .gitlab/tinybird/*{% if data_project_dir != '.' %}
|
|
74
75
|
- {{ data_project_dir }}/*
|
|
75
76
|
- {{ data_project_dir }}/**/*{% end %}
|
|
76
77
|
before_script:
|
|
78
|
+
- apt update && apt install -y curl
|
|
77
79
|
- curl -LsSf https://api.tinybird.co/static/install.sh | sh
|
|
78
80
|
script:
|
|
81
|
+
- export PATH="$HOME/.local/bin:$PATH"
|
|
79
82
|
- cd $CI_PROJECT_DIR/{{ data_project_dir }}
|
|
80
83
|
- tb build
|
|
84
|
+
- tb test run
|
|
81
85
|
services:
|
|
82
86
|
- name: tinybirdco/tinybird-local:latest
|
|
83
87
|
alias: tinybird-local
|
|
84
88
|
"""
|
|
85
89
|
|
|
86
90
|
|
|
87
|
-
EXEC_TEST_SH = """
|
|
88
|
-
#!/usr/bin/env bash
|
|
89
|
-
set -euxo pipefail
|
|
90
|
-
|
|
91
|
-
export TB_VERSION_WARNING=0
|
|
92
|
-
|
|
93
|
-
run_test() {
|
|
94
|
-
t=$1
|
|
95
|
-
echo "** Running $t **"
|
|
96
|
-
echo "** $(cat $t)"
|
|
97
|
-
tmpfile=$(mktemp)
|
|
98
|
-
retries=0
|
|
99
|
-
TOTAL_RETRIES=3
|
|
100
|
-
|
|
101
|
-
# When appending fixtures, we need to retry in case of the data is not replicated in time
|
|
102
|
-
while [ $retries -lt $TOTAL_RETRIES ]; do
|
|
103
|
-
# Run the test and store the output in a temporary file
|
|
104
|
-
bash $t $2 >$tmpfile
|
|
105
|
-
exit_code=$?
|
|
106
|
-
if [ "$exit_code" -eq 0 ]; then
|
|
107
|
-
# If the test passed, break the loop
|
|
108
|
-
if diff -B ${t}.result $tmpfile >/dev/null 2>&1; then
|
|
109
|
-
break
|
|
110
|
-
# If the test failed, increment the retries counter and try again
|
|
111
|
-
else
|
|
112
|
-
retries=$((retries+1))
|
|
113
|
-
fi
|
|
114
|
-
# If the bash command failed, print an error message and break the loop
|
|
115
|
-
else
|
|
116
|
-
break
|
|
117
|
-
fi
|
|
118
|
-
done
|
|
119
|
-
|
|
120
|
-
if diff -B ${t}.result $tmpfile >/dev/null 2>&1; then
|
|
121
|
-
echo "✅ Test $t passed"
|
|
122
|
-
rm $tmpfile
|
|
123
|
-
return 0
|
|
124
|
-
elif [ $retries -eq $TOTAL_RETRIES ]; then
|
|
125
|
-
echo "🚨 ERROR: Test $t failed, diff:";
|
|
126
|
-
diff -B ${t}.result $tmpfile
|
|
127
|
-
rm $tmpfile
|
|
128
|
-
return 1
|
|
129
|
-
else
|
|
130
|
-
echo "🚨 ERROR: Test $t failed with bash command exit code $?"
|
|
131
|
-
cat $tmpfile
|
|
132
|
-
rm $tmpfile
|
|
133
|
-
return 1
|
|
134
|
-
fi
|
|
135
|
-
echo ""
|
|
136
|
-
}
|
|
137
|
-
export -f run_test
|
|
138
|
-
|
|
139
|
-
fail=0
|
|
140
|
-
find ./tests -name "*.test" -print0 | xargs -0 -I {} -P 4 bash -c 'run_test "$@"' _ {} || fail=1
|
|
141
|
-
|
|
142
|
-
if [ $fail == 1 ]; then
|
|
143
|
-
exit -1;
|
|
144
|
-
fi
|
|
145
|
-
"""
|
|
146
|
-
|
|
147
|
-
APPEND_FIXTURES_SH = """
|
|
148
|
-
#!/usr/bin/env bash
|
|
149
|
-
set -euxo pipefail
|
|
150
|
-
|
|
151
|
-
directory="datasources/fixtures"
|
|
152
|
-
extensions=("csv" "ndjson")
|
|
153
|
-
|
|
154
|
-
absolute_directory=$(realpath "$directory")
|
|
155
|
-
|
|
156
|
-
for extension in "${extensions[@]}"; do
|
|
157
|
-
file_list=$(find "$absolute_directory" -type f -name "*.$extension")
|
|
158
|
-
|
|
159
|
-
for file_path in $file_list; do
|
|
160
|
-
file_name=$(basename "$file_path")
|
|
161
|
-
file_name_without_extension="${file_name%.*}"
|
|
162
|
-
|
|
163
|
-
command="tb datasource append $file_name_without_extension datasources/fixtures/$file_name"
|
|
164
|
-
echo $command
|
|
165
|
-
$command
|
|
166
|
-
done
|
|
167
|
-
done
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
|
|
171
91
|
class CICDFile:
|
|
172
92
|
def __init__(
|
|
173
93
|
self,
|
|
@@ -17,7 +17,7 @@ from contextlib import closing
|
|
|
17
17
|
from copy import deepcopy
|
|
18
18
|
from enum import Enum
|
|
19
19
|
from functools import wraps
|
|
20
|
-
from os import
|
|
20
|
+
from os import environ, getcwd, getenv
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
|
|
23
23
|
from urllib.parse import urlparse
|
|
@@ -61,9 +61,7 @@ if TYPE_CHECKING:
|
|
|
61
61
|
from tinybird.connectors import Connector
|
|
62
62
|
|
|
63
63
|
from tinybird.feedback_manager import FeedbackManager, warning_message
|
|
64
|
-
from tinybird.git_settings import DEFAULT_TINYENV_FILE
|
|
65
64
|
from tinybird.syncasync import async_to_sync, sync_to_async
|
|
66
|
-
from tinybird.tb.modules.cicd import APPEND_FIXTURES_SH, DEFAULT_REQUIREMENTS_FILE, EXEC_TEST_SH
|
|
67
65
|
from tinybird.tb.modules.config import CLIConfig
|
|
68
66
|
from tinybird.tb.modules.exceptions import (
|
|
69
67
|
CLIAuthException,
|
|
@@ -371,111 +369,6 @@ async def folder_init(
|
|
|
371
369
|
for path in Path(folder).glob(f"*.{format}"):
|
|
372
370
|
await _generate_datafile(str(path), client, format=format, force=force)
|
|
373
371
|
|
|
374
|
-
if generate_releases:
|
|
375
|
-
base = Path(".")
|
|
376
|
-
f = base / (".tinyenv")
|
|
377
|
-
if not f.exists() or force:
|
|
378
|
-
async with aiofiles.open(".tinyenv", "w") as file:
|
|
379
|
-
await file.write(DEFAULT_TINYENV_FILE)
|
|
380
|
-
click.echo(FeedbackManager.info_file_created(file=".tinyenv"))
|
|
381
|
-
else:
|
|
382
|
-
click.echo(FeedbackManager.info_dottinyenv_already_exists())
|
|
383
|
-
|
|
384
|
-
base = Path(".")
|
|
385
|
-
f = base / ("requirements.txt")
|
|
386
|
-
if not f.exists() or force:
|
|
387
|
-
async with aiofiles.open("requirements.txt", "w") as file:
|
|
388
|
-
await file.write(DEFAULT_REQUIREMENTS_FILE)
|
|
389
|
-
click.echo(FeedbackManager.info_file_created(file="requirements.txt"))
|
|
390
|
-
|
|
391
|
-
base = Path("scripts")
|
|
392
|
-
if not base.exists():
|
|
393
|
-
base = Path()
|
|
394
|
-
f = base / ("exec_test.sh")
|
|
395
|
-
if not f.exists() or force:
|
|
396
|
-
async with aiofiles.open(f"{f}", "w") as t_file:
|
|
397
|
-
await t_file.write(EXEC_TEST_SH)
|
|
398
|
-
click.echo(FeedbackManager.info_file_created(file="scripts/exec_test.sh"))
|
|
399
|
-
chmod(f, 0o755)
|
|
400
|
-
|
|
401
|
-
f = base / ("append_fixtures.sh")
|
|
402
|
-
if not f.exists() or force:
|
|
403
|
-
async with aiofiles.open(f"{f}", "w") as t_file:
|
|
404
|
-
await t_file.write(APPEND_FIXTURES_SH)
|
|
405
|
-
click.echo(FeedbackManager.info_file_created(file="scripts/append_fixtures.sh"))
|
|
406
|
-
chmod(f, 0o755)
|
|
407
|
-
|
|
408
|
-
base = Path("tests")
|
|
409
|
-
if not base.exists():
|
|
410
|
-
base = Path()
|
|
411
|
-
f = base / ("example.yml")
|
|
412
|
-
if not base.exists() or force:
|
|
413
|
-
async with aiofiles.open(f"{f}", "w") as t_file:
|
|
414
|
-
await t_file.write(
|
|
415
|
-
"""
|
|
416
|
-
##############################################################################################################################
|
|
417
|
-
### Visit https://www.tinybird.co/docs/production/implementing-test-strategies.html#data-quality-tests ###
|
|
418
|
-
### for more details on Data Quality tests ###
|
|
419
|
-
##############################################################################################################################
|
|
420
|
-
|
|
421
|
-
- example_no_negative_numbers:
|
|
422
|
-
max_bytes_read: null
|
|
423
|
-
max_time: null
|
|
424
|
-
sql: |
|
|
425
|
-
SELECT
|
|
426
|
-
number
|
|
427
|
-
FROM numbers(10)
|
|
428
|
-
WHERE
|
|
429
|
-
number < 0
|
|
430
|
-
|
|
431
|
-
# - example_top_products_params_no_empty_top_10_on_2023:
|
|
432
|
-
# max_bytes_read: null
|
|
433
|
-
# max_time: null
|
|
434
|
-
# sql: |
|
|
435
|
-
# SELECT *
|
|
436
|
-
# FROM top_products_params
|
|
437
|
-
# WHERE empty(top_10)
|
|
438
|
-
# pipe:
|
|
439
|
-
# name: top_products_params
|
|
440
|
-
# params:
|
|
441
|
-
# start: '2023-01-01'
|
|
442
|
-
# end: '2023-12-31'
|
|
443
|
-
|
|
444
|
-
"""
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
f = base / ("regression.yaml")
|
|
448
|
-
if not base.exists() or force:
|
|
449
|
-
async with aiofiles.open(f"{f}", "w") as t_file:
|
|
450
|
-
await t_file.write(
|
|
451
|
-
"""
|
|
452
|
-
############################################################################################################################
|
|
453
|
-
### Visit https://www.tinybird.co/docs/production/implementing-test-strategies.html#regression-tests ###
|
|
454
|
-
### for more details on Regression tests ###
|
|
455
|
-
############################################################################################################################
|
|
456
|
-
|
|
457
|
-
###
|
|
458
|
-
### New pipes are covered by this rule, rules below this one supersede this setting
|
|
459
|
-
###
|
|
460
|
-
- pipe: '.*'
|
|
461
|
-
tests:
|
|
462
|
-
- coverage:
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
###
|
|
467
|
-
### These are rules to customize regression testing by pipe using regular expressions
|
|
468
|
-
### For instance skip regression tests for the pipes matching `endpoint_name.*`
|
|
469
|
-
###
|
|
470
|
-
- pipe: 'endpoint_name.*'
|
|
471
|
-
tests:
|
|
472
|
-
- coverage:
|
|
473
|
-
config:
|
|
474
|
-
skip: True
|
|
475
|
-
|
|
476
|
-
"""
|
|
477
|
-
)
|
|
478
|
-
|
|
479
372
|
|
|
480
373
|
async def configure_connector(connector):
|
|
481
374
|
if connector not in SUPPORTED_CONNECTORS:
|