tinybird 0.0.1.dev30__tar.gz → 0.0.1.dev32__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 (101) hide show
  1. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/__cli__.py +2 -2
  3. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/config.py +1 -0
  4. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/context.py +1 -0
  5. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/sql_template.py +21 -2
  6. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/__cli__.py +2 -2
  7. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/build.py +48 -21
  8. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/cli.py +8 -7
  9. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/create.py +2 -2
  10. tinybird-0.0.1.dev32/tinybird/tb/modules/llm.py +38 -0
  11. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/local_common.py +1 -1
  12. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/mock.py +1 -1
  13. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/shell.py +28 -33
  14. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/test.py +1 -1
  15. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/update.py +2 -2
  16. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/watch.py +76 -1
  17. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird.egg-info/PKG-INFO +1 -1
  18. tinybird-0.0.1.dev30/tinybird/tb/modules/llm.py +0 -120
  19. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/setup.cfg +0 -0
  20. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/ch_utils/constants.py +0 -0
  21. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/ch_utils/engine.py +0 -0
  22. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/check_pypi.py +0 -0
  23. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/client.py +0 -0
  24. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/connectors.py +0 -0
  25. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/datafile.py +0 -0
  26. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/datatypes.py +0 -0
  27. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/feedback_manager.py +0 -0
  28. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/git_settings.py +0 -0
  29. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/prompts.py +0 -0
  30. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/sql.py +0 -0
  31. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/sql_template_fmt.py +0 -0
  32. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/sql_toolset.py +0 -0
  33. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/syncasync.py +0 -0
  34. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/cli.py +0 -0
  35. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/auth.py +0 -0
  36. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/build_client.py +0 -0
  37. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/cicd.py +0 -0
  38. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/common.py +0 -0
  39. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/config.py +0 -0
  40. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/connection.py +0 -0
  41. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/build.py +0 -0
  42. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/build_common.py +0 -0
  43. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  44. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  45. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/common.py +0 -0
  46. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/diff.py +0 -0
  47. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  48. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/fixture.py +0 -0
  49. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/format_common.py +0 -0
  50. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  51. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  52. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  53. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  54. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  55. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datafile/pull.py +0 -0
  56. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/datasource.py +0 -0
  57. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/deploy.py +0 -0
  58. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/exceptions.py +0 -0
  59. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/feedback_manager.py +0 -0
  60. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/fmt.py +0 -0
  61. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/job.py +0 -0
  62. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/llm_utils.py +0 -0
  63. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/local.py +0 -0
  64. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/login.py +0 -0
  65. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/pipe.py +0 -0
  66. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/regions.py +0 -0
  67. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/table.py +0 -0
  68. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/tag.py +0 -0
  69. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/telemetry.py +0 -0
  70. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  71. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  72. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/token.py +0 -0
  73. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/workspace.py +0 -0
  74. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb/modules/workspace_members.py +0 -0
  75. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli.py +0 -0
  76. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/auth.py +0 -0
  77. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/branch.py +0 -0
  78. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/cicd.py +0 -0
  79. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/cli.py +0 -0
  80. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/common.py +0 -0
  81. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/config.py +0 -0
  82. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/connection.py +0 -0
  83. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/datasource.py +0 -0
  84. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/exceptions.py +0 -0
  85. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/fmt.py +0 -0
  86. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/job.py +0 -0
  87. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/pipe.py +0 -0
  88. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/regions.py +0 -0
  89. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/tag.py +0 -0
  90. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/telemetry.py +0 -0
  91. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/test.py +0 -0
  92. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  93. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  94. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/workspace.py +0 -0
  95. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  96. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird/tornado_template.py +0 -0
  97. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird.egg-info/SOURCES.txt +0 -0
  98. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird.egg-info/dependency_links.txt +0 -0
  99. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird.egg-info/entry_points.txt +0 -0
  100. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/tinybird.egg-info/requires.txt +0 -0
  101. {tinybird-0.0.1.dev30 → tinybird-0.0.1.dev32}/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.dev30
3
+ Version: 0.0.1.dev32
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -1,6 +1,6 @@
1
- __name__ = "tinybird-cli"
1
+ __name__ = "tinybird_cli"
2
2
  __description__ = "Tinybird Command Line Tool"
