tinybird 0.0.1.dev190__tar.gz → 0.0.1.dev192__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 (110) hide show
  1. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/prompts.py +19 -0
  3. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/__cli__.py +2 -2
  4. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/build.py +1 -2
  5. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/cli.py +12 -17
  6. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/common.py +6 -2
  7. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datasource.py +57 -16
  8. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/deployment.py +30 -17
  9. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/feedback_manager.py +3 -0
  10. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/info.py +8 -3
  11. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/project.py +12 -3
  12. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird.egg-info/PKG-INFO +1 -1
  13. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/setup.cfg +0 -0
  14. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/__cli__.py +0 -0
  15. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/ch_utils/constants.py +0 -0
  16. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/ch_utils/engine.py +0 -0
  17. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/check_pypi.py +0 -0
  18. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/client.py +0 -0
  19. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/config.py +0 -0
  20. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/connectors.py +0 -0
  21. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/context.py +0 -0
  22. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/datafile.py +0 -0
  23. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/datatypes.py +0 -0
  24. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/feedback_manager.py +0 -0
  25. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/git_settings.py +0 -0
  26. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/sql.py +0 -0
  27. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/sql_template.py +0 -0
  28. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/sql_template_fmt.py +0 -0
  29. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/sql_toolset.py +0 -0
  30. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/syncasync.py +0 -0
  31. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/check_pypi.py +0 -0
  32. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/cli.py +0 -0
  33. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/client.py +0 -0
  34. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/config.py +0 -0
  35. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/cicd.py +0 -0
  36. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/config.py +0 -0
  37. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/connection.py +0 -0
  38. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/copy.py +0 -0
  39. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/create.py +0 -0
  40. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/build.py +0 -0
  41. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/build_common.py +0 -0
  42. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  43. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  44. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/common.py +0 -0
  45. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/diff.py +0 -0
  46. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  47. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/fixture.py +0 -0
  48. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/format_common.py +0 -0
  49. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  50. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  51. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  52. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  53. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  54. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/playground.py +0 -0
  55. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/datafile/pull.py +0 -0
  56. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/deprecations.py +0 -0
  57. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/dev_server.py +0 -0
  58. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/endpoint.py +0 -0
  59. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/exceptions.py +0 -0
  60. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/infra.py +0 -0
  61. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/job.py +0 -0
  62. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/llm.py +0 -0
  63. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/llm_utils.py +0 -0
  64. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/local.py +0 -0
  65. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/local_common.py +0 -0
  66. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/login.py +0 -0
  67. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/logout.py +0 -0
  68. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/materialization.py +0 -0
  69. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/mock.py +0 -0
  70. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/open.py +0 -0
  71. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/pipe.py +0 -0
  72. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/regions.py +0 -0
  73. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/secret.py +0 -0
  74. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/shell.py +0 -0
  75. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/table.py +0 -0
  76. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/telemetry.py +0 -0
  77. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/test.py +0 -0
  78. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  79. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  80. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/token.py +0 -0
  81. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/watch.py +0 -0
  82. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/workspace.py +0 -0
  83. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb/modules/workspace_members.py +0 -0
  84. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli.py +0 -0
  85. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/auth.py +0 -0
  86. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/branch.py +0 -0
  87. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/cicd.py +0 -0
  88. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/cli.py +0 -0
  89. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/common.py +0 -0
  90. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/config.py +0 -0
  91. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/connection.py +0 -0
  92. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/datasource.py +0 -0
  93. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/exceptions.py +0 -0
  94. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/fmt.py +0 -0
  95. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/job.py +0 -0
  96. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/pipe.py +0 -0
  97. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/regions.py +0 -0
  98. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/tag.py +0 -0
  99. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/telemetry.py +0 -0
  100. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/test.py +0 -0
  101. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  102. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  103. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/workspace.py +0 -0
  104. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  105. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird/tornado_template.py +0 -0
  106. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird.egg-info/SOURCES.txt +0 -0
  107. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird.egg-info/dependency_links.txt +0 -0
  108. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird.egg-info/entry_points.txt +0 -0
  109. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/tinybird.egg-info/requires.txt +0 -0
  110. {tinybird-0.0.1.dev190 → tinybird-0.0.1.dev192}/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.dev190
