tinybird-cli 6.5.7.dev0__py3-none-any.whl → 7.0.0.dev0__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.5.7.dev0'
8
- __revision__ = '532ee82'
7
+ __version__ = '7.0.0.dev0'
8
+ __revision__ = '3fcfdc16'
@@ -263,4 +263,5 @@ VALID_QUERY_FORMATS = (
263
263
  "JSONCompact",
264
264
  "JSONStringsEachRowWithProgress",
265
265
  "ODBCDriver2",
266
+ "Markdown",
266
267
  )
@@ -1,3 +1,4 @@
1
+ import functools
1
2
  import logging
2
3
  import re
3
4
  from collections import defaultdict
@@ -25,6 +26,9 @@ DEFAULT_EMPTY_PARAMETERS = ["ttl", "partition_key", "sorting_key"]
25
26
  # * `N` is a number
26
27
  SIMPLE_TTL_DEFINITION = re.compile(r"""^([a-zA-Z0-9_\-\.\(\)\ \/\*]*) \+ (toInterval[a-zA-Z]*\([0-9]+\))$""")
27
28
 
29
+ # ClickHouse 25.8 quotes the engine name `Null`, as `null` is a keyword.
30
+ _BACKQUOTED_ENGINE_NAME = re.compile(r"^`(\w+)`")
31
+
28
32
 
29
33
  class TableDetails:
