tinybird 4.6.21.dev0__tar.gz → 4.6.22.dev0__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 (130) hide show
  1. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/PKG-INFO +6 -1
  2. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/pyproject.toml +12 -1
  3. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/client.py +4 -0
  4. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/datafile/common.py +9 -1
  5. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/datatypes.py +34 -12
  6. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/feedback_manager.py +5 -0
  7. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/sql_template.py +7 -2
  8. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/__cli__.py +2 -2
  9. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/client.py +4 -0
  10. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/copy.py +33 -1
  11. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/build.py +1 -0
  12. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/format_pipe.py +4 -0
  13. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/playground.py +1 -0
  14. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/feedback_manager.py +5 -0
  15. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/pipe.py +40 -21
  16. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tornado_template.py +204 -0
  17. tinybird-4.6.22.dev0/tinybird/utils/__init__.py +0 -0
  18. tinybird-4.6.22.dev0/tinybird/utils/bools.py +19 -0
  19. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird.egg-info/PKG-INFO +6 -1
  20. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird.egg-info/SOURCES.txt +3 -1
  21. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/setup.cfg +0 -0
  22. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/__cli__.py +0 -0
  23. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/ch_utils/constants.py +0 -0
  24. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/ch_utils/engine.py +0 -0
  25. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/check_pypi.py +0 -0
  26. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/config.py +0 -0
  27. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/context.py +0 -0
  28. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/datafile/exceptions.py +0 -0
  29. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/datafile/parse_connection.py +0 -0
  30. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/datafile/parse_datasource.py +0 -0
  31. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/datafile/parse_pipe.py +0 -0
  32. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/git_settings.py +0 -0
  33. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/iterating/__init__.py +0 -0
  34. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/iterating/data_branch_modes.py +0 -0
  35. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/prompts.py +0 -0
  36. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/service_datasources.py +0 -0
  37. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/sql.py +0 -0
  38. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/sql_template_fmt.py +0 -0
  39. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/sql_toolset.py +0 -0
  40. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/syncasync.py +0 -0
  41. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/check_pypi.py +0 -0
  42. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/cli.py +0 -0
  43. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/config.py +0 -0
  44. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/branch.py +0 -0
  45. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/build.py +0 -0
  46. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/build_common.py +0 -0
  47. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/cicd.py +0 -0
  48. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/cli.py +0 -0
  49. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/common.py +0 -0
  50. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/config.py +0 -0
  51. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/connection.py +0 -0
  52. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/connection_dynamodb.py +0 -0
  53. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/connection_kafka.py +0 -0
  54. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/connection_s3.py +0 -0
  55. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/create.py +0 -0
  56. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/build_common.py +0 -0
  57. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  58. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  59. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/diff.py +0 -0
  60. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/fixture.py +0 -0
  61. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/format_common.py +0 -0
  62. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/format_connection.py +0 -0
  63. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  64. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  65. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datafile/pull.py +0 -0
  66. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/datasource.py +0 -0
  67. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/deployment.py +0 -0
  68. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/deployment_common.py +0 -0
  69. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/deprecations.py +0 -0
  70. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/endpoint.py +0 -0
  71. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/exceptions.py +0 -0
  72. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/fmt.py +0 -0
  73. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/info.py +0 -0
  74. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/infra.py +0 -0
  75. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/job.py +0 -0
  76. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/job_common.py +0 -0
  77. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/llm.py +0 -0
  78. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/llm_utils.py +0 -0
  79. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/local.py +0 -0
  80. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/local_common.py +0 -0
  81. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/local_logs.py +0 -0
  82. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/login.py +0 -0
  83. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/login_common.py +0 -0
  84. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/logout.py +0 -0
  85. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/logs.py +0 -0
  86. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/materialization.py +0 -0
  87. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/open.py +0 -0
  88. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/pipe.py +0 -0
  89. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/preview.py +0 -0
  90. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/project.py +0 -0
  91. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/project_commands.py +0 -0
  92. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/py_project.py +0 -0
  93. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/query_output.py +0 -0
  94. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/regions.py +0 -0
  95. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/secret.py +0 -0
  96. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/secret_common.py +0 -0
  97. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/sink.py +0 -0
  98. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/table.py +0 -0
  99. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/telemetry.py +0 -0
  100. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/test.py +0 -0
  101. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/test_common.py +0 -0
  102. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  103. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/token.py +0 -0
  104. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/ts_project.py +0 -0
  105. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/watch.py +0 -0
  106. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/workspace.py +0 -0
  107. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb/modules/workspace_members.py +0 -0
  108. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli.py +0 -0
  109. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/auth.py +0 -0
  110. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/branch.py +0 -0
  111. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
  112. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/cli.py +0 -0
  113. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/common.py +0 -0
  114. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/config.py +0 -0
  115. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
  116. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/datasource.py +0 -0
  117. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
  118. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/fmt.py +0 -0
  119. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/job.py +0 -0
  120. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
  121. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/tag.py +0 -0
  122. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
  123. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/test.py +0 -0
  124. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  125. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/workspace.py +0 -0
  126. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  127. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird.egg-info/dependency_links.txt +0 -0
  128. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird.egg-info/entry_points.txt +0 -0
  129. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird.egg-info/requires.txt +0 -0
  130. {tinybird-4.6.21.dev0 → tinybird-4.6.22.dev0}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 4.6.21.dev0
