tinybird 0.0.1.dev102__tar.gz → 0.0.1.dev104__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.

Potentially problematic release.


This version of tinybird might be problematic. Click here for more details.

Files changed (109) hide show
  1. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/PKG-INFO +2 -19
  2. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/feedback_manager.py +6 -0
  3. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/sql_template.py +21 -2
  4. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/__cli__.py +2 -2
  5. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/cicd.py +1 -1
  6. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/cli.py +0 -41
  7. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/common.py +39 -5
  8. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/create.py +2 -2
  9. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/common.py +10 -1
  10. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/deployment.py +45 -35
  11. tinybird-0.0.1.dev104/tinybird/tb/modules/infra.py +852 -0
  12. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/local.py +30 -0
  13. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/workspace.py +4 -25
  14. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/connection.py +8 -2
  15. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird.egg-info/PKG-INFO +2 -19
  16. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird.egg-info/requires.txt +1 -19
  17. tinybird-0.0.1.dev102/tinybird/tb/modules/infra.py +0 -624
  18. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/setup.cfg +0 -0
  19. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/__cli__.py +0 -0
  20. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/ch_utils/constants.py +0 -0
  21. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/ch_utils/engine.py +0 -0
  22. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/check_pypi.py +0 -0
  23. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/client.py +0 -0
  24. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/config.py +0 -0
  25. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/connectors.py +0 -0
  26. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/context.py +0 -0
  27. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/datafile.py +0 -0
  28. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/datatypes.py +0 -0
  29. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/git_settings.py +0 -0
  30. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/prompts.py +0 -0
  31. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/sql.py +0 -0
  32. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/sql_template_fmt.py +0 -0
  33. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/sql_toolset.py +0 -0
  34. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/syncasync.py +0 -0
  35. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/cli.py +0 -0
  36. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/auth.py +0 -0
  37. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/build.py +0 -0
  38. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/config.py +0 -0
  39. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/connection.py +0 -0
  40. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/copy.py +0 -0
  41. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/build.py +0 -0
  42. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/build_common.py +0 -0
  43. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  44. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  45. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/diff.py +0 -0
  46. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  47. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/fixture.py +0 -0
  48. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/format_common.py +0 -0
  49. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  50. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  51. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  52. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  53. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  54. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/playground.py +0 -0
  55. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datafile/pull.py +0 -0
  56. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/datasource.py +0 -0
  57. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/endpoint.py +0 -0
  58. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/exceptions.py +0 -0
  59. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/feedback_manager.py +0 -0
  60. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/fmt.py +0 -0
  61. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/job.py +0 -0
  62. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/llm.py +0 -0
  63. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/llm_utils.py +0 -0
  64. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/local_common.py +0 -0
  65. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/login.py +0 -0
  66. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/logout.py +0 -0
  67. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/materialization.py +0 -0
  68. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/mock.py +0 -0
  69. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/open.py +0 -0
  70. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/pipe.py +0 -0
  71. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/playground.py +0 -0
  72. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/project.py +0 -0
  73. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/regions.py +0 -0
  74. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/secret.py +0 -0
  75. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/shell.py +0 -0
  76. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/table.py +0 -0
  77. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/tag.py +0 -0
  78. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/telemetry.py +0 -0
  79. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/test.py +0 -0
  80. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  81. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  82. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/token.py +0 -0
  83. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/watch.py +0 -0
  84. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb/modules/workspace_members.py +0 -0
  85. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli.py +0 -0
  86. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/auth.py +0 -0
  87. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/branch.py +0 -0
  88. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/cicd.py +0 -0
  89. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/cli.py +0 -0
  90. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/common.py +0 -0
  91. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/config.py +0 -0
  92. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/datasource.py +0 -0
  93. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/exceptions.py +0 -0
  94. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/fmt.py +0 -0
  95. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/job.py +0 -0
  96. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/pipe.py +0 -0
  97. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/regions.py +0 -0
  98. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/tag.py +0 -0
  99. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/telemetry.py +0 -0
  100. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/test.py +0 -0
  101. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  102. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  103. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/workspace.py +0 -0
  104. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  105. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird/tornado_template.py +0 -0
  106. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird.egg-info/SOURCES.txt +0 -0
  107. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird.egg-info/dependency_links.txt +0 -0
  108. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/tinybird.egg-info/entry_points.txt +0 -0
  109. {tinybird-0.0.1.dev102 → tinybird-0.0.1.dev104}/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.dev102