3
+ Version: 0.0.1.dev192
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -1010,3 +1010,22 @@ Current README.md file:
1010
1010
  <readme>[readme content here]</readme>
1011
1011
  </readme_instructions>
1012
1012
  """
1013
+
1014
+
1015
+ def quarantine_prompt(datasource_definition: str) -> str:
1016
+ return f"""
1017
+ - You are an expert in Tinybird.
1018
+ - You are given a list of rows that went to quarantine during ingestion because of data quality issues.
1019
+ - Return the errors in a human readable format so the user can understand what is the problem and fix it.
1020
+ - Be concise and to the point.
1021
+ - Do not mention clickhouse tables. Refer to data sources instead.
1022
+ - The possible fixes recommended fixes are:
1023
+ - Changing a column type in the datasource definition: tell the user what to change in the datasource file, then build again before appending the data.
1024
+ - Changing the data because of the wrong data type: tell the user what to change in the data file, then append the data again.
1025
+ - The format of the response always inside the tag <quarantine_errors>[response]</quarantine_errors>
1026
+ - Do not use markdown format in the response, because it is a CLI output.
1027
+ - The datasource definition is the following:
1028
+ <datasource_definition>
1029
+ {datasource_definition}
1030
+ </datasource_definition>
1031
+ """
@@ -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__ = '0.0.1.dev190'
8
- __revision__ = '8a00bcf'
7
+ __version__ = '0.0.1.dev192'
8
+ __revision__ = '116c2dc'
@@ -258,7 +258,6 @@ def show_data(tb_client: TinyB, filename: str, diff: Optional[str] = None):
258
258
  table_name = diff
259
259
  resource_path = Path(filename)
260
260
  resource_name = resource_path.stem
261
- resource_content = resource_path.read_text()
262
261
 
263
262
  pipeline = resource_name if filename.endswith(".pipe") else None
264
263
 
@@ -269,7 +268,7 @@ def show_data(tb_client: TinyB, filename: str, diff: Optional[str] = None):
269
268
 
270
269
  res = asyncio.run(tb_client.query(sql, pipeline=pipeline))
271
270
  print_table_formatted(res, table_name)
272
- if Project.is_endpoint(resource_content):
271
+ if Project.get_pipe_type(filename) == "endpoint":
273
272
  example_params = {
274
273
  "format": "json",
275
274
  "pipe": resource_name,
@@ -28,7 +28,9 @@ from tinybird.tb.modules.common import (
28
28
  CLIException,
29
29
  _get_tb_client,
30
30
  coro,
31
+ echo_json,
31
32
  echo_safe_format_table,
33
+ force_echo,
32
34
  getenv_bool,
33
35
  try_update_config_with_remote,
34
36
  )
@@ -72,7 +74,7 @@ VERSION = f"{__cli__.__version__} (rev {__cli__.__revision__})"
72
74
  @click.option("--cloud/--local", is_flag=True, default=False, help="Run against cloud or local.")
73
75
  @click.option("--staging", is_flag=True, default=False, help="Run against a staging deployment.")
74
76
  @click.option(
75
- "--output", type=click.Choice(["json", "human"], case_sensitive=False), default="human", help="Output format"
77
+ "--output", type=click.Choice(["human", "json", "csv"], case_sensitive=False), default="human", help="Output format"
76
78
  )
77
79
  @click.option("--max-depth", type=int, default=3, help="Maximum depth of the project files.")
78
80
  @click.version_option(version=VERSION)
@@ -95,7 +97,7 @@ async def cli(
95
97
  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.
96
98
  """