3
- __url__ = "https://www.tinybird.co/docs/cli/introduction.html"
3
+ __url__ = "https://www.tinybird.co/docs/cli"
4
4
  __author__ = "Tinybird"
5
5
  __author_email__ = "support@tinybird.co"
6
6
  __version__ = "x.y.z"
@@ -29,6 +29,7 @@ LEGACY_HOSTS = {
29
29
  "https://api.us-east.aws.tinybird.co": "https://app.tinybird.co/aws/us-east-1",
30
30
  "https://api.us-west-2.aws.tinybird.co": "https://app.tinybird.co/aws/us-west-2",
31
31
  "https://api.eu-central-1.aws.tinybird.co": "https://app.tinybird.co/aws/eu-central-1",
32
+ "https://api.eu-west-1.aws.tinybird.co": "https://app.tinybird.co/aws/eu-west-1",
32
33
  "https://api.ap-east.aws.tinybird.co": "https://app.tinybird.co/aws/ap-east",
33
34
  "https://api.wadus1.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus1",
34
35
  "https://api.wadus2.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus2",
@@ -21,3 +21,4 @@ wait_parameter: ContextVar[bool] = ContextVar("wait_parameter")
21
21
  api_host: ContextVar[str] = ContextVar("api_host")
22
22
  ff_split_to_array_escape: ContextVar[bool] = ContextVar("ff_split_to_array_escape")
23
23
  ff_preprocess_parameters_circuit_breaker: ContextVar[bool] = ContextVar("ff_preprocess_parameters_circuit_breaker")
24
+ ff_column_json_backticks_circuit_breaker: ContextVar[bool] = ContextVar("ff_column_json_backticks_circuit_breaker")
@@ -12,7 +12,11 @@ from typing import Any, Dict, List, Optional, Tuple, Union
12
12
  from tornado import escape
13
13
  from tornado.util import ObjectDict, exec_in, unicode_type
14
14
 
15
- from tinybird.context import ff_preprocess_parameters_circuit_breaker, ff_split_to_array_escape
15
+ from tinybird.context import (
16
+ ff_column_json_backticks_circuit_breaker,
17
+ ff_preprocess_parameters_circuit_breaker,
18
+ ff_split_to_array_escape,
19
+ )
16
20
 
17
21
  from .datatypes import testers
18
22
  from .tornado_template import VALID_CUSTOM_FUNCTION_NAMES, SecurityException, Template
@@ -271,6 +275,8 @@ def columns(x, default=None, fn=None):
271
275
 
272
276
 
273
277
  def column(x, default=None):
278
+ bypass_colunn_json_backticks = ff_column_json_backticks_circuit_breaker.get(False)
279
+
274
280
  if x is None or isinstance(x, Placeholder):
275
281
  if default is None:
276
282
  raise SQLTemplateException(
@@ -278,7 +284,18 @@ def column(x, default=None):
278
284
  documentation="/cli/advanced-templates.html#column",
279
285
  )
280
286
  x = default
281
- return Symbol("`" + sqlescape(x) + "`")
287
+
288
+ quote = "`"
289
+ if bypass_colunn_json_backticks:
290
+ return Symbol(quote + sqlescape(x) + quote)
291
+
292
+ try:
293
+ slices = x.split(".")
294
+ escaped_slices = [quote + sqlescape(s) + quote for s in slices]
295
+ escaped = ".".join(escaped_slices)
296
+ return Symbol(escaped)
297
+ except Exception: # in case there's a problem with .split
298
+ return Symbol(quote + sqlescape(x) + quote)
282
299
 
283
300
 
284
301
  def symbol(x, quote="`"):
@@ -2109,6 +2126,8 @@ def render_sql_template(
2109
2126
  ('SELECT `foo`', {}, [])
2110
2127
  >>> render_sql_template("SELECT {{column(agg)}}", {'agg': '"foo"'})
2111
2128
  ('SELECT `foo`', {}, [])
2129
+ >>> render_sql_template("SELECT {{column(agg)}}", {'agg': 'json.a'})
2130
+ ('SELECT `json`.`a`', {}, [])
2112
2131
  >>> render_sql_template('{% if not defined(test) %}error("This is an error"){% end %}', {})
2113
2132
  ('error("This is an error")', {}, [])
2114
2133
  >>> render_sql_template('{% if not defined(test) %}custom_error({error: "This is an error"}){% end %}', {})
@@ -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.dev30'
8
- __revision__ = 'f7bc1dc'
7
+ __version__ = '0.0.1.dev32'
8
+ __revision__ = 'fc755d1'
@@ -2,18 +2,49 @@ import asyncio
2
2
  import glob
3
3
  import json
4
4
  import logging
5
+ import threading
5
6
  from pathlib import Path
6
7
  from typing import List
7
8
 
8
9
  import click
9
10
  import requests
10
11
 
12
+ from tinybird.client import TinyB
11
13
  from tinybird.tb.modules.cli import cli
12
14
  from tinybird.tb.modules.feedback_manager import FeedbackManager
13
15
  from tinybird.tb.modules.local_common import get_tinybird_local_client
16
+ from tinybird.tb.modules.shell import Shell
17
+ from tinybird.tb.modules.watch import watch_project
14
18
 
15
19
 
16
- def project_files(project_path: Path) -> List[str]:
20
+ @cli.command()
21
+ @click.option("--folder", type=str, default=".")
22
+ @click.option("--watch", is_flag=True, default=False, help="Watch for changes and rebuild automatically")
23
+ def build(folder: str, watch: bool) -> None:
24
+ """
25
+ Validate and build the project server side.
26
+ """
27
+
28
+ tb_client = asyncio.run(get_tinybird_local_client(folder))
29
+
30
+ def process() -> None:
31
+ build_project(folder, tb_client)
32
+
33
+ process()
34
+
35
+ if watch:
36
+ shell = Shell(folder=folder, client=tb_client)
37
+ click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
38
+ watcher_thread = threading.Thread(
39
+ target=watch_project,
40
+ args=(shell, process, folder),
41
+ daemon=True,
42
+ )
43
+ watcher_thread.start()
44
+ shell.run()
45
+
46
+
47
+ def get_project_files(project_path: Path) -> List[str]:
17
48
  project_file_extensions = ("datasource", "pipe")
18
49
  project_files = []
19
50
  for extension in project_file_extensions:
@@ -23,38 +54,32 @@ def project_files(project_path: Path) -> List[str]:
23
54
  return project_files
24
55
 
25
56
 
26
- @cli.command()
27
- @click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
28
- def build(project_path: Path) -> None:
29
- """
30
- Validate and build the project server side.
31
- """
57
+ def build_project(folder: str, tb_client: TinyB) -> None:
32
58
  MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
33
59
  DATAFILE_TYPE_TO_CONTENT_TYPE = {
34
60
  ".datasource": "text/plain",
35
61
  ".pipe": "text/plain",
36
62
  }
37
-
38
- tb_client = asyncio.run(get_tinybird_local_client(str(project_path)))
39
63
  TINYBIRD_API_URL = tb_client.host + "/v1/build"
40
64
  TINYBIRD_API_KEY = tb_client.token
41
-
42
- files = [
43
- ("context://", ("cli-version", "1.0.0", "text/plain")),
44
- ]
45
- fds = []
46
- for file_path in project_files(project_path):
47
- relative_path = str(Path(file_path).relative_to(project_path))
48
- fd = open(file_path, "rb")
49
- fds.append(fd)
50
- content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
51
- files.append((MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type)))
52
-
53
65
  try:
66
+ files = [
67
+ ("context://", ("cli-version", "1.0.0", "text/plain")),
68
+ ]
69
+ fds = []
70
+ project_path = Path(folder)
71
+ project_files = get_project_files(project_path)
72
+ for file_path in project_files:
73
+ relative_path = str(Path(file_path).relative_to(project_path))
74
+ fd = open(file_path, "rb")
75
+ fds.append(fd)
76
+ content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
77
+ files.append((MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type)))
54
78
  HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
