tinybird 0.0.1.dev228__tar.gz → 0.0.1.dev313__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 (157) hide show
  1. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/PKG-INFO +10 -5
  2. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/ch_utils/constants.py +8 -0
  3. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/ch_utils/engine.py +3 -2
  4. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/client.py +3 -0
  5. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/config.py +0 -6
  6. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/connectors.py +1 -7
  7. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/context.py +3 -3
  8. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/common.py +322 -23
  9. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/parse_pipe.py +2 -2
  10. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/feedback_manager.py +6 -0
  11. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/prompts.py +7 -4
  12. tinybird-0.0.1.dev313/tinybird/service_datasources.py +1085 -0
  13. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/sql.py +31 -23
  14. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/sql_template.py +51 -10
  15. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/sql_toolset.py +6 -2
  16. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/__cli__.py +2 -2
  17. tinybird-0.0.1.dev313/tinybird/tb/check_pypi.py +19 -0
  18. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/cli.py +2 -6
  19. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/client.py +327 -357
  20. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/config.py +28 -5
  21. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/__init__.py +3 -0
  22. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/agent.py +916 -0
  23. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/animations.py +105 -0
  24. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/banner.py +87 -0
  25. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/command_agent.py +75 -0
  26. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/compactor.py +311 -0
  27. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/explore_agent.py +101 -0
  28. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/file_agent.py +64 -0
  29. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/memory.py +113 -0
  30. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/mock_agent.py +210 -0
  31. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/models.py +65 -0
  32. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/prompts.py +1113 -0
  33. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/testing_agent.py +72 -0
  34. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/__init__.py +0 -0
  35. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/analyze.py +91 -0
  36. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/append.py +176 -0
  37. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/build.py +21 -0
  38. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/datafile.py +273 -0
  39. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/deploy.py +50 -0
  40. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/deploy_check.py +29 -0
  41. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/diff_resource.py +49 -0
  42. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/execute_query.py +215 -0
  43. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/file.py +82 -0
  44. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/get_endpoint_stats.py +63 -0
  45. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/get_openapi_definition.py +66 -0
  46. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/mock.py +136 -0
  47. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/plan.py +86 -0
  48. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/request_endpoint.py +93 -0
  49. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/run_command.py +55 -0
  50. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/secret.py +113 -0
  51. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/tools/test.py +256 -0
  52. tinybird-0.0.1.dev313/tinybird/tb/modules/agent/utils.py +858 -0
  53. tinybird-0.0.1.dev313/tinybird/tb/modules/build.py +240 -0
  54. tinybird-0.0.1.dev313/tinybird/tb/modules/build_common.py +506 -0
  55. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/cicd.py +2 -2
  56. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/cli.py +211 -52
  57. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/common.py +313 -152
  58. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/config.py +2 -4
  59. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/connection.py +64 -105
  60. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/copy.py +7 -9
  61. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/create.py +87 -168
  62. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/build.py +63 -74
  63. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/build_common.py +9 -9
  64. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/build_datasource.py +44 -48
  65. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/build_pipe.py +11 -13
  66. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/diff.py +13 -13
  67. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/format_datasource.py +5 -5
  68. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/format_pipe.py +8 -8
  69. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/playground.py +97 -106
  70. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/pull.py +64 -54
  71. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datasource.py +432 -316
  72. tinybird-0.0.1.dev313/tinybird/tb/modules/deployment.py +490 -0
  73. tinybird-0.0.1.dev228/tinybird/tb/modules/deployment.py → tinybird-0.0.1.dev313/tinybird/tb/modules/deployment_common.py +105 -368
  74. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/dev_server.py +17 -3
  75. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/endpoint.py +14 -19
  76. tinybird-0.0.1.dev313/tinybird/tb/modules/environment.py +152 -0
  77. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/exceptions.py +21 -0
  78. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/feedback_manager.py +12 -10
  79. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/info.py +40 -22
  80. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/infra.py +47 -53
  81. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/job.py +7 -10
  82. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/llm.py +6 -2
  83. tinybird-0.0.1.dev313/tinybird/tb/modules/local.py +303 -0
  84. tinybird-0.0.1.dev313/tinybird/tb/modules/local_common.py +791 -0
  85. tinybird-0.0.1.dev313/tinybird/tb/modules/local_logs.py +196 -0
  86. tinybird-0.0.1.dev313/tinybird/tb/modules/login.py +57 -0
  87. tinybird-0.0.1.dev228/tinybird/tb/modules/login.py → tinybird-0.0.1.dev313/tinybird/tb/modules/login_common.py +40 -54
  88. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/materialization.py +16 -10
  89. tinybird-0.0.1.dev313/tinybird/tb/modules/mock.py +41 -0
  90. tinybird-0.0.1.dev313/tinybird/tb/modules/mock_common.py +17 -0
  91. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/open.py +1 -3
  92. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/pipe.py +2 -4
  93. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/project.py +63 -1
  94. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/secret.py +22 -64
  95. tinybird-0.0.1.dev313/tinybird/tb/modules/secret_common.py +52 -0
  96. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/shell.py +8 -21
  97. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/sink.py +6 -8
  98. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/telemetry.py +4 -0
  99. tinybird-0.0.1.dev313/tinybird/tb/modules/test.py +65 -0
  100. tinybird-0.0.1.dev228/tinybird/tb/modules/test.py → tinybird-0.0.1.dev313/tinybird/tb/modules/test_common.py +86 -147
  101. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/tinyunit/tinyunit.py +3 -17
  102. {tinybird-0.0.1.dev228/tinybird/tb_cli_modules → tinybird-0.0.1.dev313/tinybird/tb/modules}/tinyunit/tinyunit_lib.py +0 -6
  103. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/token.py +48 -35
  104. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/watch.py +8 -10
  105. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/workspace.py +28 -45
  106. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/workspace_members.py +16 -23
  107. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/cli.py +19 -2
  108. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/common.py +2 -2
  109. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/fmt.py +4 -2
  110. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/pipe.py +15 -1
  111. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -14
  112. {tinybird-0.0.1.dev228/tinybird/tb/modules → tinybird-0.0.1.dev313/tinybird/tb_cli_modules}/tinyunit/tinyunit_lib.py +0 -6
  113. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tornado_template.py +6 -7
  114. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/PKG-INFO +10 -5
  115. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/SOURCES.txt +41 -0
  116. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/requires.txt +9 -4
  117. tinybird-0.0.1.dev228/tinybird/tb/check_pypi.py +0 -24
  118. tinybird-0.0.1.dev228/tinybird/tb/modules/build.py +0 -512
  119. tinybird-0.0.1.dev228/tinybird/tb/modules/local.py +0 -179
  120. tinybird-0.0.1.dev228/tinybird/tb/modules/local_common.py +0 -440
  121. tinybird-0.0.1.dev228/tinybird/tb/modules/mock.py +0 -157
  122. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/setup.cfg +0 -0
  123. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/__cli__.py +0 -0
  124. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/check_pypi.py +0 -0
  125. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/exceptions.py +0 -0
  126. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/parse_connection.py +0 -0
  127. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datafile/parse_datasource.py +0 -0
  128. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/datatypes.py +0 -0
  129. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/git_settings.py +0 -0
  130. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/sql_template_fmt.py +0 -0
  131. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/syncasync.py +0 -0
  132. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/fixture.py +0 -0
  133. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/format_common.py +0 -0
  134. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  135. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/deprecations.py +0 -0
  136. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/llm_utils.py +0 -0
  137. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/logout.py +0 -0
  138. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/regions.py +0 -0
  139. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb/modules/table.py +0 -0
  140. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli.py +0 -0
  141. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/auth.py +0 -0
  142. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/branch.py +0 -0
  143. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/cicd.py +0 -0
  144. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/config.py +0 -0
  145. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/connection.py +0 -0
  146. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/datasource.py +0 -0
  147. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/exceptions.py +0 -0
  148. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/job.py +0 -0
  149. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/regions.py +0 -0
  150. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/tag.py +0 -0
  151. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/telemetry.py +0 -0
  152. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/test.py +0 -0
  153. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/workspace.py +0 -0
  154. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  155. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/dependency_links.txt +0 -0
  156. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/entry_points.txt +0 -0
  157. {tinybird-0.0.1.dev228 → tinybird-0.0.1.dev313}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev228
