tinybird 0.0.1.dev17__py3-none-any.whl → 0.0.1.dev19__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 CHANGED
@@ -103,7 +103,7 @@ class TinyB:
103
103
  self.send_telemetry = send_telemetry
104
104
  self.semver = semver
105
105
 
106
- async def _req(
106
+ async def _req_raw(
107
107
  self,
108
108
  endpoint: str,
109
109
  data=None,
@@ -155,10 +155,15 @@ class TinyB:
155
155
  response = await sync_to_async(session.get, thread_sensitive=False)(
156
156
  url, verify=verify_ssl, **kwargs
157
157
  )
158
-
159
158
  except Exception as e:
160
159
  raise e
161
160
 
161
+ if self.send_telemetry:
162
+ try:
163
+ add_telemetry_event("api_request", endpoint=url, token=self.token, status_code=response.status_code)
164
+ except Exception as ex:
165
+ logging.exception(f"Can't send telemetry: {ex}")
166
+
162
167
  logging.debug("== server response ==")
163
168
  logging.debug(response.content)
164
169
  logging.debug("== end ==")
@@ -169,6 +174,21 @@ class TinyB:
169
174
  except Exception as ex:
170
175
  logging.exception(f"Can't send telemetry: {ex}")
171
176
 
177
+ return response
178
+
179
+ async def _req(
180
+ self,
181
+ endpoint: str,
182
+ data=None,
183
+ files=None,
184
+ method: str = "GET",
185
+ retries: int = LIMIT_RETRIES,
186
+ use_token: Optional[str] = None,
187
+ **kwargs,
188
+ ):
189
+ token_to_use = use_token if use_token else self.token
190
+ response = await self._req_raw(endpoint, data, files, method, retries, use_token, **kwargs)
191
+
172
192
  if response.status_code == 403:
173
193
  error = parse_error_response(response)
174
194
  if not token_to_use:
tinybird/tb/__cli__.py ADDED
@@ -0,0 +1,8 @@
1
+
2
+ __name__ = 'tinybird'
3
+ __description__ = 'Tinybird Command Line Tool'
4
+ __url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
5
+ __author__ = 'Tinybird'
6
+ __author_email__ = 'support@tinybird.co'
7
+ __version__ = '0.0.1.dev19'
8
+ __revision__ = '5ef65e7'
@@ -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 coro, push_data
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
- @click.pass_context
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 = await get_tinybird_local_client(folder_path)
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,42 @@ async def build(
191
187
  ok = False
192
188
  return ok
193
189
 
194
- build_ok = await build_once(filenames)
190
+ build_ok = asyncio.run(build_once(filenames))
195
191
 
196
192
  if watch:
197
- shell = BuildShell(folder=folder, client=tb_client)
193
+ paths = [Path(f) for f in get_project_filenames(folder, with_vendor=True)]
194
+
195
+ def is_vendor(f: Path) -> bool:
196
+ return f.parts[0] == "vendor"
197
+
198
+ def get_vendor_workspace(f: Path) -> str:
199
+ return f.parts[1]
200
+
201
+ def is_endpoint(f: Path) -> bool:
202
+ return f.suffix == ".pipe" and not is_vendor(f) and f.parts[0] == "endpoints"
203
+
204
+ def is_pipe(f: Path) -> bool:
205
+ return f.suffix == ".pipe" and not is_vendor(f)
206
+
207
+ datasource_paths = [f for f in paths if f.suffix == ".datasource"]
208
+ datasources = [f.stem for f in datasource_paths if not is_vendor(f)]
209
+ shared_datasources = [f"{get_vendor_workspace(f)}.{f.stem}" for f in datasource_paths if is_vendor(f)]
210
+ pipes = [f.stem for f in paths if is_pipe(f) and not is_endpoint(f)]
211
+ endpoints = [f.stem for f in paths if is_endpoint(f)]
212
+ shell = BuildShell(
213
+ folder=folder,
214
+ client=tb_client,
215
+ datasources=datasources,
216
+ shared_datasources=shared_datasources,
217
+ pipes=pipes,
218
+ endpoints=endpoints,
219
+ )
198
220
  click.echo(FeedbackManager.highlight(message="◎ Watching for changes..."))
199
221
  watcher_thread = threading.Thread(
200
222
  target=watch_files, args=(filenames, process, shell, folder, build_ok), daemon=True
201
223
  )
202
224
  watcher_thread.start()
203
- shell.cmdloop()
225
+ shell.run_shell()
204
226
 
205
227
 
206
228
  async def build_and_print_resource(tb_client: TinyB, filename: str):
@@ -1,11 +1,19 @@
1
1
  import asyncio
2
- import cmd
2
+ import concurrent.futures
3
+ import os
3
4
  import random
4
5
  import subprocess
5
6
  import sys
7
+ from typing import List
6
8
 
7
9
  import click
8
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
9
17
 
10
18
  from tinybird.client import TinyB
11
19
  from tinybird.feedback_manager import FeedbackManager, bcolors
@@ -13,43 +21,260 @@ from tinybird.tb.modules.exceptions import CLIException
13
21
  from tinybird.tb.modules.table import format_table
14
22
 
15
23
 
16
- class BuildShell(cmd.Cmd):
17
- prompt = "\n\001\033[1;32m\002tb > \001\033[0m\002"
24
+ class DynamicCompleter(Completer):
25
+ def __init__(self, datasources: List[str], shared_datasources: List[str], endpoints: List[str], pipes: List[str]):
26
+ self.datasources = datasources
27
+ self.shared_datasources = shared_datasources
28
+ self.endpoints = endpoints
29
+ self.pipes = pipes
30
+ self.static_commands = ["create", "mock", "test", "select"]
31
+ self.test_commands = ["create", "run", "update"]
32
+ self.mock_flags = ["--prompt", "--rows"]
33
+ self.common_rows = ["10", "50", "100", "500", "1000"]
34
+ self.sql_keywords = ["select", "from", "where", "group by", "order by", "limit"]
18
35
 
19
- def __init__(self, folder: str, client: TinyB):
20
- super().__init__()
36
+ def get_completions(self, document, complete_event):
37
+ text = document.text_before_cursor.strip()
38
+ words = text.split()
39
+
40
+ # Normalize command by removing 'tb' prefix if present
41
+ if words and words[0] == "tb":
42
+ words = words[1:]
43
+
44
+ if not words:
45
+ # Show all available commands when no input
46
+ yield from self._yield_static_commands("")
47
+ return
48
+
49
+ command = words[0].lower()
50
+
51
+ if command == "mock":
52
+ yield from self._handle_mock_completions(words)
53
+ elif command == "test":
54
+ yield from self._handle_test_completions(words)
55
+ elif command == "select" or self._is_sql_query(text.lower()):
56
+ yield from self._handle_sql_completions(text)
57
+ else:
58
+ # Handle general command completions
59
+ yield from self._yield_static_commands(words[-1])
60
+
61
+ def _is_sql_query(self, text: str) -> bool:
62
+ """Check if the input looks like a SQL query."""
63
+ sql_starters = ["select", "with"]
64
+ return any(text.startswith(starter) for starter in sql_starters)
65
+
66
+ def _handle_sql_completions(self, text: str):
67
+ """Handle completions for SQL queries."""
68
+ text_lower = text.lower()
69
+
70
+ # Find the last complete word
71
+ words = text_lower.split()
72
+ if not words:
73
+ return
74
+
75
+ # If we just typed 'from' or there's a space after 'from', suggest datasources
76
+ if words[-1] == "from" or (
77
+ "from" in words and len(words) > words.index("from") + 1 and text_lower.endswith(" ")
78
+ ):
79
+ for x in self.datasources + self.shared_datasources:
80
+ yield Completion(x, start_position=0, display=x, style="class:completion.datasource")
81
+ for x in self.endpoints + self.pipes:
82
+ yield Completion(x, start_position=0, display=x, style="class:completion.pipe")
83
+ return
84
+
85
+ # If we're starting a query, suggest SQL keywords
86
+ if len(words) <= 2:
87
+ for keyword in self.sql_keywords:
88
+ if keyword.lower().startswith(words[-1]):
89
+ yield Completion(
90
+ keyword, start_position=-len(words[-1]), display=keyword, style="class:completion.keyword"
91
+ )
92
+
93
+ def _handle_mock_completions(self, words: List[str]):
94
+ if len(words) == 1:
95
+ # After 'mock', show datasources
96
+ for ds in self.datasources:
97
+ yield Completion(ds, start_position=0, display=ds, style="class:completion.datasource")
98
+ return
99
+
100
+ if len(words) == 2 or len(words) == 4:
101
+ # After datasource or after a flag value, show available flags
102
+ available_flags = [f for f in self.mock_flags if f not in words]
103
+ for flag in available_flags:
104
+ yield Completion(flag, start_position=0, display=flag, style="class:completion.cmd")
105
+ return
106
+
107
+ last_word = words[-1]
108
+ if last_word == "--prompt":
109
+ yield Completion('""', start_position=0, display='"Enter your prompt..."', style="class:completion.cmd")
110
+ elif last_word == "--rows":
111
+ for rows in self.common_rows:
112
+ yield Completion(rows, start_position=0, display=rows, style="class:completion.cmd")
113
+
114
+ def _handle_test_completions(self, words: List[str]):
115
+ if len(words) == 1:
116
+ for cmd in self.test_commands:
117
+ yield Completion(cmd, start_position=0, display=cmd, style="class:completion.cmd")
118
+ return
119
+ elif len(words) == 2:
120
+ for cmd in self.endpoints:
121
+ yield Completion(cmd, start_position=0, display=cmd, style="class:completion.pipe")
122
+ return
123
+
124
+ def _yield_static_commands(self, current_word: str):
125
+ for cmd in self.static_commands:
126
+ if cmd.startswith(current_word):
127
+ yield Completion(
128
+ cmd,
129
+ start_position=-len(current_word) if current_word else 0,
130
+ display=cmd,
131
+ style="class:completion.cmd",
132
+ )
133
+
134
+ for cmd in self.datasources + self.shared_datasources:
135
+ if cmd.startswith(current_word):
136
+ yield Completion(
137
+ cmd,
138
+ start_position=-len(current_word) if current_word else 0,
139
+ display=cmd,
140
+ style="class:completion.datasource",
141
+ )
142
+
143
+ for cmd in self.endpoints + self.pipes:
144
+ if cmd.startswith(current_word):
145
+ yield Completion(
146
+ cmd,
147
+ start_position=-len(current_word) if current_word else 0,
148
+ display=cmd,
149
+ style="class:completion.pipe",
150
+ )
151
+
152
+
153
+ style = Style.from_dict(
154
+ {
155
+ "prompt": "fg:#34D399 bold",
156
+ "completion.cmd": "fg:#34D399 bg:#111111 bold",
157
+ "completion.datasource": "fg:#AB49D0 bg:#111111",
158
+ "completion.pipe": "fg:#FEA827 bg:#111111",
159
+ "completion.keyword": "fg:#34D399 bg:#111111",
160
+ }
161
+ )
162
+
163
+ key_bindings = KeyBindings()
164
+
165
+
166
+ @key_bindings.add("c-d")
167
+ def _(event):
168
+ """
169
+ Start auto completion. If the menu is showing already, select the next
170
+ completion.
171
+ """
172
+ b = event.app.current_buffer
173
+ if b.complete_state:
174
+ b.complete_next()
175
+ else:
176
+ b.start_completion(select_first=False)
177
+
178
+
179
+ class BuildShell:
180
+ def __init__(
181
+ self,
182
+ folder: str,
183
+ client: TinyB,
184
+ datasources: List[str],
185
+ shared_datasources: List[str],
186
+ pipes: List[str],
187
+ endpoints: List[str],
188
+ ):
189
+ self.history = self.get_history()
21
190
  self.folder = folder
22
191
  self.client = client
192
+ self.datasources = datasources
193
+ self.shared_datasources = shared_datasources
194
+ self.pipes = pipes
195
+ self.endpoints = endpoints
196
+ self.prompt_message = "\ntb > "
197
+ self.commands = ["create", "mock", "test", "tb", "select"]
198
+
199
+ self.session = PromptSession(
200
+ completer=DynamicCompleter(self.datasources, self.shared_datasources, self.endpoints, self.pipes),
201
+ complete_style=CompleteStyle.COLUMN,
202
+ complete_while_typing=True,
203
+ history=self.history,
204
+ )
205
+
206
+ def get_history(self):
207
+ try:
208
+ history_file = os.path.expanduser("~/.tb_history")
209
+ return FileHistory(history_file)
210
+ except Exception:
211
+ return None
212
+
213
+ def run_shell(self):
214
+ while True:
215
+ try:
216
+ user_input = self.session.prompt(
217
+ [("class:prompt", self.prompt_message)], style=style, key_bindings=key_bindings
218
+ )
219
+ self.handle_input(user_input)
220
+ except (EOFError, KeyboardInterrupt):
221
+ sys.exit(0)
222
+ except CLIException as e:
223
+ click.echo(str(e))
224
+ except Exception as e:
225
+ # Catch-all for unexpected exceptions
226
+ click.echo(FeedbackManager.error_exception(error=str(e)))
227
+
228
+ def handle_input(self, argline):
229
+ line = argline.strip()
230
+ if not line:
231
+ return
23
232
 
24
- def do_exit(self, arg):
25
- sys.exit(0)
233
+ # Implement the command logic here
234
+ # Replace do_* methods with equivalent logic:
235
+ command_parts = line.split(maxsplit=1)
236
+ cmd = command_parts[0].lower()
237
+ arg = command_parts[1] if len(command_parts) > 1 else ""
26
238
 
27
- def do_quit(self, arg):
28
- sys.exit(0)
239
+ if cmd in ["exit", "quit"]:
240
+ sys.exit(0)
241
+ elif cmd == "build":
242
+ self.handle_build(arg)
243
+ elif cmd == "auth":
244
+ self.handle_auth(arg)
245
+ elif cmd == "workspace":
246
+ self.handle_workspace(arg)
247
+ elif cmd == "mock":
248
+ self.handle_mock(arg)
249
+ elif cmd == "tb":
250
+ self.handle_tb(arg)
251
+ else:
252
+ # Check if it looks like a SQL query or run as a tb command
253
+ self.default(line)
29
254
 
30
- def do_build(self, arg):
255
+ def handle_build(self, arg):
31
256
  click.echo(FeedbackManager.error(message=f"'tb {arg}' command is not available in watch mode"))
32
257
 
33
- def do_auth(self, arg):
258
+ def handle_auth(self, arg):
34
259
  click.echo(FeedbackManager.error(message=f"'tb {arg}' command is not available in watch mode"))
35
260
 
36
- def do_workspace(self, arg):
261
+ def handle_workspace(self, arg):
37
262
  click.echo(FeedbackManager.error(message=f"'tb {arg}' command is not available in watch mode"))
38
263
 
39
- def do_mock(self, arg):
264
+ def handle_mock(self, arg):
40
265
  subprocess.run(f"tb mock {arg} --folder {self.folder}", shell=True, text=True)
41
266
 
42
- def do_tb(self, arg):
267
+ def handle_tb(self, arg):
43
268
  click.echo("")
44
269
  arg = arg.strip().lower()
45
270
  if arg.startswith("build"):
46
- self.do_build(arg)
271
+ self.handle_build(arg)
47
272
  elif arg.startswith("auth"):
48
- self.do_auth(arg)
273
+ self.handle_auth(arg)
49
274
  elif arg.startswith("workspace"):
50
- self.do_workspace(arg)
275
+ self.handle_workspace(arg)
51
276
  elif arg.startswith("mock"):
52
- self.do_mock(arg)
277
+ self.handle_mock(arg)
53
278
  else:
54
279
  subprocess.run(f"tb --local {arg}", shell=True, text=True)
55
280
 
@@ -59,27 +284,21 @@ class BuildShell(cmd.Cmd):
59
284
  if not arg:
60
285
  return
61
286
  if arg.startswith("with") or arg.startswith("select"):
62
- try:
63
- self.run_sql(argline)
64
- except Exception as e:
65
- click.echo(FeedbackManager.error(message=str(e)))
287
+ self.run_sql(argline)
288
+ elif len(arg.split()) == 1 and arg in self.endpoints + self.pipes + self.datasources + self.shared_datasources:
289
+ self.run_sql(f"select * from {arg}")
66
290
  else:
67
291
  subprocess.run(f"tb --local {arg}", shell=True, text=True)
68
292
 
69
- def reprint_prompt(self):
70
- self.stdout.write(self.prompt)
71
- self.stdout.flush()
72
-
73
293
  def run_sql(self, query, rows_limit=20):
74
294
  try:
75
295
  q = query.strip()
76
- if q.startswith("insert"):
296
+ if q.lower().startswith("insert"):
77
297
  click.echo(FeedbackManager.info_append_data())
78
298
  raise CLIException(FeedbackManager.error_invalid_query())
79
- if q.startswith("delete"):
299
+ if q.lower().startswith("delete"):
80
300
  raise CLIException(FeedbackManager.error_invalid_query())
81
301
 
82
- # fuck my life
83
302
  def run_query_in_thread():
84
303
  loop = asyncio.new_event_loop()
85
304
  asyncio.set_event_loop(loop)
@@ -90,23 +309,23 @@ class BuildShell(cmd.Cmd):
90
309
  finally:
91
310
  loop.close()
92
311
 
93
- # Run the query in a separate thread
94
- import concurrent.futures
95
-
96
312
  with concurrent.futures.ThreadPoolExecutor() as executor:
97
313
  res = executor.submit(run_query_in_thread).result()
98
314
 
99
315
  except Exception as e:
100
- raise CLIException(FeedbackManager.error_exception(error=str(e)))
316
+ click.echo(FeedbackManager.error_exception(error=str(e)))
101
317
 
102
318
  if isinstance(res, dict) and "error" in res:
103
- raise CLIException(FeedbackManager.error_exception(error=res["error"]))
319
+ click.echo(FeedbackManager.error_exception(error=res["error"]))
104
320
 
105
321
  if isinstance(res, dict) and "data" in res and res["data"]:
106
322
  print_table_formatted(res, "QUERY")
107
323
  else:
108
324
  click.echo(FeedbackManager.info_no_rows())
109
325
 
326
+ def reprint_prompt(self):
327
+ click.echo(f"{bcolors.OKGREEN}{self.prompt_message}{bcolors.ENDC}", nl=False)
328
+
110
329
 
111
330
  def print_table_formatted(res: dict, name: str):
112
331
  rebuild_colors = [bcolors.FAIL, bcolors.OKBLUE, bcolors.WARNING, bcolors.OKGREEN, bcolors.HEADER]
@@ -14,9 +14,7 @@ class Provider(Enum):
14
14
  GitLab = 1
15
15
 
16
16
 
17
- WORKFLOW_VERSION = "v3.1.0"
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"{% if data_project_dir != '.' %}
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,
@@ -26,8 +26,9 @@ from tinybird.client import (
26
26
  DoesNotExistException,
27
27
  TinyB,
28
28
  )
29
- from tinybird.config import SUPPORTED_CONNECTORS, VERSION, FeatureFlags, get_config
29
+ from tinybird.config import SUPPORTED_CONNECTORS, FeatureFlags, get_config
30
30
  from tinybird.feedback_manager import FeedbackManager
31
+ from tinybird.tb import __cli__
31
32
  from tinybird.tb.modules.common import (
32
33
  OLDEST_ROLLBACK,
33
34
  CatchAuthExceptions,
@@ -70,6 +71,7 @@ __old_click_secho = click.secho
70
71
  DEFAULT_PATTERNS: List[Tuple[str, Union[str, Callable[[str], str]]]] = [
71
72
  (r"p\.ey[A-Za-z0-9-_\.]+", lambda v: f"{v[:4]}...{v[-8:]}")
72
73
  ]
74
+ VERSION = f"{__cli__.__version__} (rev {__cli__.__revision__})"
73
75
 
74
76
 
75
77
  @click.group(cls=CatchAuthExceptions, context_settings={"help_option_names": ["-h", "--help"]})