30
34
  """
@@ -78,6 +82,16 @@ class TableDetails:
78
82
 
79
83
  @property
80
84
  def engine_full(self) -> Optional[str]:
85
+ """
86
+ >>> TableDetails({"engine_full": "`Null`", "engine": "Null"}).engine_full
87
+ 'Null'
88
+ >>> TableDetails({"engine_full": "`Null`()", "engine": "Null"}).engine_full
89
+ 'Null()'
90
+ >>> TableDetails({"engine_full": "Null", "engine": "Null"}).to_json()
91
+ {'engine_full': 'Null', 'engine': 'Null'}
92
+ >>> TableDetails({"engine_full": "`Null`", "engine": "Null"}).to_json()
93
+ {'engine_full': 'Null', 'engine': 'Null'}
94
+ """
81
95
  _engine_full: Optional[str] = self.details.get("engine_full", None)
82
96
  if not _engine_full:
83
97
  return None
@@ -86,7 +100,7 @@ class TableDetails:
86
100
  # We cannot remove index_granularity = 8192 blindly because it might be followed by other settings
87
101
  if settings and settings.strip().lower() == "index_granularity = 8192":
88
102
  _engine_full = _engine_full.replace(" SETTINGS index_granularity = 8192", "")
89
- return engine_replicated_to_local(_engine_full)
103
+ return engine_replicated_to_local(_BACKQUOTED_ENGINE_NAME.sub(r"\1", _engine_full))
90
104
 
91
105
  @property
92
106
  def original_engine_full(self) -> Optional[str]:
@@ -111,8 +125,7 @@ class TableDetails:
111
125
 
112
126
  @property
113
127
  def version(self):
114
- _version = self.details.get("version", None)
115
- return _version
128
+ return self.details.get("version", None)
116
129
 
117
130
  def is_replicated(self) -> bool:
118
131
  engine: Optional[str] = self.details.get("engine", None)
@@ -183,6 +196,7 @@ class TableDetails:
183
196
  settings = self.details.get("settings", None)
184
197
  if settings and settings.strip().lower() != "index_granularity = 8192":
185
198
  return settings
199
+ return None
186
200
 
187
201
  @property
188
202
  def ttl(self):
@@ -190,23 +204,19 @@ class TableDetails:
190
204
 
191
205
  @property
192
206
  def ver(self):
193
- _ver = self.details.get("ver", None)
194
- return _ver
207
+ return self.details.get("ver", None)
195
208
 
196
209
  @property
197
210
  def is_deleted(self):
198
- _is_deleted = self.details.get("is_deleted", None)
199
- return _is_deleted
211
+ return self.details.get("is_deleted", None)
200
212
 
201
213
  @property
202
214
  def columns(self):
203
- _columns = self.details.get("columns", None)
204
- return _columns
215
+ return self.details.get("columns", None)
205
216
 
206
217
  @property
207
218
  def sign(self):
208
- _sign = self.details.get("sign", None)
209
- return _sign
219
+ return self.details.get("sign", None)
210
220
 
211
221
  @property
212
222
  def statistics(self) -> Dict[str, Any]:
@@ -444,38 +454,70 @@ def get_engine_config(engine: str):
444
454
  )
445
455
 
446
456
 
447
- def engine_params(columns, params: List[EngineParam], args: Dict):
448
- params_values = []
457
+ def validate_engine_config(
458
+ engine: str, args: dict, schema: Optional[str] = None, columns: Optional[List[Dict[str, Any]]] = None
459
+ ) -> None:
460
+ if schema is not None and columns is not None:
461
+ raise ValueError("You can not use 'schema' and 'columns' at the same time")
462
+ _, (params, options) = get_engine_config(engine)
463
+ schema_columns = parse_table_structure(schema) if schema is not None else (columns or [])
464
+ engine_settings = {key.replace("engine_", ""): value for key, value in args.items()}
465
+
466
+ for arg in engine_settings:
467
+ if not hasattr(TableDetails, arg):
468
+ raise ValueError(f"engine_{arg} is not a valid option")
469
+
449
470
  for p in params:
450
- if p.required and p.name not in args:
471
+ if p.required and p.name not in engine_settings:
451
472
  raise ValueError(f"Missing required parameter '{p.name}'")
452
- param_value = args.get(p.name, None) or p.default_value
473
+ param_value = engine_settings.get(p.name) or p.default_value
453
474
  if not param_value:
454
475
  continue
455
476
  if p.is_valid:
456
477
  check_is_valid(
457
- valid_check=p.is_valid, check_type="parameter", columns=columns, tb_param=p.tb_param, value=param_value
478
+ valid_check=p.is_valid,
479
+ check_type="parameter",
480
+ columns=schema_columns,
481
+ tb_param=p.tb_param,
482
+ value=param_value,
458
483
  )
484
+
485
+ for o in options:
486
+ if o.required and o.name not in engine_settings:
487
+ raise ValueError(f"Missing required option '{o.name}'")
488
+ option_value = engine_settings.get(o.name) or o.default_value
489
+ if o.is_valid:
490
+ check_is_valid(
491
+ valid_check=o.is_valid,
492
+ check_type="option",
493
+ columns=schema_columns,
494
+ tb_param=o.tb_param,
495
+ value=option_value,
496
+ )
497
+
498
+
499
+ def engine_params(params: List[EngineParam], args: Dict) -> List[str]:
500
+ params_values = []
501
+ for p in params:
502
+ param_value = args.get(p.name, None) or p.default_value
503
+ if not param_value:
504
+ continue
459
505
  params_values.append(param_value)
460
506
  return params_values
461
507
 
462
508
 
463
- def engine_options(columns, options: List[EngineOption], args: Dict):
509
+ def engine_options(options: List[EngineOption], args: Dict) -> List[str]:
464
510
  options_values = []
465
511
  engine_settings = ""
466
512
 
467
513
  for o in options:
468
- if o.required and o.name not in args:
469
- raise ValueError(f"Missing required option '{o.name}'")
470
514
  option_value = args.get(o.name) or o.default_value
471
- if o.is_valid:
472
- check_is_valid(
473
- valid_check=o.is_valid, check_type="option", columns=columns, tb_param=o.tb_param, value=option_value
474
- )
475
-
476
515
  if option_value:
477
516
  if o.sql.lower() == "settings":
478
517
  engine_settings = f"{o.sql} {option_value}"
518
+ # Conditional TTLs fail if we wrap the value in parentheses
519
+ elif o.sql.lower() == "ttl":
520
+ options_values.append(f"{o.sql} {option_value}")
479
521
  else:
480
522
  options_values.append(f"{o.sql} ({option_value})")
481
523
 
@@ -491,7 +533,7 @@ def check_is_valid(
491
533
  columns: List[Dict[str, Any]],
492
534
  tb_param: str,
493
535
  value: str,
494
- ):
536
+ ) -> None:
495
537
  """
496
538
  >>> check_is_valid(sorting_key_is_valid, 'option', ['column-name'], 'sorting_key', 'date')
497
539
 