97
99
  # We need to unpatch for our tests not to break
98
- if output == "json":
100
+ if output != "human":
99
101
  __hide_click_output()
100
102
  else:
101
103
  if show_tokens or not cloud or ctx.invoked_subcommand == "build":
@@ -195,17 +197,10 @@ async def pull(ctx: Context, force: bool, fmt: bool) -> None:
195
197
 
196
198
  @cli.command()
197
199
  @click.argument("query", required=False)
198
- @click.option("--rows_limit", default=100, help="Max number of rows retrieved")
200
+ @click.option("--rows-limit", default=100, help="Max number of rows retrieved")
199
201
  @click.option("--pipeline", default=None, help="The name of the pipe to run the SQL Query")
200
202
  @click.option("--pipe", default=None, help="The path to the .pipe file to run the SQL Query of a specific NODE")
201
203
  @click.option("--node", default=None, help="The NODE name")
202
- @click.option(
203
- "--format",
204
- "format_",
205
- type=click.Choice(["json", "csv", "human"], case_sensitive=False),
206
- default="human",
207
- help="Output format",
208
- )
209
204
  @click.option("--stats/--no-stats", default=False, help="Show query stats")
210
205
  @click.pass_context
211
206
  @coro
@@ -216,13 +211,13 @@ async def sql(
216
211
  pipeline: Optional[str],
217
212
  pipe: Optional[str],
218
213
  node: Optional[str],
219
- format_: str,
220
214
  stats: bool,
221
215
  ) -> None:
222
216
  """Run SQL query over data sources and pipes."""
223
-
224
217
  client = ctx.ensure_object(dict)["client"]
225
- req_format = "CSVWithNames" if format_ == "csv" else "JSON"
218
+ output = ctx.ensure_object(dict)["output"]
219
+
220
+ req_format = "CSVWithNames" if output == "csv" else "JSON"
226
221
  res = None
227
222
  try:
228
223
  if query:
@@ -275,11 +270,11 @@ async def sql(
275
270
  bytes_read = humanfriendly.format_size(stats_dict["bytes_read"])
276
271
  click.echo(FeedbackManager.info_query_stats(seconds=seconds, rows=rows_read, bytes=bytes_read))
277
272
 
278
- if format_ == "csv":
279
- click.echo(res)
273
+ if output == "csv":
274
+ force_echo(str(res))
280
275
  elif isinstance(res, dict) and "data" in res and res["data"]:
281
- if format_ == "json":
282
- click.echo(json.dumps(res, indent=8))
276
+ if output == "json":
277
+ echo_json(res, indent=8)
283
278
  else:
284
279
  dd = []
285
280
  for d in res["data"]:
@@ -2250,5 +2250,9 @@ def get_error_event(error: str) -> Tuple[str, str]:
2250
2250
  return error_event, silent_error_msg
2251
2251
 
2252
2252
 
2253
- def echo_json(data: Dict[str, Any]) -> None:
2254
- click.echo(json.dumps(data), force_output=True) # type: ignore
2253
+ def force_echo(string: str) -> None:
2254
+ click.echo(string, force_output=True) # type: ignore
2255
+
2256
+
2257
+ def echo_json(data: Dict[str, Any], indent: Union[None, int, str] = None) -> None:
2258
+ force_echo(json.dumps(data, indent=indent))
@@ -18,6 +18,7 @@ import humanfriendly
18
18
  import requests
19
19
  from click import Context
20
20
 
21
+ from tinybird.prompts import quarantine_prompt
21
22
  from tinybird.syncasync import sync_to_async
22
23
  from tinybird.tb.client import AuthNoTokenException, DoesNotExistException, TinyB
23
24
  from tinybird.tb.modules.cli import cli
@@ -42,7 +43,10 @@ from tinybird.tb.modules.datafile.common import get_name_version
42
43
  from tinybird.tb.modules.datafile.fixture import persist_fixture
43
44
  from tinybird.tb.modules.exceptions import CLIDatasourceException
44
45
  from tinybird.tb.modules.feedback_manager import FeedbackManager
46
+ from tinybird.tb.modules.llm import LLM
47
+ from tinybird.tb.modules.llm_utils import extract_xml
45
48
  from tinybird.tb.modules.project import Project
49
+ from tinybird.tb.modules.telemetry import add_telemetry_event
46
50
 
47
51
 
48
52
  @cli.group()
@@ -151,6 +155,7 @@ async def datasource_append(
151
155
  """
152
156
  env: str = ctx.ensure_object(dict)["env"]
153
157
  client: TinyB = ctx.obj["client"]
158
+ project: Project = ctx.ensure_object(dict)["project"]
154
159
 
155
160
  # If data is passed as argument, we detect if it's a JSON object, a URL or a file
156
161
  if data:
@@ -244,15 +249,10 @@ async def datasource_append(
244
249
 
245
250
  if events:
246
251
  click.echo(FeedbackManager.highlight(message=f"\n» Sending events to {datasource_name}"))
247
- try:
248
- json_data = json.loads(events)
249
- except Exception:
250
- raise CLIDatasourceException(FeedbackManager.error(message="Invalid events data"))
251
-
252
252
  response = await sync_to_async(requests.post)(
253
253
  f"{client.host}/v0/events?name={datasource_name}",
254
254
  headers={"Authorization": f"Bearer {client.token}"},
255
- json=json_data,
255
+ data=events,
256
256
  )
257
257
 
258
258
  try:
@@ -269,21 +269,32 @@ async def datasource_append(
269
269
  )
270
270
  )
271
271
  if quarantined_rows > 0:
272
- raise CLIDatasourceException(
272
+ click.echo(
273
273
  FeedbackManager.error(
274
- message=f"{quarantined_rows} row{'' if quarantined_rows == 1 else 's'} went to quarantine"
274
+ message=f"{quarantined_rows} row{'' if quarantined_rows == 1 else 's'} went to quarantine"
275
275
  )
276
276
  )
277
+ await analyze_quarantine(datasource_name, project, client)
278
+ return
277
279
  else:
278
280
  click.echo(FeedbackManager.highlight(message=f"\n» Appending data to {datasource_name}"))
279
- await push_data(
280
- client,
281
- datasource_name,
282
- data,
283
- mode="append",
284
- concurrency=concurrency,
285
- silent=True,
286
- )
281
+ try:
282
+ await push_data(
283
+ client,
284
+ datasource_name,
285
+ data,
286
+ mode="append",
287
+ concurrency=concurrency,
288
+ silent=True,
289
+ )
290
+ except Exception as e:
291
+ is_quarantined = "quarantine" in str(e)
292
+ click.echo(FeedbackManager.error(message="✗ " + str(e)))
293
+ if is_quarantined:
294
+ await analyze_quarantine(datasource_name, project, client)
295
+ return
296
+ else:
297
+ raise e
287
298
  click.echo(FeedbackManager.success(message="✓ Rows appended!"))
288
299
 
289
300
 
@@ -902,3 +913,33 @@ def generate_short_id():
902
913
 
903
914
  def generate_kafka_group_id(topic: str):
904
915
  return f"{topic}_{int(datetime.timestamp(datetime.now()))}"
916
+
917
+
918
+ async def analyze_quarantine(datasource_name: str, project: Project, client: TinyB):
919
+ config = CLIConfig.get_project_config()
920
+ res = await client.query(
921
+ f"SELECT * FROM {datasource_name}_quarantine ORDER BY insertion_date DESC LIMIT 1 FORMAT JSON"
922
+ )
923
+ quarantine_data = res["data"]
924
+ error_message = json.dumps(res["data"])
925
+ user_token = config.get_user_token()
926
+ click.echo(FeedbackManager.gray(message=f"\n» Analyzing errors in {datasource_name}_quarantine..."))
927
+ if user_token:
928
+ llm = LLM(user_token=user_token, host=config.get_client().host)
929
+ ds_filenames = project.get_datasource_files()
930
+ datasource_definition = next(
931
+ (Path(f).read_text() for f in ds_filenames if f.endswith(f"{datasource_name}.datasource")), ""
932
+ )
933
+ response_llm = llm.ask(
934
+ system_prompt=quarantine_prompt(datasource_definition),
935
+ prompt=f"The quarantine errors are:\n{json.dumps(quarantine_data)}",
936
+ )
937
+ response = extract_xml(response_llm, "quarantine_errors")
938
+ error_message += "\n" + response
939
+ click.echo(response)
940
+ else:
941
+ echo_safe_humanfriendly_tables_format_smart_table(
942
+ data=[d.values() for d in res["data"]], column_names=res["data"][0].keys()
943
+ )
944
+
945
+ add_telemetry_event("datasource_error", error=f"quarantine_error: {error_message}")
@@ -15,7 +15,7 @@ from tinybird.tb.modules.common import (
15
15
  get_display_cloud_host,
16
16
  sys_exit,
17
17
  )
18
- from tinybird.tb.modules.feedback_manager import FeedbackManager
18
+ from tinybird.tb.modules.feedback_manager import FeedbackManager, bcolors
19
19
  from tinybird.tb.modules.project import Project
20
20
 
21
21
 
@@ -229,10 +229,17 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
229
229
 
230
230
  last_deployment = result.get("deployment")
231
231
  if last_deployment.get("status") == "deleted":
232
- click.echo(FeedbackManager.success(message="✓ Deployment is live!"))
232
+ click.echo(FeedbackManager.success(message=f"✓ Deployment #{candidate_deployment.get('id')} is live!"))
233
233
  break
234
234
 
235
235
  time.sleep(5)
236
+ if last_deployment.get("id") == "0":
237
+ # This is the first deployment, so we prompt the user to ingest data
238
+ click.echo(
239
+ FeedbackManager.info(
240
+ message="A deployment with no data is useless. Learn how to ingest at https://www.tinybird.co/docs/forward/get-data-in"
241
+ )
242
+ )
236
243
 
237
244
 
238
245
  # TODO(eclbg): This logic should be in the server, and there should be a dedicated endpoint for discarding a
@@ -590,8 +597,8 @@ def create_deployment(
590
597
  if status == "success":
591
598
  host = get_display_cloud_host(client.host)
592
599
  click.echo(
593
- FeedbackManager.gray(message="Deployment URL: ")
594
- + FeedbackManager.info(message=f"{host}/{config.get('name')}/deployments/{deployment.get('id')}")
600
+ FeedbackManager.info(message="Deployment URL: ")
601
+ + f"{bcolors.UNDERLINE}{host}/{config.get('name')}/deployments/{deployment.get('id')}{bcolors.ENDC}"
595
602
  )
596
603
 
597
604
  if wait:
@@ -655,40 +662,46 @@ def create_deployment(
655
662
 
656
663
  def print_changes(result: dict, project: Project) -> None:
657
664
  deployment = result.get("deployment", {})
658
- resources_columns = ["status", "name", "path"]
665
+ resources_columns = ["status", "name", "type", "path"]
659
666
  resources: list[list[Union[str, None]]] = []
660
667
  tokens_columns = ["Change", "Token name", "Added permissions", "Removed permissions"]
661
668
  tokens: list[Tuple[str, str, str, str]] = []
662
669
 
663
670
  for ds in deployment.get("new_datasource_names", []):
664
- resources.append(["new", ds, project.get_resource_path(ds, "datasource")])
671
+ resources.append(["new", ds, "datasource", project.get_resource_path(ds, "datasource")])
665
672
 
666
673
  for p in deployment.get("new_pipe_names", []):
667
- resources.append(["new", p, project.get_resource_path(p, "pipe")])
674
+ path = str(project.get_resource_path(p, "pipe"))
675
+ pipe_type = project.get_pipe_type(path)
676
+ resources.append(["new", p, pipe_type, path])
668
677
 
669
678
  for dc in deployment.get("new_data_connector_names", []):
670
- resources.append(["new", dc, project.get_resource_path(dc, "connection")])
679
+ resources.append(["new", dc, "connection", project.get_resource_path(dc, "connection")])
671
680
 
672
681
  for ds in deployment.get("changed_datasource_names", []):
673
- resources.append(["modified", ds, project.get_resource_path(ds, "datasource")])
682
+ resources.append(["modified", ds, "datasource", project.get_resource_path(ds, "datasource")])
674
683
 
675
684
  for p in deployment.get("changed_pipe_names", []):
676
- resources.append(["modified", p, project.get_resource_path(p, "pipe")])
685
+ path = str(project.get_resource_path(p, "pipe"))
686
+ pipe_type = project.get_pipe_type(path)
687
+ resources.append(["modified", p, pipe_type, path])
677
688
 
678
689
  for dc in deployment.get("changed_data_connector_names", []):
679
- resources.append(["modified", dc, project.get_resource_path(dc, "connection")])
690
+ resources.append(["modified", dc, "connection", project.get_resource_path(dc, "connection")])
680
691
 
681
692
  for ds in deployment.get("disconnected_data_source_names", []):
682
- resources.append(["modified", ds, project.get_resource_path(ds, "datasource")])
693
+ resources.append(["modified", ds, "datasource", project.get_resource_path(ds, "datasource")])
683
694
 
684
695
  for ds in deployment.get("deleted_datasource_names", []):
685
- resources.append(["deleted", ds, project.get_resource_path(ds, "datasource")])
696
+ resources.append(["deleted", ds, "datasource", project.get_resource_path(ds, "datasource")])
686
697
 
687
698
  for p in deployment.get("deleted_pipe_names", []):
688
- resources.append(["deleted", p, project.get_resource_path(p, "pipe")])
699
+ path = str(project.get_resource_path(p, "pipe"))
700
+ pipe_type = project.get_pipe_type(path)
701
+ resources.append(["deleted", p, pipe_type, path])
689
702
 
690
703
  for dc in deployment.get("deleted_data_connector_names", []):
691
- resources.append(["deleted", dc, project.get_resource_path(dc, "connection")])
704
+ resources.append(["deleted", dc, "connection", project.get_resource_path(dc, "connection")])
692
705
 
693
706
  for token_change in deployment.get("token_changes", []):
694
707
  token_name = token_change.get("token_name")
@@ -707,9 +720,9 @@ def print_changes(result: dict, project: Project) -> None:
707
720
  click.echo(FeedbackManager.info(message="\n* Changes to be deployed:"))
708
721
  echo_safe_humanfriendly_tables_format_smart_table(resources, column_names=resources_columns)
709
722
  else:
710
- click.echo(FeedbackManager.info(message="\n* No changes to be deployed"))
723
+ click.echo(FeedbackManager.gray(message="\n* No changes to be deployed"))
711
724
  if tokens:
712
725
  click.echo(FeedbackManager.info(message="\n* Changes in tokens to be deployed:"))
713
726
  echo_safe_humanfriendly_tables_format_smart_table(tokens, column_names=tokens_columns)
714
727
  else:
715
- click.echo(FeedbackManager.info(message="* No changes in tokens to be deployed"))
728
+ click.echo(FeedbackManager.gray(message="* No changes in tokens to be deployed"))
@@ -437,6 +437,9 @@ class FeedbackManager:
437
437
  error_tag_not_found = error_message("Tag {tag_name} not found.")
438
438
  error_build_failed = error_message("Build failed")
439
439
  error_request_failed = error_message("Request failed with status code {status_code}, please try again later.")
440
+ error_invalid_output_format = error_message(
441
+ "Invalid output format for this command. Supported formats are: {formats}"
442
+ )
440
443
 
441
444
  info_incl_relative_path = info_message("** Relative path {path} does not exist, skipping.")
442
445
  info_ignoring_incl_file = info_message(
@@ -6,7 +6,7 @@ import click
6
6
  from tinybird.tb.client import TinyB
7
7
  from tinybird.tb.config import get_display_cloud_host
8
8
  from tinybird.tb.modules.cli import CLIConfig, cli
9
- from tinybird.tb.modules.common import coro, echo_json, format_robust_table
9
+ from tinybird.tb.modules.common import coro, echo_json, force_echo, format_robust_table
10
10
  from tinybird.tb.modules.feedback_manager import FeedbackManager
11
11
  from tinybird.tb.modules.local_common import TB_LOCAL_ADDRESS, get_tinybird_local_config
12
12
  from tinybird.tb.modules.project import Project
@@ -19,14 +19,19 @@ async def info(ctx: click.Context) -> None:
19
19
  """Get information about the project that is currently being used"""
20
20
  ctx_config = ctx.ensure_object(dict)["config"]
21
21
  project: Project = ctx.ensure_object(dict)["project"]
22
- is_json = ctx.ensure_object(dict)["output"] == "json"
22
+ output = ctx.ensure_object(dict)["output"]
23
+
24
+ if output not in {"human", "json"}:
25
+ force_echo(FeedbackManager.error_invalid_output_format(formats=", ".join(["human", "json"])))
26
+ return
27
+
23
28
  click.echo(FeedbackManager.highlight(message="» Tinybird Cloud:"))
24
29
  cloud_table, cloud_columns = await get_cloud_info(ctx_config)
25
30
  click.echo(FeedbackManager.highlight(message="\n» Tinybird Local:"))
26
31
  local_table, local_columns = await get_local_info(ctx_config)
27
32
  click.echo(FeedbackManager.highlight(message="\n» Project:"))
28
33
  project_table, project_columns = await get_project_info(project.folder)
29
- if is_json:
34
+ if output == "json":
30
35
  cloud_data = {}
31
36
  if cloud_columns and cloud_table and isinstance(cloud_table, list) and len(cloud_table) > 0:
32
37
  cloud_data = {column: cloud_table[0][i] for i, column in enumerate(cloud_columns)}
@@ -51,7 +51,9 @@ class Project:
51
51
  return project_files
52
52
 
53
53
  def get_resource_path(self, resource_name: str, resource_type: str) -> Optional[str]:
54
- full_path = next((p for p in self.get_project_files() if p.endswith(resource_name + f".{resource_type}")), "")
54
+ full_path = next(
55
+ (p for p in self.get_project_files() if p.endswith("/" + resource_name + f".{resource_type}")), ""
56
+ )
55
57
  if not full_path:
56
58
  return None
57
59
  return Path(full_path).relative_to(self.path).as_posix()
@@ -114,8 +116,15 @@ class Project:
114
116
  return datafiles
115
117
 
116
118
  @staticmethod
117
- def is_endpoint(content: str) -> bool:
118
- return re.search(r"TYPE endpoint", content, re.IGNORECASE) is not None
119
+ def get_pipe_type(path: str) -> str:
120
+ content = Path(path).read_text()
121
+ if re.search(r"TYPE endpoint", content, re.IGNORECASE):
122
+ return "endpoint"
123
+ elif re.search(r"TYPE materialized", content, re.IGNORECASE):
124
+ return "materialization"
125
+ elif re.search(r"TYPE copy", content, re.IGNORECASE):
126
+ return "copy"
127
+ return "pipe"
119
128
 
120
129
  @staticmethod
121
130
  def is_kafka_connection(content: str) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev190
3
+ Version: 0.0.1.dev192
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird