tinybird 4.6.22.dev0__tar.gz → 4.6.24.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.22.dev0 → tinybird-4.6.24.dev0}/PKG-INFO +11 -1
  2. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/pyproject.toml +29 -25
  3. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/client.py +0 -8
  4. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/sql_template.py +54 -25
  5. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/sql_template_fmt.py +2 -2
  6. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/syncasync.py +0 -43
  7. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/__cli__.py +2 -2
  8. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/client.py +0 -8
  9. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/common.py +3 -3
  10. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/create.py +2 -4
  11. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/pull.py +73 -3
  12. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datasource.py +1 -1
  13. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/deployment_common.py +4 -6
  14. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/exceptions.py +0 -21
  15. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/common.py +3 -3
  16. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/datasource.py +1 -1
  17. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tornado_template.py +38 -93
  18. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird.egg-info/PKG-INFO +11 -1
  19. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/setup.cfg +0 -0
  20. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/__cli__.py +0 -0
  21. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/ch_utils/constants.py +0 -0
  22. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/ch_utils/engine.py +0 -0
  23. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/check_pypi.py +0 -0
  24. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/config.py +0 -0
  25. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/context.py +0 -0
  26. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/datafile/common.py +0 -0
  27. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/datafile/exceptions.py +0 -0
  28. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/datafile/parse_connection.py +0 -0
  29. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/datafile/parse_datasource.py +0 -0
  30. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/datafile/parse_pipe.py +0 -0
  31. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/datatypes.py +0 -0
  32. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/feedback_manager.py +0 -0
  33. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/git_settings.py +0 -0
  34. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/iterating/__init__.py +0 -0
  35. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/iterating/data_branch_modes.py +0 -0
  36. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/prompts.py +0 -0
  37. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/service_datasources.py +0 -0
  38. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/sql.py +0 -0
  39. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/sql_toolset.py +0 -0
  40. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/check_pypi.py +0 -0
  41. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/cli.py +0 -0
  42. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/config.py +0 -0
  43. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/branch.py +0 -0
  44. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/build.py +0 -0
  45. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/build_common.py +0 -0
  46. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/cicd.py +0 -0
  47. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/cli.py +0 -0
  48. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/config.py +0 -0
  49. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/connection.py +0 -0
  50. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/connection_dynamodb.py +0 -0
  51. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/connection_kafka.py +0 -0
  52. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/connection_s3.py +0 -0
  53. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/copy.py +0 -0
  54. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/build.py +0 -0
  55. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/build_common.py +0 -0
  56. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  57. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  58. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/diff.py +0 -0
  59. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/fixture.py +0 -0
  60. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/format_common.py +0 -0
  61. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/format_connection.py +0 -0
  62. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  63. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  64. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  65. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/datafile/playground.py +0 -0
  66. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/deployment.py +0 -0
  67. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/deprecations.py +0 -0
  68. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/endpoint.py +0 -0
  69. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/feedback_manager.py +0 -0
  70. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/fmt.py +0 -0
  71. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/info.py +0 -0
  72. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/infra.py +0 -0
  73. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/job.py +0 -0
  74. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/job_common.py +0 -0
  75. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/llm.py +0 -0
  76. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/llm_utils.py +0 -0
  77. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/local.py +0 -0
  78. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/local_common.py +0 -0
  79. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/local_logs.py +0 -0
  80. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/login.py +0 -0
  81. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/login_common.py +0 -0
  82. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/logout.py +0 -0
  83. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/logs.py +0 -0
  84. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/materialization.py +0 -0
  85. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/open.py +0 -0
  86. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/pipe.py +0 -0
  87. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/preview.py +0 -0
  88. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/project.py +0 -0
  89. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/project_commands.py +0 -0
  90. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/py_project.py +0 -0
  91. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/query_output.py +0 -0
  92. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/regions.py +0 -0
  93. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/secret.py +0 -0
  94. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/secret_common.py +0 -0
  95. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/sink.py +0 -0
  96. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/table.py +0 -0
  97. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/telemetry.py +0 -0
  98. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/test.py +0 -0
  99. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/test_common.py +0 -0
  100. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  101. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/token.py +0 -0
  102. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/ts_project.py +0 -0
  103. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/watch.py +0 -0
  104. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/workspace.py +0 -0
  105. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb/modules/workspace_members.py +0 -0
  106. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli.py +0 -0
  107. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/auth.py +0 -0
  108. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/branch.py +0 -0
  109. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
  110. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/cli.py +0 -0
  111. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/config.py +0 -0
  112. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
  113. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
  114. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/fmt.py +0 -0
  115. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/job.py +0 -0
  116. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/pipe.py +0 -0
  117. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
  118. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/tag.py +0 -0
  119. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
  120. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/test.py +0 -0
  121. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  122. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/workspace.py +0 -0
  123. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  124. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/utils/__init__.py +0 -0
  125. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird/utils/bools.py +0 -0
  126. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird.egg-info/SOURCES.txt +0 -0
  127. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird.egg-info/dependency_links.txt +0 -0
  128. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird.egg-info/entry_points.txt +0 -0
  129. {tinybird-4.6.22.dev0 → tinybird-4.6.24.dev0}/tinybird.egg-info/requires.txt +0 -0
  130. {tinybird-4.6.22.dev0 → tinybird-4.6.24.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.22.dev0
3
+ Version: 4.6.24.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,16 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
52
52
  Changelog
53
53
  ----------
54
54
 
55
+ 4.6.22
56
+ ********
57
+
58
+ - `Added` ``tb pull`` demotes sink and copy pipes that exceed the workspace's sink/copy pipe plan limit to plain pipes instead of failing, stripping their ``TYPE``/``EXPORT_*``/``TARGET_DATASOURCE``/``COPY_SCHEDULE`` settings and warning that the plan must be upgraded to restore them.
59
+
60
+ 4.6.21
61
+ ********
62
+
63
+ - `Fixed` ``tb deployment discard`` now discards the deployment that is not live, instead of assuming the live deployment is listed first. Previously, if removing the old deployment failed after a promotion, the command refused to discard it.
64
+
55
65
  4.6.20
56
66
  ********
57
67
 
@@ -18,34 +18,35 @@ authors = [
18
18
  dependencies = [
19
19
  "GitPython==3.1.44",
20
20
  "Markdown==3.5.1",
21
- "aioboto3==14.0.0",
21
+ "aioboto3~=15.0",
22
22
  "aiofiles==24.1.0",
23
- "aiohttp==3.11.2",
24
- "anthropic==0.55.0",
23
+ "aiohttp~=3.11",
24
+ "anthropic~=1.9",
25
+ "async-lru==2.3.0",
25
26
  "aws-msk-iam-sasl-signer-python==1.0.2",
26
27
  "cachetools==5.5.0",
27
28
  "cffi==1.17.1",
28
29
  "chardet==4.0.0",
29
30
  "ciso8601==2.2.0",
30
- "click==8.1.8",
31
+ "click~=8.5",
31
32
  "clickhouse-cityhash==1.0.2.5",
32
33
  "clickhouse-driver==0.2.9",
33
34
  "confluent-kafka[avro,json,schemaregistry]==2.12.2",
34
35
  "contextvars==2.4",
35
36
  "cramjam==2.11.0",
36
37
  "croniter==6.2.2",
37
- "cryptography==3.4.8",
38
+ "cryptography~=38.0",
38
39
  "datasketch==1.6.5",
39
40
  "dnspython==2.8.0",
40
41
  "docker==7.1.0",
41
42
  "fastavro==1.12.2",
42
43
  "fastmcp==2.12.2",
43
- "google-cloud-aiplatform==1.148.1",
44
+ "google-cloud-aiplatform~=1.148",
44
45
  "google-cloud-compute==1.48.0",
45
46
  "google-cloud-scheduler==2.20.0",
46
47
  "google-cloud-secret-manager==2.29.0",
47
- "google-cloud-storage==3.4.1",
48
- "google_api_core==2.25.2",
48
+ "google-cloud-storage~=3.4",
49
+ "google_api_core~=2.25",
49
50
  "googleapis_common_protos==1.66.0",
50
51
  "grpcio_status==1.67.1",
51
52
  "humanfriendly==10.0",
@@ -68,7 +69,7 @@ dependencies = [
68
69
  "opentelemetry-sdk==1.38.0",
69
70
  "orb-billing==2.17.0",
70
71
  "orjson==3.11.5",
71
- "packaging==23.2",
72
+ "packaging~=26.3",
72
73
  "passlib==1.7.1",
73
74
  "posthog==7.28.0",
74
75
  "pre-commit==4.6.2",
@@ -82,10 +83,10 @@ dependencies = [
82
83
  "py-spy==0.4.2",
83
84
  "pyarrow==23.0.1",
84
85
  "pycurl==7.45.7",
85
- "pydantic-ai-slim[anthropic]==0.5.0",
86
- "pydantic-ai-slim[google]==0.5.0",
87
- "pydantic-ai-slim[retries]==0.5.0",
88
- "pydantic==2.12.5",
86
+ "pydantic-ai-slim[anthropic]~=2.0",
87
+ "pydantic-ai-slim[google]~=2.0",
88
+ "pydantic-ai-slim[retries]~=2.0",
89
+ "pydantic~=2.12",
89
90
  "pyjwt[crypto]==2.9.0",
90
91
  "pympler==1.1",
91
92
  "pynacl==1.5.0",
@@ -93,10 +94,10 @@ dependencies = [
93
94
  "python-dateutil==2.9.0.post0",
94
95
  "python-dotenv==1.1.0",
95
96
  "python-ulid==2.7.0",
96
- "pyyaml==6.0.1",
97
+ "pyyaml~=6.0",
97
98
  "redis[hiredis]==5.3.1",
98
99
  "requests==2.32.4",
99
- "sentry-sdk==2.69.1",
100
+ "sentry-sdk==2.69.2",
100
101
  "setproctitle==1.3.3",
101
102
  "shandy-sqlfmt==0.11.1",
102
103
  "shandy-sqlfmt[jinjafmt]==0.11.1",
@@ -107,7 +108,7 @@ dependencies = [
107
108
  "tabulate==0.8.10",
108
109
  "thefuzz==0.22.1",
109
110
  "tinybird-python-sdk[ai]==0.3.7",
110
- "tinybird-toolset==2.6.2",
111
+ "tinybird-toolset==2.6.6",
111
112
  "toposort==1.10",
112
113
  "tornado==6.5.5",
113
114
  "urllib3==1.26.20",
@@ -124,9 +125,9 @@ deploy = [
124
125
  "GitPython==3.1.44",
125
126
  "ansible==8.5.0",
126
127
  "ara==1.6.1",
127
- "boto3==1.36.21",
128
+ "boto3~=1.40",
128
129
  "fabric==3.2.2",
129
- "google-auth==2.47.0",
130
+ "google-auth~=2.47",
130
131
  "jsonnet",
131
132
  "prettytable==3.8.0",
132
133
  "requests==2.32.4",
@@ -141,7 +142,7 @@ devtools = [
141
142
  ]
142
143
  doc = [
143
144
  "Pallets-Sphinx-Themes==2.3.0",
144
- "pyOpenSSL<24.0.0",
145
+ "pyOpenSSL~=23.2",
145
146
  "sphinx-autobuild==2024.2.4",
146
147
  "sphinx-sitemap",
147
148
  "sphinx==7.3.7",
@@ -149,15 +150,15 @@ doc = [
149
150
  "sphinxcontrib-jquery==4.1",
150
151
  ]
151
152
  lint = [
152
- "botocore-stubs==1.36.23",
153
+ "botocore-stubs~=1.40",
153
154
  "flake8~=7.3",
154
155
  "flake8-async~=26.8",
155
156
  "mypy==2.3.1",
156
- "ruff==0.16.8",
157
+ "ruff==0.16.9",
157
158
  "types-Markdown",
158
159
  "types-PyYAML",
159
160
  "types-aiofiles==24.1.0.20241221",
160
- "types-boto3-lite==1.36.21",
161
+ "types-boto3-lite~=1.40",
161
162
  "types-cachetools",
162
163
  "types-chardet",
163
164
  "types-paramiko",
@@ -171,18 +172,19 @@ lint = [
171
172
  test = [
172
173
  "coverage==7.10.6",
173
174
  "freezegun==1.5.5",
174
- "google-auth==2.47.0",
175
+ "google-auth~=2.47",
175
176
  "gsutil==4.58",
177
+ "httpx2~=2.13",
176
178
  "hypothesis==6.156.1",
177
179
  "oauth2client==3.0.0",
178
180
  "py==1.11.0",
179
- "pytest-asyncio==0.23.8",
181
+ "pytest-asyncio~=1.4",
180
182
  "pytest-cov==4.1.0",
181
183
  "pytest-env==1.1.3",
182
184
  "pytest-flakefinder==1.1.0",
183
185
  "pytest-timeout==2.3.1",
184
186
  "pytest-xdist==3.6.1",
185
- "pytest==8.1.2",
187
+ "pytest~=8.1",
186
188
  "rangehttpserver==1.4.0",
187
189
  "yappi==1.7.6",
188
190
  ]
@@ -204,6 +206,8 @@ colibri = "tinybird.colibri.cli:cli"
204
206
  members = [
205
207
  "chbackups",
206
208
  "data_connector",
209
+ "gatherer",
210
+ "tinybird_orchestrator",
207
211
  ]
208
212
 
209
213
  [tool.setuptools]
@@ -47,14 +47,6 @@ class TimeoutException(Exception):
47
47
  pass
48
48
 
49
49
 
50
- class ReachRetryLimit(Exception):
51
- pass
52
-
53
-
54
- class ConnectorNothingToLoad(Exception):
55
- pass
56
-
57
-
58
50
  class JobException(Exception):
59
51
  pass
60
52
 
@@ -1,5 +1,4 @@
1
1
  import ast
2
- import builtins
3
2
  import linecache
4
3
  import logging
5
4
  import re
@@ -1336,8 +1335,10 @@ sqlescape_for_string_expression = sqlescape_generator(
1336
1335
  )
1337
1336
 
1338
1337
 
1339
- def escape_single_quote_str(s):
1340
- return "'" + s.replace("'", "''") + "'"
1338
+ def escape_single_quote_str(s: str) -> str:
1339
+ # ClickHouse string literals treat backslash as an escape char, so it must be doubled too:
1340
+ # otherwise a trailing backslash escapes the closing quote and the following input becomes SQL
1341
+ return "'" + s.replace("\\", "\\\\").replace("'", "''") + "'"
1341
1342
 
1342
1343
 
1343
1344
  def expression_wrapper(x, name, escape_arrays: bool = False):
@@ -1385,16 +1386,16 @@ _namespace = {
1385
1386
  "close": None,
1386
1387
  "print": None,
1387
1388
  "input": None,
1388
- # Keep customer parameters named `type`/`dir` usable without restoring the unsafe builtins.
1389
- "type": None,
1390
- "dir": None,
1391
1389
  }
1392
1390
 
1393
1391
 
1394
- reserved_vars = {"_tt_tmp", "_tt_append", "isinstance", "str", "error", "custom_error", *list(vars(builtins))}
1392
+ # Built from the builtins templates can actually resolve: names missing from SAFE_BUILTINS (`type`, `dir`,
1393
+ # `property`, `object`, ...) can only be template parameters, so they must be discovered as such to get a
1394
+ # value (None or a test-mode placeholder) instead of raising NameError.
1395
+ reserved_vars = {"_tt_tmp", "_tt_append", "isinstance", "str", "error", "custom_error", *SAFE_BUILTINS}
1395
1396
  for p in DEFAULT_PARAM_NAMES: # we handle these in an specific manner
1396
1397
  reserved_vars.discard(p) # `format` is part of builtins
1397
- # Allow 'id' to be used as a template parameter - https://gitlab.com/tinybird/analytics/-/issues/19119
1398
+ # Allow 'id' as a template parameter name despite being a builtin - https://gitlab.com/tinybird/analytics/-/issues/19119
1398
1399
  reserved_vars.discard("id")
1399
1400
  error_vars = ["error", "custom_error"]
1400
1401
 
@@ -1471,8 +1472,7 @@ def generate(self, **kwargs) -> Tuple[str, TemplateExecutionResults]:
1471
1472
  return Expression(f"-- disable {feature}\n")
1472
1473
 
1473
1474
  namespace.update(_namespace)
1474
- # This is to fix the issue https://gitlab.com/tinybird/analytics/-/issues/19119
1475
- # We need to do the override here because if we modify the _namespace, we would filter out parameters from the users
1475
+ # Default it here, not in _namespace, so it stays visible to variable extraction.
1476
1476
  namespace.update({"id": None})
1477
1477
  namespace.update(kwargs)
1478
1478
  namespace.update(
@@ -1822,7 +1822,7 @@ def get_var_names(t: Template):
1822
1822
  >>> [v['name'] for v in get_var_names(Template("{% for name in secret_names %}{{tb_secret(name)}}{% end %}"))]
1823
1823
  ['secret_names', 'name', 'tb_secret', 'name']
1824
1824
 
1825
- Type casting with date function in elif chain (type is in reserved_vars):
1825
+ Type casting with date function in elif chain:
1826
1826
  >>> t = Template("{% if kind == 'int' %}{{Int32(val, 0)}}{% elif kind == 'date' %}{{DateTime(val)}}{% elif kind == 'diff' %}{{date_diff_in_days(val, now)}}{% end %}")
1827
1827
  >>> [v['name'] for v in get_var_names(t)]
1828
1828
  ['kind', 'Int32', 'val', 'kind', 'DateTime', 'val', 'kind', 'date_diff_in_days', 'val', 'now']
@@ -2374,7 +2374,24 @@ def wrap_vars(t, escape_arrays: bool = False):
2374
2374
  return var
2375
2375
 
2376
2376
 
2377
- def get_used_tables_in_template(sql):
2377
+ def _constant_table_name(node: ast.expr) -> Optional[str]:
2378
+ """Value of a constant table() argument: a literal or a concatenation of string literals."""
2379
+ if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
2380
+ return str(node.value)
2381
+ return _string_literal(node)
2382
+
2383
+
2384
+ def _string_literal(node: ast.expr) -> Optional[str]:
2385
+ if isinstance(node, ast.Constant) and isinstance(node.value, str):
2386
+ return node.value
2387
+ if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
2388
+ left, right = _string_literal(node.left), _string_literal(node.right)
2389
+ if left is not None and right is not None:
2390
+ return left + right
2391
+ return None
2392
+
2393
+
2394
+ def get_used_tables_in_template(sql: str) -> List[str]:
2378
2395
  """
2379
2396
  >>> get_used_tables_in_template("select * from {{table('test')}}")
2380
2397
  ['test']
@@ -2388,30 +2405,42 @@ def get_used_tables_in_template(sql):
2388
2405
  []
2389
2406
  >>> get_used_tables_in_template("select * from {{table('my.test')}}, {{table('another.one')}}")
2390
2407
  ['my.test', 'another.one']
2408
+
2409
+ Only constant table names can be known without rendering, so anything else is skipped
2410
+ >>> get_used_tables_in_template("select * from {{table(tbl)}}, {{TABLE('b')}}, {{table('my.' + 'c')}}")
2411
+ ['b', 'my.c']
2391
2412
  """
2413
+ return list(_get_used_tables_in_template_cached(sql))
2414
+
2415
+
2416
+ # Building the Template dominates the cost and the result only depends on the SQL, which deployments and
2417
+ # /v0/sql pipelines scan repeatedly
2418
+ @lru_cache(maxsize=2**10)
2419
+ def _get_used_tables_in_template_cached(sql: str) -> Tuple[str, ...]:
2392
2420
  try:
2393
2421
  t = Template(sql)
2394
2422
 
2395
2423
  def _n(chunks, tables):
2396
2424
  for x in chunks:
2397
2425
  if type(x).__name__ == "_Expression":
2398
- c = compile(x.expression, "<string>", "exec", dont_inherit=True)
2399
- v = [x.lower() for x in c.co_names if x not in _namespace and x not in reserved_vars]
2400
- if "table" in v:
2401
-
2402
- def _t(*args, **kwargs):
2403
- return str(args[0])
2404
-
2405
- n = {"table": _t, "TABLE": _t}
2406
- e = "_tt_tmp = %s" % x.expression
2407
- exec_in(e, n)
2408
- tables += [n["_tt_tmp"]]
2426
+ # Read the names from the AST instead of executing the expression: this runs outside
2427
+ # generate(), without the restricted builtins, on every pipe, query and deployment.
2428
+ for node in ast.walk(ast.parse(x.expression)):
2429
+ if (
2430
+ isinstance(node, ast.Call)
2431
+ and isinstance(node.func, ast.Name)
2432
+ and node.func.id in ("table", "TABLE")
2433
+ and node.args
2434
+ ):
2435
+ name = _constant_table_name(node.args[0])
2436
+ if name is not None:
2437
+ tables.append(name)
2409
2438
  elif type(x).__name__ == "_ControlBlock":
2410
2439
  _n(x.body.chunks, tables)
2411
2440
 
2412
- tables = []
2441
+ tables: List[str] = []
2413
2442
  _n(t.file.body.chunks, tables)
2414
- return tables
2443
+ return tuple(tables)
2415
2444
  except SecurityException as e:
2416
2445
  raise SQLTemplateException(e)
2417
2446
 
@@ -214,8 +214,8 @@ class TinybirdDialect(ClickHouse):
214
214
  ],
215
215
  }
216
216
 
217
- for section in override_rules:
218
- for rule in override_rules[section]:
217
+ for section, rules in override_rules.items():
218
+ for rule in rules:
219
219
  for rr in self.RULES[section]:
220
220
  if rr.name == rule.name:
221
221
  self.RULES[section].remove(rr)
@@ -260,49 +260,6 @@ def _iscoroutinefunction_or_partial(func: Any) -> bool:
260
260
  return asyncio.iscoroutinefunction(func)
261
261
 
262
262
 
263
- class ThreadSensitiveContext:
264
- """Async context manager to manage context for thread sensitive mode
265
- This context manager controls which thread pool executor is used when in
266
- thread sensitive mode. By default, a single thread pool executor is shared
267
- within a process.
268
- In Python 3.7+, the ThreadSensitiveContext() context manager may be used to
269
- specify a thread pool per context.
270
- In Python 3.6, usage of this context manager has no effect.
271
- This context manager is re-entrant, so only the outer-most call to
272
- ThreadSensitiveContext will set the context.
273
- """
274
-
275
- def __init__(self):
276
- self.token = None
277
-
278
- if contextvars:
279
-
280
- async def __aenter__(self):
281
- try:
282
- SyncToAsync.thread_sensitive_context.get()
283
- except LookupError:
284
- self.token = SyncToAsync.thread_sensitive_context.set(self)
285
-
286
- return self
287
-
288
- async def __aexit__(self, exc, value, tb):
289
- if not self.token:
290
- return
291
-
292
- executor = SyncToAsync.context_to_thread_executor.pop(self, None)
293
- if executor:
294
- executor.shutdown()
295
- SyncToAsync.thread_sensitive_context.reset(self.token)
296
-
297
- else:
298
-
299
- async def __aenter__(self):
300
- return self
301
-
302
- async def __aexit__(self, exc, value, tb):
303
- pass
304
-
305
-
306
263
  class AsyncToSync:
307
264
  """
308
265
  Utility class which turns an awaitable that only works on the thread with
@@ -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.22.dev0'
8
- __revision__ = '3a75e0ef'
7
+ __version__ = '4.6.24.dev0'
8
+ __revision__ = '5269fe22'
@@ -58,14 +58,6 @@ class TimeoutException(Exception):
58
58
  pass
59
59
 
60
60
 
61
- class ReachRetryLimit(Exception):
62
- pass
63
-
64
-
65
- class ConnectorNothingToLoad(Exception):
66
- pass
67
-
68
-
69
61
  class JobException(Exception):
70
62
  pass
71
63
 
@@ -1277,11 +1277,11 @@ class ConnectionReplacements:
1277
1277
  """Maps prompt parameters to API parameters."""
1278
1278
 
1279
1279
  api_params = {}
1280
- for key in params.keys():
1280
+ for key, value in params.items():
1281
1281
  try:
1282
- api_params[ConnectionReplacements._PARAMS_REPLACEMENTS[service][key]] = params[key]
1282
+ api_params[ConnectionReplacements._PARAMS_REPLACEMENTS[service][key]] = value
1283
1283
  except KeyError:
1284
- api_params[key] = params[key]
1284
+ api_params[key] = value
1285
1285
 
1286
1286
  api_params["service"] = service
1287
1287
  return api_params
@@ -215,14 +215,12 @@ def create_project_structure(folder: str):
215
215
  "connections →": "Connect to and ingest data from popular sources: Kafka, S3 or GCS.",
216
216
  }
217
217
 
218
- for x in PROJECT_PATHS_DESCRIPTIONS.keys():
218
+ for x, description in PROJECT_PATHS_DESCRIPTIONS.items():
219
219
  try:
220
220
  path = x.split("→")[0].strip()
221
221
  f = folder_path / path
222
222
  f.mkdir()
223
- click.echo(
224
- FeedbackManager.info(message=f"./{x} ") + FeedbackManager.gray(message=PROJECT_PATHS_DESCRIPTIONS[x])
225
- )
223
+ click.echo(FeedbackManager.info(message=f"./{x} ") + FeedbackManager.gray(message=description))
226
224
  except FileExistsError:
227
225
  pass
228
226
 
@@ -1,8 +1,11 @@
1
+ from datetime import datetime
1
2
  from pathlib import Path
2
3
  from typing import Any, Optional
3
4
 
4
5
  import click
5
6
 
7
+ from tinybird.datafile.common import PipeNodeTypes
8
+ from tinybird.datafile.parse_pipe import parse_pipe
6
9
  from tinybird.tb.client import AuthNoTokenException, TinyB
7
10
  from tinybird.tb.modules.datafile.format_datasource import format_datasource
8
11
  from tinybird.tb.modules.datafile.format_pipe import format_pipe
@@ -10,6 +13,52 @@ from tinybird.tb.modules.feedback_manager import FeedbackManager
10
13
 
11
14
  PULL_EXISTING_FILE_MAX_DEPTH = 5
12
15
 
16
+ DEMOTABLE_NODE_TYPES = {
17
+ PipeNodeTypes.DATA_SINK: ("sink_pipes", "Sink"),
18
+ PipeNodeTypes.COPY: ("copy_pipes", "Copy"),
19
+ }
20
+
21
+
22
+ def _parse_created_at(value: Any) -> datetime:
23
+ try:
24
+ return datetime.fromisoformat(str(value))
25
+ except ValueError:
26
+ return datetime.min
27
+
28
+
29
+ def pipes_over_node_type_limit(pipes: list[dict[str, Any]], node_type: str, limit: Optional[int]) -> set[str]:
30
+ if limit is None:
31
+ return set()
32
+
33
+ matching_pipes = [p for p in pipes if p.get("type") == node_type]
34
+ if len(matching_pipes) <= limit:
35
+ return set()
36
+
37
+ matching_pipes.sort(key=lambda p: _parse_created_at(p.get("created_at")))
38
+ return {p["name"] for p in matching_pipes[limit:]}
39
+
40
+
41
+ def pipes_over_plan_limits(pipes: list[dict[str, Any]], limits: dict[str, Any]) -> dict[str, str]:
42
+ demoted: dict[str, str] = {}
43
+ for node_type, (limits_key, _label) in DEMOTABLE_NODE_TYPES.items():
44
+ limit = limits.get(limits_key, {}).get("max")
45
+ for name in pipes_over_node_type_limit(pipes, node_type, limit):
46
+ demoted[name] = node_type
47
+ return demoted
48
+
49
+
50
+ def demote_pipe_content(name: str, content: str, node_type: str) -> str:
51
+ datafile = parse_pipe(name, content=content, replace_includes=False).datafile
52
+ demoted = False
53
+ for node in datafile.nodes:
54
+ if (node.get("type") or "").lower() == node_type:
55
+ node.pop("type", None)
56
+ demoted = True
57
+
58
+ if not demoted:
59
+ return content
60
+ return format_pipe(name, datafile=datafile)
61
+
13
62
 
14
63
  def folder_pull(
15
64
  client: TinyB,
@@ -61,6 +110,7 @@ def folder_pull(
61
110
  get_resource_function: str,
62
111
  progress_bar: bool = False,
63
112
  fmt: bool = False,
113
+ demoted_pipe_types: Optional[dict[str, str]] = None,
64
114
  ) -> list[Path]:
65
115
  def write_resource(k: dict[str, Any]) -> Optional[Path]:
66
116
  name = f"{k['name']}.{extension}"
@@ -73,8 +123,19 @@ def folder_pull(
73
123
  else:
74
124
  resource = getattr(client, get_resource_function)(k["name"])
75
125
  resource_to_write = resource
76
-
77
- if fmt:
126
+ resource_type = k.get("type")
127
+
128
+ demoted_node_type = demoted_pipe_types.get(k["name"]) if demoted_pipe_types else None
129
+ if demoted_node_type:
130
+ resource_to_write = demote_pipe_content(name, resource, demoted_node_type)
131
+ resource_type = None
132
+ label = DEMOTABLE_NODE_TYPES[demoted_node_type][1]
133
+ warnings.append(
134
+ f"{label} pipe '{k['name']}' exceeds the {demoted_node_type} pipe limit of your current "
135
+ f"plan and was pulled as a plain pipe without its {demoted_node_type} configuration. "
136
+ "Upgrade your plan and reconfigure it to restore it."
137
+ )
138
+ elif fmt:
78
139
  if extension == "datasource":
79
140
  resource_to_write = format_datasource(name, content=resource)
80
141
  elif extension == "pipe":
@@ -85,7 +146,7 @@ def folder_pull(
85
146
  dest_folder = Path(folder) / "vendor" / k["name"].split(".", 1)[0]
86
147
  name = f"{k['name'].split('.', 1)[1]}.{extension}"
87
148
 
88
- file_folder = get_file_folder(extension, k.get("type"))
149
+ file_folder = get_file_folder(extension, resource_type)
89
150
  f = Path(dest_folder) / file_folder if file_folder is not None else Path(dest_folder)
90
151
 
91
152
  existing_file = find_existing_resource_file(Path(dest_folder), name)
@@ -128,6 +189,14 @@ def folder_pull(
128
189
  pipes = client.pipes()
129
190
  connections = client.connections()
130
191
 
192
+ demoted_pipe_types: dict[str, str] = {}
193
+ if not only_vendored:
194
+ try:
195
+ limits = client.workspace_info().get("limits", {})
196
+ except Exception:
197
+ limits = {}
198
+ demoted_pipe_types = pipes_over_plan_limits(pipes, limits)
199
+
131
200
  if only_vendored:
132
201
  written_files = write_files(
133
202
  resources=[ds for ds in datasources if "shared_from" in ds],
@@ -154,6 +223,7 @@ def folder_pull(
154
223
  get_resource_function="pipe_file",
155
224
  progress_bar=progress_bar,
156
225
  fmt=fmt,
226
+ demoted_pipe_types=demoted_pipe_types,
157
227
  )
158
228
  )
159
229
  written_files.extend(
@@ -677,7 +677,7 @@ def datasource_start(ctx: Context, datasource_name: str) -> None:
677
677
 
678
678
  @datasource.command(name="delete")
679
679
  @click.argument("datasource_name")
680
- @click.option("--sql-condition", default=None, help="SQL WHERE condition to remove rows", hidden=True, required=True)
680
+ @click.option("--sql-condition", help="SQL WHERE condition to remove rows", hidden=True, required=True)
681
681
  @click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
682
682
  @click.option(
683
683
  "--wait/--no-wait",
@@ -335,13 +335,12 @@ def discard_deployment(host: Optional[str], headers: dict, wait: bool, request_f
335
335
  click.echo(FeedbackManager.error(message="Only one deployment found. Cannot discard the only deployment."))
336
336
  return
337
337
 
338
- current_deployment, deployment_to_discard = deployments[0], deployments[1]
338
+ live_deployment = next((deployment for deployment in deployments if deployment.get("live")), deployments[0])
339
+ deployment_to_discard = next(deployment for deployment in deployments if deployment is not live_deployment)
339
340
 
340
- # NOTE(eclbg): we never get here. We wrote this code when we though we'd enable promoting back and forth between
341
- # staging and live, but the current CLI commands don't allow getting in that state
342
- if current_deployment.get("status") != "data_ready":
341
+ if live_deployment.get("status") != "data_ready":
343
342
  click.echo(FeedbackManager.error(message="Previous deployment is not ready"))
344
- deploy_errors = current_deployment.get("errors", [])
343
+ deploy_errors = live_deployment.get("errors", [])
345
344
  for deploy_error in deploy_errors:
346
345
  click.echo(FeedbackManager.error(message=f"* {deploy_error}"))
347
346
  return
@@ -359,7 +358,6 @@ def discard_deployment(host: Optional[str], headers: dict, wait: bool, request_f
359
358
  click.echo(FeedbackManager.error(message=result.get("error")))
360
359
  sys_exit("deployment_error", result.get("error", "Unknown error"))
361
360
 
362
- deployment_to_discard = deployments[1]
363
361
  click.echo(FeedbackManager.success(message="Discard process successfully started"))
364
362
 
365
363
  if wait:
@@ -92,20 +92,6 @@ class CLITokenException(CLIException):
92
92
  super().__init__(message, "token_error", **kw_telemetry_event_data)
93
93
 
94
94
 
95
- class CLIBuildException(CLIException):
96
- """Exceptions generated by the build commands"""
97
-
98
- def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
99
- super().__init__(message, "build_error", **kw_telemetry_event_data)
100
-
101
-
102
- class CLIDeploymentException(CLIException):
103
- """Exceptions generated by the deployment commands"""
104
-
105
- def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
106
- super().__init__(message, "deployment_error", **kw_telemetry_event_data)
107
-
108
-
109
95
  class CLITestException(CLIException):
110
96
  """Exceptions generated by the test commands"""
111
97
 
@@ -127,13 +113,6 @@ class CLIMockException(CLIException):
127
113
  super().__init__(message, "mock_error", **kw_telemetry_event_data)
128
114
 
129
115
 
130
- class CLIAgentException(CLIException):
131
- """Exceptions generated by the agent commands"""
132
-
133
- def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
134
- super().__init__(message, "agent_error", **kw_telemetry_event_data)
135
-
136
-
137
116
  class CLILoginException(CLIException):
138
117
  """Exceptions generated by the login commands"""
139
118