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.
- tinybird/datafile/common.py +4 -1
- tinybird/feedback_manager.py +3 -0
- tinybird/service_datasources.py +57 -8
- tinybird/sql_template.py +1 -1
- tinybird/sql_template_fmt.py +14 -4
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +1 -0
- tinybird/tb/client.py +104 -22
- tinybird/tb/modules/agent/tools/execute_query.py +1 -1
- tinybird/tb/modules/agent/tools/request_endpoint.py +1 -1
- tinybird/tb/modules/branch.py +150 -0
- tinybird/tb/modules/build.py +51 -10
- tinybird/tb/modules/build_common.py +4 -2
- tinybird/tb/modules/cli.py +32 -10
- tinybird/tb/modules/common.py +161 -134
- tinybird/tb/modules/connection.py +125 -194
- tinybird/tb/modules/connection_kafka.py +382 -0
- tinybird/tb/modules/copy.py +3 -1
- tinybird/tb/modules/create.py +11 -0
- tinybird/tb/modules/datafile/build.py +1 -1
- tinybird/tb/modules/datafile/format_pipe.py +44 -5
- tinybird/tb/modules/datafile/playground.py +1 -1
- tinybird/tb/modules/datasource.py +475 -324
- tinybird/tb/modules/deployment.py +2 -0
- tinybird/tb/modules/deployment_common.py +81 -43
- tinybird/tb/modules/deprecations.py +4 -4
- tinybird/tb/modules/dev_server.py +33 -12
- tinybird/tb/modules/info.py +50 -7
- tinybird/tb/modules/job_common.py +15 -0
- tinybird/tb/modules/local.py +91 -21
- tinybird/tb/modules/local_common.py +320 -13
- tinybird/tb/modules/local_logs.py +209 -0
- tinybird/tb/modules/login.py +3 -2
- tinybird/tb/modules/login_common.py +252 -9
- tinybird/tb/modules/open.py +10 -5
- tinybird/tb/modules/project.py +14 -5
- tinybird/tb/modules/shell.py +14 -6
- tinybird/tb/modules/sink.py +3 -1
- tinybird/tb/modules/telemetry.py +7 -3
- tinybird/tb_cli_modules/telemetry.py +1 -1
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/METADATA +29 -4
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/RECORD +45 -41
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/WHEEL +1 -1
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/entry_points.txt +0 -0
- {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,
|
|
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,
|
|
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
|
|
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 =
|
|
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))
|
tinybird/tb/modules/open.py
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
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)
|
tinybird/tb/modules/project.py
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
tinybird/tb/modules/shell.py
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
tinybird/tb/modules/sink.py
CHANGED
|
@@ -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
|
-
|
|
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:
|
tinybird/tb/modules/telemetry.py
CHANGED
|
@@ -42,8 +42,11 @@ def get_ci_product_name() -> Optional[str]:
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def is_ci_environment() -> bool:
|
|
45
|
-
|
|
46
|
-
|
|
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:
|
|
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
|
-
|
|
61
|
-
|
|
60
|
+
1.0.5
|
|
61
|
+
*******
|
|
62
62
|
|
|
63
|
-
|
|
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.
|