3
+ Version: 0.0.1.dev104
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -9,6 +9,7 @@ Requires-Python: >=3.9, <3.14
9
9
  Description-Content-Type: text/x-rst
10
10
  Requires-Dist: aiofiles==24.1.0
11
11
  Requires-Dist: anthropic==0.42.0
12
+ Requires-Dist: boto3
12
13
  Requires-Dist: click<8.2,>=8.1.6
13
14
  Requires-Dist: clickhouse-toolset==0.34.dev0
14
15
  Requires-Dist: colorama==0.4.6
@@ -32,29 +33,11 @@ Requires-Dist: wheel
32
33
  Requires-Dist: packaging<24,>=23.1
33
34
  Requires-Dist: llm>=0.19
34
35
  Requires-Dist: thefuzz==0.22.1
35
- Provides-Extra: bigquery
36
- Requires-Dist: gsutil==4.58; extra == "bigquery"
37
- Requires-Dist: google-api-python-client==2.0.2; extra == "bigquery"
38
- Requires-Dist: google-auth==1.27.1; extra == "bigquery"
39
- Requires-Dist: google-auth-httplib2==0.1.0; extra == "bigquery"
40
- Requires-Dist: google-cloud-storage==2.4.0; extra == "bigquery"
41
- Requires-Dist: google-cloud-bigquery==2.11.0; extra == "bigquery"
42
- Provides-Extra: snowflake
43
- Requires-Dist: snowflake-connector-python~=3.12.3; extra == "snowflake"
44
- Requires-Dist: gsutil==4.58; extra == "snowflake"
45
- Requires-Dist: google-api-python-client==2.0.2; extra == "snowflake"
46
- Requires-Dist: google-auth==1.27.1; extra == "snowflake"
47
- Requires-Dist: google-auth-httplib2==0.1.0; extra == "snowflake"
48
- Requires-Dist: google-cloud-storage==2.4.0; extra == "snowflake"
49
- Requires-Dist: oauth2client==3.0.0; extra == "snowflake"
50
- Requires-Dist: chardet<4,>=3.0.2; extra == "snowflake"
51
- Requires-Dist: pyOpenSSL<20.0.0,>=16.2.0; extra == "snowflake"
52
36
  Dynamic: author
53
37
  Dynamic: author-email
54
38
  Dynamic: description
55
39
  Dynamic: description-content-type
56
40
  Dynamic: home-page
57
- Dynamic: provides-extra
58
41
  Dynamic: requires-dist
59
42
  Dynamic: requires-python
60
43
  Dynamic: summary
@@ -651,6 +651,12 @@ Ready? """
651
651
  warning_tag_remove = prompt_message(
652
652
  "Tag {tag_name} is used by {resources_len} resources. Do you want to remove it?"
653
653
  )
654
+ warning_bigquery_connector_deprecated = warning_message(
655
+ "🚨🚨🚨 [DEPRECATED] BigQuery connector is deprecated. Use ingestion from GCS instead. https://www.tinybird.co/docs/get-data-in/guides/ingest-from-bigquery-using-google-cloud-storage **"
656
+ )
657
+ warning_s3_access_key_secret_deprecated = warning_message(
658
+ "🚨🚨🚨 [DEPRECATED] S3 (Access Key + Secret) connector is deprecated. Use S3 IAM role instead. https://www.tinybird.co/docs/get-data-in/connectors/s3 **"
659
+ )
654
660
 
655
661
  info_fixtures_branch = info_message("** Data Fixtures are only pushed to Branches")
656
662
  info_materialize_push_datasource_exists = warning_message("** Data Source {name} already exists")
@@ -2427,6 +2427,20 @@ def render_template_with_secrets(name: str, content: str, secrets: Optional[Dict
2427
2427
  ... )
2428
2428
  'KAFKA_BOOTSTRAP_SERVERS localhost:9092'
2429
2429
 
2430
+ >>> render_template_with_secrets(
2431
+ ... "my_kafka_connection",
2432
+ ... "KAFKA_BOOTSTRAP_SERVERS {{ tb_secret('MISSING_SECRET', '') }}",
2433
+ ... secrets = {}
2434
+ ... )
2435
+ 'KAFKA_BOOTSTRAP_SERVERS ""'
2436
+
2437
+ >>> render_template_with_secrets(
2438
+ ... "my_kafka_connection",
2439
+ ... "KAFKA_BOOTSTRAP_SERVERS {{ tb_secret('MISSING_SECRET', 0) }}",
2440
+ ... secrets = {}
2441
+ ... )
2442
+ 'KAFKA_BOOTSTRAP_SERVERS 0'
2443
+
2430
2444
  >>> render_template_with_secrets(
2431
2445
  ... "my_kafka_connection",
2432
2446
  ... "KAFKA_BOOTSTRAP_SERVERS {{ tb_secret('MISSING_SECRET') }}",
@@ -2453,8 +2467,13 @@ def render_template_with_secrets(name: str, content: str, secrets: Optional[Dict
2453
2467
  SQLTemplateException: If the secret is not found and no default is provided
2454
2468
  """
