tinybird 0.0.1.dev34__tar.gz → 0.0.1.dev36__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 (104) hide show
  1. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/context.py +1 -1
  3. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/datafile.py +55 -1
  4. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/feedback_manager.py +6 -0
  5. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/prompts.py +6 -0
  6. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/sql_toolset.py +9 -2
  7. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/__cli__.py +2 -2
  8. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/cli.py +2 -2
  9. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/build.py +53 -12
  10. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/cli.py +7 -94
  11. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/create.py +4 -4
  12. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/build.py +2 -2
  13. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/common.py +17 -1
  14. tinybird-0.0.1.dev36/tinybird/tb/modules/datasource.py +360 -0
  15. tinybird-0.0.1.dev34/tinybird/tb/modules/deploy.py → tinybird-0.0.1.dev36/tinybird/tb/modules/deployment.py +22 -12
  16. tinybird-0.0.1.dev36/tinybird/tb/modules/endpoint.py +187 -0
  17. tinybird-0.0.1.dev36/tinybird/tb/modules/llm.py +32 -0
  18. tinybird-0.0.1.dev36/tinybird/tb/modules/llm_utils.py +111 -0
  19. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/local.py +4 -1
  20. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/local_common.py +2 -2
  21. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/mock.py +3 -4
  22. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/pipe.py +1 -254
  23. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/shell.py +8 -1
  24. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/test.py +2 -2
  25. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/update.py +4 -4
  26. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/watch.py +4 -4
  27. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/workspace.py +0 -96
  28. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/common.py +19 -17
  29. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird.egg-info/PKG-INFO +1 -1
  30. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird.egg-info/SOURCES.txt +2 -2
  31. tinybird-0.0.1.dev34/tinybird/tb/modules/connection.py +0 -803
  32. tinybird-0.0.1.dev34/tinybird/tb/modules/datasource.py +0 -828
  33. tinybird-0.0.1.dev34/tinybird/tb/modules/llm.py +0 -38
  34. tinybird-0.0.1.dev34/tinybird/tb/modules/llm_utils.py +0 -24
  35. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/setup.cfg +0 -0
  36. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/__cli__.py +0 -0
  37. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/ch_utils/constants.py +0 -0
  38. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/ch_utils/engine.py +0 -0
  39. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/check_pypi.py +0 -0
  40. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/client.py +0 -0
  41. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/config.py +0 -0
  42. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/connectors.py +0 -0
  43. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/datatypes.py +0 -0
  44. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/git_settings.py +0 -0
  45. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/sql.py +0 -0
  46. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/sql_template.py +0 -0
  47. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/sql_template_fmt.py +0 -0
  48. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/syncasync.py +0 -0
  49. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/auth.py +0 -0
  50. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/build_client.py +0 -0
  51. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/cicd.py +0 -0
  52. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/common.py +0 -0
  53. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/config.py +0 -0
  54. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/build_common.py +0 -0
  55. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  56. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  57. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/diff.py +0 -0
  58. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  59. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/fixture.py +0 -0
  60. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/format_common.py +0 -0
  61. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  62. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  63. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  64. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  65. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  66. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/datafile/pull.py +0 -0
  67. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/exceptions.py +0 -0
  68. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/feedback_manager.py +0 -0
  69. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/fmt.py +0 -0
  70. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/job.py +0 -0
  71. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/login.py +0 -0
  72. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/regions.py +0 -0
  73. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/table.py +0 -0
  74. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/tag.py +0 -0
  75. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/telemetry.py +0 -0
  76. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  77. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  78. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/token.py +0 -0
  79. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb/modules/workspace_members.py +0 -0
  80. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli.py +0 -0
  81. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/auth.py +0 -0
  82. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/branch.py +0 -0
  83. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/cicd.py +0 -0
  84. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/cli.py +0 -0
  85. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/config.py +0 -0
  86. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/connection.py +0 -0
  87. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/datasource.py +0 -0
  88. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/exceptions.py +0 -0
  89. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/fmt.py +0 -0
  90. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/job.py +0 -0
  91. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/pipe.py +0 -0
  92. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/regions.py +0 -0
  93. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/tag.py +0 -0
  94. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/telemetry.py +0 -0
  95. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/test.py +0 -0
  96. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  97. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  98. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/workspace.py +0 -0
  99. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  100. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird/tornado_template.py +0 -0
  101. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird.egg-info/dependency_links.txt +0 -0
  102. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird.egg-info/entry_points.txt +0 -0
  103. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird.egg-info/requires.txt +0 -0
  104. {tinybird-0.0.1.dev34 → tinybird-0.0.1.dev36}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinybird
3
- Version: 0.0.1.dev34
3
+ Version: 0.0.1.dev36
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -7,7 +7,7 @@ if TYPE_CHECKING:
7
7
 
8
8
  workspace_id: ContextVar[str] = ContextVar("workspace_id")
9
9
  workspace: ContextVar["User"] = ContextVar("workspace")
10
- table_id: ContextVar[str] = ContextVar("table_id")
10
+ datasource_id: ContextVar[str] = ContextVar("datasource_id")
11
11
  hfi_frequency: ContextVar[float] = ContextVar("hfi_frequency")
12
12
  hfi_frequency_gatherer: ContextVar[float] = ContextVar("hfi_frequency_gatherer")
13
13
  use_gatherer: ContextVar[bool] = ContextVar("use_gatherer")
@@ -3434,14 +3434,33 @@ async def new_ds(
3434
3434
  DATASOURCE_VALID_SERVICES_TO_UPDATE = ["bigquery", "snowflake"]
3435
3435
  if datasource_exists and service and service in [*DATASOURCE_VALID_SERVICES_TO_UPDATE, *PREVIEW_CONNECTOR_SERVICES]:
3436
3436
  connector_required_params = {
3437
- "bigquery": ["service", "cron", "external_data_source"],
3437
+ "bigquery": ["service", "cron"],
3438
3438
  "snowflake": ["connector", "service", "cron", "external_data_source"],
3439
3439
  "s3": ["connector", "service", "cron", "bucket_uri"],
3440
3440
  "s3_iamrole": ["connector", "service", "cron", "bucket_uri"],
3441
3441
  "gcs": ["connector", "service", "cron", "bucket_uri"],
3442
3442
  }.get(service, [])
3443
3443
 
3444
+ connector_at_least_one_required_param = {
3445
+ "bigquery": ["external_data_source", "query"],
3446
+ }.get(service, [])
3447
+
3448
+ if connector_at_least_one_required_param and not any(
3449
+ key in ds_params for key in connector_at_least_one_required_param
3450
+ ):
3451
+ params = [
3452
+ (ImportReplacements.get_datafile_param_for_linker_param(service, param) or param).upper()
3453
+ for param in connector_at_least_one_required_param
3454
+ ]
3455
+ click.echo(FeedbackManager.error_updating_connector_missing_at_least_one_param(param=" or ".join(params)))
3456
+ return
3457
+
3444
3458
  if not all(key in ds_params for key in connector_required_params):
3459
+ params = [
3460
+ (ImportReplacements.get_datafile_param_for_linker_param(service, param) or param).upper()
3461
+ for param in connector_required_params
3462
+ ]
3463
+ click.echo(FeedbackManager.error_updating_connector_missing_params(param=", ".join(params)))
3445
3464
  return
3446
3465
 
3447
3466
  connector = ds_params.get("connector", None)
@@ -5589,3 +5608,38 @@ def is_file_a_datasource(filename: str) -> bool:
5589
5608
  return True
5590
5609
 
5591
5610
  return False
5611
+
5612
+
5613
+ def update_connector_params(service: str, ds_params: Dict[str, Any], connector_required_params: List[str]) -> None:
5614
+ """
5615
+ Update connector parameters for a given service, ensuring required parameters exist.
5616
+
5617
+ :param service: The name of the service (e.g., 'bigquery').
5618
+ :param ds_params: The data source parameters to be checked.
5619
+ :param connector_required_params: The list of required parameters for the connector.
5620
+ :return: None
5621
+ """
5622
+
5623
+ connector_at_least_one_required_param: List[str] = {
5624
+ "bigquery": ["external_data_source", "query"],
5625
+ }.get(service, [])
5626
+
5627
+ # Handle the "at least one param" requirement
5628
+ if connector_at_least_one_required_param and not any(
5629
+ key in ds_params for key in connector_at_least_one_required_param
5630
+ ):
5631
+ params = [
5632
+ (ImportReplacements.get_datafile_param_for_linker_param(service, param) or param).upper()
5633
+ for param in connector_at_least_one_required_param
5634
+ ]
5635
+ click.echo(FeedbackManager.error_updating_connector_missing_at_least_one_param(param=" or ".join(params)))
5636
+ return
5637
+
5638
+ # Handle the mandatory params requirement
5639
+ if not all(key in ds_params for key in connector_required_params):
5640
+ params = [
5641
+ (ImportReplacements.get_datafile_param_for_linker_param(service, param) or param).upper()
5642
+ for param in connector_required_params
5643
+ ]
5644
+ click.echo(FeedbackManager.error_updating_connector_missing_params(param=", ".join(params)))
5645
+ return
@@ -130,6 +130,12 @@ class FeedbackManager:
130
130
  error_remove_no_endpoint = error_message("Pipe does not have any endpoint")
131
131
  error_updating_pipe = error_message("Failed updating pipe {error}")
132
132
  error_updating_connector_not_supported = error_message("Changing {param} is not currently supported")
133
+ error_updating_connector_missing_at_least_one_param = error_message(
134
+ "Connection settings not updated. Connection info should have at least one of {param} settings"
135
+ )
136
+ error_updating_connector_missing_params = error_message(
137
+ "Connection settings not updated. Connection info should have {param} settings"
138
+ )
133
139
  error_removing_node = error_message("Failed removing node from pipe {pipe}: {error}")
134
140
  error_pushing_pipe = error_message("Failed pushing pipe {pipe}: {error}")
135
141
  error_creating_endpoint = error_message("Failed creating endpoint in node {node} on pipe {pipe}: {error}")
@@ -641,8 +641,11 @@ The previous instructions are explanations of how things work in Tinybird. Answe
641
641
 
642
642
  datasource_instructions = """
643
643
  <datasource_file_instructions>
644
+ - Content cannot be empty.
644
645
  - The datasource names must be unique.
645
646
  - No indentation is allowed for property names: DESCRIPTION, SCHEMA, ENGINE, ENGINE_PARTITION_KEY, ENGINE_SORTING_KEY, etc.
647
+ - Use MergeTree engine by default.
648
+ - Use AggregatingMergeTree engine when the datasource is the target of a materialized pipe.
646
649
  </datasource_file_instructions>
647
650
  """
648
651
 
@@ -699,6 +702,7 @@ sql_instructions = """
699
702
  </valid_query_with_parameters_with_%_on_top>
700
703
  - The Parameter functions like this one {{{{String(my_param_name,default_value)}}}} can be one of the following: String, DateTime, Date, Float32, Float64, Int, Integer, UInt8, UInt16, UInt32, UInt64, UInt128, UInt256, Int8, Int16, Int32, Int64, Int128, Int256
701
704
  - Parameter names must be different from column names. Pass always the param name and a default value to the function.
705
+ - Use ALWAYS hardcoded values for default values for parameters.
702
706
  - Code inside the template {{{{template_expression}}}} follows the rules of Tornado templating language so no module is allowed to be imported. So for example you can't use now() as default value for a DateTime parameter. You need an if else block like this:
703
707
  <invalid_condition_with_now>
704
708
  AND timestamp BETWEEN {{DateTime(start_date, now() - interval 30 day)}} AND {{DateTime(end_date, now())}}
@@ -732,6 +736,8 @@ sql_instructions = """
732
736
  - When aliasing a column, use first the column name and then the alias.
733
737
  - General functions and aggregate functions are case sensitive.
734
738
  - Character insensitive functions are case insensitive.
739
+ - When you use defined function with a paremeter inside, do NOT add quotes around the parameter.
740
+ - Parameters are never quoted in any case.
735
741
  </sql_instructions>
736
742
  """.format(
737
743
  general_functions=general_functions,
@@ -3,7 +3,7 @@ import logging
3
3
  from collections import defaultdict
4
4
  from datetime import datetime
5
5
  from functools import lru_cache
6
- from typing import Any, FrozenSet, List, Optional, Set, Tuple
6
+ from typing import FrozenSet, List, Optional, Set, Tuple
7
7
 
8
8
  from chtoolset import query as chquery
9
9
  from toposort import toposort
@@ -172,7 +172,7 @@ def tables_or_sql(replacement: dict, table_functions=False) -> set:
172
172
  return {replacement}
173
173
 
174
174
 
175
- def _separate_as_tuple_if_contains_database_and_table(definition: str) -> Any:
175
+ def _separate_as_tuple_if_contains_database_and_table(definition: str) -> str | Tuple[str, str]:
176
176
  if "." in definition:
177
177
  database_and_table_separated = definition.split(".")
178
178
  return database_and_table_separated[0], database_and_table_separated[1]
@@ -219,6 +219,7 @@ def replace_tables(
219
219
  output_one_line: bool = False,
220
220
  timestamp: Optional[datetime] = None,
221
221
  function_allow_list: Optional[FrozenSet[str]] = None,
222
+ original_replacements: Optional[dict] = None,
222
223
  ) -> str:
223
224
  """
224
225
  Given a query and a list of table replacements, returns the query after applying the table replacements.
@@ -239,6 +240,12 @@ def replace_tables(
239
240
  _replacements[rk] = r if isinstance(r, tuple) else (default_database, r)
240
241
  _replaced_with.add(r)
241
242
 
243
+ if original_replacements:
244
+ # Some replacements have been expanded by filters and turned to a query str, but we need to send the original
245
+ # ones to is_invalid_resource()
246
+ for r in original_replacements.values():
247
+ _replaced_with.add(r)
248
+
242
249
  deps: defaultdict = defaultdict(set)
243
250
  _tables = sql_get_used_tables(
244
251
  sql,
@@ -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.dev34'
8
- __revision__ = '08098c9'
7
+ __version__ = '0.0.1.dev36'
8
+ __revision__ = '5ad3ab4'
@@ -9,10 +9,10 @@ import tinybird.tb.modules.build
9
9
  import tinybird.tb.modules.build_client
10
10
  import tinybird.tb.modules.cli
11
11
  import tinybird.tb.modules.common
12
- import tinybird.tb.modules.connection
13
12
  import tinybird.tb.modules.create
14
13
  import tinybird.tb.modules.datasource
15
- import tinybird.tb.modules.deploy
14
+ import tinybird.tb.modules.deployment
15
+ import tinybird.tb.modules.endpoint
16
16
  import tinybird.tb.modules.fmt
17
17
  import tinybird.tb.modules.job
18
18
  import tinybird.tb.modules.local
@@ -2,7 +2,9 @@ import asyncio
2
2
  import glob
3
3
  import json
4
4
  import logging
5
+ import os
5
6
  import threading
7
+ import time
6
8
  from pathlib import Path
7
9
  from typing import List
8
10
 
@@ -12,6 +14,7 @@ import requests
12
14
  from tinybird.client import TinyB
13
15
  from tinybird.tb.modules.cli import cli
14
16
  from tinybird.tb.modules.common import push_data
17
+ from tinybird.tb.modules.datafile.fixture import build_fixture_name, get_fixture_dir
15
18
  from tinybird.tb.modules.feedback_manager import FeedbackManager
16
19
  from tinybird.tb.modules.local_common import get_tinybird_local_client
17
20
  from tinybird.tb.modules.shell import Shell
@@ -19,7 +22,7 @@ from tinybird.tb.modules.watch import watch_project
19
22
 
20
23
 
21
24
  @cli.command()
22
- @click.option("--folder", type=str, default=".")
25
+ @click.option("--folder", type=str, default=os.getcwd())
23
26
  @click.option("--watch", is_flag=True, default=False, help="Watch for changes and rebuild automatically")
24
27
  def build(folder: str, watch: bool) -> None:
25
28
  """
@@ -27,12 +30,19 @@ def build(folder: str, watch: bool) -> None:
27
30
  """
28
31
 
29
32
  tb_client = asyncio.run(get_tinybird_local_client(folder))
33
+ click.echo(FeedbackManager.highlight(message="\n» Building project..."))
34
+
35
+ time_start = time.time()
30
36
 
31
37
  def process() -> None:
32
38
  build_project(folder, tb_client)
33
39
 
34
40
  process()
35
41
 
42
+ time_end = time.time()
43
+ elapsed_time = time_end - time_start
44
+ click.echo(FeedbackManager.success(message=f"\n✓ Build completed in {elapsed_time:.1f}s"))
45
+
36
46
  if watch:
37
47
  shell = Shell(folder=folder, client=tb_client)
38
48
  click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
@@ -71,6 +81,7 @@ def build_project(folder: str, tb_client: TinyB) -> None:
71
81
  fds = []
72
82
  project_path = Path(folder)
73
83
  project_files = get_project_files(project_path)
84
+
74
85
  for file_path in project_files:
75
86
  relative_path = str(Path(file_path).relative_to(project_path))
76
87
  fd = open(file_path, "rb")
@@ -91,7 +102,34 @@ def build_project(folder: str, tb_client: TinyB) -> None:
91
102
 
92
103
  build_result = result.get("result")
93
104
  if build_result == "success":
94
- click.echo(FeedbackManager.success(message="Build completed successfully"))
105
+ datasources = result.get("datasources", [])
106
+ pipes = result.get("pipes", [])
107
+ for ds in datasources:
108
+ ds_path = next((p for p in project_files if p.endswith(ds.get("name") + ".datasource")), None)
109
+ if ds_path:
110
+ ds_path = ds_path.replace(f"{folder}/", "")
111
+ click.echo(FeedbackManager.info(message=f"✓ {ds_path} created"))
112
+ for pipe in pipes:
113
+ pipe_name = pipe.get("name")
114
+ pipe_path = next((p for p in project_files if p.endswith(pipe_name + ".pipe")), None)
115
+ if pipe_path:
116
+ pipe_path = pipe_path.replace(f"{folder}/", "")
117
+ click.echo(FeedbackManager.info(message=f"✓ {pipe_path} created"))
118
+
119
+ for filename in project_files:
120
+ if filename.endswith(".datasource"):
121
+ ds_path = Path(filename)
122
+ ds_name = ds_path.stem
123
+ name = build_fixture_name(filename, ds_name, ds_path.read_text())
124
+ fixture_folder = get_fixture_dir(folder)
125
+ fixture_path = fixture_folder / f"{name}.ndjson"
126
+
127
+ if not fixture_path.exists():
128
+ fixture_path = fixture_folder / f"{ds_name}.ndjson"
129
+
130
+ if fixture_path.exists():
131
+ append_fixture(tb_client, ds_name, str(fixture_path))
132
+
95
133
  elif build_result == "failed":
96
134
  click.echo(FeedbackManager.error(message="Build failed"))
97
135
  build_errors = result.get("errors")
@@ -101,24 +139,27 @@ def build_project(folder: str, tb_client: TinyB) -> None:
101
139
  click.echo(FeedbackManager.error(message=error_msg))
102
140
  else:
103
141
  click.echo(FeedbackManager.error(message=f"Unknown build result. Error: {result.get('error')}"))
142
+
104
143
  except Exception as e:
105
- click.echo(FeedbackManager.error_exception(error="Error building project: " + str(e)))
144
+ click.echo(FeedbackManager.error_exception(error="Error: " + str(e)))
106
145
  finally:
107
146
  for fd in fds:
108
147
  fd.close()
109
148
 
110
149
 
111
- async def append_fixture(
150
+ def append_fixture(
112
151
  tb_client: TinyB,
113
152
  datasource_name: str,
114
153
  url: str,
115
154
  ):
116
- await tb_client.datasource_truncate(datasource_name)
117
- await push_data(
118
- tb_client,
119
- datasource_name,
120
- url,
121
- mode="append",
122
- concurrency=1,
123
- silent=True,
155
+ asyncio.run(tb_client.datasource_truncate(datasource_name))
156
+ asyncio.run(
157
+ push_data(
158
+ tb_client,
159
+ datasource_name,
160
+ url,
161
+ mode="append",
162
+ concurrency=1,
163
+ silent=True,
164
+ )
124
165
  )
@@ -67,58 +67,13 @@ VERSION = f"{__cli__.__version__} (rev {__cli__.__revision__})"
67
67
  )
68
68
  @click.option("--token", help="Use auth token, defaults to TB_TOKEN envvar, then to the .tinyb file")
69
69
  @click.option("--host", help="Use custom host, defaults to TB_HOST envvar, then to https://api.tinybird.co")
70
- @click.option("--gcp-project-id", help="The Google Cloud project ID", hidden=True)
71
- @click.option(
72
- "--gcs-bucket", help="The Google Cloud Storage bucket to write temp files when using the connectors", hidden=True
73
- )
74
- @click.option(
75
- "--google-application-credentials",
76
- envvar="GOOGLE_APPLICATION_CREDENTIALS",
77
- help="Set GOOGLE_APPLICATION_CREDENTIALS",
78
- hidden=True,
79
- )
80
- @click.option("--sf-account", help="The Snowflake Account (e.g. your-domain.west-europe.azure)", hidden=True)
81
- @click.option("--sf-warehouse", help="The Snowflake warehouse name", hidden=True)
82
- @click.option("--sf-database", help="The Snowflake database name", hidden=True)
83
- @click.option("--sf-schema", help="The Snowflake schema name", hidden=True)
84
- @click.option("--sf-role", help="The Snowflake role name", hidden=True)
85
- @click.option("--sf-user", help="The Snowflake user name", hidden=True)
86
- @click.option("--sf-password", help="The Snowflake password", hidden=True)
87
- @click.option(
88
- "--sf-storage-integration",
89
- help="The Snowflake GCS storage integration name (leave empty to auto-generate one)",
90
- hidden=True,
91
- )
92
- @click.option("--sf-stage", help="The Snowflake GCS stage name (leave empty to auto-generate one)", hidden=True)
93
- @click.option(
94
- "--with-headers", help="Flag to enable connector to export with headers", is_flag=True, default=False, hidden=True
95
- )
96
70
  @click.option("--show-tokens", is_flag=True, default=False, help="Enable the output of tokens")
97
- @click.option("--prod/--local", is_flag=True, default=False, help="Run against production or local")
71
+ @click.option("--show-tokens", is_flag=True, default=False, help="Enable the output of tokens")
72
+ @click.option("--prod", is_flag=True, default=False, help="Run against production")
98
73
  @click.version_option(version=VERSION)
99
74
  @click.pass_context
100
75
  @coro
101
- async def cli(
102
- ctx: Context,
103
- debug: bool,
104
- token: str,
105
- host: str,
106
- gcp_project_id: str,
107
- gcs_bucket: str,
108
- google_application_credentials: str,
109
- sf_account: str,
110
- sf_warehouse: str,
111
- sf_database: str,
112
- sf_schema: str,
113
- sf_role: str,
114
- sf_user: str,
115
- sf_password: str,
116
- sf_storage_integration: str,
117
- sf_stage,
118
- with_headers: bool,
119
- show_tokens: bool,
120
- prod: bool,
121
- ) -> None:
76
+ async def cli(ctx: Context, debug: bool, token: str, host: str, show_tokens: bool, prod: bool) -> None:
122
77
  """
123
78
  Use `OBFUSCATE_REGEX_PATTERN` and `OBFUSCATE_PATTERN_SEPARATOR` environment variables to define a regex pattern and a separator (in case of a single string with multiple regex) to obfuscate secrets in the CLI output.
124
79
  """
@@ -166,51 +121,9 @@ async def cli(
166
121
 
167
122
  ctx.ensure_object(dict)["config"] = config
168
123
 
169
- if ctx.invoked_subcommand == "auth":
170
- return
171
-
172
- from tinybird.connectors import create_connector
173
-
174
- if gcp_project_id and gcs_bucket and google_application_credentials and not sf_account:
175
- bq_config = {
176
- "project_id": gcp_project_id,
177
- "bucket_name": gcs_bucket,
178
- "service_account": google_application_credentials,
179
- "with_headers": with_headers,
180
- }
181
- ctx.ensure_object(dict)["bigquery"] = create_connector("bigquery", bq_config)
182
- if (
183
- sf_account
184
- and sf_warehouse
185
- and sf_database
186
- and sf_schema
187
- and sf_role
188
- and sf_user
189
- and sf_password
190
- and gcs_bucket
191
- and google_application_credentials
192
- and gcp_project_id
193
- ):
194
- sf_config = {
195
- "account": sf_account,
196
- "warehouse": sf_warehouse,
197
- "database": sf_database,
198
- "schema": sf_schema,
199
- "role": sf_role,
200
- "user": sf_user,
201
- "password": sf_password,
202
- "storage_integration": sf_storage_integration,
203
- "stage": sf_stage,
204
- "bucket_name": gcs_bucket,
205
- "service_account": google_application_credentials,
206
- "project_id": gcp_project_id,
207
- "with_headers": with_headers,
208
- }
209
- ctx.ensure_object(dict)["snowflake"] = create_connector("snowflake", sf_config)
210
-
211
124
  logging.debug("debug enabled")
212
125
 
213
- skip_client = ctx.invoked_subcommand in ["login", "workspace", "local"]
126
+ skip_client = ctx.invoked_subcommand in ["auth", "login", "workspace", "local", "build"]
214
127
  client = await create_ctx_client(config, prod, skip_client)
215
128
 
216
129
  if client:
@@ -487,7 +400,7 @@ async def sql(
487
400
 
488
401
  @cli.command(hidden=True)
489
402
  @click.argument("prompt")
490
- @click.option("--folder", default=".", help="The folder to use for the project")
403
+ @click.option("--folder", default=os.getcwd(), help="The folder to use for the project")
491
404
  @coro
492
405
  async def ask(prompt: str, folder: str) -> None:
493
406
  """Ask things about your data project."""
@@ -529,8 +442,8 @@ async def ask(prompt: str, folder: str) -> None:
529
442
  )
530
443
 
531
444
  client = config.get_client()
532
- llm = LLM(user_token=user_token, client=client)
533
- click.echo(await llm.ask(system_prompt=ask_prompt(resources_xml), prompt=prompt))
445
+ llm = LLM(user_token=user_token, host=client.host)
446
+ click.echo(llm.ask(system_prompt=ask_prompt(resources_xml), prompt=prompt))
534
447
  except Exception as e:
535
448
  raise CLIException(FeedbackManager.error_exception(error=e))
536
449
 
@@ -103,13 +103,13 @@ async def create(
103
103
  datasource_files = [f for f in os.listdir(Path(folder) / "datasources") if f.endswith(".datasource")]
104
104
  for datasource_file in datasource_files:
105
105
  datasource_path = Path(folder) / "datasources" / datasource_file
106
- llm = LLM(user_token=user_token, client=tb_client)
106
+ llm = LLM(user_token=user_token, host=tb_client.host)
107
107
  datasource_name = datasource_path.stem
108
108
  datasource_content = datasource_path.read_text()
109
109
  has_json_path = "`json:" in datasource_content
110
110
  if has_json_path:
111
111
  prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
112
- response = await llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
112
+ response = llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
113
113
  sql = extract_xml(response, "sql")
114
114
  sql = sql.split("FORMAT")[0]
115
115
  result = await local_client.query(f"{sql} FORMAT JSON")
@@ -205,8 +205,8 @@ TYPE ENDPOINT
205
205
  ]
206
206
  ]
207
207
  )
208
- llm = LLM(user_token=user_token, client=tb_client)
209
- result = await llm.ask(system_prompt=create_prompt(resources_xml), prompt=prompt)
208
+ llm = LLM(user_token=user_token, host=tb_client.host)
209
+ result = llm.ask(system_prompt=create_prompt(resources_xml), prompt=prompt)
210
210
  result = extract_xml(result, "response")
211
211
  resources = parse_xml(result, "resource")
212
212
  datasources = []
@@ -44,7 +44,7 @@ from tinybird.tb.modules.datafile.exceptions import AlreadyExistsException, Incl
44
44
  from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
45
45
  from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
46
46
  from tinybird.tb.modules.feedback_manager import FeedbackManager
47
- from tinybird.tb.modules.local_common import get_client_config_for_build
47
+ from tinybird.tb.modules.local_common import get_tinybird_local_config
48
48
 
49
49
 
50
50
  async def folder_build(
@@ -58,7 +58,7 @@ async def folder_build(
58
58
  local_ws: Optional[Dict[str, Any]] = None,
59
59
  watch: bool = False,
60
60
  ):
61
- config = await get_client_config_for_build(folder)
61
+ config = await get_tinybird_local_config(folder)
62
62
  build = True
63
63
  dry_run = False
64
64
  force = True
@@ -204,6 +204,10 @@ class Datafile:
204
204
  if self.kind == DatafileKind.pipe:
205
205
  # TODO(eclbg):
206
206
  # [x] node names are unique
207
+ # [x] SQL in all nodes
208
+ # [x] Materialized nodes have target datasource
209
+ # [x] Only one materialized node
210
+ # [ ] Only one node of any specific type
207
211
  # [ ] ...
208
212
  repeated_node_names = [
209
213
  name for name, count in filter(lambda x: x[1] > 1, Counter(n["name"] for n in self.nodes).items())
@@ -212,7 +216,19 @@ class Datafile:
212
216
  raise DatafileValidationError(
213
217
  f"Pipe node names must be unique. These names are repeated: {repeated_node_names}"
214
218
  )
215
- pass
219
+ for node in self.nodes:
220
+ if "sql" not in node:
221
+ raise DatafileValidationError(f"SQL missing for node {repr(node['name'])}")
222
+ materialized_nodes_count = 0
223
+ for node in self.nodes:
224
+ if node.get("type", "").lower() == "materialized":
225
+ materialized_nodes_count += 1
226
+ if materialized_nodes_count > 1:
227
+ raise DatafileValidationError("Multiple materialized nodes in pipe. There can only be one")
228
+ if "datasource" not in node:
229
+ raise DatafileValidationError(
230
+ f"Materialized node {repr(node['name'])} missing target datasource"
231
+ )
216
232
  elif self.kind == DatafileKind.datasource:
217
233
  # TODO(eclbg):
218
234
  # [x] Just one node