tinybird-cli 5.22.2.dev0__tar.gz → 6.0.1__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.
Files changed (49) hide show
  1. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/PKG-INFO +18 -20
  2. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/__cli__.py +2 -2
  3. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/ch_utils/engine.py +2 -77
  4. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/client.py +3 -141
  5. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/config.py +0 -5
  6. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/context.py +0 -1
  7. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/datafile_common.py +22 -38
  8. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/datatypes.py +13 -0
  9. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/feedback_manager.py +1 -41
  10. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/sql.py +6 -5
  11. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/sql_template.py +616 -119
  12. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/sql_toolset.py +208 -54
  13. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/auth.py +1 -11
  14. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/cli.py +1 -83
  15. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/common.py +27 -153
  16. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/config.py +1 -3
  17. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/connection.py +1 -225
  18. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/datasource.py +11 -95
  19. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tornado_template.py +8 -3
  20. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird_cli.egg-info/PKG-INFO +18 -20
  21. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird_cli.egg-info/SOURCES.txt +0 -1
  22. tinybird_cli-6.0.1/tinybird_cli.egg-info/requires.txt +19 -0
  23. tinybird_cli-5.22.2.dev0/tinybird/connectors.py +0 -422
  24. tinybird_cli-5.22.2.dev0/tinybird_cli.egg-info/requires.txt +0 -38
  25. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/setup.cfg +0 -0
  26. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/ch_utils/constants.py +0 -0
  27. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/check_pypi.py +0 -0
  28. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/git_settings.py +0 -0
  29. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/sql_template_fmt.py +0 -0
  30. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/syncasync.py +0 -0
  31. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli.py +0 -0
  32. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/branch.py +0 -0
  33. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/cicd.py +0 -0
  34. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/exceptions.py +0 -0
  35. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/fmt.py +0 -0
  36. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/job.py +0 -0
  37. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/pipe.py +0 -0
  38. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/regions.py +0 -0
  39. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/tag.py +0 -0
  40. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/telemetry.py +0 -0
  41. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/test.py +0 -0
  42. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  43. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  44. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/token.py +0 -0
  45. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/workspace.py +0 -0
  46. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  47. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird_cli.egg-info/dependency_links.txt +0 -0
  48. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird_cli.egg-info/entry_points.txt +0 -0
  49. {tinybird_cli-5.22.2.dev0 → tinybird_cli-6.0.1}/tinybird_cli.egg-info/top_level.txt +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird_cli
3
- Version: 5.22.2.dev0
3
+ Version: 6.0.1
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli
6
6
  Author: Tinybird
7
7
  Author-email: support@tinybird.co
8
- Requires-Python: >=3.9, <3.14
8
+ Requires-Python: >=3.10, <3.14
9
9
  Description-Content-Type: text/x-rst
10
10
  Requires-Dist: aiofiles==24.1.0
11
11
  Requires-Dist: clickhouse-toolset==0.34.dev0
@@ -26,29 +26,11 @@ Requires-Dist: tornado~=6.0.0
26
26
  Requires-Dist: urllib3<2,>=1.26.14
27
27
  Requires-Dist: wheel
28
28
  Requires-Dist: packaging<24,>=23.1
29
- Provides-Extra: bigquery
30
- Requires-Dist: gsutil==4.58; extra == "bigquery"
31
- Requires-Dist: google-api-python-client==2.0.2; extra == "bigquery"
32
- Requires-Dist: google-auth==1.27.1; extra == "bigquery"
33
- Requires-Dist: google-auth-httplib2==0.1.0; extra == "bigquery"
34
- Requires-Dist: google-cloud-storage==2.4.0; extra == "bigquery"
35
- Requires-Dist: google-cloud-bigquery==2.11.0; extra == "bigquery"
36
- Provides-Extra: snowflake
37
- Requires-Dist: snowflake-connector-python~=3.12.3; extra == "snowflake"
38
- Requires-Dist: gsutil==4.58; extra == "snowflake"
39
- Requires-Dist: google-api-python-client==2.0.2; extra == "snowflake"
40
- Requires-Dist: google-auth==1.27.1; extra == "snowflake"
41
- Requires-Dist: google-auth-httplib2==0.1.0; extra == "snowflake"
42
- Requires-Dist: google-cloud-storage==2.4.0; extra == "snowflake"
43
- Requires-Dist: oauth2client==3.0.0; extra == "snowflake"
44
- Requires-Dist: chardet<4,>=3.0.2; extra == "snowflake"
45
- Requires-Dist: pyOpenSSL<20.0.0,>=16.2.0; extra == "snowflake"
46
29
  Dynamic: author