2455
2469
  if secret_name in secrets:
2456
- return secrets[secret_name]
2470
+ value = secrets[secret_name]
2471
+ if isinstance(value, str) and len(value) == 0:
2472
+ return '""'
2473
+ return value
2457
2474
  elif default is not None:
2475
+ if isinstance(default, str) and len(default) == 0:
2476
+ return '""'
2458
2477
  return default
2459
2478
  else:
2460
2479
  raise SQLTemplateException(
@@ -2462,7 +2481,7 @@ def render_template_with_secrets(name: str, content: str, secrets: Optional[Dict
2462
2481
  )
2463
2482
 
2464
2483
  # Create the template
2465
- t = Template(content, name=name)
2484
+ t = Template(content, name=name, autoescape=None)
2466
2485
 
2467
2486
  try:
2468
2487
  # Create namespace with our tb_secret function
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev102'
8
- __revision__ = '627c8ef'
7
+ __version__ = '0.0.1.dev104'
8
+ __revision__ = 'f6c1941'
@@ -55,7 +55,7 @@ jobs:
55
55
  - name: Test project
56
56
  run: tb test run
57
57
  - name: Deployment check
58
- run: tb --cloud --host ${{! TINYBIRD_HOST }} --token ${{! TINYBIRD_TOKEN }} deploy --check
58
+ run: tb --cloud --host ${{! env.TINYBIRD_HOST }} --token ${{! env.TINYBIRD_TOKEN }} deploy --check
59
59
  """
60
60
 
61
61
 
@@ -158,47 +158,6 @@ async def pull(ctx: Context, force: bool, fmt: bool) -> None:
158
158
  return await folder_pull(client, project.path, force, fmt=fmt)
159
159
 
160
160
 
161
- @cli.command(hidden=True)
162
- @click.option("--no-deps", is_flag=True, default=False, help="Print only data sources with no pipes using them")
163
- @click.option("--match", default=None, help="Retrieve any resource matching the pattern")
164
- @click.option("--pipe", default=None, help="Retrieve any resource used by pipe")
165
- @click.option("--datasource", default=None, help="Retrieve resources depending on this data source")
166
- @click.option(
167
- "--check-for-partial-replace",
168
- is_flag=True,
169
- default=False,
170
- help="Retrieve dependant data sources that will have their data replaced if a partial replace is executed in the data source selected",
171
- )
172
- @click.option("--recursive", is_flag=True, default=False, help="Calculate recursive dependencies")
173
- @click.pass_context
174
- @coro
175
- async def dependencies(
176
- ctx: Context,
177
- no_deps: bool,
178
- match: Optional[str],
179
- pipe: Optional[str],
180
- datasource: Optional[str],
181
- check_for_partial_replace: bool,
182
- recursive: bool,
183
- ) -> None:
184
- """Print all data sources dependencies."""
185
-
186
- client = ctx.ensure_object(dict)["client"]
187
-
188
- response = await client.datasource_dependencies(
189
- no_deps, match, pipe, datasource, check_for_partial_replace, recursive
190
- )
191
- for ds in response["dependencies"]:
192
- click.echo(FeedbackManager.info_dependency_list(dependency=ds))
193
- for pipe in response["dependencies"][ds]:
194
- click.echo(FeedbackManager.info_dependency_list_item(dependency=pipe))
195
- if "incompatible_datasources" in response and len(response["incompatible_datasources"]):
196
- click.echo(FeedbackManager.info_no_compatible_dependencies_found())
197
- for ds in response["incompatible_datasources"]:
198
- click.echo(FeedbackManager.info_dependency_list(dependency=ds))
199
- raise CLIException(FeedbackManager.error_partial_replace_cant_be_executed(datasource=datasource))
200
-
201
-
202
161
  @cli.command(
203
162
  name="diff",
204
163
  short_help="Diff local datafiles to the corresponding remote files in the workspace. Only diffs VERSION and SCHEMA for .datasource files.",
@@ -81,8 +81,6 @@ SUPPORTED_FORMATS = ["csv", "ndjson", "json", "parquet"]
81
81
  OLDEST_ROLLBACK = "oldest_rollback"
82
82
  MAIN_BRANCH = "main"
83
83
 
84
- CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
85
-
86
84
 
87
85
  def obfuscate_token(value: Optional[str]) -> Optional[str]:
88
86
  if not value:
@@ -1984,12 +1982,12 @@ async def send_batch_events(
1984
1982
  click.echo(FeedbackManager.highlight(message=f"» {rows} rows x {cols} cols in {elapsed_time:.1f}s"))
1985
1983
 
1986
1984
 
1987
- async def get_organizations_by_user(config: CLIConfig, user_token: str) -> List[Dict[str, Any]]:
1985
+ async def get_organizations_by_user(config: CLIConfig, user_token: Optional[str] = None) -> List[Dict[str, str]]:
1988
1986
  """Fetches all organizations by user using the provided user token"""
1989
1987
  organizations = []
1990
1988
 
1991
1989
  try:
1992
- user_client = config.get_client(token=user_token)
1990
+ user_client = config.get_client(token=user_token) if user_token else config.get_user_client()
1993
1991
  user_workspaces = await user_client.user_workspaces_with_organization(version="v1")
1994
1992
  admin_org_id = user_workspaces.get("organization_id")
1995
1993
  seen_org_ids = set()
@@ -2089,7 +2087,7 @@ async def ask_for_organization_name(config: CLIConfig) -> str:
2089
2087
  user_email.split("@")[0] if user_email else None
2090
2088
  ) # Example: "jane.doe@gmail.com" -> "jane.doe"
2091
2089
  return click.prompt(
2092
- "\nYou need to create an organization to continue. We will include your existing workspaces in it.\nThis operation will only happen once and then your new workspaces will be automatically included in your organization.\n\nEnter organization name",
2090
+ "\nYou need to create an organization to continue.\nEnter organization name",
2093
2091
  hide_input=False,
2094
2092
  show_default=True,
2095
2093
  default=default_organization_name,
@@ -2166,3 +2164,39 @@ def get_ui_url(api_host: str) -> str:
2166
2164
  return f"https://cloud.tinybird.co/{cloud}/{region}"
2167
2165
 
2168
2166
  return api_host
2167
+
2168
+
2169
+ async def ask_for_organization(
2170
+ organizations: Optional[List[Dict[str, Any]]],
2171
+ organization_id: Optional[str] = None,
2172
+ user_token: Optional[str] = None,
2173
+ ) -> Tuple[Optional[str], Optional[str]]:
2174
+ config = CLIConfig.get_project_config()
2175
+ user_email = config.get_user_email()
2176
+
2177
+ if organization_id:
2178
+ if organizations and len(organizations) > 0:
2179
+ organization = next((org for org in organizations if org.get("id") == organization_id), None)
2180
+ if not organization:
2181
+ raise CLIException(FeedbackManager.error_organization_not_found(organization_id=organization_id))
2182
+ organization_name = organization.get("name")
2183
+ return organization_id, organization_name
2184
+
2185
+ if organizations is None or len(organizations) == 0:
2186
+ organization_name = await ask_for_organization_name(config)
2187
+ user_token = await get_user_token(config, user_token)
2188
+ organization = await create_organization_and_add_workspaces(config, organization_name, user_token)
2189
+ organization_id = organization.get("id")
2190
+ else:
2191
+ if len(organizations) == 1:
2192
+ organization_name = organizations[0]["name"]
2193
+ organization_id = organizations[0]["id"]
2194
+ else:
2195
+ sorted_organizations = sort_organizations_by_user(organizations, user_email=user_email)
2196
+ current_organization = await ask_for_organization_interactively(sorted_organizations)
2197
+ if current_organization:
2198
+ organization_id = current_organization.get("id")
2199
+ organization_name = current_organization.get("name")
2200
+ else:
2201
+ return None, None
2202
+ return organization_id, organization_name
@@ -299,9 +299,9 @@ def init_git(folder: str):
299
299
  if gitignore_file.exists():
300
300
  content = gitignore_file.read_text()
301
301
  if ".tinyb" not in content:
302
- gitignore_file.write_text(content + "\n.tinyb\n")
302
+ gitignore_file.write_text(content + "\n.tinyb\n.terraform\n")
303
303
  else:
304
- gitignore_file.write_text(".tinyb\n")
304
+ gitignore_file.write_text(".tinyb\n.terraform\n")
305
305
 
306
306
  click.echo(FeedbackManager.info_file_created(file=".gitignore"))
307
307
  except Exception as e:
@@ -1241,6 +1241,13 @@ def parse(
1241
1241
 
1242
1242
  return _f
1243
1243
 
1244
+ def kafka_key_avro_deserialization(*args: str, **kwargs: Any):
1245
+ raise DatafileSyntaxError(
1246
+ f'{kwargs["cmd"].upper()} has been deprecated. Use "KAFKA_KEY_FORMAT avro" instead',
1247
+ lineno=kwargs["lineno"],
1248
+ pos=1,
1249
+ )
1250
+
1244
1251
  @deprecated
1245
1252
  def sources(x: str, **kwargs: Any) -> None:
1246
1253
  pass # Deprecated
@@ -1459,11 +1466,13 @@ def parse(
1459
1466
  "kafka_store_raw_value": assign_var("kafka_store_raw_value"),
1460
1467
  "kafka_store_headers": assign_var("kafka_store_headers"),
1461
1468
  "kafka_store_binary_headers": assign_var("kafka_store_binary_headers"),
1462
- "kafka_key_avro_deserialization": assign_var("kafka_key_avro_deserialization"),
1469
+ "kafka_key_avro_deserialization": kafka_key_avro_deserialization,
1463
1470
  "kafka_ssl_ca_pem": assign_var("kafka_ssl_ca_pem"),
1464
1471
  "kafka_security_protocol": assign_var("kafka_security_protocol"),
1465
1472
  "kafka_sasl_mechanism": assign_var("kafka_sasl_mechanism"),
1466
1473
  "import_service": assign_var("import_service"),
1474
+ "s3_region": assign_var("s3_region"),
1475
+ "s3_arn": assign_var("s3_arn"),
1467
1476
  "import_connection_name": assign_var("import_connection_name"),
1468
1477
  "import_schedule": assign_var("import_schedule"),
1469
1478
  "import_strategy": assign_var("import_strategy"),
@@ -19,6 +19,7 @@ from tinybird.tb.modules.project import Project
19
19
  def api_fetch(url: str, headers: dict) -> dict:
20
20
  r = requests.get(url, headers=headers)
21
21
  if r.status_code == 200:
22
+ logging.debug(json.dumps(r.json(), indent=2))
22
23
  return r.json()
23
24
  # Try to parse and print the error from the response
24
25
  try:
@@ -32,13 +33,33 @@ def api_fetch(url: str, headers: dict) -> dict:
32
33
  sys.exit(1)
33
34
 
34
35
 
36
+ def api_post(
37
+ url: str,
38
+ headers: dict,
39
+ files: Optional[list] = None,
40
+ params: Optional[dict] = None,
41
+ ) -> dict:
42
+ r = requests.post(url, headers=headers, files=files, params=params)
43
+ if r.status_code == 200:
44
+ logging.debug(json.dumps(r.json(), indent=2))
45
+ return r.json()
46
+ # Try to parse and print the error from the response
47
+ try:
48
+ result = r.json()
49
+ logging.debug(json.dumps(result, indent=2))
50
+ error = result.get("error")
51
+ click.echo(FeedbackManager.error(message=f"Error: {error}"))
52
+ sys.exit(1)
53
+ except Exception:
54
+ click.echo(FeedbackManager.error(message="Error parsing response from API"))
55
+ sys.exit(1)
56
+
57
+
35
58
  # TODO(eclbg): This logic should be in the server, and there should be a dedicated endpoint for promoting a deployment
36
59
  # potato
37
60
  def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
38
61
  TINYBIRD_API_URL = f"{host}/v1/deployments"
39
- r = requests.get(TINYBIRD_API_URL, headers=headers)
40
- result = r.json()
41
- logging.debug(json.dumps(result, indent=2))
62
+ result = api_fetch(TINYBIRD_API_URL, headers)
42
63
 
43
64
  deployments = result.get("deployments")
44
65
  if not deployments:
@@ -64,12 +85,7 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
64
85
  click.echo(FeedbackManager.success(message="Setting candidate deployment as live"))
65
86
 
66
87
  TINYBIRD_API_URL = f"{host}/v1/deployments/{candidate_deployment.get('id')}/set-live"
67
- r = requests.post(TINYBIRD_API_URL, headers=headers)
68
- result = r.json()
69
- logging.debug(json.dumps(result, indent=2))
70
- if result.get("error"):
71
- click.echo(FeedbackManager.error(message=result.get("error")))
72
- sys.exit(1)
88
+ result = api_post(TINYBIRD_API_URL, headers=headers)
73
89
 
74
90
  click.echo(FeedbackManager.success(message="Removing old deployment"))
75
91
 
@@ -86,9 +102,7 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
86
102
  if wait:
87
103
  while True:
88
104
  TINYBIRD_API_URL = f"{host}/v1/deployments/{last_deployment.get('id')}"
89
- r = requests.get(TINYBIRD_API_URL, headers=headers)
90
- result = r.json()
91
- logging.debug(json.dumps(result, indent=2))
105
+ result = api_fetch(TINYBIRD_API_URL, headers=headers)
92
106
 
93
107
  last_deployment = result.get("deployment")
94
108
  if last_deployment.get("status") == "deleted":
@@ -102,9 +116,7 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
102
116
  # deployment
103
117
  def rollback_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
104
118
  TINYBIRD_API_URL = f"{host}/v1/deployments"
105
- r = requests.get(TINYBIRD_API_URL, headers=headers)
106
- result = r.json()
107
- logging.debug(json.dumps(result, indent=2))
119
+ result = api_fetch(TINYBIRD_API_URL, headers=headers)
108
120
 
109
121
  deployments = result.get("deployments")
110
122
  if not deployments:
@@ -130,12 +142,7 @@ def rollback_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
130
142
  click.echo(FeedbackManager.success(message="Promoting previous deployment"))
131
143
 
132
144
  TINYBIRD_API_URL = f"{host}/v1/deployments/{previous_deployment.get('id')}/set-live"
133
- r = requests.post(TINYBIRD_API_URL, headers=headers)
134
- result = r.json()
135
- logging.debug(json.dumps(result, indent=2))
136
- if result.get("error"):
137
- click.echo(FeedbackManager.error(message=result.get("error")))
138
- sys.exit(1)
145
+ result = api_post(TINYBIRD_API_URL, headers=headers)
139
146
 
140
147
  click.echo(FeedbackManager.success(message="Removing current deployment"))
141
148
 
@@ -152,9 +159,7 @@ def rollback_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
152
159
  if wait:
153
160
  while True:
154
161
  TINYBIRD_API_URL = f"{host}/v1/deployments/{current_deployment.get('id')}"
155
- r = requests.get(TINYBIRD_API_URL, headers=headers)
156
- result = r.json()
157
- logging.debug(json.dumps(result, indent=2))
162
+ result = api_fetch(TINYBIRD_API_URL, headers)
158
163
 
159
164
  current_deployment = result.get("deployment")
160
165
  if current_deployment.get("status") == "deleted":
@@ -219,7 +224,6 @@ def deployment_ls(ctx: click.Context) -> None:
219
224
  url = f"{client.host}/v1/deployments"
220
225
 
221
226
  result = api_fetch(url, HEADERS)
222
- logging.debug(json.dumps(result, indent=2))
223
227
 
224
228
  status_map = {"data_ready": "Ready", "failed": "Failed"}
225
229
  columns = ["ID", "Status", "Created at", "Live"]
@@ -354,13 +358,13 @@ def create_deployment(
354
358
  params["check"] = "true"
355
359
  if allow_destructive_operations:
356
360
  params["allow_destructive_operations"] = "true"
357
- r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS, params=params)
358
- result = r.json()
359
- logging.debug(json.dumps(result, indent=2))
361
+
362
+ result = api_post(TINYBIRD_API_URL, headers=HEADERS, files=files, params=params)
360
363
 
361
364
  print_changes(result, project)
362
365
 
363
- feedback = result.get("deployment", {}).get("feedback", [])
366
+ deployment = result.get("deployment", {})
367
+ feedback = deployment.get("feedback", [])
364
368
  for f in feedback:
365
369
  if f.get("level", "").upper() == "ERROR":
366
370
  feedback_func = FeedbackManager.error
@@ -372,7 +376,7 @@ def create_deployment(
372
376
  resource_bit = f"{resource}: " if resource else ""
373
377
  click.echo(feedback_func(message=f"{feedback_icon}{f.get('level')}: {resource_bit}{f.get('message')}"))
374
378
 
375
- deploy_errors = result.get("deployment", {}).get("errors")
379
+ deploy_errors = deployment.get("errors")
376
380
  for deploy_error in deploy_errors:
377
381
  if deploy_error.get("filename", None):
378
382
  click.echo(
@@ -380,19 +384,21 @@ def create_deployment(
380
384
  )
381
385
  else:
382
386
  click.echo(FeedbackManager.error(message=f"{deploy_error.get('error')}"))
387
+ click.echo() # For spacing
383
388
 
384
389
  status = result.get("result")
385
390
  if check:
386
391
  if status == "success":
387
392
  click.echo(FeedbackManager.success(message="\n✓ Deployment is valid"))
388
393
  sys.exit(0)
394
+ elif status == "no_changes":
395
+ sys.exit(0)
389
396
 
390
397
  click.echo(FeedbackManager.error(message="\n✗ Deployment is not valid"))
391
398
  sys.exit(1)
392
399
 
393
400
  status = result.get("result")
394
401
  if status == "success":
395
- deployment = result.get("deployment", {})
396
402
  # TODO: This is a hack to show the url in the case of region is public. The URL should be returned by the API
397
403
  if client.host == "https://api.europe-west2.gcp.tinybird.co":
398
404
  click.echo(
@@ -406,10 +412,14 @@ def create_deployment(
406
412
  click.echo(FeedbackManager.info(message="\n✓ Deployment submitted successfully"))
407
413
  else:
408
414
  click.echo(FeedbackManager.success(message="\n✓ Deployment submitted successfully"))
415
+ elif status == "no_changes":
416
+ click.echo(FeedbackManager.highlight(message="Not deploying. No changes."))
417
+ sys.exit(0)
409
418
  elif status == "failed":
410
419
  click.echo(FeedbackManager.error(message="Deployment failed"))
420
+ sys.exit(1)
411
421
  else:
412
- click.echo(FeedbackManager.error(message=f"Unknown build result {status}"))
422
+ click.echo(FeedbackManager.error(message=f"Unknown deployment result {status}"))
413
423
  except Exception as e:
414
424
  click.echo(FeedbackManager.error_exception(error=e))
415
425
  finally:
@@ -463,7 +473,7 @@ def print_changes(result: dict, project: Project) -> None:
463
473
  resources.append(["new", p, project.get_resource_path(p, "pipe")])
464
474
 
465
475
  for dc in deployment.get("new_data_connector_names", []):
466
- resources.append(["new", dc, project.get_resource_path(dc, "data_connector")])
476
+ resources.append(["new", dc, project.get_resource_path(dc, "connection")])
467
477
 
468
478
  for ds in deployment.get("changed_datasource_names", []):
469
479
  resources.append(["modified", ds, project.get_resource_path(ds, "datasource")])
@@ -472,7 +482,7 @@ def print_changes(result: dict, project: Project) -> None:
472
482
  resources.append(["modified", p, project.get_resource_path(p, "pipe")])
473
483
 
474
484
  for dc in deployment.get("changed_data_connector_names", []):
475
- resources.append(["modified", dc, project.get_resource_path(dc, "data_connector")])
485
+ resources.append(["modified", dc, project.get_resource_path(dc, "connection")])
476
486
 
477
487
  for ds in deployment.get("deleted_datasource_names", []):
478
488
  resources.append(["deleted", ds, project.get_resource_path(ds, "datasource")])
@@ -481,7 +491,7 @@ def print_changes(result: dict, project: Project) -> None:
481
491
  resources.append(["deleted", p, project.get_resource_path(p, "pipe")])
482
492
 
483
493
  for dc in deployment.get("deleted_data_connector_names", []):
484
- resources.append(["deleted", dc, project.get_resource_path(dc, "data_connector")])
494
+ resources.append(["deleted", dc, project.get_resource_path(dc, "connection")])
485
495
 
486
496
  for token_change in deployment.get("token_changes", []):
487
497
  token_name = token_change.get("token_name")