@@ -508,15 +550,13 @@ def check_is_valid(
508
550
  raise ValueError(f"Invalid value '{value}' for {check_type} '{tb_param}', reason: {e}")
509
551
 
510
552
 
511
- def build_engine(
512
- engine: str, columns: Optional[List], params: List[EngineParam], options: List[EngineOption], args: Dict
513
- ):
514
- return f"{engine}({', '.join(engine_params(columns, params, args))}) {' '.join(engine_options(columns, options, args))}".strip()
553
+ def build_engine(engine: str, params: List[EngineParam], options: List[EngineOption], args: Dict) -> str:
554
+ return f"{engine}({', '.join(engine_params(params, args))}) {' '.join(engine_options(options, args))}".strip()
515
555
 
516
556
 
517
557
  def engine_full_from_dict(
518
558
  engine: str, args: dict, schema: Optional[str] = None, columns: Optional[List[Dict[str, Any]]] = None
519
- ):
559
+ ) -> str:
520
560
  """
521
561
  >>> schema = ''
522
562
  >>> engine_full_from_dict('wadus', {}, schema=schema)
@@ -536,7 +576,9 @@ def engine_full_from_dict(
536
576
  >>> engine_full_from_dict('MergeTree', {'sorting_key': 'local_date, cod_store'}, schema=schema)
537
577
  'MergeTree() ORDER BY (local_date, cod_store)'
538
578
  >>> engine_full_from_dict('MergeTree', {'partition_key': 'toDate(timestamp)', 'sorting_key': 'local_date, cod_store', 'settings': 'index_granularity = 32, index_granularity_bytes = 2048', 'ttl': 'toDate(local_date) + INTERVAL 1 DAY'}, schema=schema)
539
- 'MergeTree() PARTITION BY (toDate(timestamp)) ORDER BY (local_date, cod_store) TTL (toDate(local_date) + INTERVAL 1 DAY) SETTINGS index_granularity = 32, index_granularity_bytes = 2048'
579
+ 'MergeTree() PARTITION BY (toDate(timestamp)) ORDER BY (local_date, cod_store) TTL toDate(local_date) + INTERVAL 1 DAY SETTINGS index_granularity = 32, index_granularity_bytes = 2048'
580
+ >>> engine_full_from_dict('MergeTree', {'sorting_key': 'timestamp', 'ttl': 'timestamp + INTERVAL 1 DAY DELETE WHERE is_deleted = 1'}, schema=schema)
581
+ 'MergeTree() ORDER BY (timestamp) TTL timestamp + INTERVAL 1 DAY DELETE WHERE is_deleted = 1'
540
582
 
541
583
  >>> schema = ''
542
584
  >>> engine_full_from_dict('CollapsingMergeTree', {'sign': 'sign_column'}, schema=schema)
@@ -605,20 +647,10 @@ def engine_full_from_dict(
605
647
  'ReplacingMergeTree(updated_at) PARTITION BY (tuple()) ORDER BY (project_id) SETTINGS index_granularity = 32'
606
648
  """
607
649
 
608
- if schema is not None and columns is not None:
609
- raise ValueError("You can not use 'schema' and 'columns' at the same time")
610
- engine_config = get_engine_config(engine)
611
- name, (params, options) = engine_config
612
- if columns is None and schema is not None:
613
- columns = parse_table_structure(schema)
614
-
650
+ validate_engine_config(engine, args, schema=schema, columns=columns)
651
+ name, (params, options) = get_engine_config(engine)
615
652
  engine_settings = {key.replace("engine_", ""): value for key, value in args.items()}
616
-
617
- for arg in engine_settings:
618
- if not hasattr(TableDetails, arg):
619
- raise ValueError(f"engine_{arg} is not a valid option")
620
-
621
- return build_engine(name, columns, params, options, engine_settings)
653
+ return build_engine(name, params, options, engine_settings)
622
654
 
623
655
 
624
656
  def engine_params_from_engine_full(engine_full: str) -> Dict[str, Any]:
@@ -694,8 +726,7 @@ def ttl_from_engine(engine: str) -> Optional[str]:
694
726
  return None
695
727
  settings_array = engine.split(" SETTINGS ")
696
728
  settings = " SETTINGS " + settings_array[1] if len(settings_array) > 1 else None
697
- ttl = ttl_array[1][: -(len(settings))] if settings else ttl_array[1]
698
- return ttl
729
+ return ttl_array[1][: -(len(settings))] if settings else ttl_array[1]
699
730
 
700
731
 
701
732
  def ttl_condition_from_engine_full(engine_full: Optional[str]) -> Optional[str]:
@@ -759,17 +790,22 @@ def ttl_condition_from_engine_full(engine_full: Optional[str]) -> Optional[str]:
759
790
  return None
760
791
 
761
792
 