3
+ Version: 0.0.1.dev313
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -8,20 +8,24 @@ Author-email: support@tinybird.co
8
8
  Requires-Python: >=3.9, <3.14
9
9
  Description-Content-Type: text/x-rst
10
10
  Requires-Dist: aiofiles==24.1.0
11
- Requires-Dist: anthropic==0.42.0
11
+ Requires-Dist: anthropic==0.55.0
12
12
  Requires-Dist: boto3
13
13
  Requires-Dist: click<8.2,>=8.1.6
14
14
  Requires-Dist: clickhouse-toolset==0.34.dev0
15
15
  Requires-Dist: colorama==0.4.6
16
- Requires-Dist: confluent-kafka==2.8.0
16
+ Requires-Dist: confluent-kafka==2.8.2
17
17
  Requires-Dist: cryptography~=41.0.0
18
18
  Requires-Dist: croniter==1.3.15
19
19
  Requires-Dist: docker==7.1.0
20
20
  Requires-Dist: GitPython~=3.1.32
21
21
  Requires-Dist: humanfriendly~=8.2
22
+ Requires-Dist: plotext==5.3.2
22
23
  Requires-Dist: prompt_toolkit==3.0.48
23
- Requires-Dist: pydantic~=2.8.0
24
- Requires-Dist: pyperclip==1.8.2
24
+ Requires-Dist: logfire-api==4.2.0
25
+ Requires-Dist: pydantic~=2.11.7
26
+ Requires-Dist: pydantic-ai-slim[anthropic]~=0.5.0
27
+ Requires-Dist: pydantic-ai-slim[retries]~=0.5.0
28
+ Requires-Dist: pyperclip==1.9.0
25
29
  Requires-Dist: pyyaml<6.1,>=6.0
26
30
  Requires-Dist: requests<3,>=2.28.1
27
31
  Requires-Dist: shandy-sqlfmt==0.11.1
@@ -35,6 +39,7 @@ Requires-Dist: packaging<24,>=23.1
35
39
  Requires-Dist: llm>=0.19
36
40
  Requires-Dist: thefuzz==0.22.1
37
41
  Requires-Dist: python-dotenv==1.1.0
42
+ Requires-Dist: pyjwt[crypto]==2.9.0
38
43
  Dynamic: author
39
44
  Dynamic: author-email
40
45
  Dynamic: description
@@ -255,4 +255,12 @@ VALID_QUERY_FORMATS = (
255
255
  "JSONStrings",
256
256
  "Prometheus",
257
257
  "Native",
258
+ "RowBinaryWithNamesAndTypes",
259
+ "TabSeparated",
260
+ "JSONCompactEachRowWithNamesAndTypes",
261
+ "TabSeparatedWithNamesAndTypes",
262
+ "JSONCompactEachRow",
263
+ "JSONCompact",
264
+ "JSONStringsEachRowWithProgress",
265
+ "ODBCDriver2",
258
266
  )
@@ -134,8 +134,9 @@ class TableDetails:
134
134
  _version = self.details.get("version", None)
135
135
  return _version
136
136
 
137
- def is_replicated(self):
138
- return "Replicated" in self.details.get("engine", None)
137
+ def is_replicated(self) -> bool:
138
+ engine: Optional[str] = self.details.get("engine", None)
139
+ return engine is not None and "Replicated" in engine
139
140
 
140
141
  def is_mergetree_family(self) -> bool:
141
142
  return self.engine is not None and "mergetree" in self.engine.lower()
@@ -569,6 +569,7 @@ class TinyB:
569
569
  populate_condition: Optional[str] = None,
570
570
  truncate: bool = True,
571
571
  unlink_on_populate_error: bool = False,
572
+ on_demand_compute: bool = False,
572
573
  ):
