tinybird 0.0.1.dev306__py3-none-any.whl → 1.0.5__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.
Files changed (45) hide show
  1. tinybird/datafile/common.py +4 -1
  2. tinybird/feedback_manager.py +3 -0
  3. tinybird/service_datasources.py +57 -8
  4. tinybird/sql_template.py +1 -1
  5. tinybird/sql_template_fmt.py +14 -4
  6. tinybird/tb/__cli__.py +2 -2
  7. tinybird/tb/cli.py +1 -0
  8. tinybird/tb/client.py +104 -22
  9. tinybird/tb/modules/agent/tools/execute_query.py +1 -1
  10. tinybird/tb/modules/agent/tools/request_endpoint.py +1 -1
  11. tinybird/tb/modules/branch.py +150 -0
  12. tinybird/tb/modules/build.py +51 -10
  13. tinybird/tb/modules/build_common.py +4 -2
  14. tinybird/tb/modules/cli.py +32 -10
  15. tinybird/tb/modules/common.py +161 -134
  16. tinybird/tb/modules/connection.py +125 -194
  17. tinybird/tb/modules/connection_kafka.py +382 -0
  18. tinybird/tb/modules/copy.py +3 -1
  19. tinybird/tb/modules/create.py +11 -0
  20. tinybird/tb/modules/datafile/build.py +1 -1
  21. tinybird/tb/modules/datafile/format_pipe.py +44 -5
  22. tinybird/tb/modules/datafile/playground.py +1 -1
  23. tinybird/tb/modules/datasource.py +475 -324
  24. tinybird/tb/modules/deployment.py +2 -0
  25. tinybird/tb/modules/deployment_common.py +81 -43
  26. tinybird/tb/modules/deprecations.py +4 -4
  27. tinybird/tb/modules/dev_server.py +33 -12
  28. tinybird/tb/modules/info.py +50 -7
  29. tinybird/tb/modules/job_common.py +15 -0
  30. tinybird/tb/modules/local.py +91 -21
  31. tinybird/tb/modules/local_common.py +320 -13
  32. tinybird/tb/modules/local_logs.py +209 -0
  33. tinybird/tb/modules/login.py +3 -2
  34. tinybird/tb/modules/login_common.py +252 -9
  35. tinybird/tb/modules/open.py +10 -5
  36. tinybird/tb/modules/project.py +14 -5
  37. tinybird/tb/modules/shell.py +14 -6
  38. tinybird/tb/modules/sink.py +3 -1
  39. tinybird/tb/modules/telemetry.py +7 -3
  40. tinybird/tb_cli_modules/telemetry.py +1 -1
  41. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/METADATA +29 -4
  42. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/RECORD +45 -41
  43. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/WHEEL +1 -1
  44. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/entry_points.txt +0 -0
  45. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/top_level.txt +0 -0
@@ -11,17 +11,21 @@ import threading
11
11
  import time
12
12
  import urllib.parse
13
13
  import webbrowser
14
+ from datetime import datetime
15
+ from pathlib import Path
14
16
  from typing import Any, Dict, Optional
15
17
  from urllib.parse import urlencode
16
18
 
17
19
  import click
18
20
  import requests
21
+ from click import Context
19
22
 
20
23
  from tinybird.tb.config import DEFAULT_API_HOST
21
- from tinybird.tb.modules.common import ask_for_region_interactively, get_regions
24
+ from tinybird.tb.modules.common import ask_for_region_interactively, get_region_from_host, get_regions
22
25
  from tinybird.tb.modules.config import CLIConfig
23
26
  from tinybird.tb.modules.exceptions import CLILoginException
24
27
  from tinybird.tb.modules.feedback_manager import FeedbackManager
28
+ from tinybird.tb.modules.telemetry import is_ci_environment
25
29
 
26
30
  SERVER_MAX_WAIT_TIME = 180
27
31
 
@@ -163,14 +167,13 @@ def login(
163
167
  "method": "code",
164
168
  }
165
169
  response = requests.get(f"{auth_host}/api/cli-login?{urlencode(params)}")
166
-
167
170
  try:
168
171
  if response.status_code == 200:
169
172
  data = response.json()
170
173
  user_token = data.get("user_token", "")
171
174
  workspace_token = data.get("workspace_token", "")
172
175
  if user_token and workspace_token:
173
- authenticate_with_tokens(data, host, cli_config)
176
+ authenticate_with_tokens(data, cli_config)
174
177
  break
175
178
  except Exception:
176
179
  pass
@@ -216,7 +219,7 @@ def login(
216
219
  )
217
220
 
218
221
  data = response.json()
219
- authenticate_with_tokens(data, host, cli_config)
222
+ authenticate_with_tokens(data, cli_config)
220
223
  else:
221
224
  raise Exception("Authentication failed or timed out.")
222
225
  except Exception as e:
@@ -294,9 +297,246 @@ def create_one_time_code():
294
297
  return seperator.join(parts), full_code
295
298
 
296
299
 
297
- def authenticate_with_tokens(data: Dict[str, Any], host: Optional[str], cli_config: CLIConfig):
300
+ def check_current_folder_in_sessions(ctx: Context) -> None:
301
+ """
302
+ Check if the current folder is tracked in the sessions file before running a command.
303
+
304
+ If the current folder is not in sessions.txt:
305
+ - If no sessions exist or file doesn't exist: silently add it (first usage is fine)
306
+ - If other folders exist: warn the user they're running from an untracked folder
307
+
308
+ Args:
309
+ provider: The provider name (e.g., 'gcp', 'aws')
310
+ region: The region name
311
+ workspace_name: The workspace name
312
+ current_folder: The current working directory
313
+ """
314
+ try:
315
+ env = ctx.ensure_object(dict)["env"]
316
+ if env != "cloud" or ctx.invoked_subcommand == "login" or is_ci_environment():
317
+ return
318
+
319
+ current_folder = os.getcwd()
320
+ cli_config = CLIConfig.get_project_config()
321
+ regions = get_regions(cli_config)
322
+ host = cli_config.get_host()
323
+ if not host:
324
+ return
325
+
326
+ region = get_region_from_host(host, regions)
327
+
328
+ if not region:
329
+ return
330
+
331
+ provider = region.get("provider", "unknown")
332
+ region_name = region.get("name", "unknown")
333
+ current_folder = os.getcwd()
334
+ home_dir = Path.home()
335
+ workspace_name = cli_config.get("name", "unknown")
336
+ sessions_dir = home_dir / ".tinybird" / provider / region_name / workspace_name
337
+ sessions_file = sessions_dir / "sessions.txt"
338
+
339
+ # Normalize the current folder path
340
+ current_folder = os.path.abspath(current_folder)
341
+
342
+ # Read existing sessions
343
+ existing_sessions: dict[str, str] = {} # folder -> timestamp
344
+ if sessions_file.exists():
345
+ try:
346
+ with open(sessions_file, "r") as f:
347
+ for line in f:
348
+ line = line.strip()
349
+ if line:
350
+ # Format: <folder_path>\t<timestamp>
351
+ parts = line.split("\t")
352
+ if len(parts) >= 2 and parts[0]:
353
+ existing_sessions[parts[0]] = parts[1]
354
+ except Exception:
355
+ # If we can't read the file, just continue silently
356
+ return
357
+
358
+ # Check if current folder is already tracked
359
+ if current_folder in existing_sessions:
360
+ # Already tracked, update the timestamp
361
+ try:
362
+ existing_sessions[current_folder] = datetime.now().isoformat()
363
+ sessions_dir.mkdir(parents=True, exist_ok=True)
364
+ with open(sessions_file, "w") as f:
365
+ f.writelines(f"{folder}\t{timestamp}\n" for folder, timestamp in existing_sessions.items())
366
+ except Exception:
367
+ # Silently fail, don't block the command
368
+ pass
369
+ return
370
+
371
+ # Current folder is not tracked
372
+ if not existing_sessions:
373
+ # No previous sessions, this is the first time - silently add it
374
+ try:
375
+ sessions_dir.mkdir(parents=True, exist_ok=True)
376
+ timestamp = datetime.now().isoformat()
377
+ with open(sessions_file, "a") as f:
378
+ f.write(f"{current_folder}\t{timestamp}\n")
379
+ except Exception:
380
+ # Silently fail, don't block the command
381
+ pass
382
+ else:
383
+ # Other folders exist, warn the user
384
+ click.echo("")
385
+ click.echo(FeedbackManager.warning(message="Running command from an untracked folder"))
386
+ click.echo(FeedbackManager.gray(message="Current folder: ") + FeedbackManager.info(message=current_folder))
387
+ tracked_folders = ", ".join(existing_sessions.keys())
388
+ click.echo(
389
+ FeedbackManager.gray(message="Tracked folders: ") + FeedbackManager.info(message=tracked_folders)
390
+ )
391
+ confirmed = click.confirm(
392
+ FeedbackManager.highlight(message="» Are you sure you want to continue?"),
393
+ default=True,
394
+ )
395
+
396
+ if not confirmed:
397
+ raise TrackFolderCancelled("Command cancelled by user.")
398
+
399
+ # Add current folder to the tracked list
400
+ try:
401
+ timestamp = datetime.now().isoformat()
402
+ with open(sessions_file, "a") as f:
403
+ f.write(f"{current_folder}\t{timestamp}\n")
404
+ except Exception:
405
+ # Silently fail, don't block the command
406
+ pass
407
+
408
+ except TrackFolderCancelled:
409
+ raise
410
+ except Exception:
411
+ # Don't block execution if folder check fails
412
+ pass
413
+
414
+
415
+ class TrackFolderCancelled(Exception):
416
+ """Exception raised when the user cancels the folder tracking"""
417
+
418
+ pass
419
+
420
+
421
+ def check_and_warn_folder_change(cli_config: CLIConfig) -> None:
422
+ """
423
+ Check if the user is logging in from a folder that hasn't been tracked before.
424
+
425
+ Reads from ~/.tinybird/<provider>/<region>/<workspace_name>/sessions.txt to track
426
+ folder usage history. If the current folder is not in the list of tracked folders
427
+ and there are existing sessions, prompts the user to confirm.
428
+
429
+ Behavior:
430
+ - If no sessions exist: Silently add current folder
431
+ - If current folder is already tracked: Update timestamp and continue
432
+ - If current folder is not tracked but other folders are: Show warning and ask for confirmation
433
+
434
+ Raises:
435
+ TrackFolderCancelled: If user declines the folder change confirmation
436
+ """
437
+ if is_ci_environment():
438
+ return
439
+
440
+ host = cli_config.get_host()
441
+ workspace_name = cli_config.get("name", None)
442
+ if not host or not workspace_name:
443
+ return
444
+
445
+ regions = get_regions(cli_config)
446
+ region = get_region_from_host(host, regions)
447
+
448
+ if not region:
449
+ return
450
+
451
+ provider = region.get("provider", "unknown")
452
+ region_name = region.get("name", "unknown")
453
+
454
+ current_folder = os.getcwd()
455
+
456
+ home_dir = Path.home()
457
+ sessions_dir = home_dir / ".tinybird" / provider / region_name / workspace_name
458
+ sessions_file = sessions_dir / "sessions.txt"
459
+
460
+ # Normalize the current folder path
461
+ current_folder = os.path.abspath(current_folder)
462
+
463
+ # Read existing sessions if the file exists
464
+ existing_sessions: dict[str, str] = {} # folder -> timestamp
465
+ if sessions_file.exists():
466
+ try:
467
+ with open(sessions_file, "r") as f:
468
+ for line in f:
469
+ line = line.strip()
470
+ if line:
471
+ # Format: <folder_path>\t<timestamp>
472
+ parts = line.split("\t")
473
+ if len(parts) >= 2 and parts[0]:
474
+ existing_sessions[parts[0]] = parts[1]
475
+ except Exception as e:
476
+ # If we can't read the file, just continue without warning
477
+ click.echo(FeedbackManager.warning(message=f"Warning: Could not read sessions file: {e}"))
478
+
479
+ # If current folder is already tracked, update timestamp and return
480
+ if current_folder in existing_sessions:
481
+ try:
482
+ existing_sessions[current_folder] = datetime.now().isoformat()
483
+ sessions_dir.mkdir(parents=True, exist_ok=True)
484
+ with open(sessions_file, "w") as f:
485
+ f.writelines(f"{folder}\t{timestamp}\n" for folder, timestamp in existing_sessions.items())
486
+ except Exception as e:
487
+ click.echo(FeedbackManager.warning(message=f"Warning: Could not update sessions file: {e}"))
488
+ return
489
+
490
+ # If there are existing tracked folders but current folder is not tracked, warn the user
491
+ if existing_sessions:
492
+ click.echo("")
493
+ click.echo(
494
+ FeedbackManager.warning(message="Login from a different folder than previous sessions has been detected.")
495
+ )
496
+ click.echo(FeedbackManager.gray(message="Current folder: ") + FeedbackManager.info(message=current_folder))
497
+ tracked_folders = ", ".join(existing_sessions.keys())
498
+ click.echo(FeedbackManager.gray(message="Tracked folders: ") + FeedbackManager.info(message=tracked_folders))
499
+
500
+ # Ask for confirmation
501
+ confirmed = click.confirm(
502
+ FeedbackManager.highlight(message="» Are you sure you want to continue?"),
503
+ default=True,
504
+ )
505
+
506
+ if not confirmed:
507
+ raise TrackFolderCancelled("Login cancelled by user.")
508
+
509
+ # User accepted, show pull suggestion
510
+ click.echo(
511
+ FeedbackManager.warning(
512
+ message="Remember to run `tb --cloud pull` to have your latest resources available."
513
+ )
514
+ )
515
+
516
+ # Record the current session
517
+ try:
518
+ # Create the directory if it doesn't exist
519
+ sessions_dir.mkdir(parents=True, exist_ok=True)
520
+
521
+ # Add current folder to sessions and update timestamp
522
+ timestamp = datetime.now().isoformat()
523
+ existing_sessions[current_folder] = timestamp
524
+
525
+ # Write all sessions to file
526
+ with open(sessions_file, "w") as f:
527
+ f.writelines(f"{folder}\t{ts}\n" for folder, ts in existing_sessions.items())
528
+ except Exception as e:
529
+ # If we can't write the file, just warn but don't block the login
530
+ click.echo(FeedbackManager.warning(message=f"Warning: Could not update sessions file: {e}"))
531
+
532
+
533
+ def authenticate_with_tokens(data: Dict[str, Any], cli_config: CLIConfig):
298
534
  cli_config.set_token(data.get("workspace_token", ""))
299
- host = host or data.get("api_host", "")
535
+ host = data.get("api_host", "")
536
+
537
+ if not host:
538
+ raise Exception("API host not found in the authentication response")
539
+
300
540
  cli_config.set_token_for_host(data.get("workspace_token", ""), host)
301
541
  cli_config.set_user_token(data.get("user_token", ""))
302
542
  cli_config.set_host(host)
@@ -305,9 +545,6 @@ def authenticate_with_tokens(data: Dict[str, Any], host: Optional[str], cli_conf
305
545
  if k in ws:
306
546
  cli_config[k] = ws[k]
307
547
 
308
- path = os.path.join(os.getcwd(), ".tinyb")
309
- cli_config.persist_to_file(override_with_path=path)
310
-
311
548
  auth_info: Dict[str, Any] = cli_config.get_user_client().check_auth_login()
312
549
  if not auth_info.get("is_valid", False):
313
550
  raise Exception(FeedbackManager.error_auth_login_not_valid(host=cli_config.get_host()))
@@ -315,6 +552,12 @@ def authenticate_with_tokens(data: Dict[str, Any], host: Optional[str], cli_conf
315
552
  if not auth_info.get("is_user", False):
316
553
  raise Exception(FeedbackManager.error_auth_login_not_user(host=cli_config.get_host()))
317
554
 
555
+ # Check for folder change before persisting config
556
+ check_and_warn_folder_change(cli_config)
557
+
558
+ path = os.path.join(os.getcwd(), ".tinyb")
559
+ cli_config.persist_to_file(override_with_path=path)
560
+
318
561
  click.echo(FeedbackManager.gray(message="\nWorkspace: ") + FeedbackManager.info(message=ws["name"]))
319
562
  click.echo(FeedbackManager.gray(message="User: ") + FeedbackManager.info(message=ws["user_email"]))
320
563
  click.echo(FeedbackManager.gray(message="Host: ") + FeedbackManager.info(message=host))
@@ -7,7 +7,6 @@ from tinybird.tb.config import get_display_cloud_host
7
7
  from tinybird.tb.modules.cli import cli
8
8
  from tinybird.tb.modules.exceptions import CLIException
9
9
  from tinybird.tb.modules.feedback_manager import FeedbackManager
10
- from tinybird.tb.modules.local_common import get_build_workspace_name
11
10
 
12
11
 
13
12
  @cli.command()
@@ -21,12 +20,12 @@ def open(ctx: Context, workspace: str):
21
20
 
22
21
  config = ctx.ensure_object(dict)["config"]
23
22
  client = ctx.ensure_object(dict)["client"]
24
- env = ctx.ensure_object(dict)["env"]
23
+ branch = ctx.ensure_object(dict)["branch"]
25
24
 
26
25
  url_host = get_display_cloud_host(client.host)
27
26
 
28
27
  if not workspace:
29
- workspace = get_build_workspace_name(config.get("path")) if env == "build" else config.get("name")
28
+ workspace = config.get("name")
30
29
 
31
30
  if not workspace:
32
31
  raise CLIException(
@@ -35,7 +34,13 @@ def open(ctx: Context, workspace: str):
35
34
  )
36
35
  )
37
36
 
38
- click.echo(FeedbackManager.highlight(message=f"» Opening workspace {workspace} in the browser"))
37
+ if branch:
38
+ click.echo(
39
+ FeedbackManager.highlight(message=f"» Opening branch {branch} of workspace {workspace} in the browser")
40
+ )
41
+ auth_url = f"{url_host}/{workspace}~{branch}"
42
+ else:
43
+ click.echo(FeedbackManager.highlight(message=f"» Opening workspace {workspace} in the browser"))
44
+ auth_url = f"{url_host}/{workspace}"
39
45
 
40
- auth_url = f"{url_host}/{workspace}"
41
46
  webbrowser.open(auth_url)
@@ -130,7 +130,7 @@ class Project:
130
130
 
131
131
  @property
132
132
  def connections(self) -> List[str]:
133
- return sorted([Path(f).stem for f in self.get_connection_files()])
133
+ return sorted([Path(f).stem for f in self._get_connection_files()])
134
134
 
135
135
  def get_datasource_files(self) -> List[str]:
136
136
  return self.get_files("datasource")
@@ -138,17 +138,26 @@ class Project:
138
138
  def get_pipe_files(self) -> List[str]:
139
139
  return self.get_files("pipe")
140
140
 
141
- def get_connection_files(self) -> List[str]:
141
+ def _get_connection_files(self) -> List[str]:
142
142
  return self.get_files("connection")
143
143
 
144
+ def get_connection_files(self, connection_type: Optional[str] = None) -> List[str]:
145
+ if connection_type == "kafka":
146
+ return self.get_kafka_connection_files()
147
+ if connection_type == "s3":
148
+ return self.get_s3_connection_files()
149
+ if connection_type == "gcs":
150
+ return self.get_gcs_connection_files()
151
+ return self._get_connection_files()
152
+
144
153
  def get_kafka_connection_files(self) -> List[str]:
145
- return [f for f in self.get_connection_files() if self.is_kafka_connection(Path(f).read_text())]
154
+ return [f for f in self._get_connection_files() if self.is_kafka_connection(Path(f).read_text())]
146
155
 
147
156
  def get_s3_connection_files(self) -> List[str]:
148
- return [f for f in self.get_connection_files() if self.is_s3_connection(Path(f).read_text())]
157
+ return [f for f in self._get_connection_files() if self.is_s3_connection(Path(f).read_text())]
149
158
 
150
159
  def get_gcs_connection_files(self) -> List[str]:
151
- return [f for f in self.get_connection_files() if self.is_gcs_connection(Path(f).read_text())]
160
+ return [f for f in self._get_connection_files() if self.is_gcs_connection(Path(f).read_text())]
152
161
 
153
162
  def get_pipe_datafile(self, filename: str) -> Optional[Datafile]:
154
163
  try:
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import subprocess
3
3
  import sys
4
- from typing import List
4
+ from typing import List, Optional
5
5
 
6
6
  import click
7
7
  import humanfriendly
@@ -192,11 +192,14 @@ def _(event):
192
192
 
193
193
 
194
194
  class Shell:
195
- def __init__(self, project: Project, tb_client: TinyB, playground: bool = False):
195
+ def __init__(self, project: Project, tb_client: TinyB, playground=False, branch: Optional[str] = None):
196
196
  self.history = self.get_history()
197
197
  self.project = project
198
198
  self.tb_client = tb_client
199
- self.env = "cloud" if playground else "local"
199
+ if playground:
200
+ self.env = "--cloud"
201
+ else:
202
+ self.env = f"--branch={branch}" if branch else "--local"
200
203
  self.prompt_message = "\ntb » "
201
204
  self.session: PromptSession = PromptSession(
202
205
  completer=DynamicCompleter(project),
@@ -245,6 +248,8 @@ class Shell:
245
248
  self.handle_auth()
246
249
  elif cmd == "workspace":
247
250
  self.handle_workspace()
251
+ elif cmd == "branch":
252
+ self.handle_branch()
248
253
  elif cmd == "deploy":
249
254
  self.handle_deploy()
250
255
  elif cmd == "mock":
@@ -264,13 +269,16 @@ class Shell:
264
269
  def handle_workspace(self):
265
270
  click.echo(FeedbackManager.error(message="'tb workspace' is not available in the dev shell"))
266
271
 
272
+ def handle_branch(self):
273
+ click.echo(FeedbackManager.error(message="'tb branch' is not available in the dev shell"))
274
+
267
275
  def handle_deploy(self):
268
276
  click.echo(FeedbackManager.error(message="'tb deploy' is not available in the dev shell"))
269
277
 
270
278
  def handle_mock(self, arg):
271
279
  if "mock" in arg.strip().lower():
272
280
  arg = arg.replace("mock", "")
273
- subprocess.run(f"tb --{self.env} mock {arg}", shell=True, text=True)
281
+ subprocess.run(f"tb {self.env} mock {arg}", shell=True, text=True)
274
282
 
275
283
  def handle_tb(self, argline):
276
284
  click.echo("")
@@ -287,7 +295,7 @@ class Shell:
287
295
  need_skip = ("mock", "test create", "create")
288
296
  if any(arg.startswith(cmd) for cmd in need_skip):
289
297
  argline = f"{argline}"
290
- subprocess.run(f"tb --{self.env} {argline}", shell=True, text=True)
298
+ subprocess.run(f"tb {self.env} {argline}", shell=True, text=True)
291
299
 
292
300
  def default(self, argline):
293
301
  click.echo("")
@@ -302,7 +310,7 @@ class Shell:
302
310
  need_skip = ("mock", "test create", "create")
303
311
  if any(arg.startswith(cmd) for cmd in need_skip):
304
312
  argline = f"{argline}"
305
- subprocess.run(f"tb --{self.env} {argline}", shell=True, text=True)
313
+ subprocess.run(f"tb {self.env} {argline}", shell=True, text=True)
306
314
 
307
315
  def run_sql(self, query, rows_limit=20):
308
316
  try:
@@ -11,6 +11,7 @@ from tinybird.tb.modules.cli import cli
11
11
  from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_smart_table, wait_job
12
12
  from tinybird.tb.modules.exceptions import CLIPipeException
13
13
  from tinybird.tb.modules.feedback_manager import FeedbackManager
14
+ from tinybird.tb.modules.job_common import echo_job_url
14
15
 
15
16
 
16
17
  @cli.group()
@@ -84,12 +85,13 @@ def sink_run(ctx: click.Context, pipe_name_or_id: str, wait: bool, mode: str, pa
84
85
  params = dict(key_value.split("=") for key_value in param) if param else {}
85
86
  click.echo(FeedbackManager.highlight(message=f"\n» Running sink '{pipe_name_or_id}'"))
86
87
  client: TinyB = ctx.ensure_object(dict)["client"]
88
+ config = ctx.ensure_object(dict)["config"]
87
89
 
88
90
  try:
89
91
  response = client.pipe_run(pipe_name_or_id, "sink", params, mode)
90
92
  job_id = response["job"]["id"]
91
93
  job_url = response["job"]["job_url"]
92
- click.echo(FeedbackManager.gray(message="Job URL: ") + FeedbackManager.info(message=f"{job_url}"))
94
+ echo_job_url(client.token, client.host, config.get("name") or "", job_url)
93
95
  click.echo(FeedbackManager.success(message="✓ Sink job created"))
94
96
 
95
97
  if wait:
@@ -42,8 +42,11 @@ def get_ci_product_name() -> Optional[str]:
42
42
 
43
43
 
44
44
  def is_ci_environment() -> bool:
45
- ci_product: Optional[str] = get_ci_product_name()
46
- return ci_product is not None
45
+ try:
46
+ ci_product: Optional[str] = get_ci_product_name()
47
+ return ci_product is not None
48
+ except Exception:
49
+ return False
47
50
 
48
51
 
49
52
  def silence_errors(f: Callable) -> Callable:
@@ -86,7 +89,7 @@ class TelemetryHelper:
86
89
  tb_host: Optional[str] = None,
87
90
  max_enqueued_events: int = 5,
88
91
  ) -> None:
89
- self.tb_host = tb_host or os.getenv("TB_CLI_TELEMETRY_HOST", "https://api.tinybird.co")
92
+ self.tb_host = tb_host or os.getenv("TB_CLI_TELEMETRY_HOST", "https://api.europe-west2.gcp.tinybird.co")
90
93
  self.max_enqueued_events: int = max_enqueued_events
91
94
 
92
95
  self.enabled: bool = True
@@ -133,6 +136,7 @@ class TelemetryHelper:
133
136
  event_dict["event"] = event
134
137
  event_dict["event_data"] = json.dumps(event_data)
135
138
  event_dict["timestamp"] = datetime.utcnow().isoformat()
139
+ event_dict["cli"] = "forward"
136
140
 
137
141
  self.events.append(event_dict)
138
142
  if len(self.events) >= self.max_enqueued_events:
@@ -81,7 +81,7 @@ def _hide_tokens(text: str) -> str:
81
81
 
82
82
  class TelemetryHelper:
83
83
  def __init__(self, tb_host: Optional[str] = None, max_enqueued_events: int = 5) -> None:
84
- self.tb_host = tb_host or os.getenv("TB_CLI_TELEMETRY_HOST", "https://api.tinybird.co")
84
+ self.tb_host = tb_host or os.getenv("TB_CLI_TELEMETRY_HOST", "https://api.europe-west2.gcp.tinybird.co")
85
85
  self.max_enqueued_events: int = max_enqueued_events
86
86
 
87
87
  self.enabled: bool = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev306
3
+ Version: 1.0.5
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -57,7 +57,32 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
57
57
  Changelog
58
58
  ----------
59
59
 
60
- 0.0.1dev1
61
- ***********
60
+ 1.0.5
61
+ *******
62
62
 
63
- * Initial release of the Tinybird CLI
63
+ - `Added` Internal changes
64
+
65
+ 1.0.4
66
+ *******
67
+
68
+ - `Changed` `tb login` help text has been updated to provide more clarity on the available options.
69
+
70
+ 1.0.3
71
+ *******
72
+
73
+ - `Changed` `tb login` now will warn the user if they are trying to login from a different folder than the last one and will ask for confirmation to continue.
74
+
75
+ 1.0.2
76
+ *******
77
+
78
+ - `Changed` If connection name is not provided in `tb connection data`, it will prompt the user to select a connection from the list of available connections.
79
+
80
+ 1.0.1
81
+ *******
82
+
83
+ - `Added` Support for `schema_registry_url` and `auto_offset_reset` when creating a Kafka connection.
84
+
85
+ 1.0.0
86
+ *******
87
+
88
+ - `Released` Version 1.0.0, from now on the tinybird-cli package uses the standard semver convention for stable versions. Development versions will be tagged as with the `.devX` suffix where `X` is an integer number.