47
30
  Dynamic: author-email
48
31
  Dynamic: description
49
32
  Dynamic: description-content-type
50
33
  Dynamic: home-page
51
- Dynamic: provides-extra
52
34
  Dynamic: requires-dist
53
35
  Dynamic: requires-python
54
36
  Dynamic: summary
@@ -61,6 +43,22 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
61
43
  Changelog
62
44
  ----------
63
45
 
46
+ 6.0.1
47
+ ***********
48
+
49
+ - `Fixed` false circular dependency detection when a shared datasource has the same name as a local pipe
50
+
51
+ 6.0.0
52
+ ***********
53
+
54
+ - `Removed` support for Python 3.9. The minimum supported Python version is now 3.10.
55
+ - `Removed` BigQuery and Snowflake CDK connector commands from `tb connection create`
56
+
57
+ 5.22.2
58
+ ***********
59
+
60
+ - `Fixed` tokens in existing datasource files not being updated when using `tb push -f`
61
+
64
62
  5.22.1
65
63
  ***********
66
64
 
@@ -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__ = '5.22.2.dev0'
8
- __revision__ = 'e1b50a6'
7
+ __version__ = '6.0.1'
8
+ __revision__ = '8bacae6'
@@ -14,7 +14,6 @@ from ..sql import (
14
14
  )
15
15
 
16
16
  DEFAULT_EMPTY_PARAMETERS = ["ttl", "partition_key", "sorting_key"]
17
- DEFAULT_JOIN_EMPTY_PARAMETERS = ["join_strictness", "join_type", "key_columns"]
18
17
 
19
18
  # Currently we only support the simplest TTLs
20
19
  # f(X) + toIntervalZ(N)
@@ -62,25 +61,6 @@ class TableDetails:
62
61
  >>> ed.to_datafile()
63
62
  'ENGINE "MergeTree"\\nENGINE_PARTITION_KEY "toYear(timestamp)"\\nENGINE_SORTING_KEY "timestamp, cityHash64(location)"\\nENGINE_SAMPLING_KEY "cityHash64(location)"\\nENGINE_SETTINGS "index_granularity = 32, index_granularity_bytes = 2048"\\nENGINE_TTL "toDate(timestamp) + INTERVAL 1 DAY"'
64
63
 
65
- >>> ed = TableDetails({"engine_full": "Join(ANY, LEFT, id)", "engine": "Join", "partition_key": "", "sorting_key": "", "primary_key": "", "sampling_key": ""})
66
- >>> ed.engine_full
67
- 'Join(ANY, LEFT, id)'
68
- >>> ed.engine
69
- 'Join'
70
- >>> ed.to_json()
71
- {'engine_full': 'Join(ANY, LEFT, id)', 'engine': 'Join', 'join_strictness': 'ANY', 'join_type': 'LEFT', 'key_columns': 'id'}
72
- >>> ed.to_datafile()
73
- 'ENGINE "Join"\\nENGINE_JOIN_STRICTNESS "ANY"\\nENGINE_JOIN_TYPE "LEFT"\\nENGINE_KEY_COLUMNS "id"'
74
-
75
- >>> ed = TableDetails({"database": "d_01", "name": "t_01", "engine": "Join", "join_strictness": "ANY", "join_type": "LEFT", "key_columns": "id"})
76
- >>> ed.engine_full == None
77
- True
78
- >>> ed.engine
79
- 'Join'
80
- >>> ed.to_json()
81
- {'engine_full': None, 'engine': 'Join', 'join_strictness': 'ANY', 'join_type': 'LEFT', 'key_columns': 'id'}
82
- >>> ed.to_datafile()
83
- 'ENGINE "Join"\\nENGINE_JOIN_STRICTNESS "ANY"\\nENGINE_JOIN_TYPE "LEFT"\\nENGINE_KEY_COLUMNS "id"'
84
64
  >>> ed = TableDetails({ "engine_full": "MergeTree() PARTITION BY toYear(timestamp) ORDER BY (timestamp, cityHash64(location)) SAMPLE BY cityHash64(location) SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 1800 TTL toDate(timestamp) + INTERVAL 1 DAY"})
85
65
  >>> ed.engine_full
86
66
  'MergeTree() PARTITION BY toYear(timestamp) ORDER BY (timestamp, cityHash64(location)) SAMPLE BY cityHash64(location) SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 1800 TTL toDate(timestamp) + INTERVAL 1 DAY'
@@ -222,21 +202,6 @@ class TableDetails:
222
202
  _sign = self.details.get("sign", None)
223
203
  return _sign
224
204
 
225
- @property
226
- def join_strictness(self):
227
- _join_strictness = self.details.get("join_strictness", None)
228
- return _join_strictness
229
-
230
- @property
231
- def join_type(self):
232
- _join_type = self.details.get("join_type", None)
233
- return _join_type
234
-
235
- @property
236
- def key_columns(self):
237
- _key_columns = self.details.get("key_columns", None)
238
- return _key_columns
239
-
240
205
  @property
241
206
  def statistics(self) -> Dict[str, Any]:
242
207
  return {
@@ -268,12 +233,6 @@ class TableDetails:
268
233
  d["sampling_key"] = self.sampling_key
269
234
  if self.settings:
270
235
  d["settings"] = self.settings
271
- if self.join_strictness:
272
- d["join_strictness"] = self.join_strictness
273
- if self.join_type:
274
- d["join_type"] = self.join_type
275
- if self.key_columns:
276
- d["key_columns"] = self.key_columns
277
236
  if self.ver:
278
237
  d["ver"] = self.ver
279
238
  if self.is_deleted:
@@ -292,10 +251,7 @@ class TableDetails:
292
251
  d = {**d, **engine_params}
293
252
 
294
253
  if include_empty_details:
295
- if self.engine and self.engine.lower() == "join":
296
- d = set_empty_details(d, DEFAULT_JOIN_EMPTY_PARAMETERS)
297
- else:
298
- d = set_empty_details(d, DEFAULT_EMPTY_PARAMETERS)
254
+ d = set_empty_details(d, DEFAULT_EMPTY_PARAMETERS)
299
255
 
300
256
  if exclude:
301
257
  for attr in exclude:
@@ -480,20 +436,6 @@ ENABLED_ENGINES = [
480
436
  ],
481
437
  MERGETREE_OPTIONS,
482
438
  ),
483
- # Join(join_strictness, join_type, k1[, k2, ...])
484
- engine_config(
485
- "Join",
486
- [
487
- # https://github.com/ClickHouse/ClickHouse/blob/fa8e4e4735b932f08b6beffcb2d069b72de34401/src/Storages/StorageJoin.cpp
488
- EngineParam(
489
- name="join_strictness", required=True, is_valid=case_insensitive_check(["ANY", "ALL", "SEMI", "ANTI"])
490
- ),
491
- EngineParam(
492
- name="join_type", required=True, is_valid=case_insensitive_check(["LEFT", "INNER", "RIGHT", "FULL"])
493
- ),
494
- EngineParam(name="key_columns", required=True, is_valid=columns_are_valid),
495
- ],
496
- ),
497
439
  # Null()
498
440
  engine_config("Null"),
499
441
  ]