573
574
  params: Dict[str, Any] = {
574
575
  "truncate": "true" if truncate else "false",
@@ -578,6 +579,8 @@ class TinyB:
578
579
  params.update({"populate_subset": populate_subset})
579
580
  if populate_condition:
580
581
  params.update({"populate_condition": populate_condition})
582
+ if on_demand_compute:
583
+ params.update({"on_demand_compute": "true"})
581
584
  response = await self._req(
582
585
  f"/v0/pipes/{pipe_name}/nodes/{node_name}/population?{urlencode(params)}", method="POST"
583
586
  )
@@ -38,13 +38,10 @@ LEGACY_HOSTS = {
38
38
  "https://api.wadus3.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus3",
39
39
  "https://api.wadus4.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus4",
40
40
  "https://api.wadus5.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus5",
41
- "https://api.wadus6.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus6",
42
41
  "https://api.wadus1.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus1",
43
42
  "https://api.wadus2.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus2",
44
43
  "https://api.wadus3.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus3",
45
44
  "https://api.wadus4.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus4",
46
- "https://api.wadus5.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus5",
47
- "https://api.wadus6.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus6",
48
45
  "https://ui.tinybird.co": "https://app.tinybird.co/gcp/europe-west3",
49
46
  "https://ui.us-east.tinybird.co": "https://app.tinybird.co/gcp/us-east4",
50
47
  "https://ui.us-east.aws.tinybird.co": "https://app.tinybird.co/aws/us-east-1",
@@ -62,13 +59,10 @@ LEGACY_HOSTS = {
62
59
  "https://ui.wadus3.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus3",
63
60
  "https://ui.wadus4.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus4",
64
61
  "https://ui.wadus5.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus5",
65
- "https://ui.wadus6.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus6",
66
62
  "https://ui.wadus1.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus1",
67
63
  "https://ui.wadus2.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus2",
68
64
  "https://ui.wadus3.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus3",
69
65
  "https://ui.wadus4.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus4",
70
- "https://ui.wadus5.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus5",
71
- "https://ui.wadus6.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus6",
72
66
  }
73
67
 
74
68
 
@@ -369,13 +369,7 @@ class Snowflake(Connector):
369
369
  the_type = "String"
370
370
  if t.startswith("NUMBER"):
371
371
  the_type = "Int32"
372
- if (
373
- t.startswith("FLOAT")
374
- or t.startswith("DOUBLE")
375
- or t.startswith("REAL")
376
- or t.startswith("NUMERIC")
377
- or t.startswith("DECIMAL")
378
- ):
372
+ if t.startswith(("FLOAT", "DOUBLE", "REAL", "NUMERIC", "DECIMAL")):
379
373
  the_type = "Float32"
380
374
  if t == "DATE":
381
375
  the_type = "Date"
@@ -3,15 +3,15 @@ from typing import TYPE_CHECKING
3
3
 
4
4
  # Avoid circular import error
5
5
  if TYPE_CHECKING:
6
- from tinybird.user import User
6
+ from hfi.hfi_workspace_data import HfiWorkspaceData
7
+
7
8
 
8
9
  workspace_id: ContextVar[str] = ContextVar("workspace_id")
9
- workspace: ContextVar["User"] = ContextVar("workspace")
10
+ hfi_workspace_data: ContextVar["HfiWorkspaceData"] = ContextVar("hfi_workspace_data")
10
11
  table_id: ContextVar[str] = ContextVar("table_id")
11
12
  hfi_frequency: ContextVar[float] = ContextVar("hfi_frequency")
12
13
  hfi_frequency_gatherer: ContextVar[float] = ContextVar("hfi_frequency_gatherer")
13
14
  use_gatherer: ContextVar[bool] = ContextVar("use_gatherer")
14
- allow_gatherer_fallback: ContextVar[bool] = ContextVar("allow_gatherer_fallback")
15
15
  gatherer_allow_s3_backup_on_user_errors: ContextVar[bool] = ContextVar("gatherer_allow_s3_backup_on_user_errors")
16
16
  disable_template_security_validation: ContextVar[bool] = ContextVar("disable_template_security_validation")
17
17
  origin: ContextVar[str] = ContextVar("origin")
@@ -122,7 +122,12 @@ VALID_PIPE_NODE_TYPES = {
122
122
  PipeNodeTypes.STREAM,
123
123
  PipeNodeTypes.DATA_SINK,
124
124
  }
125
- VISIBLE_PIPE_NODE_TYPES = {PipeNodeTypes.MATERIALIZED, PipeNodeTypes.COPY, PipeNodeTypes.ENDPOINT}
125
+ VISIBLE_PIPE_NODE_TYPES = {
126
+ PipeNodeTypes.MATERIALIZED,
127
+ PipeNodeTypes.COPY,
128
+ PipeNodeTypes.ENDPOINT,
129
+ PipeNodeTypes.DATA_SINK,
130
+ }
126
131
 
127
132
 
128
133
  class DataFileExtensions:
@@ -179,7 +184,23 @@ class CopyParameters(Parameters):
179
184
 
180
185
  class MaterializedParameters(Parameters):
181
186
  MANDATORY_ATTRIBUTES = PipeParameters.MANDATORY_ATTRIBUTES.union({"datasource"})
182
- ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(MANDATORY_ATTRIBUTES)
187
+ ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(MANDATORY_ATTRIBUTES).union({"deployment_method"})
188
+
189
+
190
+ class SinkParameters(Parameters):
191
+ # For Kafka sinks
192
+ KAFKA_MANDATORY_ATTRIBUTES = PipeParameters.MANDATORY_ATTRIBUTES.union(
193
+ {"export_connection_name", "export_kafka_topic", "export_schedule"}
194
+ )
195
+ KAFKA_ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(KAFKA_MANDATORY_ATTRIBUTES)
196
+
197
+ # For S3/GCS sinks
198
+ BLOB_MANDATORY_ATTRIBUTES = PipeParameters.MANDATORY_ATTRIBUTES.union(
199
+ {"export_connection_name", "export_schedule", "export_bucket_uri", "export_file_template"}
200
+ )
201
+ BLOB_ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(BLOB_MANDATORY_ATTRIBUTES).union(
202
+ {"export_format", "export_compression", "export_write_strategy", "export_strategy"}
203
+ )
183
204
 
184
205
 
185
206
  DATAFILE_NEW_LINE = "\n"
@@ -273,6 +294,13 @@ class Datafile:
273
294
  def set_kind(self, kind: DatafileKind):
274
295
  self.kind = kind
275
296
 
297
+ def validate_standard_node(self, node: Dict[str, Any]):
298
+ for key in node.keys():
299
+ if key not in PipeParameters.valid_params():
300
+ raise DatafileValidationError(
301
+ f"Standard node {repr(node['name'])} has an invalid attribute ({PipeParameters.canonical_name(key)})"
302
+ )
303
+
276
304
  def validate_copy_node(self, node: Dict[str, Any]):
277
305
  if missing := [param for param in CopyParameters.required_params() if param not in node]:
278
306
  raise DatafileValidationError(
@@ -305,15 +333,95 @@ class Datafile:
305
333
  f"Materialized node {repr(node['name'])} has an invalid attribute ({MaterializedParameters.canonical_name(key)})"
306
334
  )
307
335
 
336
+ def validate_sink_node(self, node: Dict[str, Any]):
337
+ export_connection_name = node.get("export_connection_name")
338
+
339
+ if not export_connection_name:
340
+ raise DatafileValidationError(
341
+ f"Sink node {repr(node['name'])} is missing required parameter 'export_connection_name'"
342
+ )
343
+
344
+ # Determine connection type to validate appropriate parameters
345
+ # First, try to determine from presence of Kafka-specific parameters
346
+ has_kafka_topic = "export_kafka_topic" in node
347
+ has_s3_gcs_params = any(param in node for param in ["export_bucket_uri", "export_file_template"])
348
+
349
+ # If both types of parameters are present, that's an error
350
+ if has_kafka_topic and has_s3_gcs_params:
351
+ raise DatafileValidationError(
352
+ f"Sink node {repr(node['name'])} has mixed Kafka and S3/GCS parameters. Use either Kafka parameters (export_kafka_topic) or S3/GCS parameters (export_bucket_uri, export_file_template), not both."
353
+ )
354
+
355
+ # If we have Kafka-specific parameters, treat as Kafka sink
356
+ if has_kafka_topic:
357
+ # Kafka sink validation
358
+ if missing := [param for param in SinkParameters.KAFKA_MANDATORY_ATTRIBUTES if param not in node]:
359
+ raise DatafileValidationError(
360
+ f"Kafka sink node {repr(node['name'])} is missing required parameters: {missing}"
361
+ )
362
+
363
+ # For Kafka sinks, only specific parameters should be present
364
+ kafka_specific_params = (
365
+ {"export_kafka_topic", "export_schedule", "export_connection_name"}
366
+ | PipeParameters.MANDATORY_ATTRIBUTES
367
+ | PipeParameters.ACCEPTED_ATTRIBUTES
368
+ )
369
+ for key in node.keys():
370
+ if key not in kafka_specific_params:
371
+ raise DatafileValidationError(
372
+ f"Kafka sink node {repr(node['name'])} has invalid parameter '{key}'. Only export_kafka_topic, export_schedule, and export_connection_name are allowed for Kafka sinks."
373
+ )
374
+
375
+ # If we have S3/GCS-specific parameters, treat as S3/GCS sink
376
+ elif has_s3_gcs_params:
377
+ # S3/GCS sink validation
378
+ if missing := [param for param in SinkParameters.BLOB_MANDATORY_ATTRIBUTES if param not in node]:
379
+ raise DatafileValidationError(
380
+ f"S3/GCS sink node {repr(node['name'])} is missing required parameters: {missing}"
381
+ )
382
+
383
+ # Check that only valid parameters are present
384
+ for key in node.keys():
385
+ if key not in SinkParameters.BLOB_ACCEPTED_ATTRIBUTES:
386
+ raise DatafileValidationError(
387
+ f"S3/GCS sink node {repr(node['name'])} has invalid parameter '{key}'"
388
+ )
389
+
390
+ # If no type-specific parameters are present, we can't determine the type
391
+ # This means the sink is missing required parameters for any sink type
392
+ else:
393
+ # Check if we have any export parameters at all besides connection_name and schedule
394
+ export_params = {
395
+ k
396
+ for k in node.keys()
397
+ if k.startswith("export_") and k not in {"export_connection_name", "export_schedule"}
398
+ }
399
+ if not export_params:
400
+ raise DatafileValidationError(
401
+ f"Sink node {repr(node['name'])} is missing required parameters. "
402
+ f"For Kafka sinks, provide 'export_kafka_topic'. "
403
+ f"For S3/GCS sinks, provide 'export_bucket_uri' and 'export_file_template'."
404
+ )
405
+ else:
406
+ # There are some export parameters, but they don't match known patterns
407
+ raise DatafileValidationError(
408
+ f"Sink node {repr(node['name'])} has unrecognized export parameters: {export_params}. "
409
+ f"For Kafka sinks, use 'export_kafka_topic'. "
410
+ f"For S3/GCS sinks, use 'export_bucket_uri' and 'export_file_template'."
411
+ )
412
+
413
+ # Validate schedule format (common for both Kafka and S3/GCS)
414
+ export_schedule = node.get("export_schedule")
415
+ if export_schedule and export_schedule != ON_DEMAND and not croniter.is_valid(export_schedule):
416
+ raise DatafileValidationError(
417
+ f"Sink node {repr(node['name'])} has invalid export_schedule '{export_schedule}'. Must be @on-demand or a valid cron expression."
418
+ )
419
+
308
420
  def validate(self):
309
421
  if self.kind == DatafileKind.pipe:
310
- # TODO(eclbg):
311
- # [x] node names are unique
312
- # [x] SQL in all nodes
313
- # [x] Materialized nodes have target datasource
314
- # [x] Only one materialized node
315
- # [x] Only one node of any specific type
316
- # (rbarbadillo): there's a HUGE amount of validations in api_pipes.py, we should somehow merge them
422
+ if len(self.nodes) == 0:
423
+ raise DatafileValidationError("Pipe data file must have at least one node")
424
+
317
425
  non_standard_nodes_count = 0
318
426
  for node in self.nodes:
319
427
  node_type = node.get("type", "").lower()
@@ -325,10 +433,15 @@ class Datafile:
325
433
  self.validate_materialized_node(node)
326
434
  if node_type == PipeNodeTypes.COPY:
327
435
  self.validate_copy_node(node)
436
+ if node_type == PipeNodeTypes.DATA_SINK:
437
+ self.validate_sink_node(node)
438
+ if node_type in {PipeNodeTypes.STANDARD, ""}:
439
+ self.validate_standard_node(node)
328
440
  if node_type not in VALID_PIPE_NODE_TYPES:
329
441
  raise DatafileValidationError(
330
- f"Invalid node type ({node_type}) in pipe {repr(node['name'])}. Allowed node types: {VISIBLE_PIPE_NODE_TYPES}"
442
+ f"Invalid node '{repr(node['name'])}' of type ({node_type}). Allowed node types: {VISIBLE_PIPE_NODE_TYPES}"
331
443
  )
444
+
332
445
  for token in self.tokens:
333
446
  if token["permission"].upper() != "READ":
334
447
  raise DatafileValidationError(
@@ -354,6 +467,15 @@ class Datafile:
354
467
  raise DatafileValidationError(
355
468
  f"Invalid permission {token['permission']} for token {token['token_name']}. Only READ and APPEND are allowed for datasources"
356
469
  )
470
+
471
+ # Validate sorting key if present
472
+ if "engine" in node and isinstance(node["engine"], dict) and "args" in node["engine"]:
473
+ for arg_name, arg_value in node["engine"]["args"]:
474
+ if arg_name.lower() == "sorting_key":
475
+ # Check for sorting key constraints
476
+ self._validate_sorting_key(arg_value, node)
477
+ break
478
+
357
479
  # Validate Kafka params
358
480
  if any(param in node for param in KAFKA_PARAMS) and (
359
481
  missing := [param for param in REQUIRED_KAFKA_PARAMS if param not in node]
@@ -376,6 +498,151 @@ class Datafile:
376
498
  # We cannot validate a datafile whose kind is unknown
377
499
  pass
378
500
 
501
+ def _validate_sorting_key(self, sorting_key: str, node: Dict[str, Any]) -> None:
502
+ """
503
+ Validates that a sorting key doesn't reference:
504
+ - Nullable columns
505
+ - AggregateFunction types
506
+ - Engine version columns for ReplacingMergeTree
507
+ """
508
+ if sorting_key == "tuple()" or not sorting_key:
509
+ return # Empty sorting key is valid
510
+
511
+ engine_ver_column = self._extract_engine_ver_column(node)
512
+ schema_columns = {col["name"]: col for col in node["columns"]}
513
+ sorting_key_columns = self._parse_sorting_key_columns(sorting_key, engine_ver_column)
514
+
515
+ self._validate_columns_against_schema(sorting_key_columns, schema_columns)
516
+
517
+ def _extract_engine_ver_column(self, node: Dict[str, Any]) -> Optional[str]:
518
+ engine_info = node.get("engine", {})
519
+
520
+ if not isinstance(engine_info, dict):
521
+ return None
522
+
523
+ engine_type = engine_info.get("type", "")
524
+ if engine_type != "ReplacingMergeTree":
525
+ return None
526
+
527
+ engine_args = engine_info.get("args", [])
528
+ for arg_name, arg_value in engine_args:
529
+ if arg_name == "ver":
530
+ return arg_value
531
+
532
+ return None
533
+
534
+ def _parse_sorting_key_columns(self, sorting_key: str, engine_ver_column: Optional[str]) -> List[str]:
535
+ """Parse sorting key to extract column names and validate constraints."""
536
+ # Validate ENGINE_VER column constraint early
537
+ if engine_ver_column and engine_ver_column in sorting_key:
538
+ raise DatafileValidationError(
539
+ f"ENGINE_VER column '{engine_ver_column}' cannot be included in the sorting key for ReplacingMergeTree. "
540
+ f"Including the version column in the sorting key prevents deduplication because rows with different "
541
+ f"versions will have different sorting keys and won't be considered duplicates. The sorting key should "
542
+ f"define the record identity (what makes it unique), while ENGINE_VER tracks which version to keep."
543
+ )
544
+
545
+ # Remove tuple() wrapper if present
546
+ column_str = sorting_key
547
+ if column_str.startswith("tuple(") and column_str.endswith(")"):
548
+ column_str = column_str[6:-1]
549
+
550
+ sorting_key_columns = []
551
+
552
+ for part in column_str.split(","):
553
+ part = part.strip()
554
+
555
+ if self._is_aggregate_function_expression(part):
556
+ raise DatafileValidationError(
557
+ f"Sorting key contains aggregate function expression '{part}'. Aggregate function expressions cannot be used in sorting keys."
558
+ )
559
+
560
+ # Extract column names from the part
561
+ extracted_columns = self._extract_column_names_from_part(part)
562
+ sorting_key_columns.extend(extracted_columns)
563
+
564
+ return sorting_key_columns
565
+
566
+ def _is_aggregate_function_expression(self, part: str) -> bool:
567
+ """Check if a sorting key part is an aggregate function expression."""
568
+ if not ("(" in part and part.endswith(")")):
569
+ return False
570
+
571
+ func_start = part.find("(")
572
+ func_name = part[:func_start].strip().lower()
573
+
574
+ aggregate_function_names = {
575
+ "sum",
576
+ "count",
577
+ "avg",
578
+ "min",
579
+ "max",
580
+ "any",
581
+ "grouparray",
582
+ "groupuniqarray",
583
+ "uniq",
584
+ "summerge",
585
+ "countmerge",
586
+ "avgmerge",
587
+ "minmerge",
588
+ "maxmerge",
589
+ "anymerge",
590
+ "grouparraymerge",
591
+ "groupuniqarraymerge",
592
+ "uniqmerge",
593
+ }
594
+
595
+ return func_name in aggregate_function_names
596
+
597
+ def _extract_column_names_from_part(self, part: str) -> List[str]:
598
+ """Extract column names from a sorting key part."""
599
+ columns = []
600
+
601
+ if "(" in part and part.endswith(")"):
602
+ # Function expression - extract column names from inside parentheses
603
+ func_start = part.find("(")
604
+ inner_content = part[func_start + 1 : -1].strip()
605
+ for inner_part in inner_content.split(","):
606
+ inner_part = inner_part.strip().strip("`")
607
+ if inner_part and inner_part.isidentifier():
608
+ columns.append(inner_part)
609
+ elif part:
610
+ # Simple column name
611
+ column_name = part.strip("`")
612
+ if column_name:
613
+ columns.append(column_name)
614
+
615
+ return columns
616
+
617
+ def _validate_columns_against_schema(
618
+ self, sorting_key_columns: List[str], schema_columns: Dict[str, Dict[str, Any]]
619
+ ) -> None:
620
+ """Validate each column in the sorting key against the schema."""
621
+ if not schema_columns:
622
+ return # No schema information available, can't validate
623
+
624
+ for col_name in sorting_key_columns:
625
+ if col_name not in schema_columns:
626
+ continue
627
+
628
+ self._validate_single_column(col_name, schema_columns[col_name])
629
+
630
+ def _validate_single_column(self, col_name: str, column_info: Dict[str, Any]) -> None:
631
+ """Validate a single column for use in sorting keys."""
632
+ col_type = column_info.get("type", "").lower()
633
+
634
+ # we need to check any presence of Nullable in the column type
635
+ is_nullable = column_info.get("nullable", False) or "Nullable(" in column_info.get("type", "")
636
+
637
+ if is_nullable:
638
+ raise DatafileValidationError(
639
+ f"Sorting key contains nullable column '{col_name}'. Nullable columns cannot be used in sorting keys."
640
+ )
641
+ if "aggregatefunction" in col_type:
642
+ raise DatafileValidationError(
643
+ f"Sorting key contains column '{col_name}' with AggregateFunction type. AggregateFunction columns cannot be used in sorting keys."
644
+ )
645
+
379
646
 
380
647
  def format_filename(filename: str, hide_folders: bool = False):
381
648
  return os.path.basename(filename) if hide_folders else filename
@@ -1265,6 +1532,8 @@ def parse(
1265
1532
  default_node: Optional[str] = None,
1266
1533
  basepath: str = ".",
1267
1534
  replace_includes: bool = True,
1535
+ # TODO(eclbg): I think we could remove `skip_eval` in Forward, and pin it to False. This would let us remove some
1536
+ # other functions like `eval_var` that obscure things a bit.
1268
1537
  skip_eval: bool = False,
1269
1538
  ) -> ParseResult:
1270
1539
  lines = list(StringIO(s, newline=None))
@@ -1375,11 +1644,18 @@ def parse(
1375
1644
 
1376
1645
  parser_state.current_node["indexes"] = indexes
1377
1646
 
1378
- def assign_var(v: str) -> Callable[[VarArg(str), KwArg(Any)], None]:
1647
+ def assign_var(v: str, allowed_values: Optional[set[str]] = None) -> Callable[[VarArg(str), KwArg(Any)], None]:
1379
1648
  @multiline_not_supported
1380
1649
  def _f(*args: str, **kwargs: Any):
1381
1650
  s = _unquote((" ".join(args)).strip())
1382
- parser_state.current_node[v.lower()] = eval_var(s, skip=skip_eval)
1651
+ val = eval_var(s, skip=skip_eval)
1652
+ if allowed_values and val.lower() not in {v.lower() for v in allowed_values}:
1653
+ raise DatafileSyntaxError(
1654
+ f"{val} is not an allowed value for {kwargs['cmd'].upper()}. Use one of: {allowed_values}",
1655
+ lineno=kwargs["lineno"],
1656
+ pos=1,
1657
+ )
1658
+ parser_state.current_node[v.lower()] = val
1383
1659
 
1384
1660
  return _f
1385
1661
 
@@ -1474,6 +1750,10 @@ def parse(
1474
1750
  else:
1475
1751
  doc.description = description
1476
1752
 
1753
+ def kafka_ssl_ca_pem(*args: str, **kwargs: Any) -> None:
1754
+ kafka_ssl_ca_pem = ("\n".join(args)).strip()
1755
+ parser_state.current_node["kafka_ssl_ca_pem"] = kafka_ssl_ca_pem
1756
+
1477
1757
  def sql(var_name: str, **kwargs: Any) -> Callable[[str, KwArg(Any)], None]:
1478
1758
  # TODO(eclbg): We shouldn't allow SQL in datasource files
1479
1759
  def _f(sql: str, *args: Any, **kwargs: Any) -> None:
@@ -1587,11 +1867,26 @@ def parse(
1587
1867
  def version(*args: str, **kwargs: Any) -> None:
1588
1868
  pass # whatever, it's deprecated
1589
1869
 
1590
- @not_supported_yet()
1591
1870
  def shared_with(*args: str, **kwargs: Any) -> None:
1871
+ # Count total workspaces collected
1872
+ total_workspaces = 0
1873
+
1592
1874
  for entries in args:
1593
- # In case they specify multiple workspaces
1594
- doc.shared_with += [workspace.strip() for workspace in entries.splitlines()]
1875
+ # In case they specify multiple workspaces, handle both line-separated and comma-separated values
1876
+ lines = _unquote(entries).splitlines()
1877
+ for line in lines:
1878
+ # Split by comma and strip whitespace from each workspace name
1879
+ workspaces = [workspace.strip().rstrip(",") for workspace in line.split(",") if workspace.strip()]
1880
+ doc.shared_with += workspaces
1881
+ total_workspaces += len(workspaces)
1882
+
1883
+ # Validate that at least one workspace was provided
1884
+ if total_workspaces == 0:
1885
+ raise DatafileSyntaxError(
1886
+ "SHARED_WITH requires at least one workspace name",
1887
+ lineno=kwargs["lineno"],
1888
+ pos=1,
1889
+ )
1595
1890
 
1596
1891
  def __init_engine(v: str):
1597
1892
  if not parser_state.current_node:
@@ -1608,6 +1903,8 @@ def parse(
1608
1903
  def _f(*args: str, **kwargs: Any):
1609
1904
  __init_engine(f"ENGINE_{v}".upper())
1610
1905
  engine_arg = eval_var(_unquote((" ".join(args)).strip()), skip=skip_eval)
1906
+ if v.lower() == "ttl" and not engine_arg:
1907
+ return
1611
1908
  parser_state.current_node["engine"]["args"].append((v, engine_arg))
1612
1909
 
1613
1910
  return _f
@@ -1663,9 +1960,10 @@ def parse(
1663
1960
  "import_query": assign_var("import_query"), # Deprecated, BQ and SFK
1664
1961
  "import_table_arn": assign_var("import_table_arn"), # Only for DynamoDB
1665
1962
  "import_export_bucket": assign_var("import_export_bucket"), # For DynamoDB
1666
- "shared_with": shared_with, # Not supported yet
1963
+ "shared_with": shared_with,
1667
1964
  "export_service": export_service, # Deprecated
1668
1965
  "forward_query": sql("forward_query"),
1966
+ "backfill": assign_var("backfill", allowed_values={"skip"}),
1669
1967
  # ENGINE_* commands are added dynamically after this dict's definition
1670
1968
  },
1671
1969
  DatafileKind.pipe: {
@@ -1684,6 +1982,7 @@ def parse(
1684
1982
  "include": include,
1685
1983
  "sql": sql("sql"),
1686
1984
  "version": version,
1985
+ "deployment_method": assign_var("deployment_method", allowed_values={"alter"}),
1687
1986
  "export_connection_name": assign_var("export_connection_name"),
1688
1987
  "export_schedule": assign_var("export_schedule"),
1689
1988
  "export_bucket_uri": assign_var("export_bucket_uri"),
@@ -1701,9 +2000,13 @@ def parse(
1701
2000
  "kafka_key": assign_var("kafka_key"),
1702
2001
  "kafka_secret": assign_var("kafka_secret"),
1703
2002
  "kafka_schema_registry_url": assign_var("kafka_schema_registry_url"),
1704
- "kafka_ssl_ca_pem": assign_var("kafka_ssl_ca_pem"),
2003
+ "kafka_ssl_ca_pem": kafka_ssl_ca_pem,
1705
2004
  "kafka_security_protocol": assign_var("kafka_security_protocol"),
1706
2005
  "kafka_sasl_mechanism": assign_var("kafka_sasl_mechanism"),
2006
+ "kafka_sasl_oauthbearer_method": assign_var("kafka_sasl_oauthbearer_method"),
2007
+ "kafka_sasl_oauthbearer_aws_region": assign_var("kafka_sasl_oauthbearer_aws_region"),
2008
+ "kafka_sasl_oauthbearer_aws_role_arn": assign_var("kafka_sasl_oauthbearer_aws_role_arn"),
2009
+ "kafka_sasl_oauthbearer_aws_external_id": assign_var("kafka_sasl_oauthbearer_aws_external_id"),
1707
2010
  "kafka_key_avro_deserialization": kafka_key_avro_deserialization_deprecated,
1708
2011
  "s3_region": assign_var("s3_region"),
1709
2012
  "s3_arn": assign_var("s3_arn"),
@@ -1748,11 +2051,7 @@ def parse(
1748
2051
  lexer = list(sa)
1749
2052
  if lexer:
1750
2053
  cmd, args = lexer[0], lexer[1:]
1751
- if (
1752
- parser_state.multiline
1753
- and cmd.lower() in cmds
1754
- and not (line.startswith(" ") or line.startswith("\t"))
1755
- ):
2054
+ if parser_state.multiline and cmd.lower() in cmds and not line.startswith((" ", "\t")):
1756
2055
  cmds[parser_state.command](
1757
2056
  parser_state.multiline_string,
1758
2057
  lineno=lineno,
@@ -2205,7 +2504,7 @@ def is_file_a_datasource(filename: str) -> bool:
2205
2504
 
2206
2505
  for line in lines:
2207
2506
  trimmed_line = line.strip().lower()
2208
- if trimmed_line.startswith("schema") or trimmed_line.startswith("engine"):
2507
+ if trimmed_line.startswith(("schema", "engine")):
2209
2508
  return True
2210
2509
 
2211
2510
  return False
@@ -11,7 +11,7 @@ from tinybird.datafile.common import (
11
11
  parse,
12
12
  )
13
13
  from tinybird.datafile.exceptions import IncludeFileNotFoundException, ParseException
14
- from tinybird.sql_template import get_template_and_variables, render_sql_template, secret_template_key
14
+ from tinybird.sql_template import get_template_and_variables, render_sql_template
15
15
  from tinybird.tb.modules.feedback_manager import FeedbackManager
16
16
  from tinybird.tornado_template import UnClosedIfError
17
17
 
@@ -54,7 +54,7 @@ def parse_pipe(
54
54
  if sql.strip()[0] == "%":
55
55
  secrets_list: Optional[List[str]] = None
56
56
  if secrets:
57
- secrets_list = [f"{secret_template_key(secret)}" for secret in secrets.keys()]
57
+ secrets_list = list(secrets.keys())
58
58
  # Setting test_mode=True to ignore errors on required parameters and
59
59
  # secrets_in_test_mode=False to raise errors on missing secrets
60
60
  sql, _, variable_warnings = render_sql_template(
@@ -598,6 +598,12 @@ Ready? """
598
598
  warning_confirm_delete_branch = prompt_message("Do you want to remove '{branch}' Branch?")
599
599
  warning_confirm_delete_release = prompt_message("Do you want to remove Release {semver}?")
600
600
  warning_confirm_rollback_release = prompt_message("Do you want to rollback current Release {semver} to {rollback}?")
601
+ warning_confirm_on_demand_compute = warning_message(
602
+ "On-demand compute will incur additional costs beyond your regular usage.\n"
603
+ "This feature uses dedicated compute resources that are billed separately.\n"
604
+ "You can read more about the pricing at https://www.tinybird.co/docs/classic/work-with-data/process-and-copy/materialized-views#compute-compute-separation-for-populates\n\n"
605
+ "Do you want to proceed with on-demand compute?"
606
+ )
601
607
 
602
608
  warning_confirm_delete_token = prompt_message("Do you want to delete Token {token}?")
603
609
  warning_confirm_refresh_token = prompt_message("Do you want to refresh Token {token}?")