tinybird-cli 6.4.2.dev0__py3-none-any.whl → 6.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tinybird/__cli__.py CHANGED
@@ -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.4.2.dev0'
8
- __revision__ = 'b2cf0b8'
7
+ __version__ = '6.5.0'
8
+ __revision__ = '676e19d'
tinybird/client.py CHANGED
@@ -340,10 +340,14 @@ class TinyB:
340
340
  for c in connectors
341
341
  ]
342
342
 
343
- async def get_datasource(self, ds_name: str, used_by: bool = False) -> Dict[str, Any]:
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(self, datasource_id: str, current_workspace_id: str, destination_workspace_id: str):
493
- params = {"origin_workspace_id": current_workspace_id, "destination_workspace_id": destination_workspace_id}
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(self, datasource_id: str, current_workspace_id: str, destination_workspace_id: str):
497
- params = {"origin_workspace_id": current_workspace_id, "destination_workspace_id": destination_workspace_id}
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
@@ -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([self.repo.is_dirty(path=p) for p in self.paths]) or self.has_untracked_files()
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
 
@@ -1235,6 +1235,10 @@ def parse(
1235
1235
  "kafka_key_avro_deserialization": assign_var("kafka_key_avro_deserialization"),
1236
1236
  "kafka_ssl_ca_pem": assign_var("kafka_ssl_ca_pem"),
1237
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"),
1238
1242
  "import_service": assign_var("import_service"),
1239
1243
  "import_connection_name": assign_var("import_connection_name"),
1240
1244
  "import_schedule": assign_var("import_schedule"),
@@ -1368,6 +1372,7 @@ async def process_file(
1368
1372
 
1369
1373
  if not skip_connectors:
1370
1374
  try:
1375
+ is_oauthbearer = params.get("kafka_sasl_mechanism") == "OAUTHBEARER"
1371
1376
  connector_params = {
1372
1377
  "kafka_bootstrap_servers": params.get("kafka_bootstrap_servers", None),
1373
1378
  "kafka_key": params.get("kafka_key", None),
@@ -1377,6 +1382,12 @@ async def process_file(
1377
1382
  "kafka_schema_registry_url": params.get("kafka_schema_registry_url", None),
1378
1383
  "kafka_ssl_ca_pem": get_ca_pem_content(params.get("kafka_ssl_ca_pem", None), filename),
1379
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
+ ),
1380
1391
  }
1381
1392
 
1382
1393
  connector = await tb_client.get_connection(**connector_params)
@@ -1384,11 +1395,19 @@ async def process_file(
1384
1395
  click.echo(
1385
1396
  FeedbackManager.info_creating_kafka_connection(connection_name=params["kafka_connection_name"])
1386
1397
  )
1387
- required_params = [
1388
- connector_params["kafka_bootstrap_servers"],
1389
- connector_params["kafka_key"],
1390
- connector_params["kafka_secret"],
1391
- ]
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
+ ]
1392
1411
 
1393
1412
  if not all(required_params):
1394
1413
  raise click.ClickException(FeedbackManager.error_unknown_kafka_connection(datasource=name))
@@ -1549,10 +1568,7 @@ async def process_file(
1549
1568
  period: int = DEFAULT_CRON_PERIOD
1550
1569
 
1551
1570
  if current_ws:
1552
- workspaces = (await tb_client.user_workspaces()).get("workspaces", [])
1553
- workspace_rate_limits: Dict[str, Dict[str, int]] = next(
1554
- (w.get("rate_limits", {}) for w in workspaces if w["id"] == current_ws["id"]), {}
1555
- )
1571
+ workspace_rate_limits: Dict[str, Dict[str, int]] = current_ws.get("rate_limits", {})
1556
1572
  period = workspace_rate_limits.get("api_datasources_create_append_replace", {}).get(
1557
1573
  "period", DEFAULT_CRON_PERIOD
1558
1574
  )
@@ -1632,7 +1648,7 @@ async def process_file(
1632
1648
  deps = []
1633
1649
  nodes: List[Dict[str, Any]] = []
1634
1650
 
1635
- is_copy = any([node for node in doc.nodes if node.get("type", "standard").lower() == PipeNodeTypes.COPY])
1651
+ is_copy = any(node for node in doc.nodes if node.get("type", "standard").lower() == PipeNodeTypes.COPY)
1636
1652
  for node in doc.nodes:
1637
1653
  sql = node["sql"]
1638
1654
  node_type = node.get("type", "standard").lower()
@@ -2912,7 +2928,7 @@ async def new_pipe(
2912
2928
  current_pipe = r.json() if r.status_code == 200 else None
2913
2929
  pipe_exists = current_pipe is not None
2914
2930
 
2915
- is_materialized = any([node.get("params", {}).get("type", None) == "materialized" for node in p["nodes"]])
2931
+ is_materialized = any(node.get("params", {}).get("type", None) == "materialized" for node in p["nodes"])
2916
2932
  copy_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "copy"), None)
2917
2933
  sink_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "sink"), None)
2918
2934
  stream_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "stream"), None)