793
+ @functools.lru_cache(maxsize=1024)
794
+ def _index_lines(create_table_query_expr: str) -> tuple[str, ...]:
795
+ from tinybird.sql_toolset import format_sql
796
+
797
+ return tuple(
798
+ line.strip() for line in format_sql(create_table_query_expr).splitlines() if line.strip().startswith("INDEX")
799
+ )
800
+
801
+
762
802
  def _parse_indexes(create_table_query_expr: str) -> List[TableIndex]:
763
- if create_table_query_expr == "":
803
+ # format_sql is CPU-heavy and runs on the event loop. ClickHouse writes the
804
+ # INDEX keyword in uppercase in create_table_query.
805
+ if "INDEX" not in create_table_query_expr:
764
806
  return []
765
807
  try:
766
- from tinybird.sql_toolset import format_sql
767
-
768
- indexes = [
769
- line.strip()
770
- for line in format_sql(create_table_query_expr).splitlines()
771
- if line.strip().startswith("INDEX")
772
- ]
808
+ indexes = list(_index_lines(create_table_query_expr))
773
809
  except ModuleNotFoundError:
774
810
  # this is not needed from CLI
775
811
  return []
tinybird/client.py CHANGED
@@ -47,14 +47,6 @@ class TimeoutException(Exception):
47
47
  pass
48
48
 
49
49
 
50
- class ReachRetryLimit(Exception):
51
- pass
52
-
53
-
54
- class ConnectorNothingToLoad(Exception):
55
- pass
56
-
57
-
58
50
  class JobException(Exception):
59
51
  pass
60
52
 
@@ -266,14 +258,13 @@ class TinyB:
266
258
 
267
259
  async def alter_tokens(self, name: str, scopes: List[str]):
268
260
  if not scopes:
269
- return
261
+ return None
270
262
  scopes_url: str = "&".join([f"scope={scope}" for scope in scopes])
271
263
  url = f"/v0/tokens/{name}"
272
264
  if len(url + "?" + scopes_url) > TinyB.MAX_GET_LENGTH:
273
265
  return await self._req(url, method="PUT", data=scopes_url)
274
- else:
275
- url = url + "?" + scopes_url
276
- return await self._req(url, method="PUT", data="")
266
+ url = url + "?" + scopes_url
267
+ return await self._req(url, method="PUT", data="")
277
268
 
278
269
  async def datasources(self, branch: Optional[str] = None, used_by: bool = False) -> List[Dict[str, Any]]:
279
270
  params = {}
@@ -294,16 +285,13 @@ class TinyB:
294
285
  return await self._req(f"/v0/variables/{name}")
295
286
 
296
287
  async def create_secret(self, name: str, value: str):
297
- response = await self._req("/v0/variables", method="POST", data={"name": name, "value": value})
298
- return response
288
+ return await self._req("/v0/variables", method="POST", data={"name": name, "value": value})
299
289
 
300
290
  async def update_secret(self, name: str, value: str):
301
- response = await self._req(f"/v0/variables/{name}", method="PUT", data={"value": value})
302
- return response
291
+ return await self._req(f"/v0/variables/{name}", method="PUT", data={"value": value})
303
292
 
304
293
  async def delete_secret(self, name: str):
305
- response = await self._req(f"/v0/variables/{name}", method="DELETE")
306
- return response
294
+ return await self._req(f"/v0/variables/{name}", method="DELETE")
307
295
 
308
296
  async def get_connections(self, service: Optional[str] = None):
309
297
  params = {}
@@ -412,6 +400,16 @@ class TinyB:
412
400
  format: str = "csv",
413
401
  replace_options: Optional[Set[str]] = None,
414
402
  ) -> Dict[str, Any]:
403
+ if mode in ("append", "replace"):
404
+ return await self.datasource_append_url(
405
+ table_name,
406
+ url,
407
+ mode=mode,
408
+ sql_condition=sql_condition,
409
+ format=format,
410
+ replace_options=replace_options,
411
+ )
412
+
415
413
  params = {"name": table_name, "url": url, "mode": mode, "debug": "blocks_block_log", "format": format}
416
414
 
417
415
  if sql_condition:
@@ -428,6 +426,33 @@ class TinyB:
428
426
 
429
427
  return await self.wait_for_job(res["id"], status_callback, backoff_multiplier=1.5, maximum_backoff_seconds=20)
430
428
 