3
+ Version: 4.6.22.dev0
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -52,6 +52,11 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
52
52
  Changelog
53
53
  ----------
54
54
 
55
+ 4.6.20
56
+ ********
57
+
58
+ - `Added` Copy pipes accept ``ON_DEMAND_COMPUTE true`` so scheduled runs use on-demand compute. Manual ``tb copy run`` still requires ``--on-demand-compute``, and asks for confirmation when the local datafile does not enable it (skip it with ``--yes``).
59
+
55
60
  4.6.19
56
61
  ********
57
62
 
@@ -150,6 +150,8 @@ doc = [
150
150
  ]
151
151
  lint = [
152
152
  "botocore-stubs==1.36.23",
153
+ "flake8~=7.3",
154
+ "flake8-async~=26.8",
153
155
  "mypy==2.3.1",
154
156
  "ruff==0.16.8",
155
157
  "types-Markdown",
@@ -184,6 +186,12 @@ test = [
184
186
  "rangehttpserver==1.4.0",
185
187
  "yappi==1.7.6",
186
188
  ]
189
+ # Synced automatically by plain `uv run`/`uv sync`, no `--group` needed.
190
+ dev = [
191
+ { include-group = "test" },
192
+ { include-group = "deploy" },
193
+ { include-group = "lint" },
194
+ ]
187
195
 
188
196
  [project.scripts]
189
197
  tinybird_server = "tinybird.app:run"
@@ -193,7 +201,10 @@ tb = "tinybird.tb.cli:cli"
193
201
  colibri = "tinybird.colibri.cli:cli"
194
202
 
195
203
  [tool.uv.workspace]
196
- members = ["data_connector"]
204
+ members = [
205
+ "chbackups",
206
+ "data_connector",
207
+ ]
197
208
 
198
209
  [tool.setuptools]
199
210
  include-package-data = true
@@ -691,6 +691,7 @@ class TinyB:
691
691
  target_datasource: Optional[str] = None,
692
692
  schedule_cron: Optional[str] = None,
693
693
  mode: Optional[str] = None,
694
+ on_demand_compute: Optional[bool] = None,
694
695
  ):
695
696
  data = {"schedule_cron": schedule_cron}
696
697
 
@@ -700,6 +701,9 @@ class TinyB:
700
701
  if mode:
701
702
  data["mode"] = mode
702
703
 
704
+ if on_demand_compute is not None:
705
+ data["on_demand_compute"] = "true" if on_demand_compute else "false"
706
+
703
707
  return await self._req(f"/v0/pipes/{pipe_name_or_id}/nodes/{node_id}/copy", method="PUT", data=data)
704
708
 
705
709
  async def pipe_remove_copy(self, pipe_name_or_id: str, node_id: str):
@@ -28,6 +28,7 @@ from tinybird.ch_utils.engine import VALID_ENGINE_PARAMS, EngineParam
28
28
  from tinybird.datafile.exceptions import IncludeFileNotFoundException, ParseException, ValidationException
29
29
  from tinybird.tb.modules.exceptions import CLIPipeException
30
30
  from tinybird.tb.modules.feedback_manager import FeedbackManager
31
+ from tinybird.utils.bools import parse_optional_bool
31
32
 
32
33
  # Code from sql.py has been duplicated so I can change it without breaking absolutely everything in the app
33
34
  # I'll try not to make logic changes, just error reporting changes
@@ -179,9 +180,10 @@ class CopyParameters(Parameters):
179
180
  COPY_SCHEDULE = "copy_schedule"
180
181
  COPY_MODE = "copy_mode"
181
182
  COPY_MODE_ALIAS = "mode" # we need this because bot MODE and COPY_MODE go to `mode` variable inside the node
183
+ ON_DEMAND_COMPUTE = "on_demand_compute"
182
184
  MANDATORY_ATTRIBUTES = PipeParameters.MANDATORY_ATTRIBUTES.union({TARGET_DATASOURCE})
183
185
  ACCEPTED_ATTRIBUTES = PipeParameters.ACCEPTED_ATTRIBUTES.union(MANDATORY_ATTRIBUTES).union(
184
- {COPY_SCHEDULE, COPY_MODE_ALIAS}
186
+ {COPY_SCHEDULE, COPY_MODE_ALIAS, ON_DEMAND_COMPUTE}
185
187
  )
186
188
 
187
189
 
@@ -416,6 +418,11 @@ class Datafile:
416
418
  )
417
419
  if not croniter.is_valid(copy_schedule):
418
420
  raise DatafileValidationError("COPY node schedule must be @on-demand or a valid cron expression.")
421
+ if CopyParameters.ON_DEMAND_COMPUTE in node:
422
+ try:
423
+ node[CopyParameters.ON_DEMAND_COMPUTE] = parse_optional_bool(node[CopyParameters.ON_DEMAND_COMPUTE])
424
+ except ValueError:
425
+ raise DatafileValidationError("ON_DEMAND_COMPUTE must be true or false")
419
426
  for key in node.keys():
420
427
  if key not in CopyParameters.valid_params():
