tinybird-cli 5.3.1.dev3__tar.gz → 5.3.1.dev5__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 (46) hide show
  1. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/PKG-INFO +17 -2
  2. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/__cli__.py +2 -2
  3. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/client.py +3 -0
  4. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/datafile.py +67 -8
  5. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/feedback_manager.py +1 -0
  6. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/sql_template.py +4 -1
  7. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/sql_toolset.py +9 -1
  8. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/pipe.py +11 -1
  9. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tornado_template.py +3 -6
  10. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird_cli.egg-info/PKG-INFO +17 -2
  11. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird_cli.egg-info/requires.txt +1 -1
  12. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/setup.cfg +0 -0
  13. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/ch_utils/constants.py +0 -0
  14. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/ch_utils/engine.py +0 -0
  15. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/check_pypi.py +0 -0
  16. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/config.py +0 -0
  17. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/connectors.py +0 -0
  18. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/context.py +0 -0
  19. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/datatypes.py +0 -0
  20. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/git_settings.py +0 -0
  21. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/sql.py +0 -0
  22. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/sql_template_fmt.py +0 -0
  23. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/syncasync.py +0 -0
  24. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli.py +0 -0
  25. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/auth.py +0 -0
  26. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/branch.py +0 -0
  27. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/cicd.py +0 -0
  28. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/cli.py +0 -0
  29. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/common.py +0 -0
  30. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/config.py +0 -0
  31. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/connection.py +0 -0
  32. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/datasource.py +0 -0
  33. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/exceptions.py +0 -0
  34. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/job.py +0 -0
  35. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/regions.py +0 -0
  36. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/telemetry.py +0 -0
  37. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/test.py +0 -0
  38. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  39. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  40. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/token.py +0 -0
  41. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/workspace.py +0 -0
  42. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  43. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird_cli.egg-info/SOURCES.txt +0 -0
  44. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird_cli.egg-info/dependency_links.txt +0 -0
  45. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird_cli.egg-info/entry_points.txt +0 -0
  46. {tinybird-cli-5.3.1.dev3 → tinybird-cli-5.3.1.dev5}/tinybird_cli.egg-info/top_level.txt +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinybird-cli
3
- Version: 5.3.1.dev3
3
+ Version: 5.3.1.dev5
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
7
7
  Author-email: support@tinybird.co
8
- Requires-Python: >=3.8, <3.12
8
+ Requires-Python: >=3.8, <3.13
9
9
  Description-Content-Type: text/x-rst
10
10
  Provides-Extra: bigquery
11
11
  Provides-Extra: snowflake
@@ -18,6 +18,21 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
18
18
  Changelog
19
19
  ----------
20
20
 
21
+ 5.3.1.dev5
22
+ **********
23
+
24
+ - `Fixed` Do not try to deploy shared Datafiles from the `vendor` folder on `tb deploy`
25
+
26
+ 5.3.1.dev4
27
+ **********
28
+
29
+ - `Added` support to Python 3.12
30
+
31
+ 5.3.1.dev3
32
+ **********
33
+
34
+ - `Added` support to Stream Pipes in `tb push` and `tb pull` commands. (Feature in development. It requires access)
35
+
21
36
  5.3.1.dev2
22
37
  **********
23
38
 
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '5.3.1.dev3'
8
- __revision__ = '00ee83f'
7
+ __version__ = '5.3.1.dev5'
8
+ __revision__ = '4613f9c'
@@ -600,6 +600,9 @@ class TinyB(object):
600
600
  async def pipe_remove_sink(self, pipe_name_or_id: str, node_id: str):
601
601
  return await self._req(f"/v0/pipes/{pipe_name_or_id}/nodes/{node_id}/sink", method="DELETE")
602
602
 
603
+ async def pipe_remove_stream(self, pipe_name_or_id: str, node_id: str):
604
+ return await self._req(f"/v0/pipes/{pipe_name_or_id}/nodes/{node_id}/stream", method="DELETE")
605
+
603
606
  async def pipe_run_sink(self, pipe_name_or_id: str, params: Optional[Dict[str, str]] = None):
604
607
  params = {**params} if params else {}
605
608
  return await self._req(f"/v0/pipes/{pipe_name_or_id}/sink?{urlencode(params)}", method="POST")
@@ -95,6 +95,7 @@ class PipeTypes:
95
95
  ENDPOINT = "endpoint"
96
96
  COPY = "copy"
97
97
  DATA_SINK = "sink"
98
+ STREAM = "stream"
98
99
  DEFAULT = "default"
99
100
 
100
101
 
@@ -105,6 +106,7 @@ class PipeNodeTypes:
105
106
  DEFAULT = "default"
106
107
  DATA_SINK = "sink"
107
108
  COPY = "copy"
109
+ STREAM = "stream"
108
110
 
109
111
 
110
112
  class DataFileExtensions:
@@ -190,8 +192,8 @@ class ImportReplacements:
190
192
 
191
193
 
192
194
  class ExportReplacements:
193
- SERVICES = ("gcs_hmac", "s3", "s3_iamrole")
194
- NODE_TYPES = PipeNodeTypes.DATA_SINK
195
+ SERVICES = ("gcs_hmac", "s3", "s3_iamrole", "kafka")
196
+ NODE_TYPES = (PipeNodeTypes.DATA_SINK, PipeNodeTypes.STREAM)
195
197
  _REPLACEMENTS = (
196
198
  ("export_service", "service", None),
197
199
  ("export_connection_name", "connection", None),
@@ -201,11 +203,20 @@ class ExportReplacements:
201
203
  ("export_format", "format", "csv"),
202
204
  ("export_compression", "compression", None),
203
205
  ("export_strategy", "strategy", "@new"),
206
+ ("kafka_connection_name", "connection", None),
207
+ ("kafka_topic", "kafka_topic", None),
208
+ ("kafka_group_id", "kafka_group_id", None),
204
209
  )
205
210
 
211
+ @staticmethod
212
+ def get_export_service(node: Dict[str, Optional[str]]) -> str:
213
+ if (node.get("type", "standard") or "standard").lower() == PipeNodeTypes.STREAM:
214
+ return "kafka"
215
+ return (node.get("export_service", "") or "").lower()
216
+
206
217
  @staticmethod
207
218
  def is_export_node(node: Dict[str, Optional[str]]) -> bool:
208
- export_service = node.get("export_service", None)
219
+ export_service = ExportReplacements.get_export_service(node)
209
220
  node_type = (node.get("type", "standard") or "standard").lower()
210
221
  if not export_service:
211
222
  return False
@@ -219,16 +230,26 @@ class ExportReplacements:
219
230
  def get_params_from_datafile(node: Dict[str, Optional[str]]) -> Dict[str, Optional[str]]:
220
231
  """Returns the export parameters for a given node."""
221
232
  params = {}
233
+ export_service = ExportReplacements.get_export_service(node)
222
234
  for datafile_key, export_key, default_value in ExportReplacements._REPLACEMENTS:
235
+ if export_service != "kafka" and "kafka_" in datafile_key:
236
+ continue
237
+ if export_service == "kafka" and "export_" in datafile_key:
238
+ continue
223
239
  if datafile_key == "export_schedule" and node.get(datafile_key, None) == ON_DEMAND:
224
240
  node[datafile_key] = ""
225
241
  params[export_key] = node[datafile_key] if datafile_key in node else default_value
226
242
  return params
227
243
 
228
244
  @staticmethod
229
- def get_datafile_key(param: str) -> Optional[str]:
245
+ def get_datafile_key(param: str, node: Dict[str, Optional[str]]) -> Optional[str]:
230
246
  """Returns the datafile key for a given export parameter."""
247
+ export_service = ExportReplacements.get_export_service(node)
231
248
  for datafile_k, export_k, _ in ExportReplacements._REPLACEMENTS:
249
+ if export_service != "kafka" and "kafka_" in datafile_k:
250
+ continue
251
+ if export_service == "kafka" and "export_" in datafile_k:
252
+ continue
232
253
  if export_k == param.lower():
233
254
  return datafile_k.upper()
234
255
  return None
@@ -2323,6 +2344,8 @@ async def check_pipe(
2323
2344
  await cl.pipe_remove_copy(current_pipe["id"], current_pipe["copy_node"])
2324
2345
  if pipe_type == PipeTypes.DATA_SINK:
2325
2346
  await cl.pipe_remove_sink(current_pipe["id"], current_pipe["sink_node"])
2347
+ if pipe_type == PipeTypes.STREAM:
2348
+ await cl.pipe_remove_stream(current_pipe["id"], current_pipe["stream_node"])
2326
2349
 
2327
2350
  # In case of doing --force for a materialized view, checker is being created as standard pipe
2328
2351
  for node in checker_pipe["nodes"]:
@@ -2525,6 +2548,9 @@ async def check_copy_pipe(pipe, copy_node, tb_client: TinyB):
2525
2548
  if pipe_type == PipeTypes.DATA_SINK:
2526
2549
  await tb_client.pipe_remove_sink(pipe_name, pipe["sink_node"])
2527
2550
 
2551
+ if pipe_type == PipeTypes.STREAM:
2552
+ await tb_client.pipe_remove_stream(pipe_name, pipe["stream_node"])
2553
+
2528
2554
 
2529
2555
  async def check_sink_pipe(pipe, sink_node, tb_client: TinyB):
2530
2556
  if not sink_node["export_params"]:
@@ -2548,6 +2574,29 @@ async def check_sink_pipe(pipe, sink_node, tb_client: TinyB):
2548
2574
  if pipe_type == PipeTypes.COPY:
2549
2575
  await tb_client.pipe_remove_copy(pipe_name, pipe["copy_node"])
2550
2576
 
2577
+ if pipe_type == PipeTypes.STREAM:
2578
+ await tb_client.pipe_remove_stream(pipe_name, pipe["stream_node"])
2579
+
2580
+
2581
+ async def check_stream_pipe(pipe, stream_node, tb_client: TinyB):
2582
+ if not stream_node["params"]:
2583
+ return
2584
+
2585
+ if not pipe:
2586
+ return
2587
+
2588
+ pipe_name = pipe["name"]
2589
+ pipe_type = pipe["type"]
2590
+
2591
+ if pipe_type == PipeTypes.ENDPOINT:
2592
+ await tb_client.pipe_remove_endpoint(pipe_name, pipe["endpoint"])
2593
+
2594
+ if pipe_type == PipeTypes.COPY:
2595
+ await tb_client.pipe_remove_copy(pipe_name, pipe["copy_node"])
2596
+
2597
+ if pipe_type == PipeTypes.DATA_SINK:
2598
+ await tb_client.pipe_remove_sink(pipe_name, pipe["sink_node"])
2599
+
2551
2600
 
2552
2601
  def show_materialized_view_warnings(warnings):
2553
2602
  """
@@ -2635,6 +2684,7 @@ async def new_pipe(
2635
2684
  is_materialized = any([node.get("params", {}).get("type", None) == "materialized" for node in p["nodes"]])
2636
2685
  copy_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "copy"), None)
2637
2686
  sink_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "sink"), None)
2687
+ stream_node = next((node for node in p["nodes"] if node.get("params", {}).get("type", None) == "stream"), None)
2638
2688
 
2639
2689
  for node in p["nodes"]:
2640
2690
  if node["params"]["name"] == p["name"]:
@@ -2644,7 +2694,7 @@ async def new_pipe(
2644
2694
  if force or run_tests:
2645
2695
  # TODO: this should create a different node and rename it to the final one on success
2646
2696
  if check and not populate:
2647
- if not is_materialized and not copy_node and not sink_node:
2697
+ if not is_materialized and not copy_node and not sink_node and not stream_node:
2648
2698
  await check_pipe(
2649
2699
  p,
2650
2700
  host,
@@ -2680,6 +2730,8 @@ async def new_pipe(
2680
2730
  await check_copy_pipe(pipe=current_pipe, copy_node=copy_node, tb_client=tb_client)
2681
2731
  if sink_node:
2682
2732
  await check_sink_pipe(pipe=current_pipe, sink_node=sink_node, tb_client=tb_client)
2733
+ if stream_node:
2734
+ await check_stream_pipe(pipe=current_pipe, stream_node=stream_node, tb_client=tb_client)
2683
2735
  if run_tests:
2684
2736
  logging.info(f"skipping force override of {p['name']}")
2685
2737
  return
@@ -2727,6 +2779,9 @@ async def new_pipe(
2727
2779
  if sink_node:
2728
2780
  body.update(sink_node.get("export_params", {}))
2729
2781
 
2782
+ if stream_node:
2783
+ body.update(stream_node.get("export_params", {}))
2784
+
2730
2785
  post_headers = {"Content-Type": "application/json"}
2731
2786
 
2732
2787
  post_headers.update(headers)
@@ -3804,6 +3859,10 @@ async def build_graph(
3804
3859
  processed_filename = f.relative_to(os.getcwd())
3805
3860
  except ValueError:
3806
3861
  processed_filename = f
3862
+ # This is to avoid processing shared data sources
3863
+ if "vendor/" in str(processed_filename):
3864
+ click.echo(FeedbackManager.info_skipping_resource(resource=processed_filename))
3865
+ continue
3807
3866
  click.echo(FeedbackManager.info_processing_file(filename=processed_filename))
3808
3867
  await process(str(f), deps, dep_map, to_run, workspace_lib_paths)
3809
3868
 
@@ -4703,7 +4762,6 @@ async def format_engine(
4703
4762
  async def format_node_type(file_parts: List[str], node: Dict[str, Any]) -> List[str]:
4704
4763
  node_type = node.get("type", "").lower()
4705
4764
  node_type_upper = f"TYPE {node_type.upper()}"
4706
-
4707
4765
  # Materialized pipe
4708
4766
  if node_type == PipeNodeTypes.MATERIALIZED:
4709
4767
  file_parts.append(node_type_upper)
@@ -4732,7 +4790,7 @@ async def format_node_type(file_parts: List[str], node: Dict[str, Any]) -> List[
4732
4790
  file_parts.append(f"{CopyParameters.COPY_SCHEDULE.upper()} {ON_DEMAND}")
4733
4791
  file_parts.append(DATAFILE_NEW_LINE)
4734
4792
 
4735
- # Sink pipe
4793
+ # Sink or Stream pipe
4736
4794
  if ExportReplacements.is_export_node(node):
4737
4795
  file_parts.append(node_type_upper)
4738
4796
  export_params = ExportReplacements.get_params_from_datafile(node)
@@ -4740,7 +4798,7 @@ async def format_node_type(file_parts: List[str], node: Dict[str, Any]) -> List[
4740
4798
  for param, value in export_params.items():
4741
4799
  if param == "schedule_cron" and not value:
4742
4800
  value = ON_DEMAND
4743
- datafile_key = ExportReplacements.get_datafile_key(param)
4801
+ datafile_key = ExportReplacements.get_datafile_key(param, node)
4744
4802
  if datafile_key:
4745
4803
  file_parts.append(f"{datafile_key} {value}")
4746
4804
  file_parts.append(DATAFILE_NEW_LINE)
@@ -5189,6 +5247,7 @@ def is_endpoint_with_no_dependencies(
5189
5247
  PipeNodeTypes.MATERIALIZED,
5190
5248
  PipeNodeTypes.COPY,
5191
5249
  PipeNodeTypes.DATA_SINK,
5250
+ PipeNodeTypes.STREAM,
5192
5251
  ]:
5193
5252
  return False
5194
5253
 
@@ -709,6 +709,7 @@ Ready? """
709
709
  info_unlinking_materialized_pipe = info_message("** Unlinking materialized pipe {pipe}")
710
710
  info_unlinking_copy_pipe = info_message("** Unlinking copy pipe {pipe}")
711
711
  info_unlinking_sink_pipe = info_message("** Unlinking sink pipe {pipe}")
712
+ info_unlinking_stream_pipe = info_message("** Unlinking stream pipe {pipe}")
712
713
  info_materialized_unlinking_pipe_not_found = info_message("** {pipe} not found")
713
714
  info_materialized_dry_unlinking_pipe = info_message("** [DRY RUN] Unlinking materialized pipe {pipe}")
714
715
  info_copy_datasource_created = info_message("** Copy pipe '{pipe}' created the Data Source '{datasource}'")
@@ -1429,6 +1429,7 @@ def generate(self, **kwargs) -> Tuple[str, TemplateExecutionResults]:
1429
1429
  "__loader__": ObjectDict(get_source=lambda name: self.code),
1430
1430
  "max_threads": set_max_threads,
1431
1431
  "tb_secret": set_tb_secret,
1432
+ "tb_var": set_tb_secret,
1432
1433
  "backend_hint": set_backend_hint,
1433
1434
  "cache_ttl": set_cache_ttl,
1434
1435
  "activate": set_activate,
@@ -2116,6 +2117,8 @@ def render_sql_template(
2116
2117
  ("select * from table where str = 'test'", {}, ['pipeline'])
2117
2118
  >>> render_sql_template("select * from table where str = {{tb_secret('test')}}", secrets = [ 'tb_secret_test' ])
2118
2119
  ('select * from table where str = {test: String}', {}, [])
2120
+ >>> render_sql_template("select * from table where str = {{tb_var('test')}}", secrets = [ 'tb_secret_test' ])
2121
+ ('select * from table where str = {test: String}', {}, [])
2119
2122
  >>> render_sql_template("select * from table where str = {{tb_secret('test')}}", variables = { 'test': '1234' })
2120
2123
  Traceback (most recent call last):
2121
2124
  ...
@@ -2146,7 +2149,7 @@ def render_sql_template(
2146
2149
  return Comment("error launched")
2147
2150
 
2148
2151
  v: dict = {x["name"]: Placeholder(x["name"], x["line"]) for x in template_variables}
2149
- is_tb_secret = any([s for s in template_variables if s["name"] == "tb_secret"])
2152
+ is_tb_secret = any([s for s in template_variables if s["name"] == "tb_secret" or s["name"] == "tb_var"])
2150
2153
 
2151
2154
  if variables:
2152
2155
  v.update(variables)
@@ -70,7 +70,9 @@ def sql_get_used_tables_cached(
70
70
  table_functions: bool = True,
71
71
  function_allow_list: Optional[FrozenSet[str]] = None,
72
72
  ) -> List[Tuple[str, str, str]]:
73
- """
73
+ """More like: get used sql names
74
+
75
+ Returns a list of tuples: (database_or_namespace, table_name, table_func).
74
76
  >>> sql_get_used_tables("SELECT 1 FROM the_table")
75
77
  [('', 'the_table', '')]
76
78
  >>> sql_get_used_tables("SELECT 1 FROM the_database.the_table")
@@ -104,6 +106,8 @@ def sql_get_used_tables_cached(
104
106
  raise InvalidFunction(msg=msg) from e
105
107
  elif "Unknown function tb_secret" in msg:
106
108
  raise InvalidFunction(msg="Unknown function tb_secret. Usage: {{tb_secret('secret_name')}}") from e
109
+ elif "Unknown function tb_var" in msg:
110
+ raise InvalidFunction(msg="Unknown function tb_var. Usage: {{tb_var('var_name')}}") from e
107
111
  raise
108
112
  return [(default_database, sql, "")]
109
113
 
@@ -115,6 +119,10 @@ def sql_get_used_tables(
115
119
  table_functions: bool = True,
116
120
  function_allow_list: Optional[FrozenSet[str]] = None,
117
121
  ) -> List[Tuple[str, str, str]]:
122
+ """More like: get used sql names
123
+
124
+ Returns a list of tuples: (database_or_namespace, table_name, table_func).
125
+ """
118
126
  hashable_list = frozenset() if function_allow_list is None else function_allow_list
119
127
 
120
128
  return copy.copy(
@@ -16,7 +16,7 @@ from click import Context
16
16
  import tinybird.context as context
17
17
  from tinybird.client import DoesNotExistException, TinyB
18
18
  from tinybird.config import DEFAULT_API_HOST, FeatureFlags
19
- from tinybird.datafile import PipeTypes, folder_push, get_name_version, process_file, wait_job
19
+ from tinybird.datafile import PipeNodeTypes, PipeTypes, folder_push, get_name_version, process_file, wait_job
20
20
  from tinybird.feedback_manager import FeedbackManager
21
21
  from tinybird.tb_cli_modules.branch import warn_if_in_live
22
22
  from tinybird.tb_cli_modules.cli import cli
@@ -348,6 +348,16 @@ async def pipe_unlink_output_node(
348
348
  await client.pipe_remove_sink(pipe["name"], node_uid)
349
349
  click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
350
350
 
351
+ if pipe["type"] == PipeTypes.STREAM:
352
+ click.echo(FeedbackManager.info_unlinking_stream_pipe(pipe=pipe["name"]))
353
+ node_uid = next((node["id"] for node in pipe["nodes"] if node["node_type"] == PipeNodeTypes.STREAM), None)
354
+
355
+ if not node_uid:
356
+ raise CLIPipeException(FeedbackManager.error_unlinking_pipe_not_linked(pipe=pipe_name_or_id))
357
+ else:
358
+ await client.pipe_remove_stream(pipe["name"], node_uid)
359
+ click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
360
+
351
361
  except Exception as e:
352
362
  raise CLIPipeException(FeedbackManager.error_exception(error=e))
353
363
 
@@ -204,19 +204,15 @@ import os.path
204
204
  import posixpath
205
205
  import re
206
206
  import threading
207
+ from io import StringIO
207
208
  from typing import Union
208
209
 
209
210
  from tornado import escape
210
211
  from tornado.log import app_log
211
- from tornado.util import PY3, ObjectDict, exec_in, unicode_type
212
+ from tornado.util import ObjectDict, exec_in, unicode_type
212
213
 
213
214
  from .context import disable_template_security_validation
214
215
 
215
- if PY3:
216
- from io import StringIO
217
- else:
218
- from cStringIO import StringIO
219
-
220
216
  _DEFAULT_AUTOESCAPE = "xhtml_escape"
221
217
  _UNSET = object()
222
218
 
@@ -1051,6 +1047,7 @@ VALID_FUNCTION_NAMES = {
1051
1047
  "sql_unescape",
1052
1048
  "max_threads",
1053
1049
  "tb_secret",
1050
+ "tb_var",
1054
1051
  "Int",
1055
1052
  "table",
1056
1053
  "TABLE",
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinybird-cli
3
- Version: 5.3.1.dev3
3
+ Version: 5.3.1.dev5
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
7
7
  Author-email: support@tinybird.co
8
- Requires-Python: >=3.8, <3.12
8
+ Requires-Python: >=3.8, <3.13
9
9
  Description-Content-Type: text/x-rst
10
10
  Provides-Extra: bigquery
11
11
  Provides-Extra: snowflake
@@ -18,6 +18,21 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
18
18
  Changelog
19
19
  ----------
20
20
 
21
+ 5.3.1.dev5
22
+ **********
23
+
24
+ - `Fixed` Do not try to deploy shared Datafiles from the `vendor` folder on `tb deploy`
25
+
26
+ 5.3.1.dev4
27
+ **********
28
+
29
+ - `Added` support to Python 3.12
30
+
31
+ 5.3.1.dev3
32
+ **********
33
+
34
+ - `Added` support to Stream Pipes in `tb push` and `tb pull` commands. (Feature in development. It requires access)
35
+
21
36
  5.3.1.dev2
22
37
  **********
23
38
 
@@ -12,7 +12,7 @@ requests<3,>=2.28.1
12
12
  shandy-sqlfmt==0.11.1
13
13
  shandy-sqlfmt[jinjafmt]==0.11.1
14
14
  toposort==1.10
15
- tornado==5.1.1
15
+ tornado==6.0.0
16
16
  urllib3<2,>=1.26.14
17
17
  wheel
18
18
  packaging<24,>=23.1