@@ -3139,7 +3155,7 @@ async def new_pipe(
3139
3155
  async def share_and_unshare_datasource(
3140
3156
  client: TinyB,
3141
3157
  datasource: Dict[str, Any],
3142
- user_token: str,
3158
+ user_token: Optional[str],
3143
3159
  workspaces_current_shared_with: List[str],
3144
3160
  workspaces_to_share: List[str],
3145
3161
  current_ws: Optional[Dict[str, Any]],
@@ -3147,21 +3163,58 @@ async def share_and_unshare_datasource(
3147
3163
  datasource_name = datasource.get("name", "")
3148
3164
  datasource_id = datasource.get("id", "")
3149
3165
  workspaces: List[Dict[str, Any]]
3150
- # We duplicate the client to use the user_token in workspace discovery and sharing operations.
3151
- user_client: TinyB = deepcopy(client)
3152
- user_client.token = user_token
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
3153
3170
 
3154
3171
  # In case we are pushing to a branch, we don't share the datasource
3155
3172
  # FIXME: Have only once way to get the current workspace
3156
3173
  if current_ws:
3157
3174
  workspace = current_ws
3158
- else:
3175
+ elif user_token:
3159
3176
  workspace = await client.user_workspace_branches()
3177
+ else:
3178
+ workspace = await client.workspace_info()
3160
3179
 
3161
3180
  if workspace.get("is_branch", False):
3162
3181
  click.echo(FeedbackManager.info_skipping_sharing_datasources_branch(datasource=datasource["name"]))
3163
3182
  return
3164
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
+
3165
3218
  # Use the user token for workspace discovery, as workspace/admin tokens may not list all targets.
3166
3219
  workspaces = (await user_client.user_workspaces()).get("workspaces", [])
3167
3220
  if not workspaces_current_shared_with:
@@ -3245,8 +3298,12 @@ async def new_ds(
3245
3298
  scopes.append(sc)
3246
3299
  await client.alter_tokens(token_name, scopes)
3247
3300
 
3301
+ can_manage_shared_with = bool(user_token or (current_ws and current_ws.get("can_manage_datasources")))
3302
+
3248
3303
  try:
3249
- existing_ds = await client.get_datasource(ds_name)
3304
+ existing_ds = await client.get_datasource(
3305
+ ds_name, include_workspace_names=can_manage_shared_with and not user_token
3306
+ )
3250
3307
  datasource_exists = True
3251
3308
  except DoesNotExistException:
3252
3309
  datasource_exists = False
@@ -3304,7 +3361,7 @@ async def new_ds(
3304
3361
  await manage_tokens()
3305
3362
 
3306
3363
  if ds.get("shared_with"):
3307
- if not user_token:
3364
+ if not can_manage_shared_with:
3308
3365
  click.echo(FeedbackManager.info_skipping_shared_with_entry())
3309
3366
  else:
3310
3367
  await share_and_unshare_datasource(
@@ -3324,7 +3381,7 @@ async def new_ds(
3324
3381
  raise click.ClickException(FeedbackManager.error_datasource_already_exists(datasource=ds_name))
3325
3382
 
3326
3383
  if ds.get("shared_with", []) or existing_ds.get("shared_with", []):
3327
- if not user_token:
3384
+ if not can_manage_shared_with:
3328
3385
  click.echo(FeedbackManager.info_skipping_shared_with_entry())
3329
3386
  else:
3330
3387
  await share_and_unshare_datasource(
@@ -3361,7 +3418,7 @@ async def new_ds(
3361
3418
  existing = existing_ds.get("indexes", [])
3362
3419
  new.sort(key=lambda x: x["name"])
3363
3420
  existing.sort(key=lambda x: x["name"])
3364
- if len(existing) != len(new) or any([(d, d2) for d, d2 in zip(new, existing) if d != d2]):
3421
+ if len(existing) != len(new) or any((d, d2) for d, d2 in zip(new, existing) if d != d2):
3365
3422
  new_indices = ds.get("params", {}).get("indexes") or "0"
3366
3423
  if (
3367
3424
  new_description
@@ -3561,7 +3618,7 @@ async def new_token(token: Dict[str, Any], client: TinyB, force: bool = False):
3561
3618
 
3562
3619
  if force:
3563
3620
  ADMIN_SCOPES = ["ADMIN", "ADMIN_USER"]
3564
- if any([scope["type"] in ADMIN_SCOPES for scope in existing_token["scopes"]]):
3621
+ if any(scope["type"] in ADMIN_SCOPES for scope in existing_token["scopes"]):
3565
3622
  raise click.ClickException(FeedbackManager.error_token_cannot_be_overriden(token=token["name"]))
3566
3623
 
3567
3624
  await client.token_update(token)
@@ -4004,7 +4061,7 @@ async def build_graph(
4004
4061
  if (
4005
4062
  fork_downstream
4006
4063
  and r.get("resource", "") == "pipes"
4007
- and any(["engine" in x.get("params", {}) for x in r.get("nodes", [])])
4064
+ and any("engine" in x.get("params", {}) for x in r.get("nodes", []))
4008
4065
  ):
4009
4066
  raise click.ClickException(FeedbackManager.error_forkdownstream_pipes_with_engine(pipe=fn))
4010
4067
 
@@ -4036,13 +4093,10 @@ async def build_graph(
4036
4093
 
4037
4094
  # In case the datasource is to be shared and we have mapping, let's replace the name
4038
4095
  if "shared_with" in r and workspace_map:
4039
- mapped_workspaces: List[str] = []
4040
- for shared_with in r["shared_with"]:
4041
- mapped_workspaces.append(
4042
- workspace_map.get(shared_with)
4043
- if workspace_map.get(shared_with, None) is not None
4044
- else shared_with # type: ignore
4045
- )
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
+ ]
4046
4100
  r["shared_with"] = mapped_workspaces
4047
4101
 
4048
4102
  dep_map[fn] = set(dep_list)
@@ -4243,10 +4297,13 @@ async def folder_push(
4243
4297
  hide_folders: bool = False,
4244
4298
  on_demand_compute: bool = False,
4245
4299
  ):
4246
- workspaces: List[Dict[str, Any]] = (await tb_client.user_workspaces_and_branches()).get("workspaces", [])
4247
- current_ws: Dict[str, Any] = next(
4248
- (workspace for workspace in workspaces if config and workspace.get("id", ".") == config.get("id", "..")), {}
4249
- )
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()
4250
4307
  is_branch = current_ws.get("is_branch", False)
4251
4308
  has_semver: bool = release_created or False
4252
4309
 
@@ -5557,7 +5614,7 @@ def is_materialized(resource: Optional[Dict[str, Any]]) -> bool:
5557
5614
  return False
5558
5615
 
5559
5616
  is_materialized = any(
5560
- [node.get("params", {}).get("type", None) == "materialized" for node in resource.get("nodes", []) or []]
5617
+ node.get("params", {}).get("type", None) == "materialized" for node in resource.get("nodes", []) or []
5561
5618
  )
5562
5619
  return is_materialized
5563
5620
 
@@ -5636,7 +5693,7 @@ async def create_release(
5636
5693
  def has_internal_datafiles(folder: str) -> bool:
5637
5694
  folder = folder or "."
5638
5695
  filenames = get_project_filenames(folder)
5639
- return any([f for f in filenames if "spans" in str(f) and "vendor" not in str(f)])
5696
+ return any(f for f in filenames if "spans" in str(f) and "vendor" not in str(f))
5640
5697
 
5641
5698
 
5642
5699
  def is_file_a_datasource(filename: str) -> bool:
tinybird/datatypes.py CHANGED
@@ -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([p.match(x) for p in datetime64_patterns])
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([p.match(x) for p in datetime_patterns])
124
+ return any(p.match(x) for p in datetime_patterns)
125
125
 
126
126
 
127
127
  def int_8_test(x: str) -> bool:
tinybird/sql_template.py CHANGED
@@ -2826,7 +2826,7 @@ def render_sql_template(
2826
2826
  return Comment("error launched")
2827
2827
 
2828
2828
  v: dict = {x["name"]: Placeholder(x["name"], x["line"]) for x in template_variables}
2829
- is_tb_secret = any([s for s in template_variables if s["name"] == "tb_secret" or s["name"] == "tb_var"])
2829
+ is_tb_secret = any(s for s in template_variables if s["name"] == "tb_secret" or s["name"] == "tb_var")
2830
2830
 
2831
2831
  if variables:
2832
2832
  v.update(variables)
tinybird/sql_toolset.py CHANGED
@@ -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([fn for fn in COPY_ENABLED_TABLE_FUNCTIONS if fn in msg]):
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:
@@ -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
- for release in response["releases"]:
63
- table.append(
64
- (release["created_at"], release["semver"], release["status"], release["commit"], release["rollback"])
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())
@@ -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())
@@ -747,10 +747,12 @@ async def create_workspace_branch(
747
747
  async def print_data_branch_summary(client, job_id, response=None):
748
748
  response = await client.job(job_id) if job_id else response or {"partitions": []}
749
749
  columns = ["Data Source", "Partition", "Status", "Error"]
750
- table = []
750
+ table: list[list] = []
751
751
  for partition in response["partitions"]:
752
- for p in partition["partitions"]:
753
- table.append([partition["datasource"]["name"], p["partition"], p["status"], p.get("error", "")])
752
+ table.extend(
753
+ [partition["datasource"]["name"], p["partition"], p["status"], p.get("error", "")]
754
+ for p in partition["partitions"]
755
+ )
754
756
  echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
755
757
 
756
758
 
@@ -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
- for workspace in response["workspaces"]:
62
- table.append(
63
- [
64
- workspace["name"],
65
- workspace["id"],
66
- workspace["role"],
67
- _get_workspace_plan_name(workspace["plan"]),
68
- current_main_workspace["id"] == workspace["id"],
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird_cli
3
- Version: 6.4.2.dev0
3
+ Version: 6.5.0
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli
6
6
  Author: Tinybird
@@ -43,6 +43,11 @@ 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
+
46
51
  6.4.1
47
52
  ***********
48
53
 
@@ -1,44 +1,44 @@
1
- tinybird/__cli__.py,sha256=MTnPp_CJDk5yO8LKw-WUauu2RR-e5GX1XFuvzYUrOKI,236
1
+ tinybird/__cli__.py,sha256=yTOGwcU42yQBM9Er49LNM5Z4yBjW1fJL-AzsSI8QPvo,231
2
2
  tinybird/check_pypi.py,sha256=_4NkharLyR_ELrAdit-ftqIWvOf7jZNPt3i76frlo9g,975
3
- tinybird/client.py,sha256=xzogEol-GJTpDCm5MQ_9Tj_GBiBuOZfIw_O3OQTUOvY,50615
3
+ tinybird/client.py,sha256=gMgXUu7ByZ2GFPd2lLv1SzQqR49m-6vw64xCrF63r-g,52119
4
4
  tinybird/config.py,sha256=g74rE9jbVcyFj1bms5T3VEITLr21_WFy79Uj5ovvj90,7413
5
5
  tinybird/context.py,sha256=o4yvlXPkMLmdh-XJl3wpmqPAMeRRz5ScKzKlHHKn_I8,1201
6
- tinybird/datafile_common.py,sha256=BNoZqWVr7Nvn6GKb1mEvtqQieOquWW9eGBN3YhCTfFM,233244
7
- tinybird/datatypes.py,sha256=Ud_IphoDOMtTMzEsYIf2lO467EVWw_ctbsOnrEzDHvU,11359
6
+ tinybird/datafile_common.py,sha256=qoY7vpBaGTozSbAZVBZoPeegzSw9zsVw5QuHuORJPP0,236285
7
+ tinybird/datatypes.py,sha256=Lr8BIaRP--qzuZFpAdv20uQaeE1BOAMmuEwWVHheFPw,11355
8
8
  tinybird/feedback_manager.py,sha256=OehfKVruCHwUNN1bHIbDICvOaIovc3hb6RjGHTyIkBc,67667
9
9
  tinybird/git_settings.py,sha256=mqWgeboOlOFsSo97qyv595UCR2R1QCAqT4GTawBNPBg,3935
10
10
  tinybird/sql.py,sha256=8pvjlKwdJ-PuJkCo57W8e1gj5z0RzUP7vOnum6Pi134,48901
11
- tinybird/sql_template.py,sha256=65wtfKygqkYjU57MOlOTtbUk3EKc-Px7GeEfk65s0WM,128636
11
+ tinybird/sql_template.py,sha256=_J-QmHos4Sx9YS6tVsEyE68qrcFbj13VwCA1zB3Sz8Q,128634
12
12
  tinybird/sql_template_fmt.py,sha256=Ma4qcs-2r8ZXQC4GUmrCqYz34DsnGF8k5lE2Jwnr314,10638
13
- tinybird/sql_toolset.py,sha256=FhSBvHUlr5NoSdZO86v1EDPRNf4V_MWP3wX6v3kmqTA,27222
13
+ tinybird/sql_toolset.py,sha256=xWY-EtixaZKUPuNY4WBz2fDwwNnmbgtdLsmI2wy67UA,27220
14
14
  tinybird/syncasync.py,sha256=rIPmCvygWSFqfnlVqhZH4N9gVVTvD6DEPsfoxGizYrI,27776
15
15
  tinybird/tb_cli.py,sha256=q1LGAsBVVMJsjR2HK62Pu6vpVtLzNmH8wHrEVUUdVkU,744
16
16
  tinybird/tornado_template.py,sha256=1_0nYFk_xJh_TMHh6AKkJILvnNY6xYmaM-uJ3Ofv7e8,42085
17
17
  tinybird/ch_utils/constants.py,sha256=yTNizMzgYNBzUc2EV3moBfdrDIggOe9hiuAgWF7sv2c,4333
18
18
  tinybird/ch_utils/engine.py,sha256=NzYUnmXsrJQimwXfCqdtIMyuS_Ad0OSdEnqNXzzStvY,37489
19
19
  tinybird/tb_cli_modules/auth.py,sha256=3xu8STgouOgLkqlBf9LWFg9Oto_NyuDKsUWF95-zGwI,8741
20
- tinybird/tb_cli_modules/branch.py,sha256=-2OB-zNc4_bQfaPQWsbD-lR1CSKsdxAe2qKXBYHsFWo,39382
20
+ tinybird/tb_cli_modules/branch.py,sha256=peIo1kzBSsPGYFPOqE8qCjUlxBZURlCIoYuVTKwAQaw,39354
21
21
  tinybird/tb_cli_modules/cicd.py,sha256=i2Mw8AbmEVNBcEPYdio7liy3PGqh1ezVFZ0OmJ9ww5o,13809
22
- tinybird/tb_cli_modules/cli.py,sha256=2l-J4NblKBRF4XxCA6HqShk3nwhLGsncdG7itLmvOto,60283
23
- tinybird/tb_cli_modules/common.py,sha256=9jAFQXOEiS5oomC5Im4ALcqgRld7A6mnOWDfoWI4OKc,77242
22
+ tinybird/tb_cli_modules/cli.py,sha256=KvgZfdYRvgKG5QAiFmlfLbVGI-xmuxbs-EdkAslqrEQ,60242
23
+ tinybird/tb_cli_modules/common.py,sha256=WS8on_s8sGSq27YQdmMsfQ45MP9wyoJaRN_Cfrr6lUk,77275
24
24
  tinybird/tb_cli_modules/config.py,sha256=0kFDmsDcjKon32rgFGMHHKSbv4j5dOrXtVOlyuAyEkk,11510
25
25
  tinybird/tb_cli_modules/connection.py,sha256=yoYUQo-Fl36LTHeGI3HpFOCLiP0wKhWsoP9P9G26NZ8,21305
26
26
  tinybird/tb_cli_modules/datasource.py,sha256=b12ClLFISGHqK7zrLZBX5OT-8Nxd2oW734-Xon0dTE8,32541
27
27
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
28
28
  tinybird/tb_cli_modules/fmt.py,sha256=edQap4tAqWMWogSIx5zriT75naLi73XTB3NwatmcrFw,3518
29
- tinybird/tb_cli_modules/job.py,sha256=AG69LPb9MbobA1awwJFZJvxqarDKfRlsBjw2V1zvYqc,2964
29
+ tinybird/tb_cli_modules/job.py,sha256=BxiqChDYa_4W4tFXHIUz2bOzkx8WMFywynjiPWgDlnE,2936
30
30
  tinybird/tb_cli_modules/pipe.py,sha256=hIvkiGv9Zvm2KgJjFzGKjc7HQZdEmEt61yFwngXhEJ8,31672
31
31
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
32
- tinybird/tb_cli_modules/tag.py,sha256=9YHnruPnUNp1IJUe4qcSEMg9EbquIRo--Nxcsbvkvq8,3488
32
+ tinybird/tb_cli_modules/tag.py,sha256=S5x8uDW0rsJEI5lKH2mefro7tEzcHyDNWrV_ZWNyS1E,3483
33
33
  tinybird/tb_cli_modules/telemetry.py,sha256=W098H6jmS4kpE7hN3tadaREBTf7oMocel-lkKWN0pU8,10466
34
34
  tinybird/tb_cli_modules/test.py,sha256=Vf8oK96V81HdKGsT79y6MUz6oz_VrYIwTbRnzzJs4rQ,4350
35
35
  tinybird/tb_cli_modules/token.py,sha256=JXATKTlbXohP9ZDZjlz8E4VYG6zrknKZhuz_wh1zBBc,13793
36
- tinybird/tb_cli_modules/workspace.py,sha256=JljGmM2LFJIHx_lL-xXCx8TNqhWD0yFS4kKUDh0RiaY,12473
36
+ tinybird/tb_cli_modules/workspace.py,sha256=Ssu-jKk7lp4Gh3qX5DXhIbImyhs9aCvL09u_T75zw1I,12421
37
37
  tinybird/tb_cli_modules/workspace_members.py,sha256=ksXsjd233y9-sNlz4Qb-meZbX4zn1B84e_bSm2i8rhg,8731
38
38
  tinybird/tb_cli_modules/tinyunit/tinyunit.py,sha256=50uqMgJD2BqSVONtCm55nuGRWhBNZWRc2GP1Qb8URdg,11246
39
39
  tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py,sha256=NHoXcCHPDcKWYLzgP3NViho3Ey-6RV-ynPDzySPrTPE,1817
40
- tinybird_cli-6.4.2.dev0.dist-info/METADATA,sha256=NdxXrmkVZwO1TtVTM4IeEOVb05DZJZ2qqegCWu6kFwE,81596
41
- tinybird_cli-6.4.2.dev0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
42
- tinybird_cli-6.4.2.dev0.dist-info/entry_points.txt,sha256=PKPKuPmA4IfJYnCFHHUiw-aAWZuBomFvwCklv1OyCjE,43
43
- tinybird_cli-6.4.2.dev0.dist-info/top_level.txt,sha256=ZIQJTPCzMqnfDzM_hEGZrJqDSEcKnIK_49T86DGWpyQ,78
44
- tinybird_cli-6.4.2.dev0.dist-info/RECORD,,
40
+ tinybird_cli-6.5.0.dist-info/METADATA,sha256=aeTsgx3gYpTGiwQSZlHnKV4OxRou-igB7U2MGnpv8Aw,81769
41
+ tinybird_cli-6.5.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
42
+ tinybird_cli-6.5.0.dist-info/entry_points.txt,sha256=PKPKuPmA4IfJYnCFHHUiw-aAWZuBomFvwCklv1OyCjE,43
43
+ tinybird_cli-6.5.0.dist-info/top_level.txt,sha256=ZIQJTPCzMqnfDzM_hEGZrJqDSEcKnIK_49T86DGWpyQ,78
44
+ tinybird_cli-6.5.0.dist-info/RECORD,,