55
79
 
56
80
  r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS)
57
81
  result = r.json()
82
+
58
83
  logging.debug(json.dumps(result, indent=2))
59
84
 
60
85
  build_result = result.get("result")
@@ -69,6 +94,8 @@ def build(project_path: Path) -> None:
69
94
  click.echo(FeedbackManager.error(message=error_msg))
70
95
  else:
71
96
  click.echo(FeedbackManager.error(message=f"Unknown build result. Error: {result.get('error')}"))
97
+ except Exception as e:
98
+ click.echo(FeedbackManager.error_exception(error="Error building project: " + str(e)))
72
99
  finally:
73
100
  for fd in fds:
74
101
  fd.close()
@@ -210,7 +210,8 @@ async def cli(
210
210
 
211
211
  logging.debug("debug enabled")
212
212
 
213
- client = await create_ctx_client(config, prod)
213
+ skip_client = ctx.invoked_subcommand in ["login", "workspace", "local"]
214
+ client = await create_ctx_client(config, prod, skip_client)
214
215
 
215
216
  if client:
216
217
  ctx.ensure_object(dict)["client"] = client
@@ -529,7 +530,7 @@ async def ask(prompt: str, folder: str) -> None:
529
530
 
530
531
  client = config.get_client()
531
532
  llm = LLM(user_token=user_token, client=client)
532
- click.echo(await llm.ask(prompt, system_prompt=ask_prompt(resources_xml)))
533
+ click.echo(await llm.ask(system_prompt=ask_prompt(resources_xml), prompt=prompt))
533
534
  except Exception as e:
534
535
  raise CLIException(FeedbackManager.error_exception(error=e))
535
536
 
@@ -571,11 +572,11 @@ def __unpatch_click_output():
571
572
  click.secho = __old_click_secho
572
573
 
573
574
 
574
- async def create_ctx_client(config: Dict[str, Any], prod: bool):
575
+ async def create_ctx_client(config: Dict[str, Any], prod: bool, skip_client: bool):
576
+ if skip_client:
577
+ return None
578
+
575
579
  if prod:
576
580
  return _get_tb_client(config.get("token", None), config["host"])
577
581
 
578
- try:
579
- return await get_tinybird_local_client()
580
- except Exception:
581
- return None
582
+ return await get_tinybird_local_client()
@@ -109,7 +109,7 @@ async def create(
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(prompt, system_prompt=mock_prompt(rows))
112
+ response = await 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")
@@ -206,7 +206,7 @@ TYPE ENDPOINT
206
206
  ]
207
207
  )