421
428
  raise DatafileValidationError(
@@ -2097,6 +2104,7 @@ def parse(
2097
2104
  "copy_schedule": assign_node_var(CopyParameters.COPY_SCHEDULE),
2098
2105
  "copy_mode": assign_node_var("mode"),
2099
2106
  "mode": assign_node_var("mode"),
2107
+ "on_demand_compute": assign_node_var(CopyParameters.ON_DEMAND_COMPUTE),
2100
2108
  "filter": assign_node_var("filter"),
2101
2109
  "token": add_token,
2102
2110
  "include": include,
@@ -120,52 +120,74 @@ def datetime_test(x: str) -> bool:
120
120
  return any(p.match(x) for p in datetime_patterns)
121
121
 
122
122
 
123
+ def bounded_integer(x: str, pattern: re.Pattern[str]) -> Optional[int]:
124
+ if pattern.match(x) is None:
125
+ return None
126
+ digits = x.lstrip("+-").lstrip("0")
127
+ # 256-bit unsigned integers have at most 78 decimal digits.
128
+ if len(digits) > 78:
129
+ return None
130
+ return int(x) if len(x) <= 78 else int(digits or "0") * (-1 if x.startswith("-") else 1)
131
+
132
+
123
133
  def int_8_test(x: str) -> bool:
124
- return intx_re.match(x) is not None and -int_8_max <= int(x) < int_8_max
134
+ value = bounded_integer(x, intx_re)
135
+ return value is not None and -int_8_max <= value < int_8_max
125
136
 
126
137
 
127
138
  def int16_test(x: str) -> bool:
128
- return intx_re.match(x) is not None and -int16_max <= int(x) < int16_max
139
+ value = bounded_integer(x, intx_re)
140
+ return value is not None and -int16_max <= value < int16_max
129
141
 
130
142
 
131
143
  def int32_test(x: str) -> bool:
132
- return intx_re.match(x) is not None and -int32_max <= int(x) < int32_max
144
+ value = bounded_integer(x, intx_re)
145
+ return value is not None and -int32_max <= value < int32_max
133
146
 
134
147
 
135
148
  def int64_test(x: str) -> bool:
136
- return intx_re.match(x) is not None and -int64_max <= int(x) < int64_max
149
+ value = bounded_integer(x, intx_re)
150
+ return value is not None and -int64_max <= value < int64_max
137
151
 
138
152
 
139
153
  def int128_test(x: str) -> bool:
140
- return intx_re.match(x) is not None and -int128_max <= int(x) < int128_max
154
+ value = bounded_integer(x, intx_re)
155
+ return value is not None and -int128_max <= value < int128_max
141
156
 
142
157
 
143
158
  def int256_test(x: str) -> bool:
144
- return intx_re.match(x) is not None and -int256_max <= int(x) < int256_max
159
+ value = bounded_integer(x, intx_re)
160
+ return value is not None and -int256_max <= value < int256_max
145
161
 
146
162
 
147
163
  def uint_8_test(x: str) -> bool:
148
- return uintx_re.match(x) is not None and 0 <= int(x) < uint_8_max
164
+ value = bounded_integer(x, uintx_re)
165
+ return value is not None and 0 <= value < uint_8_max
149
166
 
150
167
 
151
168
  def uint16_test(x: str) -> bool:
152
- return uintx_re.match(x) is not None and 0 <= int(x) < uint16_max
169
+ value = bounded_integer(x, uintx_re)
170
+ return value is not None and 0 <= value < uint16_max
153
171
 
154
172
 
155
173
  def uint32_test(x: str) -> bool:
156
- return uintx_re.match(x) is not None and 0 <= int(x) < uint32_max
174
+ value = bounded_integer(x, uintx_re)
175
+ return value is not None and 0 <= value < uint32_max
157
176
 
158
177
 
159
178
  def uint64_test(x: str) -> bool:
160
- return uintx_re.match(x) is not None and 0 <= int(x) < uint64_max
179
+ value = bounded_integer(x, uintx_re)
180
+ return value is not None and 0 <= value < uint64_max
161
181
 
162
182
 
163
183
  def uint128_test(x: str) -> bool:
164
- return intx_re.match(x) is not None and 0 <= int(x) < uint128_max
184
+ value = bounded_integer(x, intx_re)
185
+ return value is not None and 0 <= value < uint128_max
165
186
 
166
187
 
167
188
  def uint256_test(x: str) -> bool:
168
- return intx_re.match(x) is not None and 0 <= int(x) < uint256_max
189
+ value = bounded_integer(x, intx_re)
190
+ return value is not None and 0 <= value < uint256_max
169
191
 
170
192
 
171
193
  def float_test(x: str) -> bool:
@@ -572,6 +572,11 @@ class FeedbackManager:
572
572
  "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"
573
573
  "Do you want to proceed with on-demand compute?"
574
574
  )
575
+ warning_confirm_on_demand_compute_differs_from_datafile = warning_message(
576
+ "The local datafile of pipe '{pipe}' does not set ON_DEMAND_COMPUTE true, but --on-demand-compute was passed.\n"
577
+ "On-demand compute uses dedicated resources that are billed separately from your regular usage.\n\n"
578
+ "Do you want to proceed?"
579
+ )
575
580
 
576
581
  warning_confirm_delete_token = prompt_message("Do you want to delete Token {token}?")
577
582
  warning_confirm_refresh_token = prompt_message("Do you want to refresh Token {token}?")
@@ -18,7 +18,7 @@ from tinybird.context import (
18
18
  )
19
19
 
20
20
  from .datatypes import testers
21
- from .tornado_template import VALID_CUSTOM_FUNCTION_NAMES, SecurityException, Template
21
+ from .tornado_template import SAFE_BUILTINS, VALID_CUSTOM_FUNCTION_NAMES, SecurityException, Template
22
22
 
23
23
  VALID_ACTIVATE_FEATURES = frozenset(["analyzer", "parallel_replicas", "optimize_aggregation_in_order"])
24
24
 
@@ -1375,7 +1375,7 @@ _namespace = {
1375
1375
  # they raise a pretty non understandable error but if someone
1376
1376
  # is using them they know what they are trying to do
1377
1377
  # read https://anee.me/escaping-python-jails-849c65cf306e on how to escape from python jails
1378
- "__buildins__": {},
1378
+ "__builtins__": SAFE_BUILTINS,
1379
1379
  "__import__": {},
1380
1380
  "__debug__": {},
1381
1381
  "__doc__": {},
@@ -1385,6 +1385,9 @@ _namespace = {
1385
1385
  "close": None,
1386
1386
  "print": None,
1387
1387
  "input": None,
1388
+ # Keep customer parameters named `type`/`dir` usable without restoring the unsafe builtins.
1389
+ "type": None,
1390
+ "dir": None,
1388
1391
  }
1389
1392
 
1390
1393
 
@@ -1478,6 +1481,8 @@ def generate(self, **kwargs) -> Tuple[str, TemplateExecutionResults]:
1478
1481
  # the generated source code.
1479
1482
  "__name__": self.name.replace(".", "_"),
1480
1483
  "__loader__": ObjectDict(get_source=lambda name: self.code),
1484
+ # Set again after kwargs so request parameters can't override it
1485
+ "__builtins__": SAFE_BUILTINS,
1481
1486
  "max_threads": set_max_threads,
1482
1487
  "tb_secret": set_tb_secret,
1483
1488
  "tb_var": set_tb_secret,
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/forward/commands'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '4.6.21.dev0'
8
- __revision__ = '965c47ce'
7
+ __version__ = '4.6.22.dev0'
8
+ __revision__ = '3a75e0ef'
@@ -743,6 +743,7 @@ class TinyB:
743
743
  target_datasource: Optional[str] = None,
744
744
  schedule_cron: Optional[str] = None,
745
745
  mode: Optional[str] = None,
746
+ on_demand_compute: Optional[bool] = None,
746
747
  ):
747
748
  data = {"schedule_cron": schedule_cron}
748
749
 
@@ -752,6 +753,9 @@ class TinyB:
752
753
  if mode:
753
754
  data["mode"] = mode
754
755
 
756
+ if on_demand_compute is not None:
757
+ data["on_demand_compute"] = "true" if on_demand_compute else "false"
758
+
755
759
  return self._req(f"/v0/pipes/{pipe_name_or_id}/nodes/{node_id}/copy", method="PUT", data=data)
756
760
 
757
761
  def pipe_remove_copy(self, pipe_name_or_id: str, node_id: str):
@@ -10,13 +10,29 @@ from typing import Optional, Tuple
10
10
  import click
11
11
  from click import Context
12
12
 
13
- from tinybird.datafile.common import get_name_version
13
+ from tinybird.datafile.common import CopyParameters, PipeNodeTypes, get_name_version
14
14
  from tinybird.tb.client import AuthNoTokenException, TinyB
15
15
  from tinybird.tb.modules.cli import cli
16
16
  from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_smart_table, wait_job
17
17
  from tinybird.tb.modules.exceptions import CLIPipeException
18
18
  from tinybird.tb.modules.feedback_manager import FeedbackManager
19
19
  from tinybird.tb.modules.job_common import echo_job_url
20
+ from tinybird.tb.modules.project import Project
21
+ from tinybird.utils.bools import parse_optional_bool
22
+
23
+
24
+ def local_copy_node_uses_on_demand_compute(project: Project, pipe_name: str) -> Optional[bool]:
25
+ """Reads ON_DEMAND_COMPUTE from the local datafile. Returns None when there is no parseable copy pipe to compare"""
26
+ pipe_path = project.get_resource_path(pipe_name, "pipe")
27
+ if not pipe_path:
28
+ return None
29
+ datafile = project.get_pipe_datafile(str(project.path / pipe_path))
30
+ if datafile is None:
31
+ return None
32
+ copy_node = next((node for node in datafile.nodes if node.get("type", "").lower() == PipeNodeTypes.COPY), None)
33
+ if copy_node is None:
34
+ return None
35
+ return parse_optional_bool(copy_node.get(CopyParameters.ON_DEMAND_COMPUTE))
20
36
 
21
37
 
22
38
  @cli.group()
@@ -89,6 +105,7 @@ def copy_ls(ctx: Context, match: str, format_: str):
89
105
  default=False,
90
106
  help="Use on-demand compute instances for the copy job.",
91
107
  )