429
+ async def datasource_append_url(
430
+ self,
431
+ datasource_name: str,
432
+ url: str,
433
+ mode: str = "append",
434
+ sql_condition: Optional[str] = None,
435
+ format: str = "csv",
436
+ replace_options: Optional[Set[str]] = None,
437
+ ) -> Dict[str, Any]:
438
+ params = {"format": format, "url": url}
439
+ if sql_condition:
440
+ params["replace_condition"] = sql_condition
441
+ if replace_options:
442
+ for option in replace_options:
443
+ params[option] = "true"
444
+
445
+ res = await self._req(
446
+ f"/v1/datasources/{quote(datasource_name, safe='')}/{mode}?{urlencode(params, safe='')}",
447
+ method="POST",
448
+ data=b"",
449
+ )
450
+ if not isinstance(res, dict):
451
+ raise RuntimeError("We couldn't confirm that your import started. Please try again.")
452
+ if "error" in res:
453
+ raise Exception(res["error"])
454
+ return res
455
+
431
456
  async def datasource_delete(self, datasource_name: str, force: bool = False, dry_run: bool = False):
432
457
  params = {"force": "true" if force else "false", "dry_run": "true" if dry_run else "false"}
433
458
  return await self._req(f"/v0/datasources/{datasource_name}?{urlencode(params)}", method="DELETE")
@@ -577,8 +602,7 @@ class TinyB:
577
602
  node_name = node["params"]["name"] if node.get("params", None) else node["name"]
578
603
  if datasource_name:
579
604
  params["datasource"] = datasource_name
580
- response = await self._req(f"/v0/pipes/{pipe_name}/nodes/{node_name}/analysis?{urlencode(params)}")
581
- return response
605
+ return await self._req(f"/v0/pipes/{pipe_name}/nodes/{node_name}/analysis?{urlencode(params)}")
582
606
 