208
208
  llm = LLM(user_token=user_token, client=tb_client)
209
- result = await llm.ask(prompt, system_prompt=create_prompt(resources_xml))
209
+ result = await 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 = []
@@ -0,0 +1,38 @@
1
+ from copy import deepcopy
2
+ from typing import Optional
3
+
4
+ from tinybird.client import TinyB
5
+
6
+
7
+ class LLM:
8
+ def __init__(
9
+ self,
10
+ user_token: str,
11
+ client: TinyB,
12
+ ):
13
+ self.user_client = deepcopy(client)
14
+ self.user_client.token = user_token
15
+
16
+ async def ask(self, system_prompt: str, prompt: Optional[str] = None) -> str:
17
+ """
18
+ Calls the model with the given prompt and returns the response.
19
+
20
+ Args:
21
+ system_prompt (str): The system prompt to send to the model.
22
+ prompt (str): The user prompt to send to the model.
23
+
24
+ Returns:
25
+ str: The response from the language model.
26
+ """
27
+
28
+ data = {"system": system_prompt}
29
+
30
+ if prompt:
31
+ data["prompt"] = prompt
32
+
33
+ response = await self.user_client._req(
34
+ "/v0/llm",
35
+ method="POST",
36
+ data=data,
37
+ )
38
+ return response.get("result", "")
@@ -8,7 +8,7 @@ from tinybird.client import TinyB
8
8
  from tinybird.tb.modules.config import CLIConfig
9
9
  from tinybird.tb.modules.exceptions import CLIException
10
10
 
11
- TB_IMAGE_NAME = "tinybirdco/tinybird-local:latest"
11
+ TB_IMAGE_NAME = "tinybirdco/tinybird-local:beta"
12
12
  TB_CONTAINER_NAME = "tinybird-local"
13
13
  TB_LOCAL_PORT = int(os.getenv("TB_LOCAL_PORT", 80))
14
14
  TB_LOCAL_HOST = f"http://localhost:{TB_LOCAL_PORT}"
@@ -66,7 +66,7 @@ async def mock(datasource: str, rows: int, prompt: str, folder: str) -> None:
66
66
  llm = LLM(user_token=user_token, client=user_client)
67
67
  tb_client = await get_tinybird_local_client(os.path.abspath(folder))
68
68
  prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
69
- response = await llm.ask(prompt, system_prompt=mock_prompt(rows))
69
+ response = await llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
70
70
  sql = extract_xml(response, "sql")
71
71
  if os.environ.get("TB_DEBUG", "") != "":
72
72
  logging.debug(sql)
@@ -1,8 +1,10 @@
1
1
  import asyncio
2
2
  import concurrent.futures
3
+ import glob
3
4
  import os
4
5
  import subprocess
5
6
  import sys
7
+ from pathlib import Path
6
8
  from typing import List
7
9
 
8
10
  import click
@@ -21,17 +23,22 @@ from tinybird.tb.modules.table import format_table
21
23
 
22
24
 
23
25
  class DynamicCompleter(Completer):
24
- def __init__(self, datasources: List[str], shared_datasources: List[str], endpoints: List[str], pipes: List[str]):
25
- self.datasources = datasources
26
- self.shared_datasources = shared_datasources
27
- self.endpoints = endpoints
28
- self.pipes = pipes
26
+ def __init__(self, folder: str):
27
+ self.folder = folder
29
28
  self.static_commands = ["create", "mock", "test", "select"]
30
29
  self.test_commands = ["create", "run", "update"]
31
30
  self.mock_flags = ["--prompt", "--rows"]
32
31
  self.common_rows = ["10", "50", "100", "500", "1000"]
33
32
  self.sql_keywords = ["select", "from", "where", "group by", "order by", "limit"]
34
33
 
34
+ @property
35
+ def datasources(self):
36
+ return [Path(f).stem for f in glob.glob(f"{self.folder}/**/*.datasource", recursive=True)]
37
+
38
+ @property
39
+ def pipes(self):
40
+ return [Path(f).stem for f in glob.glob(f"{self.folder}/**/*.pipe", recursive=True)]
41
+
35
42
  def get_completions(self, document, complete_event):
36
43
  text = document.text_before_cursor.strip()
37
44
  words = text.split()
@@ -75,9 +82,9 @@ class DynamicCompleter(Completer):
75
82
  if words[-1] == "from" or (
76
83
  "from" in words and len(words) > words.index("from") + 1 and text_lower.endswith(" ")
77
84
  ):
78
- for x in self.datasources + self.shared_datasources:
85
+ for x in self.datasources:
79
86
  yield Completion(x, start_position=0, display=x, style="class:completion.datasource")
80
- for x in self.endpoints + self.pipes:
87
+ for x in self.pipes:
81
88
  yield Completion(x, start_position=0, display=x, style="class:completion.pipe")
82
89
  return
83
90
 
@@ -116,7 +123,7 @@ class DynamicCompleter(Completer):
116
123
  yield Completion(cmd, start_position=0, display=cmd, style="class:completion.cmd")
117
124
  return
118
125
  elif len(words) == 2:
119
- for cmd in self.endpoints:
126
+ for cmd in self.pipes:
120
127
  yield Completion(cmd, start_position=0, display=cmd, style="class:completion.pipe")
121
128
  return
122
129
 
@@ -130,7 +137,7 @@ class DynamicCompleter(Completer):
130
137
  style="class:completion.cmd",
131
138
  )
132
139
 
133
- for cmd in self.datasources + self.shared_datasources:
140
+ for cmd in self.datasources:
134
141
  if cmd.startswith(current_word):
135
142
  yield Completion(
136
143
  cmd,
@@ -139,7 +146,7 @@ class DynamicCompleter(Completer):
139
146
  style="class:completion.datasource",
140
147
  )
141
148
 
142
- for cmd in self.endpoints + self.pipes:
149
+ for cmd in self.pipes:
143
150
  if cmd.startswith(current_word):
144
151
  yield Completion(
145
152
  cmd,
@@ -176,26 +183,14 @@ def _(event):
176
183
 
177
184
 
178
185
  class Shell:
179
- def __init__(
180
- self,
181
- folder: str,
182
- client: TinyB,
183
- datasources: List[str],
184
- shared_datasources: List[str],
185
- pipes: List[str],
186
- endpoints: List[str],
187
- ):
186
+ def __init__(self, folder: str, client: TinyB):
188
187
  self.history = self.get_history()
189
188
  self.folder = folder
190
189
  self.client = client
191
- self.datasources = datasources
192
- self.shared_datasources = shared_datasources
193
- self.pipes = pipes
194
- self.endpoints = endpoints
195
190
  self.prompt_message = "\ntb > "
196
191
  self.commands = ["create", "mock", "test", "tb", "select"]
197
192
  self.session: PromptSession = PromptSession(
198
- completer=DynamicCompleter(self.datasources, self.shared_datasources, self.endpoints, self.pipes),
193
+ completer=DynamicCompleter(folder),
199
194
  complete_style=CompleteStyle.COLUMN,
200
195
  complete_while_typing=True,
201
196
  history=self.history,
@@ -283,7 +278,7 @@ class Shell:
283
278
  return
284
279
  if arg.startswith("with") or arg.startswith("select"):
285
280
  self.run_sql(argline)
286
- elif len(arg.split()) == 1 and arg in self.endpoints + self.pipes + self.datasources + self.shared_datasources:
281
+ elif len(arg.split()) == 1 and arg in self.pipes + self.datasources:
287
282
  self.run_sql(f"select * from {arg}")
288
283
  else:
289
284
  subprocess.run(f"tb {arg}", shell=True, text=True)
@@ -310,16 +305,16 @@ class Shell:
310
305
  with concurrent.futures.ThreadPoolExecutor() as executor:
311
306
  res = executor.submit(run_query_in_thread).result()
312
307
 
313
- except Exception as e:
314
- click.echo(FeedbackManager.error_exception(error=str(e)))
308
+ if isinstance(res, dict) and "error" in res:
309
+ click.echo(FeedbackManager.error_exception(error=res["error"]))
315
310
 
316
- if isinstance(res, dict) and "error" in res:
317
- click.echo(FeedbackManager.error_exception(error=res["error"]))
311
+ if isinstance(res, dict) and "data" in res and res["data"]:
312
+ print_table_formatted(res, "QUERY")
313
+ else:
314
+ click.echo(FeedbackManager.info_no_rows())
318
315
 
319
- if isinstance(res, dict) and "data" in res and res["data"]:
320
- print_table_formatted(res, "QUERY")
321
- else:
322
- click.echo(FeedbackManager.info_no_rows())
316
+ except Exception as e:
317
+ click.echo(FeedbackManager.error_exception(error=str(e)))
323
318
 
324
319
  def reprint_prompt(self):
325
320
  click.echo(f"{bcolors.OKGREEN}{self.prompt_message}{bcolors.ENDC}", nl=False)
@@ -111,7 +111,7 @@ async def test_create(name_or_filename: str, prompt: str, folder: str) -> None:
111
111
  raise CLIException(FeedbackManager.error(message="No user token found"))
112
112
  llm = LLM(user_token=user_token, client=config.get_client())
113
113
 
114
- response_llm = await llm.ask(prompt, system_prompt=system_prompt)
114
+ response_llm = await llm.ask(system_prompt=system_prompt, prompt=prompt)
115
115
  response_xml = extract_xml(response_llm, "response")
116
116
  tests_content = parse_xml(response_xml, "test")
117
117
 
@@ -71,7 +71,7 @@ async def update(
71
71
  has_json_path = "`json:" in datasource_content
72
72
  if has_json_path:
73
73
  prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
74
- response = await llm.ask(prompt, system_prompt=mock_prompt(rows=20))
74
+ response = await llm.ask(system_prompt=mock_prompt(rows=20), prompt=prompt)
75
75
  sql = extract_xml(response, "sql")
76
76
  sql = sql.split("FORMAT")[0]
77
77
  result = await local_client.query(f"{sql} FORMAT JSON")
@@ -117,7 +117,7 @@ async def update_resources(
117
117
  ]
118
118
  )
119
119
  llm = LLM(user_token=user_token, client=tb_client)
120
- result = await llm.ask(prompt, system_prompt=update_prompt(resources_xml))
120
+ result = await llm.ask(system_prompt=update_prompt(resources_xml), prompt=prompt)
121
121
  result = extract_xml(result, "response")
122
122
  resources = parse_xml(result, "resource")
123
123
  datasources = []
@@ -1,10 +1,18 @@
1
1
  import asyncio
2
2
  import os
3
3
  import time
4
+ from pathlib import Path
4
5
  from typing import Any, Callable, List, Optional, Union
5
6
 
6
7
  import click
7
- from watchdog.events import DirMovedEvent, FileMovedEvent, FileSystemEventHandler
8
+ from watchdog.events import (
9
+ DirDeletedEvent,
10
+ DirMovedEvent,
11
+ FileDeletedEvent,
12
+ FileMovedEvent,
13
+ FileSystemEventHandler,
14
+ PatternMatchingEventHandler,
15
+ )
8
16
  from watchdog.observers import Observer
9
17
 
10
18
  from tinybird.tb.modules.feedback_manager import FeedbackManager
@@ -110,3 +118,70 @@ def watch_files(
110
118
  observer.stop()
111
119
 
112
120
  observer.join()
121
+
122
+
123
+ class WatchProjectHandler(PatternMatchingEventHandler):
124
+ def __init__(self, shell: Shell, folder: str, process: Callable[[List[str]], None]):
125
+ self.shell = shell
126
+ self.process = process
127
+ super().__init__(
128
+ patterns=[f"{folder}/**/*.datasource", f"{folder}/**/*.pipe"],
129
+ ignore_patterns=[f"{folder}/vendor/"],
130
+ )
131
+
132
+ def should_process(self, event: Any) -> Optional[str]:
133
+ if event.is_directory:
134
+ return None
135
+
136
+ if os.path.exists(event.src_path):
137
+ return event.src_path
138
+
139
+ if os.path.exists(event.dest_path):
140
+ return event.dest_path
141
+
142
+ return event.src_path
143
+
144
+ def _process(self) -> None:
145
+ click.echo(FeedbackManager.highlight(message="» Rebuilding..."))
146
+ time_start = time.time()
147
+ self.process()
148
+ time_end = time.time()
149
+ elapsed_time = time_end - time_start
150
+ click.echo(
151
+ FeedbackManager.success(message="\n✓ ")
152
+ + FeedbackManager.gray(message=f"Rebuild completed in {elapsed_time:.1f}s")
153
+ )
154
+ self.shell.reprint_prompt()
155
+
156
+ def on_modified(self, event: Any) -> None:
157
+ if path := self.should_process(event):
158
+ filename = Path(path).name
159
+ click.echo(FeedbackManager.highlight(message=f"\n\n⟲ Changes detected in {filename}\n"))
160
+ self._process()
161
+
162
+ def on_deleted(self, event: Union[DirDeletedEvent, FileDeletedEvent]) -> None:
163
+ filename = Path(event.src_path).name
164
+ if event.is_directory:
165
+ click.echo(FeedbackManager.highlight(message=f"\n\n⟲ Deleted directory: {filename}\n"))
166
+ else:
167
+ click.echo(FeedbackManager.highlight(message=f"\n\n⟲ Deleted file: {filename}\n"))
168
+ self._process()
169
+
170
+
171
+ def watch_project(
172
+ shell: Shell,
173
+ process: Callable,
174
+ folder: str,
175
+ ) -> None:
176
+ event_handler = WatchProjectHandler(shell=shell, folder=folder, process=process)
177
+ observer = Observer()
178
+ observer.schedule(event_handler, path=folder, recursive=True)
179
+ observer.start()
180
+
181
+ try:
182
+ while True:
183
+ time.sleep(1)
184
+ except KeyboardInterrupt:
185
+ observer.stop()
186
+
187
+ observer.join()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinybird
3
- Version: 0.0.1.dev30
3
+ Version: 0.0.1.dev32
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -1,120 +0,0 @@
1
- import json
2
- import os
3
- import urllib.parse
4
- from copy import deepcopy
5
- from typing import Any, List
6
-
7
- from anthropic import AnthropicVertex
8
- from pydantic import BaseModel
9
-
10
- from tinybird.client import TinyB
11
-
12
-
13
- class DataFile(BaseModel):
14
- name: str
15
- content: str
16
-
17
-
18
- class DataProject(BaseModel):
19
- datasources: List[DataFile]
20
- pipes: List[DataFile]
21
-
22
-
23
- class TestExpectation(BaseModel):
24
- name: str
25
- description: str
26
- parameters: str
27
-
28
-
29
- class TestExpectations(BaseModel):
30
- tests: List[TestExpectation]
31
-
32
-
33
- class LLM:
34
- def __init__(
35
- self,
36
- user_token: str,
37
- client: TinyB,
38
- ):
39
- self.user_client = deepcopy(client)
40
- self.user_client.token = user_token
41
-
42
- async def ask(self, prompt: str, system_prompt: str = "") -> str:
43
- """
44
- Calls the model with the given prompt and returns the response.
45
-
46
- Args:
47
- prompt (str): The user prompt to send to the model.
48
-
49
- Returns:
50
- str: The response from the language model.
51
- """
52
- messages: List[Any] = []
53
-
54
- if system_prompt:
55
- messages.append({"role": "user", "content": system_prompt})
56
-
57
- if prompt:
58
- messages.append({"role": "user", "content": prompt})
59
-
60
- if gcloud_access_token := os.getenv("GCLOUD_ACCESS_TOKEN"):
61
- client = AnthropicVertex(
62
- region="europe-west1",
63
- project_id="gen-lang-client-0705305160",
64
- access_token=gcloud_access_token,
65
- )
66
- message = client.messages.create(
67
- max_tokens=8000,
68
- messages=messages,
69
- model="claude-3-5-sonnet-v2@20241022",
70
- )
71
- return message.content[0].text or "" # type: ignore
72
-
73
- data = {
74
- "model": "o1-mini",
75
- "messages": messages,
76
- }
77
- response = await self.user_client._req(
78
- "/v0/llm",
79
- method="POST",
80
- data=json.dumps(data),
81
- headers={"Content-Type": "application/json"},
82
- )
83
- return response.get("result", "")
84
-
85
- async def create_project(self, prompt: str) -> DataProject:
86
- try:
87
- prompt = (
88
- prompt
89
- + "\n#More extra context\n- If you add some array data type remember that the json path should be like this: `json:$.array_field[:]`"
90
- )
91
- response = await self.user_client._req(
92
- "/v0/llm/create",
93
- method="POST",
94
- data=f'{{"prompt": {json.dumps(prompt)}}}',
95
- headers={"Content-Type": "application/json"},
96
- )
97
-
98
- return DataProject.model_validate(response.get("result", {}))
99
- except Exception:
100
- return DataProject(datasources=[], pipes=[])
101
-
102
- async def generate_sql_sample_data(self, schema: str, rows: int = 20, prompt: str = "") -> str:
103
- response = await self.user_client._req(
104
- "/v0/llm/mock",
105
- method="POST",
106
- data=f'{{"schema": "{urllib.parse.quote(schema)}", "rows": {rows}, "context": "{prompt}"}}',
107
- headers={"Content-Type": "application/json"},
108
- )
109
- result = response.get("result", "")
110
- return result.replace("elementAt", "arrayElement")
111
-
112
- async def create_tests(self, pipe_content: str, pipe_params: set[str], prompt: str = "") -> TestExpectations:
113
- response = await self.user_client._req(
114
- "/v0/llm/create/tests",
115
- method="POST",
116
- data=json.dumps({"pipe_content": pipe_content, "pipe_params": list(pipe_params), "prompt": prompt}),
117
- headers={"Content-Type": "application/json"},
118
- )
119
- result = response.get("result", "")
120
- return TestExpectations.model_validate(result)
File without changes