tinybird-cli 6.4.1.dev0__tar.gz → 6.5.1.dev0__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-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/PKG-INFO +13 -2
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/__cli__.py +2 -2
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/client.py +43 -7
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/config.py +0 -2
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/datafile_common.py +102 -43
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/datatypes.py +2 -2
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/git_settings.py +1 -1
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/sql.py +14 -8
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/sql_template.py +6 -5
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/sql_toolset.py +1 -19
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/auth.py +1 -1
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/branch.py +12 -14
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/cli.py +11 -13
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/common.py +16 -15
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/datasource.py +4 -4
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/job.py +1 -3
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/tag.py +2 -3
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/workspace.py +12 -13
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird_cli.egg-info/PKG-INFO +13 -2
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird_cli.egg-info/requires.txt +1 -1
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/setup.cfg +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/ch_utils/constants.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/ch_utils/engine.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/check_pypi.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/context.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/feedback_manager.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/sql_template_fmt.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/syncasync.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/token.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tornado_template.py +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird_cli.egg-info/SOURCES.txt +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird_cli.egg-info/dependency_links.txt +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird_cli.egg-info/entry_points.txt +0 -0
- {tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird_cli
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.5.1.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli
|
|
6
6
|
Author: Tinybird
|
|
@@ -12,7 +12,7 @@ Requires-Dist: clickhouse-toolset==0.34.dev0
|
|
|
12
12
|
Requires-Dist: click<8.2,>=8.1.8
|
|
13
13
|
Requires-Dist: colorama==0.4.6
|
|
14
14
|
Requires-Dist: cryptography~=41.0.0
|
|
15
|
-
Requires-Dist: croniter==
|
|
15
|
+
Requires-Dist: croniter==6.2.2
|
|
16
16
|
Requires-Dist: GitPython~=3.1.32
|
|
17
17
|
Requires-Dist: humanfriendly~=8.2
|
|
18
18
|
Requires-Dist: pydantic~=2.8.0
|
|
@@ -43,6 +43,17 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
43
43
|
Changelog
|
|
44
44
|
----------
|
|
45
45
|
|
|
46
|
+
6.5.0
|
|
47
|
+
***********
|
|
48
|
+
|
|
49
|
+
- `Changed` `tb push` can now perform sharing and unsharing operations on Datasources with a DATASOURCES:CREATE token in workspaces in the same Organization
|
|
50
|
+
|
|
51
|
+
6.4.1
|
|
52
|
+
***********
|
|
53
|
+
|
|
54
|
+
- `Improved` feedback messages when suggesting other cli commands.
|
|
55
|
+
- `Fixed` `tb push` rejecting the `%` operator in column DEFAULT/MATERIALIZED expressions, causing failures after `tb pull` on schemas using modulo arithmetic.
|
|
56
|
+
|
|
46
57
|
6.4.0
|
|
47
58
|
***********
|
|
48
59
|
|
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://www.tinybird.co/docs/cli'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '6.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '6.5.1.dev0'
|
|
8
|
+
__revision__ = '676e19d'
|
|
@@ -340,10 +340,14 @@ class TinyB:
|
|
|
340
340
|
for c in connectors
|
|
341
341
|
]
|
|
342
342
|
|
|
343
|
-
async def get_datasource(
|
|
343
|
+
async def get_datasource(
|
|
344
|
+
self, ds_name: str, used_by: bool = False, include_workspace_names: bool = False
|
|
345
|
+
) -> Dict[str, Any]:
|
|
344
346
|
params = {
|
|
345
347
|
"attrs": "used_by" if used_by else "",
|
|
346
348
|
}
|
|
349
|
+
if include_workspace_names:
|
|
350
|
+
params["include_workspace_names"] = "true"
|
|
347
351
|
return await self._req(f"/v0/datasources/{ds_name}?{urlencode(params)}")
|
|
348
352
|
|
|
349
353
|
async def alter_datasource(
|
|
@@ -489,12 +493,32 @@ class TinyB:
|
|
|
489
493
|
|
|
490
494
|
return await self._req(f"/v0/dependencies?{urlencode(params)}", timeout=60)
|
|
491
495
|
|
|
492
|
-
async def datasource_share(
|
|
493
|
-
|
|
496
|
+
async def datasource_share(
|
|
497
|
+
self,
|
|
498
|
+
datasource_id: str,
|
|
499
|
+
current_workspace_id: str,
|
|
500
|
+
destination_workspace_id: Optional[str] = None,
|
|
501
|
+
destination_workspace_name: Optional[str] = None,
|
|
502
|
+
) -> Dict[str, Any]:
|
|
503
|
+
params = {"origin_workspace_id": current_workspace_id}
|
|
504
|
+
if destination_workspace_id:
|
|
505
|
+
params["destination_workspace_id"] = destination_workspace_id
|
|
506
|
+
if destination_workspace_name:
|
|
507
|
+
params["destination_workspace_name"] = destination_workspace_name
|
|
494
508
|
return await self._req(f"/v0/datasources/{datasource_id}/share", method="POST", data=params)
|
|
495
509
|
|
|
496
|
-
async def datasource_unshare(
|
|
497
|
-
|
|
510
|
+
async def datasource_unshare(
|
|
511
|
+
self,
|
|
512
|
+
datasource_id: str,
|
|
513
|
+
current_workspace_id: str,
|
|
514
|
+
destination_workspace_id: Optional[str] = None,
|
|
515
|
+
destination_workspace_name: Optional[str] = None,
|
|
516
|
+
) -> Dict[str, Any]:
|
|
517
|
+
params = {"origin_workspace_id": current_workspace_id}
|
|
518
|
+
if destination_workspace_id:
|
|
519
|
+
params["destination_workspace_id"] = destination_workspace_id
|
|
520
|
+
if destination_workspace_name:
|
|
521
|
+
params["destination_workspace_name"] = destination_workspace_name
|
|
498
522
|
return await self._req(f"/v0/datasources/{datasource_id}/share", method="DELETE", data=params)
|
|
499
523
|
|
|
500
524
|
async def datasource_sync(self, datasource_id: str):
|
|
@@ -972,16 +996,28 @@ class TinyB:
|
|
|
972
996
|
kafka_sasl_mechanism="PLAIN",
|
|
973
997
|
kafka_security_protocol="SASL_SSL",
|
|
974
998
|
kafka_ssl_ca_pem=None,
|
|
999
|
+
kafka_sasl_oauthbearer_method=None,
|
|
1000
|
+
kafka_sasl_oauthbearer_aws_region=None,
|
|
1001
|
+
kafka_sasl_oauthbearer_aws_role_arn=None,
|
|
1002
|
+
kafka_sasl_oauthbearer_aws_external_id=None,
|
|
975
1003
|
):
|
|
1004
|
+
is_oauthbearer = kafka_sasl_mechanism == "OAUTHBEARER"
|
|
976
1005
|
params = {
|
|
977
1006
|
"service": "kafka",
|
|
978
1007
|
"kafka_security_protocol": kafka_security_protocol,
|
|
979
1008
|
"kafka_sasl_mechanism": kafka_sasl_mechanism,
|
|
980
1009
|
"kafka_bootstrap_servers": kafka_bootstrap_servers,
|
|
981
|
-
"kafka_sasl_plain_username": kafka_key,
|
|
982
|
-
"kafka_sasl_plain_password": kafka_secret,
|
|
983
1010
|
"name": kafka_connection_name,
|
|
984
1011
|
}
|
|
1012
|
+
if is_oauthbearer:
|
|
1013
|
+
params["kafka_sasl_oauthbearer_method"] = kafka_sasl_oauthbearer_method
|
|
1014
|
+
params["kafka_sasl_oauthbearer_aws_region"] = kafka_sasl_oauthbearer_aws_region
|
|
1015
|
+
params["kafka_sasl_oauthbearer_aws_role_arn"] = kafka_sasl_oauthbearer_aws_role_arn
|
|
1016
|
+
if kafka_sasl_oauthbearer_aws_external_id:
|
|
1017
|
+
params["kafka_sasl_oauthbearer_aws_external_id"] = kafka_sasl_oauthbearer_aws_external_id
|
|
1018
|
+
else:
|
|
1019
|
+
params["kafka_sasl_plain_username"] = kafka_key
|
|
1020
|
+
params["kafka_sasl_plain_password"] = kafka_secret
|
|
985
1021
|
|
|
986
1022
|
if kafka_schema_registry_url:
|
|
987
1023
|
params["kafka_schema_registry_url"] = kafka_schema_registry_url
|
|
@@ -37,7 +37,6 @@ LEGACY_HOSTS = {
|
|
|
37
37
|
"https://api.wadus2.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus2",
|
|
38
38
|
"https://api.wadus3.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus3",
|
|
39
39
|
"https://api.wadus4.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus4",
|
|
40
|
-
"https://api.wadus5.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus5",
|
|
41
40
|
"https://api.wadus1.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus1",
|
|
42
41
|
"https://api.wadus2.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus2",
|
|
43
42
|
"https://api.wadus3.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus3",
|
|
@@ -59,7 +58,6 @@ LEGACY_HOSTS = {
|
|
|
59
58
|
"https://ui.wadus2.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus2",
|
|
60
59
|
"https://ui.wadus3.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus3",
|
|
61
60
|
"https://ui.wadus4.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus4",
|
|
62
|
-
"https://ui.wadus5.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus5",
|
|
63
61
|
"https://ui.wadus1.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus1",
|
|
64
62
|
"https://ui.wadus2.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus2",
|
|
65
63
|
"https://ui.wadus3.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus3",
|
|
@@ -425,7 +425,7 @@ class CLIGitRelease:
|
|
|
425
425
|
|
|
426
426
|
def is_dirty_to_release(self, use_include_dir: bool = True) -> bool:
|
|
427
427
|
if use_include_dir:
|
|
428
|
-
return any(
|
|
428
|
+
return any(self.repo.is_dirty(path=p) for p in self.paths) or self.has_untracked_files()
|
|
429
429
|
else:
|
|
430
430
|
return self.repo.is_dirty(path=self.path) or self.has_untracked_files()
|
|
431
431
|
|
|
@@ -594,7 +594,9 @@ class Deployment:
|
|
|
594
594
|
):
|
|
595
595
|
if self.is_git_release:
|
|
596
596
|
if not self.current_release:
|
|
597
|
-
raise CLIGitReleaseException(
|
|
597
|
+
raise CLIGitReleaseException(
|
|
598
|
+
FeedbackManager.error_init_release(workspace=self.current_ws["name"], cli="tb")
|
|
599
|
+
)
|
|
598
600
|
self.cli_git_release = CLIGitRelease()
|
|
599
601
|
if not use_main:
|
|
600
602
|
self.cli_git_release.validate_local_for_release(self.current_release, check_outdated=check_outdated)
|
|
@@ -613,7 +615,7 @@ class Deployment:
|
|
|
613
615
|
# error until we support it https://gitlab.com/tinybird/analytics/-/issues/9655
|
|
614
616
|
for d in diffs:
|
|
615
617
|
if self.cli_git_release.ChangeType(d.change_type) == self.cli_git_release.ChangeType.RENAMED:
|
|
616
|
-
raise CLIGitReleaseException(FeedbackManager.error_unsupported_diff())
|
|
618
|
+
raise CLIGitReleaseException(FeedbackManager.error_unsupported_diff(cli="tb"))
|
|
617
619
|
if not diffs:
|
|
618
620
|
click.echo(FeedbackManager.info_git_release_no_diffs())
|
|
619
621
|
changed = self.cli_git_release.get_changes_from_diffs(diffs, filenames)
|
|
@@ -701,10 +703,10 @@ class Deployment:
|
|
|
701
703
|
release = await self.cli_git_release.update_release(self.tb_client, self.current_ws, commit)
|
|
702
704
|
click.echo(FeedbackManager.success_git_release(release_commit=release["commit"]))
|
|
703
705
|
else:
|
|
704
|
-
click.echo(FeedbackManager.warning_no_release())
|
|
706
|
+
click.echo(FeedbackManager.warning_no_release(cli="tb"))
|
|
705
707
|
except Exception as e:
|
|
706
708
|
if self.only_changes:
|
|
707
|
-
click.echo(FeedbackManager.warning_no_release())
|
|
709
|
+
click.echo(FeedbackManager.warning_no_release(cli="tb"))
|
|
708
710
|
else:
|
|
709
711
|
raise e
|
|
710
712
|
|
|
@@ -1233,6 +1235,10 @@ def parse(
|
|
|
1233
1235
|
"kafka_key_avro_deserialization": assign_var("kafka_key_avro_deserialization"),
|
|
1234
1236
|
"kafka_ssl_ca_pem": assign_var("kafka_ssl_ca_pem"),
|
|
1235
1237
|
"kafka_sasl_mechanism": assign_var("kafka_sasl_mechanism"),
|
|
1238
|
+
"kafka_sasl_oauthbearer_method": assign_var("kafka_sasl_oauthbearer_method"),
|
|
1239
|
+
"kafka_sasl_oauthbearer_aws_region": assign_var("kafka_sasl_oauthbearer_aws_region"),
|
|
1240
|
+
"kafka_sasl_oauthbearer_aws_role_arn": assign_var("kafka_sasl_oauthbearer_aws_role_arn"),
|
|
1241
|
+
"kafka_sasl_oauthbearer_aws_external_id": assign_var("kafka_sasl_oauthbearer_aws_external_id"),
|
|
1236
1242
|
"import_service": assign_var("import_service"),
|
|
1237
1243
|
"import_connection_name": assign_var("import_connection_name"),
|
|
1238
1244
|
"import_schedule": assign_var("import_schedule"),
|
|
@@ -1366,6 +1372,7 @@ async def process_file(
|
|
|
1366
1372
|
|
|
1367
1373
|
if not skip_connectors:
|
|
1368
1374
|
try:
|
|
1375
|
+
is_oauthbearer = params.get("kafka_sasl_mechanism") == "OAUTHBEARER"
|
|
1369
1376
|
connector_params = {
|
|
1370
1377
|
"kafka_bootstrap_servers": params.get("kafka_bootstrap_servers", None),
|
|
1371
1378
|
"kafka_key": params.get("kafka_key", None),
|
|
@@ -1375,6 +1382,12 @@ async def process_file(
|
|
|
1375
1382
|
"kafka_schema_registry_url": params.get("kafka_schema_registry_url", None),
|
|
1376
1383
|
"kafka_ssl_ca_pem": get_ca_pem_content(params.get("kafka_ssl_ca_pem", None), filename),
|
|
1377
1384
|
"kafka_sasl_mechanism": params.get("kafka_sasl_mechanism", None),
|
|
1385
|
+
"kafka_sasl_oauthbearer_method": params.get("kafka_sasl_oauthbearer_method", None),
|
|
1386
|
+
"kafka_sasl_oauthbearer_aws_region": params.get("kafka_sasl_oauthbearer_aws_region", None),
|
|
1387
|
+
"kafka_sasl_oauthbearer_aws_role_arn": params.get("kafka_sasl_oauthbearer_aws_role_arn", None),
|
|
1388
|
+
"kafka_sasl_oauthbearer_aws_external_id": params.get(
|
|
1389
|
+
"kafka_sasl_oauthbearer_aws_external_id", None
|
|
1390
|
+
),
|
|
1378
1391
|
}
|
|
1379
1392
|
|
|
1380
1393
|
connector = await tb_client.get_connection(**connector_params)
|
|
@@ -1382,11 +1395,19 @@ async def process_file(
|
|
|
1382
1395
|
click.echo(
|
|
1383
1396
|
FeedbackManager.info_creating_kafka_connection(connection_name=params["kafka_connection_name"])
|
|
1384
1397
|
)
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1398
|
+
if is_oauthbearer:
|
|
1399
|
+
required_params = [
|
|
1400
|
+
connector_params["kafka_bootstrap_servers"],
|
|
1401
|
+
connector_params["kafka_sasl_oauthbearer_method"],
|
|
1402
|
+
connector_params["kafka_sasl_oauthbearer_aws_region"],
|
|
1403
|
+
connector_params["kafka_sasl_oauthbearer_aws_role_arn"],
|
|
1404
|
+
]
|
|
1405
|
+
else:
|
|
1406
|
+
required_params = [
|
|
1407
|
+
connector_params["kafka_bootstrap_servers"],
|
|
1408
|
+
connector_params["kafka_key"],
|
|
1409
|
+
connector_params["kafka_secret"],
|
|
1410
|
+
]
|
|
1390
1411
|
|
|
1391
1412
|
if not all(required_params):
|
|
1392
1413
|
raise click.ClickException(FeedbackManager.error_unknown_kafka_connection(datasource=name))
|
|
@@ -1547,10 +1568,7 @@ async def process_file(
|
|
|
1547
1568
|
period: int = DEFAULT_CRON_PERIOD
|
|
1548
1569
|
|
|
1549
1570
|
if current_ws:
|
|
1550
|
-
|
|
1551
|
-
workspace_rate_limits: Dict[str, Dict[str, int]] = next(
|
|
1552
|
-
(w.get("rate_limits", {}) for w in workspaces if w["id"] == current_ws["id"]), {}
|
|
1553
|
-
)
|
|
1571
|
+
workspace_rate_limits: Dict[str, Dict[str, int]] = current_ws.get("rate_limits", {})
|
|
1554
1572
|
period = workspace_rate_limits.get("api_datasources_create_append_replace", {}).get(
|
|
1555
1573
|
"period", DEFAULT_CRON_PERIOD
|
|
1556
1574
|
)
|
|
@@ -1630,7 +1648,7 @@ async def process_file(
|
|
|
1630
1648
|
deps = []
|
|
1631
1649
|
nodes: List[Dict[str, Any]] = []
|
|
1632
1650
|
|
|
1633
|
-
is_copy = any(
|
|
1651
|
+
is_copy = any(node for node in doc.nodes if node.get("type", "standard").lower() == PipeNodeTypes.COPY)
|
|
1634
1652
|
for node in doc.nodes:
|
|
1635
1653
|
sql = node["sql"]
|
|
1636
1654
|
node_type = node.get("type", "standard").lower()
|
|
@@ -2910,7 +2928,7 @@ async def new_pipe(
|
|
|
2910
2928
|
current_pipe = r.json() if r.status_code == 200 else None
|
|
2911
2929
|
pipe_exists = current_pipe is not None
|
|
2912
2930
|
|
|
2913
|
-
is_materialized = any(
|
|
2931
|
+
is_materialized = any(node.get("params", {}).get("type", None) == "materialized" for node in p["nodes"])
|
|
2914
2932
|
copy_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "copy"), None)
|
|
2915
2933
|
sink_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "sink"), None)
|
|
2916
2934
|
stream_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "stream"), None)
|
|
@@ -3137,7 +3155,7 @@ async def new_pipe(
|
|
|
3137
3155
|
async def share_and_unshare_datasource(
|
|
3138
3156
|
client: TinyB,
|
|
3139
3157
|
datasource: Dict[str, Any],
|
|
3140
|
-
user_token: str,
|
|
3158
|
+
user_token: Optional[str],
|
|
3141
3159
|
workspaces_current_shared_with: List[str],
|
|
3142
3160
|
workspaces_to_share: List[str],
|
|
3143
3161
|
current_ws: Optional[Dict[str, Any]],
|
|
@@ -3145,21 +3163,58 @@ async def share_and_unshare_datasource(
|
|
|
3145
3163
|
datasource_name = datasource.get("name", "")
|
|
3146
3164
|
datasource_id = datasource.get("id", "")
|
|
3147
3165
|
workspaces: List[Dict[str, Any]]
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3166
|
+
user_client: TinyB = deepcopy(client) if user_token else client
|
|
3167
|
+
if user_token:
|
|
3168
|
+
# We duplicate the client to use the user_token in workspace discovery and sharing operations.
|
|
3169
|
+
user_client.token = user_token
|
|
3151
3170
|
|
|
3152
3171
|
# In case we are pushing to a branch, we don't share the datasource
|
|
3153
3172
|
# FIXME: Have only once way to get the current workspace
|
|
3154
3173
|
if current_ws:
|
|
3155
3174
|
workspace = current_ws
|
|
3156
|
-
|
|
3175
|
+
elif user_token:
|
|
3157
3176
|
workspace = await client.user_workspace_branches()
|
|
3177
|
+
else:
|
|
3178
|
+
workspace = await client.workspace_info()
|
|
3158
3179
|
|
|
3159
3180
|
if workspace.get("is_branch", False):
|
|
3160
3181
|
click.echo(FeedbackManager.info_skipping_sharing_datasources_branch(datasource=datasource["name"]))
|
|
3161
3182
|
return
|
|
3162
3183
|
|
|
3184
|
+
if not user_token:
|
|
3185
|
+
workspaces_current_shared_with = [
|
|
3186
|
+
shared_workspace["name"] for shared_workspace in datasource.get("shared_with_workspaces", [])
|
|
3187
|
+
]
|
|
3188
|
+
workspace_names_need_to_share = [
|
|
3189
|
+
workspace_name
|
|
3190
|
+
for workspace_name in workspaces_to_share
|
|
3191
|
+
if workspace_name not in workspaces_current_shared_with
|
|
3192
|
+
]
|
|
3193
|
+
workspace_names_need_to_unshare = [
|
|
3194
|
+
workspace_name
|
|
3195
|
+
for workspace_name in workspaces_current_shared_with
|
|
3196
|
+
if workspace_name not in workspaces_to_share
|
|
3197
|
+
]
|
|
3198
|
+
|
|
3199
|
+
for workspace_name in workspace_names_need_to_share:
|
|
3200
|
+
await user_client.datasource_share(
|
|
3201
|
+
datasource_id=datasource_id,
|
|
3202
|
+
current_workspace_id=workspace.get("id", ""),
|
|
3203
|
+
destination_workspace_name=workspace_name,
|
|
3204
|
+
)
|
|
3205
|
+
click.echo(FeedbackManager.success_datasource_shared(datasource=datasource_name, workspace=workspace_name))
|
|
3206
|
+
|
|
3207
|
+
for workspace_name in workspace_names_need_to_unshare:
|
|
3208
|
+
await user_client.datasource_unshare(
|
|
3209
|
+
datasource_id=datasource_id,
|
|
3210
|
+
current_workspace_id=workspace.get("id", ""),
|
|
3211
|
+
destination_workspace_name=workspace_name,
|
|
3212
|
+
)
|
|
3213
|
+
click.echo(
|
|
3214
|
+
FeedbackManager.success_datasource_unshared(datasource=datasource_name, workspace=workspace_name)
|
|
3215
|
+
)
|
|
3216
|
+
return
|
|
3217
|
+
|
|
3163
3218
|
# Use the user token for workspace discovery, as workspace/admin tokens may not list all targets.
|
|
3164
3219
|
workspaces = (await user_client.user_workspaces()).get("workspaces", [])
|
|
3165
3220
|
if not workspaces_current_shared_with:
|
|
@@ -3243,8 +3298,12 @@ async def new_ds(
|
|
|
3243
3298
|
scopes.append(sc)
|
|
3244
3299
|
await client.alter_tokens(token_name, scopes)
|
|
3245
3300
|
|
|
3301
|
+
can_manage_shared_with = bool(user_token or (current_ws and current_ws.get("can_manage_datasources")))
|
|
3302
|
+
|
|
3246
3303
|
try:
|
|
3247
|
-
existing_ds = await client.get_datasource(
|
|
3304
|
+
existing_ds = await client.get_datasource(
|
|
3305
|
+
ds_name, include_workspace_names=can_manage_shared_with and not user_token
|
|
3306
|
+
)
|
|
3248
3307
|
datasource_exists = True
|
|
3249
3308
|
except DoesNotExistException:
|
|
3250
3309
|
datasource_exists = False
|
|
@@ -3302,7 +3361,7 @@ async def new_ds(
|
|
|
3302
3361
|
await manage_tokens()
|
|
3303
3362
|
|
|
3304
3363
|
if ds.get("shared_with"):
|
|
3305
|
-
if not
|
|
3364
|
+
if not can_manage_shared_with:
|
|
3306
3365
|
click.echo(FeedbackManager.info_skipping_shared_with_entry())
|
|
3307
3366
|
else:
|
|
3308
3367
|
await share_and_unshare_datasource(
|
|
@@ -3322,7 +3381,7 @@ async def new_ds(
|
|
|
3322
3381
|
raise click.ClickException(FeedbackManager.error_datasource_already_exists(datasource=ds_name))
|
|
3323
3382
|
|
|
3324
3383
|
if ds.get("shared_with", []) or existing_ds.get("shared_with", []):
|
|
3325
|
-
if not
|
|
3384
|
+
if not can_manage_shared_with:
|
|
3326
3385
|
click.echo(FeedbackManager.info_skipping_shared_with_entry())
|
|
3327
3386
|
else:
|
|
3328
3387
|
await share_and_unshare_datasource(
|
|
@@ -3359,7 +3418,7 @@ async def new_ds(
|
|
|
3359
3418
|
existing = existing_ds.get("indexes", [])
|
|
3360
3419
|
new.sort(key=lambda x: x["name"])
|
|
3361
3420
|
existing.sort(key=lambda x: x["name"])
|
|
3362
|
-
if len(existing) != len(new) or any(
|
|
3421
|
+
if len(existing) != len(new) or any((d, d2) for d, d2 in zip(new, existing) if d != d2):
|
|
3363
3422
|
new_indices = ds.get("params", {}).get("indexes") or "0"
|
|
3364
3423
|
if (
|
|
3365
3424
|
new_description
|
|
@@ -3383,7 +3442,7 @@ async def new_ds(
|
|
|
3383
3442
|
|
|
3384
3443
|
if alter_response:
|
|
3385
3444
|
if git_release and not skip_confirmation:
|
|
3386
|
-
click.echo(FeedbackManager.info_custom_deployment())
|
|
3445
|
+
click.echo(FeedbackManager.info_custom_deployment(cli="tb"))
|
|
3387
3446
|
click.echo("***************************************")
|
|
3388
3447
|
click.echo("***************************************")
|
|
3389
3448
|
click.echo(FeedbackManager.info_datasource_doesnt_match(datasource=ds_name))
|
|
@@ -3559,7 +3618,7 @@ async def new_token(token: Dict[str, Any], client: TinyB, force: bool = False):
|
|
|
3559
3618
|
|
|
3560
3619
|
if force:
|
|
3561
3620
|
ADMIN_SCOPES = ["ADMIN", "ADMIN_USER"]
|
|
3562
|
-
if any(
|
|
3621
|
+
if any(scope["type"] in ADMIN_SCOPES for scope in existing_token["scopes"]):
|
|
3563
3622
|
raise click.ClickException(FeedbackManager.error_token_cannot_be_overriden(token=token["name"]))
|
|
3564
3623
|
|
|
3565
3624
|
await client.token_update(token)
|
|
@@ -4002,7 +4061,7 @@ async def build_graph(
|
|
|
4002
4061
|
if (
|
|
4003
4062
|
fork_downstream
|
|
4004
4063
|
and r.get("resource", "") == "pipes"
|
|
4005
|
-
and any(
|
|
4064
|
+
and any("engine" in x.get("params", {}) for x in r.get("nodes", []))
|
|
4006
4065
|
):
|
|
4007
4066
|
raise click.ClickException(FeedbackManager.error_forkdownstream_pipes_with_engine(pipe=fn))
|
|
4008
4067
|
|
|
@@ -4034,13 +4093,10 @@ async def build_graph(
|
|
|
4034
4093
|
|
|
4035
4094
|
# In case the datasource is to be shared and we have mapping, let's replace the name
|
|
4036
4095
|
if "shared_with" in r and workspace_map:
|
|
4037
|
-
mapped_workspaces: List[str] = [
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
if workspace_map.get(shared_with, None) is not None
|
|
4042
|
-
else shared_with # type: ignore
|
|
4043
|
-
)
|
|
4096
|
+
mapped_workspaces: List[str] = [
|
|
4097
|
+
workspace_map.get(shared_with) if workspace_map.get(shared_with, None) is not None else shared_with # type: ignore
|
|
4098
|
+
for shared_with in r["shared_with"]
|
|
4099
|
+
]
|
|
4044
4100
|
r["shared_with"] = mapped_workspaces
|
|
4045
4101
|
|
|
4046
4102
|
dep_map[fn] = set(dep_list)
|
|
@@ -4241,10 +4297,13 @@ async def folder_push(
|
|
|
4241
4297
|
hide_folders: bool = False,
|
|
4242
4298
|
on_demand_compute: bool = False,
|
|
4243
4299
|
):
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4300
|
+
if tb_client.semver:
|
|
4301
|
+
workspaces: List[Dict[str, Any]] = (await tb_client.user_workspaces_and_branches()).get("workspaces", [])
|
|
4302
|
+
current_ws: Dict[str, Any] = next(
|
|
4303
|
+
(workspace for workspace in workspaces if config and workspace.get("id", ".") == config.get("id", "..")), {}
|
|
4304
|
+
)
|
|
4305
|
+
else:
|
|
4306
|
+
current_ws = await tb_client.workspace_info()
|
|
4248
4307
|
is_branch = current_ws.get("is_branch", False)
|
|
4249
4308
|
has_semver: bool = release_created or False
|
|
4250
4309
|
|
|
@@ -4270,13 +4329,13 @@ async def folder_push(
|
|
|
4270
4329
|
existing_resources: List[str] = [x["name"] for x in datasources] + [x["name"] for x in pipes]
|
|
4271
4330
|
# replace workspace mapping names
|
|
4272
4331
|
for old_ws, new_ws in workspace_map.items():
|
|
4273
|
-
existing_resources = [re.sub(f"^{old_ws}
|
|
4332
|
+
existing_resources = [re.sub(f"^{old_ws}\\.", f"{new_ws}.", x) for x in existing_resources]
|
|
4274
4333
|
|
|
4275
4334
|
remote_resource_names = [get_remote_resource_name_without_version(x) for x in existing_resources]
|
|
4276
4335
|
|
|
4277
4336
|
# replace workspace mapping names
|
|
4278
4337
|
for old_ws, new_ws in workspace_map.items():
|
|
4279
|
-
remote_resource_names = [re.sub(f"^{old_ws}
|
|
4338
|
+
remote_resource_names = [re.sub(f"^{old_ws}\\.", f"{new_ws}.", x) for x in remote_resource_names]
|
|
4280
4339
|
|
|
4281
4340
|
if not filenames:
|
|
4282
4341
|
filenames = get_project_filenames(folder)
|
|
@@ -5555,7 +5614,7 @@ def is_materialized(resource: Optional[Dict[str, Any]]) -> bool:
|
|
|
5555
5614
|
return False
|
|
5556
5615
|
|
|
5557
5616
|
is_materialized = any(
|
|
5558
|
-
|
|
5617
|
+
node.get("params", {}).get("type", None) == "materialized" for node in resource.get("nodes", []) or []
|
|
5559
5618
|
)
|
|
5560
5619
|
return is_materialized
|
|
5561
5620
|
|
|
@@ -5634,7 +5693,7 @@ async def create_release(
|
|
|
5634
5693
|
def has_internal_datafiles(folder: str) -> bool:
|
|
5635
5694
|
folder = folder or "."
|
|
5636
5695
|
filenames = get_project_filenames(folder)
|
|
5637
|
-
return any(
|
|
5696
|
+
return any(f for f in filenames if "spans" in str(f) and "vendor" not in str(f))
|
|
5638
5697
|
|
|
5639
5698
|
|
|
5640
5699
|
def is_file_a_datasource(filename: str) -> bool:
|
|
@@ -117,11 +117,11 @@ def date_test(x: str) -> bool:
|
|
|
117
117
|
|
|
118
118
|
|
|
119
119
|
def datetime64_test(x: str) -> bool:
|
|
120
|
-
return any(
|
|
120
|
+
return any(p.match(x) for p in datetime64_patterns)
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
def datetime_test(x: str) -> bool:
|
|
124
|
-
return any(
|
|
124
|
+
return any(p.match(x) for p in datetime_patterns)
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
def int_8_test(x: str) -> bool:
|
|
@@ -71,7 +71,7 @@ VERSION=0.0.0
|
|
|
71
71
|
# TB_SKIP_REGRESSION=0
|
|
72
72
|
|
|
73
73
|
# Use `OBFUSCATE_REGEX_PATTERN` and `OBFUSCATE_PATTERN_SEPARATOR` environment variables to define a regex pattern and a separator (in case of a single string with multiple regex) to obfuscate secrets in the CLI output.
|
|
74
|
-
# OBFUSCATE_REGEX_PATTERN="https://(www
|
|
74
|
+
# OBFUSCATE_REGEX_PATTERN="https://(www\\.)?[^/]+||^Follow these instructions =>"
|
|
75
75
|
# OBFUSCATE_PATTERN_SEPARATOR=||
|
|
76
76
|
##########
|
|
77
77
|
"""
|
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|
|
6
6
|
from typing import Any, Dict, Iterable, List, Optional
|
|
7
7
|
|
|
8
8
|
valid_chars_name: str = string.ascii_letters + string.digits + "._`*<>+-'"
|
|
9
|
-
valid_chars_fn: str = valid_chars_name + "[]()
|
|
9
|
+
valid_chars_fn: str = valid_chars_name + "[](),=!?:/% \n\t\r"
|
|
10
10
|
# Use sets for O(1) membership checks in hot loops
|
|
11
11
|
_VALID_CHARS_NAME_SET = set(valid_chars_name)
|
|
12
12
|
_VALID_CHARS_FN_SET = set(valid_chars_fn)
|
|
@@ -233,10 +233,21 @@ def try_to_fix_nullable_in_simple_aggregating_function(t: str) -> Optional[str]:
|
|
|
233
233
|
if match := _RE_TRY_FIX_NULLABLE_SAF.search(t):
|
|
234
234
|
fn = match.group(1)
|
|
235
235
|
inner_type = match.group(2)
|
|
236
|
-
|
|
236
|
+
if "Nullable(" not in inner_type:
|
|
237
|
+
result = f"SimpleAggregateFunction({fn}, Nullable({inner_type}))"
|
|
237
238
|
return result
|
|
238
239
|
|
|
239
240
|
|
|
241
|
+
def wrap_nullable(col: dict[str, Any]):
|
|
242
|
+
if col["nullable"]:
|
|
243
|
+
if (col_type := try_to_fix_nullable_in_simple_aggregating_function(col["type"])) is None:
|
|
244
|
+
# Skip wrapping if Nullable already present, e.g. LowCardinality(Nullable(String))
|
|
245
|
+
col_type = col["type"] if "Nullable(" in col["type"] else "Nullable(%s)" % col["type"]
|
|
246
|
+
else:
|
|
247
|
+
col_type = col["type"]
|
|
248
|
+
return col_type
|
|
249
|
+
|
|
250
|
+
|
|
240
251
|
def schema_to_sql_columns(schema: List[Dict[str, Any]], skip_jsonpaths: bool = False) -> List[str]:
|
|
241
252
|
"""return an array with each column in SQL
|
|
242
253
|
>>> schema_to_sql_columns([{'name': 'temperature', 'type': 'Float32', 'codec': None, 'default_value': None, 'nullable': False, 'normalized_name': 'temperature'}, {'name': 'temperature_delta', 'type': 'Float32', 'codec': 'CODEC(Delta(4), LZ4))', 'default_value': 'MATERIALIZED temperature', 'nullable': False, 'normalized_name': 'temperature_delta'}])
|
|
@@ -255,12 +266,7 @@ def schema_to_sql_columns(schema: List[Dict[str, Any]], skip_jsonpaths: bool = F
|
|
|
255
266
|
columns: List[str] = []
|
|
256
267
|
for x in schema:
|
|
257
268
|
name = x["normalized_name"] if "normalized_name" in x else x["name"]
|
|
258
|
-
|
|
259
|
-
if (_type := try_to_fix_nullable_in_simple_aggregating_function(x["type"])) is None:
|
|
260
|
-
# Skip wrapping if Nullable already present, e.g. LowCardinality(Nullable(String))
|
|
261
|
-
_type = x["type"] if "Nullable(" in x["type"] else "Nullable(%s)" % x["type"]
|
|
262
|
-
else:
|
|
263
|
-
_type = x["type"]
|
|
269
|
+
_type = wrap_nullable(x)
|
|
264
270
|
parts = [col_name(name, backquotes=True), _type]
|
|
265
271
|
if x.get("jsonpath", None) and not skip_jsonpaths:
|
|
266
272
|
parts.append(f"`json:{x['jsonpath']}`")
|
|
@@ -20,6 +20,8 @@ from tinybird.context import (
|
|
|
20
20
|
from .datatypes import testers
|
|
21
21
|
from .tornado_template import VALID_CUSTOM_FUNCTION_NAMES, SecurityException, Template
|
|
22
22
|
|
|
23
|
+
VALID_ACTIVATE_FEATURES = frozenset(["analyzer", "parallel_replicas", "optimize_aggregation_in_order"])
|
|
24
|
+
|
|
23
25
|
TB_SECRET_IN_TEST_MODE = "tb_secret_dont_raise"
|
|
24
26
|
TB_SECRET_PREFIX = "tb_secret_"
|
|
25
27
|
CH_PARAM_PREFIX = "param_"
|
|
@@ -1463,10 +1465,9 @@ def generate(self, **kwargs) -> Tuple[str, TemplateExecutionResults]:
|
|
|
1463
1465
|
return Expression(f"-- cache_ttl {ttl_expression}\n")
|
|
1464
1466
|
|
|
1465
1467
|
def set_activate(feature):
|
|
1466
|
-
|
|
1467
|
-
if feature not in valid_features:
|
|
1468
|
+
if feature not in VALID_ACTIVATE_FEATURES:
|
|
1468
1469
|
raise SQLTemplateException(f"'{feature}' is not a valid 'activate' argument")
|
|
1469
|
-
template_execution_results
|
|
1470
|
+
template_execution_results.setdefault("activate", set()).add(feature)
|
|
1470
1471
|
return Expression(f"-- activate {feature}\n")
|
|
1471
1472
|
|
|
1472
1473
|
def set_disable_feature(feature):
|
|
@@ -2143,7 +2144,7 @@ def get_var_data(content, node_id=None):
|
|
|
2143
2144
|
return [dict(name=k, **v) for k, v in vars.items()]
|
|
2144
2145
|
|
|
2145
2146
|
|
|
2146
|
-
def get_var_names_and_types(t, node_id=None):
|
|
2147
|
+
def get_var_names_and_types(t: Template, node_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
2147
2148
|
"""
|
|
2148
2149
|
>>> get_var_names_and_types(Template("SELECT * FROM filter_value WHERE description = {{Float32(with_value, 0.0)}}"))
|
|
2149
2150
|
[{'name': 'with_value', 'type': 'Float32', 'default': 0.0}]
|
|
@@ -2825,7 +2826,7 @@ def render_sql_template(
|
|
|
2825
2826
|
return Comment("error launched")
|
|
2826
2827
|
|
|
2827
2828
|
v: dict = {x["name"]: Placeholder(x["name"], x["line"]) for x in template_variables}
|
|
2828
|
-
is_tb_secret = any(
|
|
2829
|
+
is_tb_secret = any(s for s in template_variables if s["name"] == "tb_secret" or s["name"] == "tb_var")
|
|
2829
2830
|
|
|
2830
2831
|
if variables:
|
|
2831
2832
|
v.update(variables)
|
|
@@ -22,7 +22,7 @@ VALID_REMOTE = "VALID_REMOTE"
|
|
|
22
22
|
|
|
23
23
|
class InvalidFunction(ValueError):
|
|
24
24
|
def __init__(self, msg: str = "", table_function_name: str = ""):
|
|
25
|
-
if any(
|
|
25
|
+
if any(fn for fn in COPY_ENABLED_TABLE_FUNCTIONS if fn in msg):
|
|
26
26
|
msg = msg.replace("is restricted", "is restricted to Copy Pipes")
|
|
27
27
|
|
|
28
28
|
if table_function_name:
|
|
@@ -75,19 +75,6 @@ def explain_plan(sql: str) -> str:
|
|
|
75
75
|
return chquery.explain_ast(sql)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
@dataclass(frozen=True)
|
|
79
|
-
class ColumnInfo:
|
|
80
|
-
name: str
|
|
81
|
-
type: str
|
|
82
|
-
nullable: bool
|
|
83
|
-
default_specifier: str = ""
|
|
84
|
-
default_expression: str | None = None
|
|
85
|
-
codec: str | None = None
|
|
86
|
-
comment: str | None = None
|
|
87
|
-
ttl: str | None = None
|
|
88
|
-
is_primary_key: bool = False
|
|
89
|
-
|
|
90
|
-
|
|
91
78
|
@dataclass
|
|
92
79
|
class MaterializedViewTarget:
|
|
93
80
|
database: Optional[str]
|
|
@@ -118,11 +105,6 @@ def parse_materialized_view_target(create_table_query: str) -> Optional[Material
|
|
|
118
105
|
)
|
|
119
106
|
|
|
120
107
|
|
|
121
|
-
def get_columns_from_create_query(sql_schema: str) -> list[ColumnInfo]:
|
|
122
|
-
columns = chquery.get_columns_from_create_query(sql_schema)
|
|
123
|
-
return [ColumnInfo(**col) for col in columns]
|
|
124
|
-
|
|
125
|
-
|
|
126
108
|
def has_join(sql: str) -> bool:
|
|
127
109
|
return any(line.rstrip().startswith("TableJoin") for line in explain_plan(sql).split())
|
|
128
110
|
|
|
@@ -234,7 +234,7 @@ async def auth_use(region_name_or_host_or_id: str) -> None:
|
|
|
234
234
|
config.set_host(host)
|
|
235
235
|
|
|
236
236
|
if not await try_authenticate(config, regions):
|
|
237
|
-
msg = FeedbackManager.error_wrong_config_file(config_file=config._path)
|
|
237
|
+
msg = FeedbackManager.error_wrong_config_file(config_file=config._path, cli="tb")
|
|
238
238
|
raise CLIAuthException(msg)
|
|
239
239
|
|
|
240
240
|
config.persist_to_file()
|
|
@@ -58,11 +58,10 @@ async def release_ls() -> None:
|
|
|
58
58
|
async def print_releases(config: CLIConfig):
|
|
59
59
|
response = await config.get_client().releases(config["id"])
|
|
60
60
|
|
|
61
|
-
table: List[Tuple[str, str, str, str, str]] = [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
61
|
+
table: List[Tuple[str, str, str, str, str]] = [
|
|
62
|
+
(release["created_at"], release["semver"], release["status"], release["commit"], release["rollback"])
|
|
63
|
+
for release in response["releases"]
|
|
64
|
+
]
|
|
66
65
|
|
|
67
66
|
columns = ["created_at", "semver", "status", "commit", "rollback release"]
|
|
68
67
|
click.echo(FeedbackManager.info_releases())
|
|
@@ -79,7 +78,7 @@ async def print_releases(config: CLIConfig):
|
|
|
79
78
|
)
|
|
80
79
|
@coro
|
|
81
80
|
async def release_generate(semver: str) -> None:
|
|
82
|
-
click.echo(FeedbackManager.warning_deprecated_releases())
|
|
81
|
+
click.echo(FeedbackManager.warning_deprecated_releases(cli="tb"))
|
|
83
82
|
if os.path.exists(".tinyenv"):
|
|
84
83
|
async with aiofiles.open(".tinyenv", "r") as env_file:
|
|
85
84
|
lines = await env_file.readlines()
|
|
@@ -147,7 +146,7 @@ set -euxo pipefail
|
|
|
147
146
|
)
|
|
148
147
|
@coro
|
|
149
148
|
async def release_create(semver: str, commit: Optional[str]) -> None:
|
|
150
|
-
click.echo(FeedbackManager.warning_deprecated_releases())
|
|
149
|
+
click.echo(FeedbackManager.warning_deprecated_releases(cli="tb"))
|
|
151
150
|
config = CLIConfig.get_project_config()
|
|
152
151
|
_ = await try_update_config_with_remote(config, only_if_needed=True)
|
|
153
152
|
|
|
@@ -165,7 +164,7 @@ async def release_promote(semver: str) -> None:
|
|
|
165
164
|
"""
|
|
166
165
|
The oldest rollback Release will be automatically removed if no usage, otherwise export TB_FORCE_REMOVE_OLDEST_ROLLBACK="1" to force deletion
|
|
167
166
|
"""
|
|
168
|
-
click.echo(FeedbackManager.warning_deprecated_releases())
|
|
167
|
+
click.echo(FeedbackManager.warning_deprecated_releases(cli="tb"))
|
|
169
168
|
config = CLIConfig.get_project_config()
|
|
170
169
|
_ = await try_update_config_with_remote(config, only_if_needed=True)
|
|
171
170
|
|
|
@@ -191,7 +190,7 @@ async def release_promote(semver: str) -> None:
|
|
|
191
190
|
)
|
|
192
191
|
@coro
|
|
193
192
|
async def release_preview(semver: str) -> None:
|
|
194
|
-
click.echo(FeedbackManager.warning_deprecated_releases())
|
|
193
|
+
click.echo(FeedbackManager.warning_deprecated_releases(cli="tb"))
|
|
195
194
|
config = CLIConfig.get_project_config()
|
|
196
195
|
_ = await try_update_config_with_remote(config, only_if_needed=True)
|
|
197
196
|
|
|
@@ -208,7 +207,7 @@ async def release_preview(semver: str) -> None:
|
|
|
208
207
|
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
|
|
209
208
|
@coro
|
|
210
209
|
async def release_rollback(yes: bool) -> None:
|
|
211
|
-
click.echo(FeedbackManager.warning_deprecated_releases())
|
|
210
|
+
click.echo(FeedbackManager.warning_deprecated_releases(cli="tb"))
|
|
212
211
|
config = CLIConfig.get_project_config()
|
|
213
212
|
_ = await try_update_config_with_remote(config, only_if_needed=False)
|
|
214
213
|
|
|
@@ -252,7 +251,7 @@ async def release_rollback(yes: bool) -> None:
|
|
|
252
251
|
)
|
|
253
252
|
@coro
|
|
254
253
|
async def release_rm(semver: str, oldest_rollback: bool, force: bool, yes: bool, dry_run: bool) -> None:
|
|
255
|
-
click.echo(FeedbackManager.warning_deprecated_releases())
|
|
254
|
+
click.echo(FeedbackManager.warning_deprecated_releases(cli="tb"))
|
|
256
255
|
if (not semver and not oldest_rollback) or (semver and oldest_rollback):
|
|
257
256
|
raise CLIException(FeedbackManager.error_release_rm_param())
|
|
258
257
|
|
|
@@ -278,7 +277,6 @@ async def release_rm(semver: str, oldest_rollback: bool, force: bool, yes: bool,
|
|
|
278
277
|
@cli.group()
|
|
279
278
|
def branch() -> None:
|
|
280
279
|
"""Branch commands. Branches are an experimental feature only available in beta. Running branch commands without activation will return an error"""
|
|
281
|
-
pass
|
|
282
280
|
|
|
283
281
|
|
|
284
282
|
@branch.command(name="ls")
|
|
@@ -427,7 +425,7 @@ async def delete_branch(branch_name_or_id: str, yes: bool) -> None:
|
|
|
427
425
|
raise CLIBranchException(FeedbackManager.error_exception(error=str(e)))
|
|
428
426
|
|
|
429
427
|
if not workspace_to_delete:
|
|
430
|
-
raise CLIBranchException(FeedbackManager.error_branch(branch=branch_name_or_id))
|
|
428
|
+
raise CLIBranchException(FeedbackManager.error_branch(branch=branch_name_or_id, cli="tb"))
|
|
431
429
|
|
|
432
430
|
if yes or click.confirm(FeedbackManager.warning_confirm_delete_branch(branch=workspace_to_delete["name"])):
|
|
433
431
|
need_to_switch_to_main = workspace_to_delete.get("main") and config["id"] == workspace_to_delete["id"]
|
|
@@ -450,7 +448,7 @@ async def delete_branch(branch_name_or_id: str, yes: bool) -> None:
|
|
|
450
448
|
if workspace_main:
|
|
451
449
|
await switch_to_workspace_by_user_workspace_data(config, workspace_main)
|
|
452
450
|
else:
|
|
453
|
-
raise CLIException(FeedbackManager.error_switching_to_main())
|
|
451
|
+
raise CLIException(FeedbackManager.error_switching_to_main(cli="tb"))
|
|
454
452
|
|
|
455
453
|
|
|
456
454
|
@branch.command(
|
|
@@ -127,7 +127,7 @@ async def cli(
|
|
|
127
127
|
click.echo(FeedbackManager.warning_development_cli())
|
|
128
128
|
|
|
129
129
|
if "x.y.z" not in CURRENT_VERSION and latest_version != CURRENT_VERSION:
|
|
130
|
-
click.echo(FeedbackManager.warning_update_version(latest_version=latest_version))
|
|
130
|
+
click.echo(FeedbackManager.warning_update_version(latest_version=latest_version, cli="tb"))
|
|
131
131
|
click.echo(FeedbackManager.warning_current_version(current_version=CURRENT_VERSION))
|
|
132
132
|
|
|
133
133
|
if debug:
|
|
@@ -237,7 +237,7 @@ async def init(
|
|
|
237
237
|
)
|
|
238
238
|
|
|
239
239
|
if current_ws.get("is_branch"):
|
|
240
|
-
raise CLIException(FeedbackManager.error_not_allowed_in_branch())
|
|
240
|
+
raise CLIException(FeedbackManager.error_not_allowed_in_branch(cli="tb"))
|
|
241
241
|
|
|
242
242
|
await folder_init(client, folder, generate_datasources, generate_releases=True, force=force)
|
|
243
243
|
|
|
@@ -255,7 +255,7 @@ async def init(
|
|
|
255
255
|
|
|
256
256
|
if sync_git:
|
|
257
257
|
if not cli_git_release.is_main_branch() and not override_commit:
|
|
258
|
-
raise CLIGitReleaseException(FeedbackManager.error_no_git_main_branch())
|
|
258
|
+
raise CLIGitReleaseException(FeedbackManager.error_no_git_main_branch(cli="tb"))
|
|
259
259
|
|
|
260
260
|
if not cli_git_release.is_dottinyb_ignored():
|
|
261
261
|
raise CLIGitReleaseException(
|
|
@@ -316,7 +316,7 @@ async def init(
|
|
|
316
316
|
|
|
317
317
|
else:
|
|
318
318
|
click.echo(FeedbackManager.info_no_git_release_yet(workspace=current_ws["name"]))
|
|
319
|
-
click.echo(FeedbackManager.info_diff_resources_for_git_init())
|
|
319
|
+
click.echo(FeedbackManager.info_diff_resources_for_git_init(cli="tb"))
|
|
320
320
|
changed = await diff_command(
|
|
321
321
|
[], True, client, with_print=False, verbose=False, clean_up=True, progress_bar=True
|
|
322
322
|
)
|
|
@@ -342,7 +342,7 @@ async def init(
|
|
|
342
342
|
if cli_git_release.is_dirty_to_init():
|
|
343
343
|
raise CLIGitReleaseException(
|
|
344
344
|
FeedbackManager.error_commit_changes_to_init_release(
|
|
345
|
-
path=cli_git_release.path, git_output=cli_git_release.status()
|
|
345
|
+
path=cli_git_release.path, git_output=cli_git_release.status(), cli="tb"
|
|
346
346
|
)
|
|
347
347
|
)
|
|
348
348
|
try:
|
|
@@ -732,19 +732,19 @@ async def diff(
|
|
|
732
732
|
for workspace in response["workspaces"]:
|
|
733
733
|
if config["id"] == workspace["id"]:
|
|
734
734
|
if not workspace.get("is_branch"):
|
|
735
|
-
raise CLIException(FeedbackManager.error_not_a_branch())
|
|
735
|
+
raise CLIException(FeedbackManager.error_not_a_branch(cli="tb"))
|
|
736
736
|
|
|
737
737
|
origin = workspace["main"]
|
|
738
738
|
workspace = await get_current_main_workspace(config)
|
|
739
739
|
|
|
740
740
|
if not workspace:
|
|
741
|
-
raise CLIException(FeedbackManager.error_workspace(workspace=origin))
|
|
741
|
+
raise CLIException(FeedbackManager.error_workspace(workspace=origin, cli="tb"))
|
|
742
742
|
|
|
743
743
|
ws_client = _get_tb_client(workspace["token"], config["host"])
|
|
744
744
|
break
|
|
745
745
|
|
|
746
746
|
if not ws_client:
|
|
747
|
-
raise CLIException(FeedbackManager.error_workspace(workspace=origin))
|
|
747
|
+
raise CLIException(FeedbackManager.error_workspace(workspace=origin, cli="tb"))
|
|
748
748
|
changed = await diff_command(
|
|
749
749
|
list(filename) if filename else None, fmt, ws_client, no_color, with_print=not only_resources_changed
|
|
750
750
|
)
|
|
@@ -867,9 +867,7 @@ async def sql(
|
|
|
867
867
|
if format_ == "json":
|
|
868
868
|
click.echo(json.dumps(res, indent=8))
|
|
869
869
|
else:
|
|
870
|
-
dd = []
|
|
871
|
-
for d in res["data"]:
|
|
872
|
-
dd.append(d.values())
|
|
870
|
+
dd = [d.values() for d in res["data"]]
|
|
873
871
|
echo_safe_humanfriendly_tables_format_smart_table(dd, column_names=res["data"][0].keys())
|
|
874
872
|
else:
|
|
875
873
|
click.echo(FeedbackManager.info_no_rows())
|
|
@@ -1347,7 +1345,7 @@ async def deploy(
|
|
|
1347
1345
|
current_semver = release.get("semver")
|
|
1348
1346
|
|
|
1349
1347
|
if not current_semver:
|
|
1350
|
-
click.echo(FeedbackManager.error_init_release(workspace=current_ws.get("name")))
|
|
1348
|
+
click.echo(FeedbackManager.error_init_release(workspace=current_ws.get("name"), cli="tb"))
|
|
1351
1349
|
sys.exit(1)
|
|
1352
1350
|
|
|
1353
1351
|
release_created = False
|
|
@@ -1358,7 +1356,7 @@ async def deploy(
|
|
|
1358
1356
|
if not semver:
|
|
1359
1357
|
semver = current_semver
|
|
1360
1358
|
else:
|
|
1361
|
-
click.echo(FeedbackManager.warning_deprecated_releases())
|
|
1359
|
+
click.echo(FeedbackManager.warning_deprecated_releases(cli="tb"))
|
|
1362
1360
|
|
|
1363
1361
|
if semver and current_semver:
|
|
1364
1362
|
new_version = version.parse(semver.split("-snapshot")[0])
|
|
@@ -234,7 +234,7 @@ variable to '1' or 'true'."""
|
|
|
234
234
|
try:
|
|
235
235
|
self.main(*args, **kwargs)
|
|
236
236
|
except AuthNoTokenException:
|
|
237
|
-
error_msg = FeedbackManager.error_notoken()
|
|
237
|
+
error_msg = FeedbackManager.error_notoken(cli="tb")
|
|
238
238
|
error_event = "auth_error"
|
|
239
239
|
exit_code = 1
|
|
240
240
|
except AuthException as ex:
|
|
@@ -315,7 +315,6 @@ async def folder_init(
|
|
|
315
315
|
except FileExistsError:
|
|
316
316
|
if not force:
|
|
317
317
|
click.echo(FeedbackManager.info_path_already_exists(path=x))
|
|
318
|
-
pass
|
|
319
318
|
|
|
320
319
|
if generate_datasources:
|
|
321
320
|
for format in SUPPORTED_FORMATS:
|
|
@@ -690,7 +689,7 @@ async def create_workspace_branch(
|
|
|
690
689
|
try:
|
|
691
690
|
workspace = await get_current_workspace(config)
|
|
692
691
|
if not workspace:
|
|
693
|
-
raise CLIWorkspaceException(FeedbackManager.error_workspace())
|
|
692
|
+
raise CLIWorkspaceException(FeedbackManager.error_workspace(cli="tb"))
|
|
694
693
|
|
|
695
694
|
if not branch_name:
|
|
696
695
|
click.echo(FeedbackManager.info_workspace_branch_create_greeting())
|
|
@@ -748,10 +747,12 @@ async def create_workspace_branch(
|
|
|
748
747
|
async def print_data_branch_summary(client, job_id, response=None):
|
|
749
748
|
response = await client.job(job_id) if job_id else response or {"partitions": []}
|
|
750
749
|
columns = ["Data Source", "Partition", "Status", "Error"]
|
|
751
|
-
table = []
|
|
750
|
+
table: list[list] = []
|
|
752
751
|
for partition in response["partitions"]:
|
|
753
|
-
|
|
754
|
-
|
|
752
|
+
table.extend(
|
|
753
|
+
[partition["datasource"]["name"], p["partition"], p["status"], p.get("error", "")]
|
|
754
|
+
for p in partition["partitions"]
|
|
755
|
+
)
|
|
755
756
|
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
756
757
|
|
|
757
758
|
|
|
@@ -1195,7 +1196,7 @@ def validate_string_connector_param(param, s):
|
|
|
1195
1196
|
|
|
1196
1197
|
async def validate_connection_name(client, connection_name, service):
|
|
1197
1198
|
if await client.get_connector(connection_name, service) is not None:
|
|
1198
|
-
raise CLIConnectionException(FeedbackManager.error_connection_already_exists(name=connection_name))
|
|
1199
|
+
raise CLIConnectionException(FeedbackManager.error_connection_already_exists(name=connection_name, cli="tb"))
|
|
1199
1200
|
|
|
1200
1201
|
|
|
1201
1202
|
def _get_setting_value(connection, setting, sensitive_settings):
|
|
@@ -1223,9 +1224,9 @@ async def switch_workspace(config: CLIConfig, workspace_name_or_id: str, only_en
|
|
|
1223
1224
|
|
|
1224
1225
|
if not workspace:
|
|
1225
1226
|
if only_environments:
|
|
1226
|
-
raise CLIException(FeedbackManager.error_branch(branch=workspace_name_or_id))
|
|
1227
|
+
raise CLIException(FeedbackManager.error_branch(branch=workspace_name_or_id, cli="tb"))
|
|
1227
1228
|
else:
|
|
1228
|
-
raise CLIException(FeedbackManager.error_workspace(workspace=workspace_name_or_id))
|
|
1229
|
+
raise CLIException(FeedbackManager.error_workspace(workspace=workspace_name_or_id, cli="tb"))
|
|
1229
1230
|
|
|
1230
1231
|
config.set_token(workspace["token"])
|
|
1231
1232
|
config.set_token_for_host(workspace["token"], config.get_host())
|
|
@@ -1391,17 +1392,17 @@ async def get_host_from_region(
|
|
|
1391
1392
|
try:
|
|
1392
1393
|
host = regions[index - 1]["api_host"]
|
|
1393
1394
|
except Exception:
|
|
1394
|
-
raise CLIException(FeedbackManager.error_getting_region_by_index())
|
|
1395
|
+
raise CLIException(FeedbackManager.error_getting_region_by_index(cli="tb"))
|
|
1395
1396
|
except ValueError:
|
|
1396
1397
|
region_name = region_name_or_host_or_id.lower()
|
|
1397
1398
|
try:
|
|
1398
1399
|
region = get_region_from_host(region_name, regions)
|
|
1399
1400
|
host = region["api_host"] if region else None
|
|
1400
1401
|
except Exception:
|
|
1401
|
-
raise CLIException(FeedbackManager.error_getting_region_by_name_or_url())
|
|
1402
|
+
raise CLIException(FeedbackManager.error_getting_region_by_name_or_url(cli="tb"))
|
|
1402
1403
|
|
|
1403
1404
|
if not host:
|
|
1404
|
-
raise CLIException(FeedbackManager.error_getting_region_by_name_or_url())
|
|
1405
|
+
raise CLIException(FeedbackManager.error_getting_region_by_name_or_url(cli="tb"))
|
|
1405
1406
|
|
|
1406
1407
|
return regions, host
|
|
1407
1408
|
|
|
@@ -1562,7 +1563,7 @@ async def try_authenticate(
|
|
|
1562
1563
|
break
|
|
1563
1564
|
|
|
1564
1565
|
if not authenticated:
|
|
1565
|
-
raise CLIAuthException(FeedbackManager.error_invalid_token())
|
|
1566
|
+
raise CLIAuthException(FeedbackManager.error_invalid_token(cli="tb"))
|
|
1566
1567
|
|
|
1567
1568
|
config.persist_to_file()
|
|
1568
1569
|
|
|
@@ -1899,14 +1900,14 @@ async def validate_aws_iamrole_connection_name(
|
|
|
1899
1900
|
) -> str:
|
|
1900
1901
|
if connection_name and no_validate is False:
|
|
1901
1902
|
if await client.get_connector(connection_name) is not None:
|
|
1902
|
-
raise CLIConnectionException(FeedbackManager.info_connection_already_exists(name=connection_name))
|
|
1903
|
+
raise CLIConnectionException(FeedbackManager.info_connection_already_exists(name=connection_name, cli="tb"))
|
|
1903
1904
|
else:
|
|
1904
1905
|
while not connection_name:
|
|
1905
1906
|
connection_name = click.prompt("Enter the name for this connection", default=None, show_default=False)
|
|
1906
1907
|
assert isinstance(connection_name, str)
|
|
1907
1908
|
|
|
1908
1909
|
if no_validate is False and await client.get_connector(connection_name) is not None:
|
|
1909
|
-
click.echo(FeedbackManager.info_connection_already_exists(name=connection_name))
|
|
1910
|
+
click.echo(FeedbackManager.info_connection_already_exists(name=connection_name, cli="tb"))
|
|
1910
1911
|
connection_name = None
|
|
1911
1912
|
assert isinstance(connection_name, str)
|
|
1912
1913
|
return connection_name
|
|
@@ -556,10 +556,10 @@ async def datasource_share(ctx: Context, datasource_name: str, workspace_name_or
|
|
|
556
556
|
current_workspace = next((workspace for workspace in workspaces if workspace["id"] == config["id"]), None)
|
|
557
557
|
|
|
558
558
|
if not destination_workspace:
|
|
559
|
-
raise CLIDatasourceException(FeedbackManager.error_workspace(workspace=workspace_name_or_id))
|
|
559
|
+
raise CLIDatasourceException(FeedbackManager.error_workspace(workspace=workspace_name_or_id, cli="tb"))
|
|
560
560
|
|
|
561
561
|
if not current_workspace:
|
|
562
|
-
raise CLIDatasourceException(FeedbackManager.error_not_authenticated())
|
|
562
|
+
raise CLIDatasourceException(FeedbackManager.error_not_authenticated(cli="tb"))
|
|
563
563
|
|
|
564
564
|
if not user_token:
|
|
565
565
|
user_token = ask_for_user_token("share a Data Source", ui_host)
|
|
@@ -626,10 +626,10 @@ async def datasource_unshare(ctx: Context, datasource_name: str, workspace_name_
|
|
|
626
626
|
current_workspace = next((workspace for workspace in workspaces if workspace["id"] == config["id"]), None)
|
|
627
627
|
|
|
628
628
|
if not destination_workspace:
|
|
629
|
-
raise CLIDatasourceException(FeedbackManager.error_workspace(workspace=workspace_name_or_id))
|
|
629
|
+
raise CLIDatasourceException(FeedbackManager.error_workspace(workspace=workspace_name_or_id, cli="tb"))
|
|
630
630
|
|
|
631
631
|
if not current_workspace:
|
|
632
|
-
raise CLIDatasourceException(FeedbackManager.error_not_authenticated())
|
|
632
|
+
raise CLIDatasourceException(FeedbackManager.error_not_authenticated(cli="tb"))
|
|
633
633
|
|
|
634
634
|
if not user_token:
|
|
635
635
|
user_token = ask_for_user_token("unshare a Data Source", ui_host)
|
|
@@ -36,9 +36,7 @@ async def jobs_ls(ctx: Context, status: str) -> None:
|
|
|
36
36
|
jobs = await client.jobs(status=status)
|
|
37
37
|
columns = ["id", "kind", "status", "created at", "updated at", "job url"]
|
|
38
38
|
click.echo(FeedbackManager.info_jobs())
|
|
39
|
-
table = []
|
|
40
|
-
for j in jobs:
|
|
41
|
-
table.append([j[c.replace(" ", "_")] for c in columns])
|
|
39
|
+
table = [[j[c.replace(" ", "_")] for c in columns] for j in jobs]
|
|
42
40
|
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
43
41
|
click.echo("\n")
|
|
44
42
|
|
|
@@ -28,11 +28,10 @@ async def tag_ls(ctx: Context, tag_name: Optional[str]) -> None:
|
|
|
28
28
|
the_tag = [tag for tag in response["tags"] if tag["name"] == tag_name]
|
|
29
29
|
|
|
30
30
|
columns = ["name", "id", "type"]
|
|
31
|
-
table = []
|
|
31
|
+
table: list[list] = []
|
|
32
32
|
|
|
33
33
|
if len(the_tag) > 0:
|
|
34
|
-
for resource in the_tag[0]["resources"]
|
|
35
|
-
table.append([resource["name"], resource["id"], resource["type"]])
|
|
34
|
+
table.extend([resource["name"], resource["id"], resource["type"]] for resource in the_tag[0]["resources"])
|
|
36
35
|
|
|
37
36
|
click.echo(FeedbackManager.info_tag_resources(tag_name=tag_name))
|
|
38
37
|
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
@@ -55,19 +55,18 @@ async def workspace_ls(ctx: Context) -> None:
|
|
|
55
55
|
raise CLIWorkspaceException(FeedbackManager.error_unable_to_identify_main_workspace())
|
|
56
56
|
|
|
57
57
|
columns = ["name", "id", "role", "plan", "current"]
|
|
58
|
-
table = []
|
|
59
58
|
click.echo(FeedbackManager.info_workspaces())
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
[
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
table = [
|
|
61
|
+
[
|
|
62
|
+
workspace["name"],
|
|
63
|
+
workspace["id"],
|
|
64
|
+
workspace["role"],
|
|
65
|
+
_get_workspace_plan_name(workspace["plan"]),
|
|
66
|
+
current_main_workspace["id"] == workspace["id"],
|
|
67
|
+
]
|
|
68
|
+
for workspace in response["workspaces"]
|
|
69
|
+
]
|
|
71
70
|
|
|
72
71
|
echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
|
|
73
72
|
|
|
@@ -114,7 +113,7 @@ async def clear_workspace(ctx: Context, yes: bool, dry_run: bool) -> None:
|
|
|
114
113
|
for workspace in response["workspaces"]:
|
|
115
114
|
if config["id"] == workspace["id"]:
|
|
116
115
|
if workspace.get("is_branch"):
|
|
117
|
-
raise CLIWorkspaceException(FeedbackManager.error_not_allowed_in_branch())
|
|
116
|
+
raise CLIWorkspaceException(FeedbackManager.error_not_allowed_in_branch(cli="tb"))
|
|
118
117
|
return
|
|
119
118
|
else:
|
|
120
119
|
click.echo(FeedbackManager.info_current_workspace())
|
|
@@ -297,7 +296,7 @@ async def delete_workspace(
|
|
|
297
296
|
)
|
|
298
297
|
|
|
299
298
|
if not workspace_to_delete:
|
|
300
|
-
raise CLIWorkspaceException(FeedbackManager.error_workspace(workspace=workspace_name_or_id))
|
|
299
|
+
raise CLIWorkspaceException(FeedbackManager.error_workspace(workspace=workspace_name_or_id, cli="tb"))
|
|
301
300
|
|
|
302
301
|
if yes or click.confirm(
|
|
303
302
|
FeedbackManager.warning_confirm_delete_workspace(workspace_name=workspace_to_delete.get("name"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird_cli
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.5.1.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli
|
|
6
6
|
Author: Tinybird
|
|
@@ -12,7 +12,7 @@ Requires-Dist: clickhouse-toolset==0.34.dev0
|
|
|
12
12
|
Requires-Dist: click<8.2,>=8.1.8
|
|
13
13
|
Requires-Dist: colorama==0.4.6
|
|
14
14
|
Requires-Dist: cryptography~=41.0.0
|
|
15
|
-
Requires-Dist: croniter==
|
|
15
|
+
Requires-Dist: croniter==6.2.2
|
|
16
16
|
Requires-Dist: GitPython~=3.1.32
|
|
17
17
|
Requires-Dist: humanfriendly~=8.2
|
|
18
18
|
Requires-Dist: pydantic~=2.8.0
|
|
@@ -43,6 +43,17 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
43
43
|
Changelog
|
|
44
44
|
----------
|
|
45
45
|
|
|
46
|
+
6.5.0
|
|
47
|
+
***********
|
|
48
|
+
|
|
49
|
+
- `Changed` `tb push` can now perform sharing and unsharing operations on Datasources with a DATASOURCES:CREATE token in workspaces in the same Organization
|
|
50
|
+
|
|
51
|
+
6.4.1
|
|
52
|
+
***********
|
|
53
|
+
|
|
54
|
+
- `Improved` feedback messages when suggesting other cli commands.
|
|
55
|
+
- `Fixed` `tb push` rejecting the `%` operator in column DEFAULT/MATERIALIZED expressions, causing failures after `tb pull` on schemas using modulo arithmetic.
|
|
56
|
+
|
|
46
57
|
6.4.0
|
|
47
58
|
***********
|
|
48
59
|
|
|
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-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py
RENAMED
|
File without changes
|
{tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py
RENAMED
|
File without changes
|
|
File without changes
|
{tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird/tb_cli_modules/workspace_members.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird_cli-6.4.1.dev0 → tinybird_cli-6.5.1.dev0}/tinybird_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|