@@ -599,7 +541,7 @@ def engine_full_from_dict(
599
541
  >>> engine_full_from_dict('wadus', {}, schema=schema)
600
542
  Traceback (most recent call last):
601
543
  ...
602
- ValueError: Engine wadus is not supported, supported engines include: MergeTree, ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree, CollapsingMergeTree, VersionedCollapsingMergeTree, Join, Null
544
+ ValueError: Engine wadus is not supported, supported engines include: MergeTree, ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree, CollapsingMergeTree, VersionedCollapsingMergeTree, Null
603
545
  >>> schema = ''
604
546
  >>> engine_full_from_dict('null', {}, schema=schema)
605
547
  'Null()'
@@ -607,19 +549,6 @@ def engine_full_from_dict(
607
549
  >>> engine_full_from_dict('null', {}, columns=[])
608
550
  'Null()'
609
551
 
610
- >>> schema = 'cid Int32'
611
- >>> engine_full_from_dict('Join', {'join_strictness': 'ANY', 'join_type': 'LEFT', 'key_columns': 'cid'}, schema=schema)
612
- 'Join(ANY, LEFT, cid)'
613
- >>> engine_full_from_dict('Join', {'join_strictness': 'ANY', 'join_type': 'LEFT', 'key_columns': 'cid'}, columns=[{'name': 'cid', 'type': 'Int32', 'codec': None, 'default_value': None, 'nullable': False, 'normalized_name': 'cid'}])
614
- 'Join(ANY, LEFT, cid)'
615
- >>> schema = 'cid1 Int32, cid2 Int8'
616
- >>> engine_full_from_dict('Join', {'join_strictness': 'ANY', 'join_type': 'LEFT', 'key_columns': 'cid1, cid2'}, schema=schema)
617
- 'Join(ANY, LEFT, cid1, cid2)'
618
- >>> engine_full_from_dict('Join', {'join_strictness': 'ANY', 'join_type': 'OUTER', 'key_columns': 'cid'}, schema=schema)
619
- Traceback (most recent call last):
620
- ...
621
- ValueError: Invalid value 'OUTER' for parameter 'engine_join_type', reason: valid values are LEFT, INNER, RIGHT, FULL
622
-
623
552
  >>> schema = ''
624
553
  >>> engine_full_from_dict('MergeTree', {}, schema=schema)
625
554
  'MergeTree() ORDER BY (tuple())'
@@ -715,10 +644,6 @@ def engine_params_from_engine_full(engine_full: str) -> Dict[str, Any]:
715
644
  """
716
645
  >>> engine_params_from_engine_full("Null()")
717
646
  {}
718
- >>> engine_params_from_engine_full("Join(ANY, LEFT, id)")
719
- {'join_strictness': 'ANY', 'join_type': 'LEFT', 'key_columns': 'id'}
720
- >>> engine_params_from_engine_full("Join(ANY, LEFT, k1, k2)")
721
- {'join_strictness': 'ANY', 'join_type': 'LEFT', 'key_columns': 'k1, k2'}
722
647
  >>> engine_params_from_engine_full("AggregatingMergeTree('/clickhouse/tables/{layer}-{shard}/d_f837aa.sales_by_country_rt__v0_staging_t_00c3091e7530472caebda05e97288a1d', '{replica}') PARTITION BY toYYYYMM(date) ORDER BY (purchase_location, cod_device, date) SETTINGS index_granularity = 8192")
723
648
  {}
724
649
  >>> engine_params_from_engine_full("ReplicatedSummingMergeTree('/clickhouse/tables/{layer}-{shard}/d_abcf3e.t_69f9da31f4524995b8911e1b24c80ab4', '{replica}') PARTITION BY toYYYYMM(date) ORDER BY (date, purchase_location, sku_rank_lc) SETTINGS index_granularity = 8192")
@@ -62,9 +62,7 @@ class JobException(Exception):
62
62
  def connector_equals(connector, datafile_params):
63
63
  if not connector:
64
64
  return False
65
- if connector["name"] == datafile_params["kafka_connection_name"]:
66
- return True
67
- return False
65
+ return connector["name"] == datafile_params["kafka_connection_name"]
68
66
 
69
67
 
70
68
  def parse_error_response(response: Response) -> str:
@@ -316,15 +314,9 @@ class TinyB:
316
314
  response = await self._req(f"/v0/connectors?{urlencode(params)}")
317
315
  return response["connectors"]
318
316
 
319
- async def connections(self, connector: Optional[str] = None, skip_bigquery: Optional[bool] = False):
317
+ async def connections(self, connector: Optional[str] = None):
320
318
  response = await self._req("/v0/connectors")
321
319
  connectors = response["connectors"]
322
- bigquery_connection = None
323
- if not skip_bigquery:
324
- bigquery_connection = (
325
- await self.bigquery_connection() if connector == "bigquery" or connector is None else None
326
- )
327
- connectors = [*connectors, bigquery_connection] if bigquery_connection else connectors
328
320
  if connector:
329
321
  return [
330
322
  {
@@ -348,22 +340,6 @@ class TinyB:
348
340
  for c in connectors
349
341
  ]
350
342
 
351
- async def bigquery_connection(self):
352
- bigquery_resources = await self.list_gcp_resources()
353
- if len(bigquery_resources) == 0:
354
- return None
355
-
356
- gcp_account_details: Dict[str, Any] = await self.get_gcp_service_account_details()
357
- datasources = await self.datasources()
358
- bigquery_datasources = [ds["name"] for ds in datasources if ds["type"] == "bigquery"]
359
- return {
360
- "id": gcp_account_details["account"].split("@")[0],
361
- "service": "bigquery",
362
- "name": "bigquery",
363
- "linkers": bigquery_datasources,
364
- "settings": gcp_account_details,
365
- }
366
-
367
343
  async def get_datasource(self, ds_name: str, used_by: bool = False) -> Dict[str, Any]:
368
344
  params = {
369
345
  "attrs": "used_by" if used_by else "",
@@ -1024,118 +1000,23 @@ class TinyB:
1024
1000
  )
1025
1001
  return [x["topic"] for x in resp["preview"]]
1026
1002
 
1027
- async def get_gcp_service_account_details(self) -> Dict[str, Any]:
1028
- return await self._req("/v0/datasources-bigquery-credentials")
1029
-
1030
- async def list_connectors(self, service: Optional[str] = None) -> List[Dict[str, Any]]:
1031
- try:
1032
- params: str = f"?service={service}" if service else ""
1033
- result = await self._req(f"/v0/connections/{params}")
1034
- if not result:
1035
- return []
1036
-
1037
- return result.get("connectors", [])
1038
- except Exception:
1039
- return []
1040
-
1041
1003
  async def get_connector(
1042
1004
  self,
1043
1005
  name_or_id: str,
1044
1006
  service: Optional[str] = None,
1045
1007
  key: Optional[str] = "name",
1046
- skip_bigquery: Optional[bool] = False,
1047
1008
  ) -> Optional[Dict[str, Any]]:
1048
1009
  return next(
1049
- (c for c in await self.connections(connector=service, skip_bigquery=skip_bigquery) if c[key] == name_or_id),
1010
+ (c for c in await self.connections(connector=service) if c[key] == name_or_id),
1050
1011
  None,
1051
1012
  )
1052
1013
 
1053
1014
  async def get_connector_by_id(self, connector_id: Optional[str] = None):
1054
1015
  return await self._req(f"/v0/connectors/{connector_id}")
1055
1016
 
1056
- async def get_snowflake_integration_query(
1057
- self, role: str, stage: Optional[str], integration: Optional[str]
1058
- ) -> Optional[Dict[str, Any]]:
1059
- try:
1060
- params = {
1061
- "role": role,
1062
- }
1063
- if stage:
1064
- params["stage"] = stage
1065
- if integration:
1066
- params["integration"] = integration
1067
-
1068
- return await self._req(f"/v0/connectors/snowflake/instructions?{urlencode(params)}")
1069
- except Exception:
1070
- return None
1071
-
1072
- async def list_gcp_resources(self) -> List[Dict[str, Any]]:
1073
- try:
1074
- resources = await self._req("/v0/connections/bigquery")
1075
- if not resources:
1076
- return []
1077
-
1078
- return resources.get("items", [])
1079
- except Exception:
1080
- return []
1081
-
1082
- async def check_gcp_read_permissions(self) -> bool:
1083
- """Returns `True` if our service account (see `TinyB::get_gcp_service_account_details()`)
1084
- has the proper permissions in GCP.
1085
-
1086
- Here we assume that we have permissions if we can list resources but currently this
1087
- logic is wrong under some circumstances.
1088
-
1089
- See https://gitlab.com/tinybird/analytics/-/issues/6485.
1090
- """
1091
- try:
1092
- items = await self.list_gcp_resources()
1093
- if not items:
1094
- return False
1095
- return len(items) > 0
1096
- except Exception:
1097
- return False
1098
-
1099
1017
  async def connector_delete(self, connection_id):
1100
1018
  return await self._req(f"/v0/connectors/{connection_id}", method="DELETE")
1101
1019
 
1102
- async def connection_create_snowflake(
1103
- self,
1104
- account_identifier: str,
1105
- user: str,
1106
- password: str,
1107
- warehouse: str,
1108
- role: str,
1109
- connection_name: str,
1110
- integration: Optional[str],
1111
- stage: Optional[str],
1112
- ) -> Dict[str, Any]:
1113
- params = {
1114
- "service": "snowflake",
1115
- "name": connection_name,
1116
- "account": account_identifier,
1117
- "username": user,
1118
- "password": password,
1119
- "role": role,
1120
- "warehouse": warehouse,
1121
- }
1122
-
1123
- if integration:
1124
- params["integration"] = integration
1125
- if stage:
1126
- params["stage"] = stage
1127
-
1128
- return await self._req(f"/v0/connectors?{urlencode(params)}", method="POST", data="")
1129
-
1130
- async def validate_snowflake_connection(self, account_identifier: str, user: str, password: str) -> bool:
1131
- try:
1132
- roles = await self.get_snowflake_roles(account_identifier, user, password)
1133
- if not roles:
1134
- return False
1135
- return len(roles) > 0
1136
- except Exception:
1137
- return False
1138
-
1139
1020
  async def validate_preview_connection(self, service: str, params: Dict[str, Any]) -> bool:
1140
1021
  params = {"service": service, "dry_run": "true", **params}
1141
1022
  bucket_list = None
@@ -1154,25 +1035,6 @@ class TinyB:
1154
1035
  async def connection_create(self, params: Dict[str, Any]) -> Dict[str, Any]:
1155
1036
  return await self._req(f"/v0/connectors?{urlencode(params)}", method="POST", data="")
1156
1037
 
1157
- async def get_snowflake_roles(self, account_identifier: str, user: str, password: str) -> Optional[List[str]]:
1158
- params = {"account": account_identifier, "username": user, "password": password}
1159
-
1160
- response = await self._req(f"/v0/connectors/snowflake/roles?{urlencode(params)}", method="POST", data="")
1161
- return response["roles"]
1162
-
1163
- async def get_snowflake_warehouses(
1164
- self, account_identifier: str, user: str, password: str, role: str
1165
- ) -> Optional[List[Dict[str, Any]]]:
1166
- params = {
1167
- "account": account_identifier,
1168
- "username": user,
1169
- "password": password,
1170
- "role": role,
1171
- }
1172
-
1173
- response = await self._req(f"/v0/connectors/snowflake/warehouses?{urlencode(params)}", method="POST", data="")
1174
- return response["warehouses"]
1175
-
1176
1038
  async def get_trust_policy(self, service: str) -> Dict[str, Any]:
1177
1039
  return await self._req(f"/v0/integrations/{service}/policies/trust-policy")
1178
1040
 
@@ -19,7 +19,6 @@ DEFAULT_LOCALHOST = "http://localhost:8001"
19
19
  CURRENT_VERSION = f"{__cli__.__version__}"
20
20
  VERSION = f"{__cli__.__version__} (rev {__revision__})"
21
21
  DEFAULT_UI_HOST = "https://app.tinybird.co"
22
- SUPPORTED_CONNECTORS = ["bigquery", "snowflake"]
23
22
  PROJECT_PATHS = ["datasources", "datasources/fixtures", "endpoints", "pipes", "tests", "scripts", "deploy"]
24
23
  DEPRECATED_PROJECT_PATHS = ["endpoints"]
25
24
  MIN_WORKSPACE_ID_LENGTH = 36
@@ -138,7 +137,3 @@ class FeatureFlags:
138
137
  @classmethod
139
138
  def is_localhost(cls) -> bool:
140
139
  return "SET_LOCALHOST" in environ
141
-
142
- @classmethod
143
- def enable_snowflake_connector_command(cls) -> bool:
144
- return "ENABLE_SNOWFLAKE_CONNECTOR_COMMAND" in environ
@@ -11,7 +11,6 @@ hfi_workspace_data: ContextVar["HfiWorkspaceData"] = ContextVar("hfi_workspace_d
11
11
  table_id: ContextVar[str] = ContextVar("table_id")
12
12
  hfi_frequency: ContextVar[float] = ContextVar("hfi_frequency")
13
13
  hfi_frequency_gatherer: ContextVar[float] = ContextVar("hfi_frequency_gatherer")
14
- use_gatherer: ContextVar[bool] = ContextVar("use_gatherer")
15
14
  gatherer_allow_s3_backup_on_user_errors: ContextVar[bool] = ContextVar("gatherer_allow_s3_backup_on_user_errors")
16
15
  disable_template_security_validation: ContextVar[bool] = ContextVar("disable_template_security_validation")
17
16
  origin: ContextVar[str] = ContextVar("origin")
@@ -81,6 +81,10 @@ from .tornado_template import UnClosedIfError
81
81
  os.environ["GIT_PYTHON_REFRESH"] = "quiet"
82
82
  from git import HEAD, Diff, GitCommandError, InvalidGitRepositoryError, Repo
83
83
 
84
+ # Pre-compiled regex patterns
85
+ _PATTERN_VERSION_NUMBER = re.compile(r"[0-9]+$")
86
+ _PATTERN_VERSIONED_RESOURCE = re.compile(r"([^\s\.]*__v\d+)")
87
+
84
88
  INTERNAL_TABLES: Tuple[str, ...] = (
85
89
  "datasources_ops_log",
86
90
  "pipe_stats",
@@ -1412,15 +1416,6 @@ async def process_file(
1412
1416
 
1413
1417
  service: Optional[str] = node.get("import_service", None)
1414
1418
 
1415
- if service and service.lower() == "bigquery":
1416
- if not await tb_client.check_gcp_read_permissions():
1417
- raise click.ClickException(FeedbackManager.error_unknown_bq_connection(datasource=datasource["name"]))
1418
-
1419
- # Bigquery doesn't have a datalink, so we can stop here
1420
- return params
1421
-
1422
- # Rest of connectors
1423
-
1424
1419
  connector_id: Optional[str] = node.get("import_connector", None)
1425
1420
  connector_name: Optional[str] = node.get("import_connection_name", None)
1426
1421
  if not connector_name and not connector_id:
@@ -1525,7 +1520,7 @@ async def process_file(
1525
1520
  params.update(get_engine_params(node))
1526
1521
 
1527
1522
  if "import_service" in node or "import_connection_name" in node:
1528
- VALID_SERVICES: Tuple[str, ...] = ("bigquery", "snowflake", "s3", "s3_iamrole", "gcs", "dynamodb")
1523
+ VALID_SERVICES: Tuple[str, ...] = ("s3", "s3_iamrole", "gcs", "dynamodb")
1529
1524
 
1530
1525
  import_params = await get_import_params(params, node)
1531
1526
 
@@ -1823,7 +1818,10 @@ def find_file_by_name(
1823
1818
  wk_path, name.replace(f"{wk_name}.", ""), verbose, is_raw, resource=resource
1824
1819
  )
1825
1820
  if file:
1826
- return file, _resource
1821
+ # Preserve workspace prefix in the returned filename to avoid conflicts
1822
+ # with local files of the same name (e.g., vendored datasource "workspace.ds"
1823
+ # vs local pipe with same base name)
1824
+ return f"{wk_name}.{file}", _resource
1827
1825
 
1828
1826
  if not is_raw:
1829
1827
  f, raw = find_file_by_name(
@@ -3259,12 +3257,6 @@ async def new_ds(
3259
3257
  ):
3260
3258
  raise click.ClickException(FeedbackManager.error_dynamodb_engine_not_supported(engine=engine_param))
3261
3259
 
3262
- if engine_param.lower() == "join":
3263
- deprecation_notice = FeedbackManager.warning_deprecated(
3264
- warning="Data Sources with Join engine are deprecated and will be removed in the next major release of tinybird-cli. Use MergeTree instead."
3265
- )
3266
- click.echo(deprecation_notice)
3267
-
3268
3260
  if not datasource_exists or fork_downstream or fork:
3269
3261
  params = ds["params"]
3270
3262
  params["branch_mode"] = "fork" if fork_downstream or fork else "None"
@@ -3434,19 +3426,15 @@ async def new_ds(
3434
3426
 
3435
3427
  ds_params = ds["params"]
3436
3428
  service = ds_params.get("service")
3437
- DATASOURCE_VALID_SERVICES_TO_UPDATE = ["bigquery", "snowflake"]
3429
+ DATASOURCE_VALID_SERVICES_TO_UPDATE: List[str] = []
3438
3430
  if datasource_exists and service and service in [*DATASOURCE_VALID_SERVICES_TO_UPDATE, *PREVIEW_CONNECTOR_SERVICES]:
3439
3431
  connector_required_params = {
3440
- "bigquery": ["service", "cron"],
3441
- "snowflake": ["connector", "service", "cron", "external_data_source"],
3442
3432
  "s3": ["connector", "service", "cron", "bucket_uri"],
3443
3433
  "s3_iamrole": ["connector", "service", "cron", "bucket_uri"],
3444
3434
  "gcs": ["connector", "service", "cron", "bucket_uri"],
3445
3435
  }.get(service, [])
3446
3436
 
3447
- connector_at_least_one_required_param = {
3448
- "bigquery": ["external_data_source", "query"],
3449
- }.get(service, [])
3437
+ connector_at_least_one_required_param: List[str] = []
3450
3438
 
3451
3439
  if connector_at_least_one_required_param and not any(
3452
3440
  key in ds_params for key in connector_at_least_one_required_param
@@ -3515,6 +3503,10 @@ async def new_ds(
3515
3503
  except Exception as e:
3516
3504
  promote_error_message = str(e)
3517
3505
 
3506
+ # Update token scopes when force-pushing an existing datasource
3507
+ if ds.get("tokens"):
3508
+ await manage_tokens()
3509
+
3518
3510
  if alter_response and make_changes:
3519
3511
  # alter operation finished
3520
3512
  pass
@@ -3701,7 +3693,7 @@ def get_name_version(ds: str) -> Dict[str, Any]:
3701
3693
  return {"name": tk[0], "version": None}
3702
3694
  elif len(tk) == 2:
3703
3695
  if len(tk[1]):
3704
- if tk[1][0] == "v" and re.match("[0-9]+$", tk[1][1:]):
3696
+ if tk[1][0] == "v" and _PATTERN_VERSION_NUMBER.match(tk[1][1:]):
3705
3697
  return {"name": tk[0], "version": int(tk[1][1:])}
3706
3698
  else:
3707
3699
  return {"name": tk[0] + "__" + tk[1], "version": None}
@@ -4359,9 +4351,7 @@ async def folder_push(
4359
4351
  )
4360
4352
  if resource_full_name not in existing_resources:
4361
4353
  return True
4362
- if force or run_tests:
4363
- return True
4364
- return False
4354
+ return force or run_tests
4365
4355
 
4366
4356
  async def push(
4367
4357
  name: str,
@@ -5323,7 +5313,7 @@ async def folder_pull(
5323
5313
 
5324
5314
  resource_to_write = f"VERSION {k['version']}\n" + resource_to_write
5325
5315
  if resource_to_write:
5326
- matches = re.findall(r"([^\s\.]*__v\d+)", resource_to_write)
5316
+ matches = _PATTERN_VERSIONED_RESOURCE.findall(resource_to_write)
5327
5317
  for match in set(matches):
5328
5318
  m = match.split("__v")[0]
5329
5319
  if m in resources or m in resource_names:
@@ -5545,9 +5535,7 @@ async def diff_files(
5545
5535
 
5546
5536
 
5547
5537
  def is_endpoint(resource: Optional[Dict[str, Any]]) -> bool:
5548
- if resource and len(resource.get("tokens", [])) != 0 and resource.get("resource") == "pipes":
5549
- return True
5550
- return False
5538
+ return bool(resource and len(resource.get("tokens", [])) != 0 and resource.get("resource") == "pipes")
5551
5539
 
5552
5540
 
5553
5541
  def is_materialized(resource: Optional[Dict[str, Any]]) -> bool:
@@ -5608,9 +5596,7 @@ def get_target_materialized_data_source_name(resource: Optional[Dict[str, Any]])
5608
5596
 
5609
5597
 
5610
5598
  def is_datasource(resource: Optional[Dict[str, Any]]) -> bool:
5611
- if resource and resource.get("resource") == "datasources":
5612
- return True
5613
- return False
5599
+ return bool(resource and resource.get("resource") == "datasources")
5614
5600
 
5615
5601
 
5616
5602
  async def create_release(
@@ -5661,15 +5647,13 @@ def update_connector_params(service: str, ds_params: Dict[str, Any], connector_r
5661
5647
  """
5662
5648
  Update connector parameters for a given service, ensuring required parameters exist.
5663
5649
 
5664
- :param service: The name of the service (e.g., 'bigquery').
5650
+ :param service: The name of the service (e.g., 's3').
5665
5651
  :param ds_params: The data source parameters to be checked.
5666
5652
  :param connector_required_params: The list of required parameters for the connector.
5667
5653
  :return: None
5668
5654
  """
5669
5655
 
5670
- connector_at_least_one_required_param: List[str] = {
5671
- "bigquery": ["external_data_source", "query"],
5672
- }.get(service, [])
5656
+ connector_at_least_one_required_param: List[str] = []
5673
5657
 
5674
5658
  # Handle the "at least one param" requirement
5675
5659
  if connector_at_least_one_required_param and not any(
@@ -35,6 +35,7 @@ date_pattern = re.compile(r"\d\d\d\d-\d\d-\d\d$")
35
35
 
36
36
  datetime64_type_pattern = re.compile(r"^DateTime64(\([1-9](, ?'.+')?\))?$")
37
37
  datetime_type_pattern = re.compile(r"^DateTime(\(('.+')?)?\)?$")
38
+ date_type_pattern = re.compile(r"^Date(\(('.+')?)?\)?$")
38
39
 
39
40
  # List from https://github.com/tinybirdco/ClickHousePrivate/blob/153473d9c1c871974688a1d72dcff7a13fc2076c/src/DataTypes/Serializations/SerializationBool.cpp#L216
40
41
  bool_allowed_values = {
@@ -95,6 +96,18 @@ def is_type_datetime(type_to_check: str) -> bool:
95
96
  return datetime_type_pattern.match(type_to_check) is not None
96
97
 
97
98
 
99
+ def is_type_date(type_to_check: str) -> bool:
100
+ """
101
+ >>> is_type_date('Date')
102
+ True
103
+ >>> is_type_date('Date()')
104
+ True
105
+ >>> is_type_date("date")
106
+ False
107
+ """
108
+ return date_type_pattern.match(type_to_check) is not None
109
+
110
+
98
111
  def string_test(x: str) -> bool:
99
112
  return True
100
113