108
+ @click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
92
109
  @click.pass_context
93
110
  def copy_run(
94
111
  ctx: click.Context,
@@ -97,9 +114,24 @@ def copy_run(
97
114
  mode: str,
98
115
  param: Optional[Tuple[str]],
99
116
  on_demand_compute: bool,
117
+ yes: bool,
100
118
  ):
101
119
  """Run an on-demand copy pipe"""
102
120
 
121
+ project: Optional[Project] = ctx.ensure_object(dict).get("project")
122
+ if (
123
+ on_demand_compute
124
+ and not yes
125
+ and project
126
+ and local_copy_node_uses_on_demand_compute(project, pipe_name_or_id) is False
127
+ and not click.confirm(
128
+ FeedbackManager.warning_confirm_on_demand_compute_differs_from_datafile(pipe=pipe_name_or_id),
129
+ default=False,
130
+ )
131
+ ):
132
+ click.echo(FeedbackManager.info_cancelled_by_user())
133
+ return
134
+
103
135
  params = dict(key_value.split("=") for key_value in param) if param else {}
104
136
  click.echo(FeedbackManager.highlight(message=f"\n» Running on-demand copy '{pipe_name_or_id}'"))
105
137
  client: TinyB = ctx.ensure_object(dict)["client"]
@@ -881,6 +881,7 @@ def process_file(
881
881
  "target_datasource": node.get("target_datasource", None),
882
882
  "copy_schedule": node.get(CopyParameters.COPY_SCHEDULE, None),
883
883
  "mode": node.get("mode", CopyModes.APPEND),
884
+ "on_demand_compute": node.get(CopyParameters.ON_DEMAND_COMPUTE, None),
884
885
  }
885
886
 
886
887
  is_export_node = ExportReplacements.is_export_node(node)
@@ -23,6 +23,7 @@ from tinybird.tb.modules.datafile.format_common import (
23
23
  format_tags,
24
24
  format_tokens,
25
25
  )
26
+ from tinybird.utils.bools import parse_optional_bool
26
27
 
27
28
 
28
29
  def format_node_sql(
@@ -81,6 +82,9 @@ def format_node_type(file_parts: List[str], node: Dict[str, Any]) -> List[str]:
81
82
  file_parts.append(
82
83
  f"{CopyParameters.COPY_SCHEDULE.upper()} {ON_DEMAND if is_ondemand else node[CopyParameters.COPY_SCHEDULE]}"
83
84
  )
85
+ if parse_optional_bool(node.get(CopyParameters.ON_DEMAND_COMPUTE)):
86
+ file_parts.append(DATAFILE_NEW_LINE)
87
+ file_parts.append("ON_DEMAND_COMPUTE true")
84
88
  file_parts.append(DATAFILE_NEW_LINE)
85
89
 
86
90
  # Sink or Stream pipe
@@ -922,6 +922,7 @@ def process_file(
922
922
  "target_datasource": node.get("target_datasource", None),
923
923
  "copy_schedule": node.get(CopyParameters.COPY_SCHEDULE, None),
924
924
  "mode": node.get("mode", CopyModes.APPEND),
925
+ "on_demand_compute": node.get(CopyParameters.ON_DEMAND_COMPUTE, None),
925
926
  }
926
927
 
927
928
  is_export_node = ExportReplacements.is_export_node(node)
@@ -749,6 +749,11 @@ STEP 3: ADD KEY TO SERVICE ACCOUNT
749
749
  )
750
750
  warning_confirm_delete_pipe = prompt_message('Do you want to remove the pipe "{pipe}"?')
751
751
  warning_confirm_copy_pipe = prompt_message('Do you want to run a copy job from the pipe "{pipe}"?')
752
+ warning_confirm_on_demand_compute_differs_from_datafile = warning_message(
753
+ "The local datafile of pipe '{pipe}' does not set ON_DEMAND_COMPUTE true, but --on-demand-compute was passed.\n"
754
+ "On-demand compute uses dedicated resources that are billed separately from your regular usage.\n\n"
755
+ "Do you want to proceed?"
756
+ )
752
757
  warning_confirm_sink_job = prompt_message('Do you want to run a sink job from the pipe "{pipe}"?')
753
758
  warning_confirm_clear_workspace = prompt_message(
754
759
  "Do you want to remove all pipes and Data Sources from this workspace?"
@@ -16,7 +16,15 @@ from click import Context
16
16
  from tinybird import context
17
17
  from tinybird.client import AuthNoTokenException, DoesNotExistException, TinyB
18
18
  from tinybird.config import DEFAULT_API_HOST, FeatureFlags
19
- from tinybird.datafile_common import PipeNodeTypes, PipeTypes, folder_push, get_name_version, process_file, wait_job
19
+ from tinybird.datafile_common import (
20
+ PipeNodeTypes,
21
+ PipeTypes,
22
+ folder_push,
23
+ get_name_version,
24
+ local_copy_node_uses_on_demand_compute,
25
+ process_file,
26
+ wait_job,
27
+ )
20
28
  from tinybird.feedback_manager import FeedbackManager
21
29
  from tinybird.tb_cli_modules.branch import warn_if_in_live
22
30
  from tinybird.tb_cli_modules.cli import cli
@@ -749,30 +757,41 @@ async def pipe_copy_run(
749
757
 
750
758
  params = dict(key_value.split("=") for key_value in param) if param else {}
751
759
 
752
- if yes or click.confirm(FeedbackManager.warning_confirm_copy_pipe(pipe=pipe_name_or_id)):
753
- click.echo(FeedbackManager.info_copy_job_running(pipe=pipe_name_or_id))
754
- client: TinyB = ctx.ensure_object(dict)["client"]
760
+ if not yes and not click.confirm(FeedbackManager.warning_confirm_copy_pipe(pipe=pipe_name_or_id)):
761
+ return
755
762
 
756
- try:
757
- response = await client.pipe_run_copy(pipe_name_or_id, params, mode, on_demand_compute=on_demand_compute)
763
+ if (
764
+ on_demand_compute
765
+ and not yes
766
+ and local_copy_node_uses_on_demand_compute(".", pipe_name_or_id) is False
767
+ and not click.confirm(
768
+ FeedbackManager.warning_confirm_on_demand_compute_differs_from_datafile(pipe=pipe_name_or_id),
769
+ default=False,
770
+ )
771
+ ):
772
+ return
758
773
 
759
- job_id = response["job"]["job_id"]
760
- job_url = response["job"]["job_url"]
761
- target_datasource_id = response["tags"]["copy_target_datasource"]
762
- target_datasource = await client.get_datasource(target_datasource_id)
763
- target_datasource_name = target_datasource["name"]
764
- click.echo(
765
- FeedbackManager.success_copy_job_created(target_datasource=target_datasource_name, job_url=job_url)
766
- )
774
+ click.echo(FeedbackManager.info_copy_job_running(pipe=pipe_name_or_id))
775
+ client: TinyB = ctx.ensure_object(dict)["client"]
767
776
 
768
- if wait:
769
- await wait_job(client, job_id, job_url, "** Copying data")
770
- click.echo(FeedbackManager.success_data_copied_to_ds(target_datasource=target_datasource_name))
777
+ try:
778
+ response = await client.pipe_run_copy(pipe_name_or_id, params, mode, on_demand_compute=on_demand_compute)
771
779
 
772
- except AuthNoTokenException:
773
- raise
774
- except Exception as e:
775
- raise CLIPipeException(FeedbackManager.error_creating_copy_job(error=e))
780
+ job_id = response["job"]["job_id"]
781
+ job_url = response["job"]["job_url"]
782
+ target_datasource_id = response["tags"]["copy_target_datasource"]
783
+ target_datasource = await client.get_datasource(target_datasource_id)
784
+ target_datasource_name = target_datasource["name"]
785
+ click.echo(FeedbackManager.success_copy_job_created(target_datasource=target_datasource_name, job_url=job_url))
786
+
787
+ if wait:
788
+ await wait_job(client, job_id, job_url, "** Copying data")
789
+ click.echo(FeedbackManager.success_data_copied_to_ds(target_datasource=target_datasource_name))
790
+
791
+ except AuthNoTokenException:
792
+ raise
793
+ except Exception as e:
794
+ raise CLIPipeException(FeedbackManager.error_creating_copy_job(error=e))
776
795
 
777
796
 
778
797
  @pipe_copy.command(name="resume", short_help="Resume a paused copy pipe")