583
607
  async def populate_node(
584
608
  self,
@@ -600,10 +624,7 @@ class TinyB:
600
624
  params.update({"populate_condition": populate_condition})
601
625
  if on_demand_compute:
602
626
  params.update({"on_demand_compute": "true"})
603
- response = await self._req(
604
- f"/v0/pipes/{pipe_name}/nodes/{node_name}/population?{urlencode(params)}", method="POST"
605
- )
606
- return response
627
+ return await self._req(f"/v0/pipes/{pipe_name}/nodes/{node_name}/population?{urlencode(params)}", method="POST")
607
628
 
608
629
  async def pipes(self, branch=None, dependencies: bool = False, node_attrs=None, attrs=None) -> List[Dict[str, Any]]:
609
630
  params = {
@@ -635,9 +656,8 @@ class TinyB:
635
656
  query_string = urlencode(params)
636
657
  if len(url + "?" + query_string) > TinyB.MAX_GET_LENGTH:
637
658
  return await self._req(f"/v0/pipes/{pipe_name_or_uid}.{format}", method="POST", data=params)
638
- else:
639
- url = url + "?" + query_string
640
- return await self._req(url)
659
+ url = url + "?" + query_string
660
+ return await self._req(url)
641
661
 
642
662
  async def pipe_create(self, pipe_name: str, sql: str):
643
663
  return await self._req(
@@ -663,6 +683,7 @@ class TinyB:
663
683
  target_datasource: Optional[str] = None,
664
684
  schedule_cron: Optional[str] = None,
665
685
  mode: Optional[str] = None,
686
+ on_demand_compute: Optional[bool] = None,
666
687
  ):
667
688
  data = {"schedule_cron": schedule_cron}
668
689
 
@@ -672,6 +693,9 @@ class TinyB:
672
693
  if mode:
673
694
  data["mode"] = mode
674
695
 
696
+ if on_demand_compute is not None:
697
+ data["on_demand_compute"] = "true" if on_demand_compute else "false"
698
+
675
699
  return await self._req(f"/v0/pipes/{pipe_name_or_id}/nodes/{node_id}/copy", method="PUT", data=data)
676
700
 
677
701
  async def pipe_remove_copy(self, pipe_name_or_id: str, node_id: str):
@@ -726,8 +750,7 @@ class TinyB:
726
750
 
727
751
  if len(sql) > TinyB.MAX_GET_LENGTH:
728
752
  return await self._req(f"/v0/sql?{urlencode(params)}", data=sql, method="POST")
729
- else:
730
- return await self._req(f"/v0/sql?q={quote(sql, safe='')}&{urlencode(params)}")
753
+ return await self._req(f"/v0/sql?q={quote(sql, safe='')}&{urlencode(params)}")
731
754
 
732
755
  async def jobs(self, status=None):
733
756
  jobs = (await self._req("/v0/jobs"))["jobs"]
@@ -828,7 +851,6 @@ class TinyB:
828
851
  assert_time_increase_percentage: Optional[float] = None,
829
852
  assert_bytes_read_increase_percentage: Optional[float] = None,
830
853
  assert_max_time: Optional[float] = None,
831
- run_in_main: Optional[bool] = False,
832
854
  ):
833
855
  test: Dict[str, Any] = {
834
856
  test_type: {
@@ -858,20 +880,12 @@ class TinyB:
858
880
  ]
859
881
 
860
882
  data = json.dumps(regression_commands)
861
- if run_in_main:
862
- url = f"/v0/environments/{branch_id}/regression/main"
863
- else:
864
- url = f"/v0/environments/{branch_id}/regression"
883
+ url = f"/v0/environments/{branch_id}/regression"
865
884
  return await self._req(url, method="POST", data=data, headers={"Content-Type": "application/json"})
866
885
 
867
- async def branch_regression_tests_file(
868
- self, branch_id: str, regression_commands: List[Dict[str, Any]], run_in_main: Optional[bool] = False
869
- ):
886
+ async def branch_regression_tests_file(self, branch_id: str, regression_commands: List[Dict[str, Any]]):
870
887
  data = json.dumps(regression_commands)
871
- if run_in_main:
872
- url = f"/v0/environments/{branch_id}/regression/main"
873
- else:
874
- url = f"/v0/environments/{branch_id}/regression"
888
+ url = f"/v0/environments/{branch_id}/regression"
875
889
  return await self._req(url, method="POST", data=data, headers={"Content-Type": "application/json"})
876
890
 
877
891
  async def delete_workspace(self, id: str, hard_delete_confirmation: Optional[str], version: str = "v0"):
@@ -922,7 +936,7 @@ class TinyB:
922
936
 
923
937
  async def add_workspaces_to_organization(self, organization_id: str, workspace_ids: List[str]):
924
938
  if not workspace_ids:
925
- return
939
+ return None
926
940
  return await self._req(
927
941
  f"/v0/organizations/{organization_id}/workspaces",
928
942
  method="PUT",
@@ -1105,8 +1119,7 @@ class TinyB:
1105
1119
  from tinybird.sql_toolset import format_sql
1106
1120
 
1107
1121
  return format_sql(sql)
1108
- else:
1109
- return await self._sql_get_format_remote(sql, with_clickhouse_format)
1122
+ return await self._sql_get_format_remote(sql, with_clickhouse_format)
1110
1123
  except ModuleNotFoundError:
1111
1124
  return await self._sql_get_format_remote(sql, with_clickhouse_format)
1112
1125
 
@@ -1168,8 +1181,7 @@ class TinyB:
1168
1181
  return next((connector for connector in result["connectors"] if connector_equals(connector, kwargs)), None)
1169
1182
 
1170
1183
  async def regions(self):
1171
- regions = await self._req("/v0/regions")
1172
- return regions
1184
+ return await self._req("/v0/regions")
1173
1185
 
1174
1186
  async def datasource_query_copy(self, datasource_name: str, sql_query: str):
1175
1187
  params = {"copy_to": datasource_name}
tinybird/config.py CHANGED
@@ -49,11 +49,7 @@ LEGACY_HOSTS = {
49
49
  "https://ui.europe-west2.gcp.tinybird.co": "https://cloud.tinybird.co/gcp/europe-west2",
50
50
  "https://ui.ap-east.aws.tinybird.co": "https://app.tinybird.co/aws/ap-east-1",
51
51
  "https://ui.ap-east-1.aws.tinybird.co": "https://app.tinybird.co/aws/ap-east-1",
52
- "https://ui.split.tinybird.co": "https://app.tinybird.co/aws/split-us-east",
53
- "https://ui.split.us-west-2.aws.tinybird.co": "https://app.tinybird.co/aws/split-us-west-2",
54
52
  "https://ui.northamerica-northeast2.gcp.tinybird.co": "https://app.tinybird.co/gcp/northamerica-northeast2",
55
- "https://api.split.tinybird.co": "https://app.tinybird.co/aws/split-us-east",
56
- "https://api.split.us-west-2.aws.tinybird.co": "https://app.tinybird.co/aws/split-us-west-2",
57
53
  "https://ui.wadus1.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus1",
58
54
  "https://ui.wadus2.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus2",
59
55
  "https://ui.wadus3.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus3",