tinybird-cli 1.1.1.dev0__tar.gz → 1.1.1.dev2__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.
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/PKG-INFO +17 -1
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/__cli__.py +2 -2
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/ch_utils/engine.py +10 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/client.py +16 -12
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/config.py +2 -1
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/datafile.py +8 -6
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/feedback_manager.py +9 -2
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/branch.py +59 -11
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/cli.py +10 -13
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/common.py +20 -15
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/config.py +10 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/datasource.py +50 -1
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/pipe.py +1 -11
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird_cli.egg-info/PKG-INFO +17 -1
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/setup.cfg +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/check_pypi.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/connector_settings.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/connectors.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/context.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/datatypes.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/sql.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/sql_template.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/sql_toolset.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/syncasync.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/token.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tornado_template.py +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird_cli.egg-info/SOURCES.txt +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird_cli.egg-info/dependency_links.txt +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird_cli.egg-info/entry_points.txt +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird_cli.egg-info/requires.txt +0 -0
- {tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tinybird-cli
|
|
3
|
-
Version: 1.1.1.
|
|
3
|
+
Version: 1.1.1.dev2
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://docs.tinybird.co/cli.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -19,10 +19,26 @@ Changelog
|
|
|
19
19
|
|
|
20
20
|
---------
|
|
21
21
|
|
|
22
|
+
1.1.1.dev2
|
|
23
|
+
************
|
|
24
|
+
|
|
25
|
+
- `Fixed` Do not print data branching summary in `tb env create <env_name>`
|
|
26
|
+
|
|
27
|
+
1.1.1.dev1
|
|
28
|
+
************
|
|
29
|
+
|
|
30
|
+
- `Changed` internal Releases management.
|
|
31
|
+
|
|
32
|
+
1.1.1.dev0
|
|
33
|
+
************
|
|
34
|
+
|
|
35
|
+
- `Changed` internal Releases management.
|
|
36
|
+
|
|
22
37
|
1.1.0
|
|
23
38
|
************
|
|
24
39
|
|
|
25
40
|
Released new version 1.1.0 with all these changes:
|
|
41
|
+
|
|
26
42
|
- `Added` Support `skip` individual regression tests through the regression.yaml configuration file. CI/CD guide at https://www.tinybird.co/docs/guides/continuous-integration.html
|
|
27
43
|
- `Added` Fail `tb env regression-tests` if no tests are run for any pipe, use `skip` to avoid the error
|
|
28
44
|
- `Added` Skip POST requests by default in `tb env regression-tests` until it's supported
|
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://docs.tinybird.co/cli.html'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '1.1.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '1.1.1.dev2'
|
|
8
|
+
__revision__ = '2438918'
|
|
@@ -64,6 +64,10 @@ class TableDetails:
|
|
|
64
64
|
>>> ed = TableDetails({ "engine_full": "MergeTree() PARTITION BY toYear(timestamp) ORDER BY (timestamp, cityHash64(location)) SAMPLE BY cityHash64(location) SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 1800 TTL toDate(timestamp) + INTERVAL 1 DAY"})
|
|
65
65
|
>>> ed.engine_full
|
|
66
66
|
'MergeTree() PARTITION BY toYear(timestamp) ORDER BY (timestamp, cityHash64(location)) SAMPLE BY cityHash64(location) SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 1800 TTL toDate(timestamp) + INTERVAL 1 DAY'
|
|
67
|
+
|
|
68
|
+
>>> x = TableDetails({'database': 'd_01', 'name': 't_01', 'create_table_query': "CREATE TABLE d_01.t_01 (`project_id` String, `project_name` String, `project_repo` String, `owner_id` String, `updated_at` DateTime64(3)) ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{layer}-{shard}/d_test_1ad5e496b29246e1ade99117e9180f6f.t_1bac899a56b34b33921fbf468b4500f7', '{replica}', updated_at) PARTITION BY tuple() PRIMARY KEY project_id ORDER BY project_id SETTINGS index_granularity = 32", 'engine': 'ReplicatedReplacingMergeTree', 'partition_key': 'tuple()', 'sorting_key': 'project_id', 'primary_key': 'project_id', 'sampling_key': '', 'engine_full': "ReplicatedReplacingMergeTree('/clickhouse/tables/{layer}-{shard}/d_01.t_01', '{replica}', updated_at) PARTITION BY tuple() PRIMARY KEY project_id ORDER BY project_id SETTINGS index_granularity = 32", 'settings': 'index_granularity = 32', 'ttl': ''})
|
|
69
|
+
>>> x.primary_key
|
|
70
|
+
|
|
67
71
|
"""
|
|
68
72
|
|
|
69
73
|
def __init__(self, details: Optional[Dict[str, Any]] = None):
|
|
@@ -144,6 +148,8 @@ class TableDetails:
|
|
|
144
148
|
@property
|
|
145
149
|
def primary_key(self) -> Optional[str]:
|
|
146
150
|
_primary_key = self.details.get("primary_key", None)
|
|
151
|
+
# When querying `system.tables`, it will return the `sorting_key` as `primary_key` even if it was not specify
|
|
152
|
+
# So we need to ignore it
|
|
147
153
|
if self.sorting_key == _primary_key:
|
|
148
154
|
return None
|
|
149
155
|
return _primary_key
|
|
@@ -615,6 +621,8 @@ def engine_full_from_dict(
|
|
|
615
621
|
Traceback (most recent call last):
|
|
616
622
|
...
|
|
617
623
|
ValueError: You can not use 'schema' and 'columns' at the same time
|
|
624
|
+
>>> engine_full_from_dict('ReplacingMergeTree', {'partition_key': 'tuple()', 'sorting_key': 'project_id', 'settings': 'index_granularity = 32', 'ver': 'updated_at'}, "`project_id` String, `project_name` String, `project_repo` String, `owner_id` String, `updated_at` DateTime64(3)")
|
|
625
|
+
'ReplacingMergeTree(updated_at) PARTITION BY (tuple()) ORDER BY (project_id) SETTINGS index_granularity = 32'
|
|
618
626
|
"""
|
|
619
627
|
|
|
620
628
|
if schema is not None and columns is not None:
|
|
@@ -653,6 +661,8 @@ def engine_params_from_engine_full(engine_full: str) -> Dict[str, Any]:
|
|
|
653
661
|
{'ver': 'insert_date'}
|
|
654
662
|
>>> engine_params_from_engine_full("ReplicatedVersionedCollapsingMergeTree('/clickhouse/tables/{layer}-{shard}/test.foo', '{replica}', sign_c,version_c) ORDER BY pk TTL toDate(local_timeplaced) + toIntervalDay(3) SETTINGS index_granularity = 8192")
|
|
655
663
|
{'sign': 'sign_c', 'version': 'version_c'}
|
|
664
|
+
>>> engine_params_from_engine_full("ReplacingMergeTree(updated_at) PARTITION BY tuple() PRIMARY KEY project_id ORDER BY project_id SETTINGS index_granularity = 32")
|
|
665
|
+
{'ver': 'updated_at'}
|
|
656
666
|
"""
|
|
657
667
|
engine_full = engine_replicated_to_local(engine_full)
|
|
658
668
|
for engine, (params, _options) in ENABLED_ENGINES:
|
|
@@ -88,6 +88,7 @@ class TinyB(object):
|
|
|
88
88
|
version: Optional[str] = None,
|
|
89
89
|
disable_ssl_checks: bool = False,
|
|
90
90
|
send_telemetry: bool = False,
|
|
91
|
+
semver: Optional[str] = None,
|
|
91
92
|
):
|
|
92
93
|
ctx = ssl.create_default_context()
|
|
93
94
|
ctx.check_hostname = False
|
|
@@ -98,6 +99,7 @@ class TinyB(object):
|
|
|
98
99
|
self.version = version
|
|
99
100
|
self.disable_ssl_checks = disable_ssl_checks
|
|
100
101
|
self.send_telemetry = send_telemetry
|
|
102
|
+
self.semver = semver
|
|
101
103
|
|
|
102
104
|
async def _req(
|
|
103
105
|
self, endpoint: str, data=None, files=None, method: str = "GET", retries: int = LIMIT_RETRIES, **kwargs
|
|
@@ -108,6 +110,8 @@ class TinyB(object):
|
|
|
108
110
|
url += ("&" if "?" in endpoint else "?") + "token=" + self.token
|
|
109
111
|
if self.version:
|
|
110
112
|
url += ("&" if "?" in url else "?") + "cli_version=" + quote(self.version)
|
|
113
|
+
if self.semver:
|
|
114
|
+
url += ("&" if "?" in url else "?") + "__tb__semver=" + self.semver
|
|
111
115
|
|
|
112
116
|
verify_ssl = not self.disable_ssl_checks
|
|
113
117
|
try:
|
|
@@ -466,7 +470,6 @@ class TinyB(object):
|
|
|
466
470
|
populate_condition: Optional[str] = None,
|
|
467
471
|
truncate: bool = True,
|
|
468
472
|
unlink_on_populate_error: bool = False,
|
|
469
|
-
semver: Optional[str] = None,
|
|
470
473
|
):
|
|
471
474
|
params: Dict[str, Any] = {
|
|
472
475
|
"truncate": "true" if truncate else "false",
|
|
@@ -476,8 +479,6 @@ class TinyB(object):
|
|
|
476
479
|
params.update({"populate_subset": populate_subset})
|
|
477
480
|
if populate_condition:
|
|
478
481
|
params.update({"populate_condition": populate_condition})
|
|
479
|
-
if semver:
|
|
480
|
-
params.update({"semver": semver})
|
|
481
482
|
response = await self._req(
|
|
482
483
|
f"/v0/pipes/{pipe_name}/nodes/{node_name}/population?{urlencode(params)}", method="POST"
|
|
483
484
|
)
|
|
@@ -583,12 +584,10 @@ class TinyB(object):
|
|
|
583
584
|
params = {**params} if params else {}
|
|
584
585
|
return await self._req(f"/v0/pipes/{pipe_name_or_id}/sink?{urlencode(params)}", method="POST")
|
|
585
586
|
|
|
586
|
-
async def query(self, sql: str, pipeline: Optional[str] = None
|
|
587
|
+
async def query(self, sql: str, pipeline: Optional[str] = None):
|
|
587
588
|
params = {}
|
|
588
589
|
if pipeline:
|
|
589
590
|
params = {"pipeline": pipeline}
|
|
590
|
-
if semver:
|
|
591
|
-
params["semver"] = semver
|
|
592
591
|
|
|
593
592
|
if len(sql) > TinyB.MAX_GET_LENGTH:
|
|
594
593
|
return await self._req(f"/v0/sql?{urlencode(params)}", data=sql, method="POST")
|
|
@@ -782,14 +781,11 @@ class TinyB(object):
|
|
|
782
781
|
backoff_seconds: float = 2.0,
|
|
783
782
|
backoff_multiplier: float = 1,
|
|
784
783
|
maximum_backoff_seconds: float = 2.0,
|
|
785
|
-
semver: Optional[str] = None,
|
|
786
784
|
) -> Dict[str, Any]:
|
|
787
785
|
res: Dict[str, Any] = {}
|
|
788
786
|
done: bool = False
|
|
789
787
|
while not done:
|
|
790
788
|
params = {"debug": "blocks,block_log"}
|
|
791
|
-
if semver:
|
|
792
|
-
params["semver"] = semver
|
|
793
789
|
res = await self._req(f"/v0/jobs/{job_id}?{urlencode(params)}")
|
|
794
790
|
|
|
795
791
|
if res["status"] == "error":
|
|
@@ -1071,10 +1067,8 @@ class TinyB(object):
|
|
|
1071
1067
|
async def regions(self):
|
|
1072
1068
|
return await self._req("/v0/regions")
|
|
1073
1069
|
|
|
1074
|
-
async def datasource_query_copy(self, datasource_name: str, sql_query: str
|
|
1070
|
+
async def datasource_query_copy(self, datasource_name: str, sql_query: str):
|
|
1075
1071
|
params = {"copy_to": datasource_name}
|
|
1076
|
-
if semver:
|
|
1077
|
-
params["semver"] = semver
|
|
1078
1072
|
return await self._req(f"/v0/sql_copy?{urlencode(params)}", data=sql_query, method="POST")
|
|
1079
1073
|
|
|
1080
1074
|
async def workspace_commit_update(self, workspace_id: str, commit: str):
|
|
@@ -1082,6 +1076,16 @@ class TinyB(object):
|
|
|
1082
1076
|
f"/v0/workspaces/{workspace_id}/releases/?commit={commit}&force=true", method="POST", data=""
|
|
1083
1077
|
)
|
|
1084
1078
|
|
|
1079
|
+
async def release_new(self, workspace_id: str, semver: str, commit: str):
|
|
1080
|
+
params = {
|
|
1081
|
+
"commit": commit,
|
|
1082
|
+
"semver": semver,
|
|
1083
|
+
}
|
|
1084
|
+
return await self._req(f"/v0/workspaces/{workspace_id}/releases/?{urlencode(params)}", method="POST", data="")
|
|
1085
|
+
|
|
1086
|
+
async def release_preview(self, workspace_id: str, semver: str):
|
|
1087
|
+
return await self._req(f"/v0/workspaces/{workspace_id}/releases/{semver}?status=preview", method="PUT")
|
|
1088
|
+
|
|
1085
1089
|
async def release_promote(self, workspace_id: str, semver: str):
|
|
1086
1090
|
return await self._req(f"/v0/workspaces/{workspace_id}/releases/{semver}?status=live", method="PUT")
|
|
1087
1091
|
|
|
@@ -22,7 +22,7 @@ PROJECT_PATHS = ["datasources", "datasources/fixtures", "endpoints", "pipes", "t
|
|
|
22
22
|
MIN_WORKSPACE_ID_LENGTH = 36
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
async def get_config(host: str, token: Optional[str]) -> Dict[str, Any]:
|
|
25
|
+
async def get_config(host: str, token: Optional[str], semver: Optional[str] = None) -> Dict[str, Any]:
|
|
26
26
|
if host:
|
|
27
27
|
host = host.rstrip("/")
|
|
28
28
|
|
|
@@ -39,6 +39,7 @@ async def get_config(host: str, token: Optional[str]) -> Dict[str, Any]:
|
|
|
39
39
|
|
|
40
40
|
config["token_passed"] = token
|
|
41
41
|
config["token"] = token or config.get("token", None)
|
|
42
|
+
config["semver"] = semver or config.get("semver", None)
|
|
42
43
|
config["host"] = host or config.get("host", DEFAULT_API_HOST)
|
|
43
44
|
config["workspaces"] = config.get("workspaces", [])
|
|
44
45
|
return config
|
|
@@ -488,8 +488,8 @@ class Deployment:
|
|
|
488
488
|
def deploying(self):
|
|
489
489
|
click.echo(FeedbackManager.info_deployment_deploying_release_header())
|
|
490
490
|
|
|
491
|
-
async def update_release(self, commit: Optional[str] = None):
|
|
492
|
-
if not self.is_git_release or self.dry_run:
|
|
491
|
+
async def update_release(self, commit: Optional[str] = None, has_semver: Optional[bool] = False):
|
|
492
|
+
if not self.is_git_release or self.dry_run or has_semver:
|
|
493
493
|
return
|
|
494
494
|
self.cli_git_release = self.cli_git_release or CLIGitRelease()
|
|
495
495
|
release = await self.tb_client.workspace_commit_update(
|
|
@@ -3397,6 +3397,9 @@ async def folder_push(
|
|
|
3397
3397
|
(workspace for workspace in workspaces if config and workspace.get("id", ".") == config.get("id", "..")), {}
|
|
3398
3398
|
)
|
|
3399
3399
|
is_environment = current_ws.get("is_branch", False)
|
|
3400
|
+
has_semver = False
|
|
3401
|
+
if config and config.get("semver"):
|
|
3402
|
+
has_semver = True
|
|
3400
3403
|
|
|
3401
3404
|
deployment = Deployment(current_ws, git_release, tb_client, dry_run)
|
|
3402
3405
|
|
|
@@ -3454,7 +3457,7 @@ async def folder_push(
|
|
|
3454
3457
|
workspace_lib_paths=workspace_lib_paths,
|
|
3455
3458
|
current_ws=current_ws,
|
|
3456
3459
|
changed=changed,
|
|
3457
|
-
only_changes=only_changes,
|
|
3460
|
+
only_changes=only_changes or (deployment.is_git_release and has_semver),
|
|
3458
3461
|
fork_downstream=fork_downstream,
|
|
3459
3462
|
is_internal=is_internal,
|
|
3460
3463
|
)
|
|
@@ -3483,7 +3486,7 @@ async def folder_push(
|
|
|
3483
3486
|
workspace_lib_paths=workspace_lib_paths,
|
|
3484
3487
|
current_ws=current_ws,
|
|
3485
3488
|
changed=changed,
|
|
3486
|
-
only_changes=only_changes,
|
|
3489
|
+
only_changes=only_changes or (deployment.is_git_release and has_semver),
|
|
3487
3490
|
skip_connectors=is_environment,
|
|
3488
3491
|
fork_downstream=fork_downstream,
|
|
3489
3492
|
is_internal=is_internal,
|
|
@@ -3659,7 +3662,6 @@ async def folder_push(
|
|
|
3659
3662
|
if not deployment.dry_run:
|
|
3660
3663
|
deployment.deploying()
|
|
3661
3664
|
await push_files(dry_run)
|
|
3662
|
-
|
|
3663
3665
|
else:
|
|
3664
3666
|
await push_files(dry_run)
|
|
3665
3667
|
|
|
@@ -3708,7 +3710,7 @@ async def folder_push(
|
|
|
3708
3710
|
if verbose:
|
|
3709
3711
|
click.echo(FeedbackManager.info_not_pushing_fixtures())
|
|
3710
3712
|
|
|
3711
|
-
await deployment.update_release()
|
|
3713
|
+
await deployment.update_release(has_semver=has_semver)
|
|
3712
3714
|
|
|
3713
3715
|
return to_run
|
|
3714
3716
|
|
|
@@ -78,6 +78,9 @@ class FeedbackManager:
|
|
|
78
78
|
error_processing_data = error_message("{error} - FAIL")
|
|
79
79
|
error_file_already_exists = error_message("{file} already exists, use --force to override")
|
|
80
80
|
error_invalid_token_for_host = error_message("Invalid token for {host}")
|
|
81
|
+
error_invalid_release_for_workspace = error_message(
|
|
82
|
+
"There's no Release with semver {semver} for Workspace {workspace}"
|
|
83
|
+
)
|
|
81
84
|
error_invalid_token = error_message(
|
|
82
85
|
"Invalid token\n** Run 'tb auth --interactive' to select region. If you belong to a custom region, include your region host in the command:\n** tb auth --host https://<region>.tinybird.co"
|
|
83
86
|
)
|
|
@@ -456,7 +459,7 @@ Ready? """
|
|
|
456
459
|
"** Do you want to override {name} with the formatted version shown above?"
|
|
457
460
|
)
|
|
458
461
|
info_populate_job_url = info_message("** Populating job url {url}")
|
|
459
|
-
info_data_branch_job_url = info_message("**
|
|
462
|
+
info_data_branch_job_url = info_message("** Environment job url {url}")
|
|
460
463
|
info_regression_tests_branch_job_url = info_message("** Environment regression tests job url {url}")
|
|
461
464
|
info_merge_branch_job_url = info_message("** Merge Environment deployment job url {url}")
|
|
462
465
|
info_copy_from_main_job_url = info_message("** Copy from 'main' Workspace to '{datasource_name}' job url {url}")
|
|
@@ -607,6 +610,7 @@ Ready? """
|
|
|
607
610
|
success_test_endpoint = info_message(
|
|
608
611
|
"** => Test endpoint with:\n** $ curl {host}/v0/pipes/{pipe}.json?token={token}"
|
|
609
612
|
)
|
|
613
|
+
success_deployment_release = success_message("** => Release {semver} created in deployment status")
|
|
610
614
|
success_test_endpoint_no_token = success_message("** => Test endpoint at {host}/v0/pipes/{pipe}.json")
|
|
611
615
|
success_promote = success_message(
|
|
612
616
|
"** Release has been promoted to Live. Run `tb release ls` to list Releases in the Workspace. To rollback to the previous Release run `tb release rollback`."
|
|
@@ -756,9 +760,12 @@ Ready? """
|
|
|
756
760
|
"** Workspace '{workspace_name}' release initialized to commit '{release_commit}'.\n Now start working with git, pushing changes to pull requests and let the CI/CD work for you. More details in this guide: https://www.tinybird.co/docs/guides/working-with-git.html."
|
|
757
761
|
)
|
|
758
762
|
success_release_promote = success_message("** Release {semver} promoted to live")
|
|
763
|
+
success_release_preview = success_message("** Release {semver} in preview status")
|
|
759
764
|
success_release_rollback = success_message("** Workspace rolled back to Release {semver}")
|
|
760
765
|
success_release_delete = success_message("** Release {semver} deleted")
|
|
761
|
-
success_release_delete_dry_run = success_message(
|
|
766
|
+
success_release_delete_dry_run = success_message(
|
|
767
|
+
"** Release {semver} delete (dry run). The following resources are not used in any other Release and would be deleted:"
|
|
768
|
+
)
|
|
762
769
|
|
|
763
770
|
success_delete_token = success_message("** Token '{token}' removed successfully")
|
|
764
771
|
success_refresh_token = success_message("** Token '{token}' refreshed successfully")
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# - If it makes sense and only when strictly necessary, you can create utility functions in this file.
|
|
4
4
|
# - But please, **do not** interleave utility functions and command definitions.
|
|
5
5
|
|
|
6
|
+
from os import getcwd
|
|
6
7
|
from typing import Any, Dict, List, Tuple, Optional
|
|
7
8
|
import click
|
|
8
9
|
from click import Context
|
|
@@ -128,6 +129,31 @@ tb deploy
|
|
|
128
129
|
click.echo(FeedbackManager.info_release_generated(semver=semver))
|
|
129
130
|
|
|
130
131
|
|
|
132
|
+
@release.command(name="create", short_help="Create a new Release in deploying status")
|
|
133
|
+
@click.option(
|
|
134
|
+
"--semver",
|
|
135
|
+
is_flag=False,
|
|
136
|
+
required=True,
|
|
137
|
+
type=str,
|
|
138
|
+
help="Semver of the new Release. Example: 1.0.0",
|
|
139
|
+
)
|
|
140
|
+
@click.pass_context
|
|
141
|
+
@coro
|
|
142
|
+
async def release_create(ctx: Context, semver: str) -> None:
|
|
143
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
144
|
+
config = ctx.ensure_object(dict)["config"]
|
|
145
|
+
folder = getcwd()
|
|
146
|
+
cli_git_release = None
|
|
147
|
+
try:
|
|
148
|
+
cli_git_release = CLIGitRelease(path=folder)
|
|
149
|
+
commit = cli_git_release.head_commit()
|
|
150
|
+
except CLIGitReleaseException:
|
|
151
|
+
raise CLIGitReleaseException(FeedbackManager.error_no_git_repo_for_init(repo_path=folder))
|
|
152
|
+
|
|
153
|
+
await client.release_new(config["id"], semver, commit)
|
|
154
|
+
click.echo(FeedbackManager.success_deployment_release(semver=semver))
|
|
155
|
+
|
|
156
|
+
|
|
131
157
|
@release.command(name="promote", short_help="Promotes to live status a preview Release")
|
|
132
158
|
@click.option(
|
|
133
159
|
"--semver",
|
|
@@ -161,6 +187,35 @@ async def release_promote(ctx: Context, semver: str) -> None:
|
|
|
161
187
|
raise CLIReleaseException(FeedbackManager.error_exception(error=str(e)))
|
|
162
188
|
|
|
163
189
|
|
|
190
|
+
@release.command(name="preview", short_help="Updates the status of a deploying Release to preview")
|
|
191
|
+
@click.option(
|
|
192
|
+
"--semver", is_flag=False, required=True, type=str, help="Semver of a preview Release to preview. Example: 1.0.0"
|
|
193
|
+
)
|
|
194
|
+
@click.pass_context
|
|
195
|
+
@coro
|
|
196
|
+
async def release_preview(ctx: Context, semver: str) -> None:
|
|
197
|
+
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
198
|
+
client: TinyB = obj["client"]
|
|
199
|
+
config = obj["config"]
|
|
200
|
+
|
|
201
|
+
if "id" not in config:
|
|
202
|
+
config = await _get_config(config["host"], config["token"], load_tb_file=False)
|
|
203
|
+
|
|
204
|
+
current_main_workspace = await get_current_main_workspace(client, config)
|
|
205
|
+
if not current_main_workspace:
|
|
206
|
+
raise CLIReleaseException(FeedbackManager.error_exception(error=str("ERROR!")))
|
|
207
|
+
# FIXME validate is not environment
|
|
208
|
+
|
|
209
|
+
if current_main_workspace["id"] != config["id"]:
|
|
210
|
+
client = _get_tb_client(current_main_workspace["token"], config["host"])
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
await client.release_preview(current_main_workspace["id"], semver)
|
|
214
|
+
click.echo(FeedbackManager.success_release_preview(semver=semver))
|
|
215
|
+
except Exception as e:
|
|
216
|
+
raise CLIReleaseException(FeedbackManager.error_exception(error=str(e)))
|
|
217
|
+
|
|
218
|
+
|
|
164
219
|
@release.command(name="rollback", short_help="Rollbacks to a previous Release")
|
|
165
220
|
@click.pass_context
|
|
166
221
|
@coro
|
|
@@ -484,7 +539,7 @@ async def data_branch(ctx: Context, last_partition: bool, all: bool, ignore_data
|
|
|
484
539
|
job_id = response["job"]["job_id"]
|
|
485
540
|
job_url = response["job"]["job_url"]
|
|
486
541
|
click.echo(FeedbackManager.info_data_branch_job_url(url=job_url))
|
|
487
|
-
job_response = await wait_job(client, job_id, job_url, "
|
|
542
|
+
job_response = await wait_job(client, job_id, job_url, "Environment creation")
|
|
488
543
|
response = job_response["result"]
|
|
489
544
|
is_job = False
|
|
490
545
|
is_summary = "partitions" in response
|
|
@@ -494,7 +549,7 @@ async def data_branch(ctx: Context, last_partition: bool, all: bool, ignore_data
|
|
|
494
549
|
else:
|
|
495
550
|
if not is_job and not is_summary:
|
|
496
551
|
FeedbackManager.warning_unknown_response(response=response)
|
|
497
|
-
elif is_summary:
|
|
552
|
+
elif is_summary and (bool(last_partition) or bool(all)):
|
|
498
553
|
await print_data_branch_summary(client, None, response)
|
|
499
554
|
click.echo(FeedbackManager.success_workspace_data_branch())
|
|
500
555
|
|
|
@@ -973,17 +1028,10 @@ def datasource(ctx: Context) -> None:
|
|
|
973
1028
|
required=False,
|
|
974
1029
|
)
|
|
975
1030
|
@click.option("--wait", is_flag=True, default=False, help="Wait for copy job to finish, disabled by default")
|
|
976
|
-
@click.option(
|
|
977
|
-
"--semver",
|
|
978
|
-
is_flag=False,
|
|
979
|
-
required=False,
|
|
980
|
-
type=str,
|
|
981
|
-
help="Release semver where to run the copy operation. Example: 1.0.0",
|
|
982
|
-
)
|
|
983
1031
|
@click.pass_context
|
|
984
1032
|
@coro
|
|
985
1033
|
async def datasource_copy_from_main(
|
|
986
|
-
ctx: Context, datasource_name: str, sql: str, sql_from_main: bool, wait: bool
|
|
1034
|
+
ctx: Context, datasource_name: str, sql: str, sql_from_main: bool, wait: bool
|
|
987
1035
|
) -> None:
|
|
988
1036
|
"""Copy data source from Main."""
|
|
989
1037
|
|
|
@@ -1007,7 +1055,7 @@ async def datasource_copy_from_main(
|
|
|
1007
1055
|
return
|
|
1008
1056
|
|
|
1009
1057
|
response = await client.datasource_query_copy(
|
|
1010
|
-
datasource_name, sql if sql else f"SELECT * FROM main.{datasource_name}"
|
|
1058
|
+
datasource_name, sql if sql else f"SELECT * FROM main.{datasource_name}"
|
|
1011
1059
|
)
|
|
1012
1060
|
if "job" not in response:
|
|
1013
1061
|
raise CLIBranchException(response)
|
|
@@ -83,6 +83,7 @@ DEFAULT_PATTERNS: List[Tuple[str, Union[str, Callable[[str], str]]]] = [
|
|
|
83
83
|
)
|
|
84
84
|
@click.option("--token", help="Use auth token, defaults to TB_TOKEN envvar, then to the .tinyb file")
|
|
85
85
|
@click.option("--host", help="Use custom host, defaults to TB_HOST envvar, then to https://api.tinybird.co")
|
|
86
|
+
@click.option("--semver", help="Semver of a Release to run the command. Example: 1.0.0")
|
|
86
87
|
@click.option("--gcp-project-id", help="The Google Cloud project ID", hidden=True)
|
|
87
88
|
@click.option(
|
|
88
89
|
"--gcs-bucket", help="The Google Cloud Storage bucket to write temp files when using the connectors", hidden=True
|
|
@@ -130,6 +131,7 @@ async def cli(
|
|
|
130
131
|
debug: bool,
|
|
131
132
|
token: str,
|
|
132
133
|
host: str,
|
|
134
|
+
semver: str,
|
|
133
135
|
gcp_project_id: str,
|
|
134
136
|
gcs_bucket: str,
|
|
135
137
|
google_application_credentials: str,
|
|
@@ -188,6 +190,8 @@ async def cli(
|
|
|
188
190
|
config_temp.set_token(token)
|
|
189
191
|
if host:
|
|
190
192
|
config_temp.set_host(host)
|
|
193
|
+
if semver:
|
|
194
|
+
config_temp.set_semver(semver)
|
|
191
195
|
|
|
192
196
|
# Overwrite token and host with env vars manually, without resorting to click.
|
|
193
197
|
#
|
|
@@ -198,8 +202,10 @@ async def cli(
|
|
|
198
202
|
token = os.environ.get("TB_TOKEN", "")
|
|
199
203
|
if not host and "TB_HOST" in os.environ:
|
|
200
204
|
host = os.environ.get("TB_HOST", "")
|
|
205
|
+
if not semver and "TB_SEMVER" in os.environ:
|
|
206
|
+
semver = os.environ.get("TB_SEMVER", "")
|
|
201
207
|
|
|
202
|
-
config = await get_config(host, token)
|
|
208
|
+
config = await get_config(host, token, semver)
|
|
203
209
|
client = _get_tb_client(config.get("token", None), config["host"])
|
|
204
210
|
|
|
205
211
|
# If they have passed a token or host as paramter and it's different that record in .tinyb, refresh the workspace id
|
|
@@ -255,7 +261,7 @@ async def cli(
|
|
|
255
261
|
|
|
256
262
|
logging.debug("debug enabled")
|
|
257
263
|
|
|
258
|
-
ctx.ensure_object(dict)["client"] =
|
|
264
|
+
ctx.ensure_object(dict)["client"] = _get_tb_client(config.get("token", None), config["host"], semver)
|
|
259
265
|
|
|
260
266
|
for connector in SUPPORTED_CONNECTORS:
|
|
261
267
|
load_connector_config(ctx, connector, debug, check_uninstalled=True)
|
|
@@ -917,14 +923,6 @@ async def diff(
|
|
|
917
923
|
default="human",
|
|
918
924
|
help="Output format",
|
|
919
925
|
)
|
|
920
|
-
@click.option(
|
|
921
|
-
"--semver",
|
|
922
|
-
is_flag=False,
|
|
923
|
-
required=False,
|
|
924
|
-
type=str,
|
|
925
|
-
help="Semver of a preview Release to run the query. Example: 1.0.0",
|
|
926
|
-
hidden=True,
|
|
927
|
-
)
|
|
928
926
|
@click.option("--stats/--no-stats", default=False, help="Show query stats")
|
|
929
927
|
@click.pass_context
|
|
930
928
|
@coro
|
|
@@ -936,7 +934,6 @@ async def sql(
|
|
|
936
934
|
pipe: Optional[str],
|
|
937
935
|
node: Optional[str],
|
|
938
936
|
format_: str,
|
|
939
|
-
semver: Optional[str],
|
|
940
937
|
stats: bool,
|
|
941
938
|
) -> None:
|
|
942
939
|
"""Run SQL query over data sources and pipes."""
|
|
@@ -954,7 +951,7 @@ async def sql(
|
|
|
954
951
|
click.echo(FeedbackManager.error_invalid_query())
|
|
955
952
|
return
|
|
956
953
|
res = await client.query(
|
|
957
|
-
f"SELECT * FROM ({query}) LIMIT {rows_limit} FORMAT {req_format}", pipeline=pipeline
|
|
954
|
+
f"SELECT * FROM ({query}) LIMIT {rows_limit} FORMAT {req_format}", pipeline=pipeline
|
|
958
955
|
)
|
|
959
956
|
elif pipe and node:
|
|
960
957
|
datasources: List[Dict[str, Any]] = await client.datasources()
|
|
@@ -1001,7 +998,7 @@ async def sql(
|
|
|
1001
998
|
query = "".join(_node["sql"])
|
|
1002
999
|
pipeline = pipe.split("/")[-1].split(".pipe")[0]
|
|
1003
1000
|
res = await client.query(
|
|
1004
|
-
f"SELECT * FROM ({query}) LIMIT {rows_limit} FORMAT {req_format}", pipeline=pipeline
|
|
1001
|
+
f"SELECT * FROM ({query}) LIMIT {rows_limit} FORMAT {req_format}", pipeline=pipeline
|
|
1005
1002
|
)
|
|
1006
1003
|
except Exception as e:
|
|
1007
1004
|
click.echo(FeedbackManager.error_exception(error=str(e)))
|
|
@@ -274,15 +274,16 @@ def getenv_bool(key: str, default: bool) -> bool:
|
|
|
274
274
|
return v.lower() == "true" or v == "1"
|
|
275
275
|
|
|
276
276
|
|
|
277
|
-
def _get_tb_client(token: str, host: str) -> TinyB:
|
|
277
|
+
def _get_tb_client(token: str, host: str, semver: Optional[str] = None) -> TinyB:
|
|
278
278
|
disable_ssl: bool = getenv_bool("TB_DISABLE_SSL_CHECKS", False)
|
|
279
|
-
return TinyB(token, host, version=VERSION, disable_ssl_checks=disable_ssl, send_telemetry=True)
|
|
279
|
+
return TinyB(token, host, version=VERSION, disable_ssl_checks=disable_ssl, send_telemetry=True, semver=semver)
|
|
280
280
|
|
|
281
281
|
|
|
282
282
|
def create_tb_client(ctx: Context) -> TinyB:
|
|
283
283
|
token = ctx.ensure_object(dict)["config"].get("token", "")
|
|
284
284
|
host = ctx.ensure_object(dict)["config"].get("host", DEFAULT_API_HOST)
|
|
285
|
-
|
|
285
|
+
semver = ctx.ensure_object(dict)["config"].get("semver", "")
|
|
286
|
+
return _get_tb_client(token, host, semver=semver)
|
|
286
287
|
|
|
287
288
|
|
|
288
289
|
async def _analyze(filename: str, client: TinyB, format: str, connector: Optional[Connector] = None):
|
|
@@ -488,7 +489,7 @@ async def configure_connector(connector):
|
|
|
488
489
|
click.echo(FeedbackManager.success_connector_config(connector=connector, file_name=file_name))
|
|
489
490
|
|
|
490
491
|
|
|
491
|
-
async def _get_config(host, token, load_tb_file=True):
|
|
492
|
+
async def _get_config(host: str, token: str, load_tb_file: bool = True, semver: Optional[str] = None):
|
|
492
493
|
config = {}
|
|
493
494
|
|
|
494
495
|
try:
|
|
@@ -497,6 +498,13 @@ async def _get_config(host, token, load_tb_file=True):
|
|
|
497
498
|
except Exception:
|
|
498
499
|
raise CLIAuthException(FeedbackManager.error_invalid_token_for_host(host=host))
|
|
499
500
|
|
|
501
|
+
if semver:
|
|
502
|
+
release_exists = any(release["semver"] == semver for release in response["releases"])
|
|
503
|
+
if not release_exists:
|
|
504
|
+
raise CLIAuthException(
|
|
505
|
+
FeedbackManager.error_invalid_release_for_workspace(semver=semver, workspace=response["name"])
|
|
506
|
+
)
|
|
507
|
+
|
|
500
508
|
from_response = load_tb_file
|
|
501
509
|
|
|
502
510
|
try:
|
|
@@ -953,7 +961,7 @@ async def create_workspace_branch(
|
|
|
953
961
|
assert isinstance(job_id, str)
|
|
954
962
|
|
|
955
963
|
# Await the job to finish and get the result dict
|
|
956
|
-
job_response = await wait_job(client, job_id, job_url, "
|
|
964
|
+
job_response = await wait_job(client, job_id, job_url, "Environment creation")
|
|
957
965
|
if job_response is None:
|
|
958
966
|
raise CLIException(f"Empty job API response (job_id: {job_id}, job_url: {job_url})")
|
|
959
967
|
else:
|
|
@@ -962,7 +970,7 @@ async def create_workspace_branch(
|
|
|
962
970
|
is_summary = "partitions" in response
|
|
963
971
|
|
|
964
972
|
await switch_workspace(ctx, branch_name, only_environments=True)
|
|
965
|
-
if is_summary:
|
|
973
|
+
if is_summary and (bool(last_partition) or bool(all)):
|
|
966
974
|
await print_data_branch_summary(client, None, response)
|
|
967
975
|
|
|
968
976
|
except Exception as e:
|
|
@@ -1511,13 +1519,14 @@ def _get_setting_value(connection, setting, sensitive_settings):
|
|
|
1511
1519
|
return connection.get(setting, "")
|
|
1512
1520
|
|
|
1513
1521
|
|
|
1514
|
-
async def _get_config_or_load_tb_file(config):
|
|
1522
|
+
async def _get_config_or_load_tb_file(config, semver: Optional[str] = None):
|
|
1515
1523
|
if "id" not in config:
|
|
1516
|
-
config = await _get_config(config["host"], config["token"], load_tb_file=False)
|
|
1524
|
+
config = await _get_config(config["host"], config["token"], load_tb_file=False, semver=semver)
|
|
1517
1525
|
else:
|
|
1518
1526
|
config_file = Path(getcwd()) / ".tinyb"
|
|
1519
1527
|
with open(config_file) as file:
|
|
1520
1528
|
config = json.loads(file.read())
|
|
1529
|
+
config["semver"] = semver
|
|
1521
1530
|
return config
|
|
1522
1531
|
|
|
1523
1532
|
|
|
@@ -1634,7 +1643,7 @@ async def print_current_branch(ctx):
|
|
|
1634
1643
|
|
|
1635
1644
|
response = await client.user_workspaces_and_branches()
|
|
1636
1645
|
|
|
1637
|
-
columns = ["name", "id", "
|
|
1646
|
+
columns = ["name", "id", "workspace"]
|
|
1638
1647
|
table = []
|
|
1639
1648
|
|
|
1640
1649
|
for workspace in response["workspaces"]:
|
|
@@ -1909,7 +1918,6 @@ async def wait_job(
|
|
|
1909
1918
|
label: str,
|
|
1910
1919
|
timeout: Optional[int] = None,
|
|
1911
1920
|
wait_observer: Optional[Callable[[Dict[str, Any], ProgressBar], None]] = None,
|
|
1912
|
-
semver: Optional[str] = None,
|
|
1913
1921
|
) -> Dict[str, Any]:
|
|
1914
1922
|
progress_bar: ProgressBar
|
|
1915
1923
|
with click.progressbar(
|
|
@@ -1932,7 +1940,7 @@ async def wait_job(
|
|
|
1932
1940
|
|
|
1933
1941
|
try:
|
|
1934
1942
|
# TODO: Simplify this as it's not needed to use two functions for
|
|
1935
|
-
result = await wait_job_no_ui(tb_client, job_id, timeout, progressbar_cb
|
|
1943
|
+
result = await wait_job_no_ui(tb_client, job_id, timeout, progressbar_cb)
|
|
1936
1944
|
if result["status"] != "done":
|
|
1937
1945
|
click.echo(FeedbackManager.error_while_running_job(error=result["error"]))
|
|
1938
1946
|
return result
|
|
@@ -1949,12 +1957,9 @@ async def wait_job_no_ui(
|
|
|
1949
1957
|
job_id: str,
|
|
1950
1958
|
timeout: Optional[int] = None,
|
|
1951
1959
|
status_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
|
|
1952
|
-
semver: Optional[str] = None,
|
|
1953
1960
|
) -> Dict[str, Any]:
|
|
1954
1961
|
try:
|
|
1955
|
-
result = await asyncio.wait_for(
|
|
1956
|
-
tb_client.wait_for_job(job_id, status_callback=status_callback, semver=semver), timeout
|
|
1957
|
-
)
|
|
1962
|
+
result = await asyncio.wait_for(tb_client.wait_for_job(job_id, status_callback=status_callback), timeout)
|
|
1958
1963
|
if result["status"] != "done":
|
|
1959
1964
|
raise JobException(result.get("error"))
|
|
1960
1965
|
return result
|
|
@@ -73,6 +73,7 @@ class CLIConfig:
|
|
|
73
73
|
"token": "TB_TOKEN",
|
|
74
74
|
"user_token": "TB_USER_TOKEN",
|
|
75
75
|
"host": "TB_HOST",
|
|
76
|
+
"semver": "TB_SEMVER",
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
DEFAULTS: Dict[str, str] = {"host": DEFAULT_API_HOST if not FeatureFlags.is_localhost() else DEFAULT_LOCALHOST}
|
|
@@ -178,6 +179,15 @@ class CLIConfig:
|
|
|
178
179
|
except KeyError:
|
|
179
180
|
return None
|
|
180
181
|
|
|
182
|
+
def set_semver(self, semver: Optional[str]) -> None:
|
|
183
|
+
self["semver"] = semver
|
|
184
|
+
|
|
185
|
+
def get_semver(self) -> Optional[str]:
|
|
186
|
+
try:
|
|
187
|
+
return self["semver"]
|
|
188
|
+
except KeyError:
|
|
189
|
+
return None
|
|
190
|
+
|
|
181
191
|
def set_token_for_host(self, token: Optional[str], host: Optional[str]) -> None:
|
|
182
192
|
"""Sets the token for the specified host.
|
|
183
193
|
|
|
@@ -16,7 +16,7 @@ import humanfriendly
|
|
|
16
16
|
from tinybird.client import CanNotBeDeletedException, DoesNotExistException, TinyB
|
|
17
17
|
from tinybird.connectors import Connector
|
|
18
18
|
from tinybird.feedback_manager import FeedbackManager
|
|
19
|
-
from tinybird.datafile import get_name_tag_version
|
|
19
|
+
from tinybird.datafile import get_name_tag_version, wait_job
|
|
20
20
|
from tinybird.tb_cli_modules.cli import cli
|
|
21
21
|
from tinybird.tb_cli_modules.common import (
|
|
22
22
|
_analyze,
|
|
@@ -747,3 +747,52 @@ async def datasource_exchange(ctx, datasource_a, datasource_b):
|
|
|
747
747
|
raise CLIDatasourceException(FeedbackManager.error_exception(error=e))
|
|
748
748
|
|
|
749
749
|
click.echo(FeedbackManager.success_exchange_datasources(datasource_a=datasource_a, datasource_b=datasource_b))
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
@datasource.command(name="copy")
|
|
753
|
+
@click.argument("datasource_name")
|
|
754
|
+
@click.option(
|
|
755
|
+
"--sql",
|
|
756
|
+
default=None,
|
|
757
|
+
help="Freeform SQL query to select what is copied from Main into the Environment Data Source",
|
|
758
|
+
required=False,
|
|
759
|
+
)
|
|
760
|
+
@click.option(
|
|
761
|
+
"--sql-from-main",
|
|
762
|
+
is_flag=True,
|
|
763
|
+
default=False,
|
|
764
|
+
help="SQL query selecting * from the same Data Source in Main",
|
|
765
|
+
required=False,
|
|
766
|
+
)
|
|
767
|
+
@click.option("--wait", is_flag=True, default=False, help="Wait for copy job to finish, disabled by default")
|
|
768
|
+
@click.pass_context
|
|
769
|
+
@coro
|
|
770
|
+
async def datasource_copy_from_main(
|
|
771
|
+
ctx: Context, datasource_name: str, sql: str, sql_from_main: bool, wait: bool
|
|
772
|
+
) -> None:
|
|
773
|
+
"""Copy data source from Main."""
|
|
774
|
+
|
|
775
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
776
|
+
|
|
777
|
+
if sql and sql_from_main:
|
|
778
|
+
click.echo(FeedbackManager.error_exception(error="Use --sql or --sql-from-main but not both"))
|
|
779
|
+
return
|
|
780
|
+
|
|
781
|
+
if not sql and not sql_from_main:
|
|
782
|
+
click.echo(FeedbackManager.error_exception(error="Use --sql or --sql-from-main"))
|
|
783
|
+
return
|
|
784
|
+
|
|
785
|
+
response = await client.datasource_query_copy(
|
|
786
|
+
datasource_name, sql if sql else f"SELECT * FROM main.{datasource_name}"
|
|
787
|
+
)
|
|
788
|
+
if "job" not in response:
|
|
789
|
+
raise Exception(response)
|
|
790
|
+
job_id = response["job"]["job_id"]
|
|
791
|
+
job_url = response["job"]["job_url"]
|
|
792
|
+
if sql:
|
|
793
|
+
click.echo(FeedbackManager.info_copy_with_sql_job_url(sql=sql, datasource_name=datasource_name, url=job_url))
|
|
794
|
+
else:
|
|
795
|
+
click.echo(FeedbackManager.info_copy_from_main_job_url(datasource_name=datasource_name, url=job_url))
|
|
796
|
+
if wait:
|
|
797
|
+
base_msg = "Copy from Main Workspace" if sql_from_main else f"Copy from {sql}"
|
|
798
|
+
await wait_job(client, job_id, job_url, f"{base_msg} to {datasource_name}")
|
|
@@ -240,14 +240,6 @@ async def pipe_ls(ctx: Context, prefix: str, match: str, format_: str):
|
|
|
240
240
|
default=False,
|
|
241
241
|
help="Waits for populate jobs to finish, showing a progress bar. Disabled by default.",
|
|
242
242
|
)
|
|
243
|
-
@click.option(
|
|
244
|
-
"--semver",
|
|
245
|
-
is_flag=False,
|
|
246
|
-
required=False,
|
|
247
|
-
type=str,
|
|
248
|
-
help="Semver of a preview Release to run the population. Example: 1.0.0",
|
|
249
|
-
hidden=True,
|
|
250
|
-
)
|
|
251
243
|
@click.pass_context
|
|
252
244
|
@coro
|
|
253
245
|
async def pipe_populate(
|
|
@@ -258,7 +250,6 @@ async def pipe_populate(
|
|
|
258
250
|
truncate: bool,
|
|
259
251
|
unlink_on_populate_error: bool,
|
|
260
252
|
wait: bool,
|
|
261
|
-
semver: str,
|
|
262
253
|
):
|
|
263
254
|
"""Populate the result of a Materialized Node into the target Materialized View"""
|
|
264
255
|
cl = create_tb_client(ctx)
|
|
@@ -268,7 +259,6 @@ async def pipe_populate(
|
|
|
268
259
|
populate_condition=sql_condition,
|
|
269
260
|
truncate=truncate,
|
|
270
261
|
unlink_on_populate_error=unlink_on_populate_error,
|
|
271
|
-
semver=semver,
|
|
272
262
|
)
|
|
273
263
|
if "job" not in response:
|
|
274
264
|
raise CLIPipeException(response)
|
|
@@ -280,7 +270,7 @@ async def pipe_populate(
|
|
|
280
270
|
else:
|
|
281
271
|
click.echo(FeedbackManager.info_populate_job_url(url=job_url))
|
|
282
272
|
if wait:
|
|
283
|
-
await wait_job(cl, job_id, job_url, "Populating"
|
|
273
|
+
await wait_job(cl, job_id, job_url, "Populating")
|
|
284
274
|
|
|
285
275
|
|
|
286
276
|
@pipe.command(name="new", hidden=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tinybird-cli
|
|
3
|
-
Version: 1.1.1.
|
|
3
|
+
Version: 1.1.1.dev2
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://docs.tinybird.co/cli.html
|
|
6
6
|
Author: Tinybird
|
|
@@ -19,10 +19,26 @@ Changelog
|
|
|
19
19
|
|
|
20
20
|
---------
|
|
21
21
|
|
|
22
|
+
1.1.1.dev2
|
|
23
|
+
************
|
|
24
|
+
|
|
25
|
+
- `Fixed` Do not print data branching summary in `tb env create <env_name>`
|
|
26
|
+
|
|
27
|
+
1.1.1.dev1
|
|
28
|
+
************
|
|
29
|
+
|
|
30
|
+
- `Changed` internal Releases management.
|
|
31
|
+
|
|
32
|
+
1.1.1.dev0
|
|
33
|
+
************
|
|
34
|
+
|
|
35
|
+
- `Changed` internal Releases management.
|
|
36
|
+
|
|
22
37
|
1.1.0
|
|
23
38
|
************
|
|
24
39
|
|
|
25
40
|
Released new version 1.1.0 with all these changes:
|
|
41
|
+
|
|
26
42
|
- `Added` Support `skip` individual regression tests through the regression.yaml configuration file. CI/CD guide at https://www.tinybird.co/docs/guides/continuous-integration.html
|
|
27
43
|
- `Added` Fail `tb env regression-tests` if no tests are run for any pipe, use `skip` to avoid the error
|
|
28
44
|
- `Added` Skip POST requests by default in `tb env regression-tests` until it's supported
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/tinyunit/tinyunit.py
RENAMED
|
File without changes
|
{tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird/tb_cli_modules/workspace_members.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-cli-1.1.1.dev0 → tinybird-cli-1.1.1.dev2}/tinybird_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|