tinybird 0.0.1.dev26__py3-none-any.whl → 0.0.1.dev27__py3-none-any.whl

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 (36) hide show
  1. tinybird/config.py +1 -1
  2. tinybird/datatypes.py +46 -57
  3. tinybird/git_settings.py +4 -4
  4. tinybird/prompts.py +644 -0
  5. tinybird/sql.py +9 -0
  6. tinybird/sql_toolset.py +17 -3
  7. tinybird/syncasync.py +1 -1
  8. tinybird/tb/__cli__.py +2 -2
  9. tinybird/tb/cli.py +2 -0
  10. tinybird/tb/modules/build.py +44 -16
  11. tinybird/tb/modules/build_server.py +75 -0
  12. tinybird/tb/modules/cli.py +22 -0
  13. tinybird/tb/modules/common.py +2 -2
  14. tinybird/tb/modules/config.py +13 -14
  15. tinybird/tb/modules/create.py +124 -119
  16. tinybird/tb/modules/datafile/build.py +28 -0
  17. tinybird/tb/modules/datafile/common.py +1 -0
  18. tinybird/tb/modules/datafile/fixture.py +10 -6
  19. tinybird/tb/modules/datafile/parse_pipe.py +2 -0
  20. tinybird/tb/modules/datasource.py +1 -1
  21. tinybird/tb/modules/deploy.py +160 -0
  22. tinybird/tb/modules/llm.py +32 -16
  23. tinybird/tb/modules/llm_utils.py +24 -0
  24. tinybird/tb/modules/local.py +2 -2
  25. tinybird/tb/modules/login.py +8 -6
  26. tinybird/tb/modules/mock.py +10 -6
  27. tinybird/tb/modules/test.py +69 -47
  28. tinybird/tb/modules/watch.py +1 -1
  29. tinybird/tb_cli_modules/common.py +2 -2
  30. tinybird/tb_cli_modules/config.py +5 -5
  31. tinybird/tornado_template.py +1 -3
  32. {tinybird-0.0.1.dev26.dist-info → tinybird-0.0.1.dev27.dist-info}/METADATA +1 -1
  33. {tinybird-0.0.1.dev26.dist-info → tinybird-0.0.1.dev27.dist-info}/RECORD +36 -33
  34. {tinybird-0.0.1.dev26.dist-info → tinybird-0.0.1.dev27.dist-info}/WHEEL +0 -0
  35. {tinybird-0.0.1.dev26.dist-info → tinybird-0.0.1.dev27.dist-info}/entry_points.txt +0 -0
  36. {tinybird-0.0.1.dev26.dist-info → tinybird-0.0.1.dev27.dist-info}/top_level.txt +0 -0
tinybird/sql_toolset.py CHANGED
@@ -69,6 +69,7 @@ def sql_get_used_tables_cached(
69
69
  default_database: str = "",
70
70
  table_functions: bool = True,
71
71
  function_allow_list: Optional[FrozenSet[str]] = None,
72
+ function_deny_list: Optional[FrozenSet[str]] = None,
72
73
  ) -> List[Tuple[str, str, str]]:
73
74
  """More like: get used sql names
74
75
 
@@ -92,9 +93,13 @@ def sql_get_used_tables_cached(
92
93
  """
93
94
  try:
94
95
  _function_allow_list = list() if function_allow_list is None else list(function_allow_list)
96
+ _function_deny_list = list() if function_deny_list is None else list(function_deny_list)
95
97
 
96
98
  tables: List[Tuple[str, str, str]] = chquery.tables(
97
- sql, default_database=default_database, function_allow_list=_function_allow_list
99
+ sql,
100
+ default_database=default_database,
101
+ function_allow_list=_function_allow_list,
102
+ function_deny_list=_function_deny_list,
98
103
  )
99
104
  if not table_functions:
100
105
  return [(t[0], t[1], "") for t in tables if t[0] or t[1]]
@@ -118,15 +123,24 @@ def sql_get_used_tables(
118
123
  default_database: str = "",
119
124
  table_functions: bool = True,
120
125
  function_allow_list: Optional[FrozenSet[str]] = None,
126
+ function_deny_list: Optional[FrozenSet[str]] = None,
121
127
  ) -> List[Tuple[str, str, str]]:
122
128
  """More like: get used sql names
123
129
 
124
130
  Returns a list of tuples: (database_or_namespace, table_name, table_func).
125
131
  """
126
- hashable_list = frozenset() if function_allow_list is None else function_allow_list
132
+ function_allow_hashable_list = frozenset() if function_allow_list is None else function_allow_list
133
+ function_deny_hashable_list = frozenset() if function_deny_list is None else function_deny_list
127
134
 
128
135
  return copy.copy(
129
- sql_get_used_tables_cached(sql, raising, default_database, table_functions, function_allow_list=hashable_list)
136
+ sql_get_used_tables_cached(
137
+ sql,
138
+ raising,
139
+ default_database,
140
+ table_functions,
141
+ function_allow_list=function_allow_hashable_list,
142
+ function_deny_list=function_deny_hashable_list,
143
+ )
130
144
  )
131
145
 
132
146
 
tinybird/syncasync.py CHANGED
@@ -73,7 +73,7 @@ class Local:
73
73
  def __init__(self, thread_critical: bool = False) -> None:
74
74
  self._thread_critical = thread_critical
75
75
  self._thread_lock = threading.RLock()
76
- self._context_refs: "weakref.WeakSet[object]" = weakref.WeakSet()
76
+ self._context_refs: weakref.WeakSet[object] = weakref.WeakSet()
77
77
  # Random suffixes stop accidental reuse between different Locals,
78
78
  # though we try to force deletion as well.
79
79
  self._attr_name = "_asgiref_local_impl_{}_{}".format(
tinybird/tb/__cli__.py CHANGED
@@ -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.dev26'
8
- __revision__ = 'f0e9917'
7
+ __version__ = '0.0.1.dev27'
8
+ __revision__ = 'a8944c0'
tinybird/tb/cli.py CHANGED
@@ -6,11 +6,13 @@ if sys.platform == "win32":
6
6
 
7
7
  import tinybird.tb.modules.auth
8
8
  import tinybird.tb.modules.build
9
+ import tinybird.tb.modules.build_server
9
10
  import tinybird.tb.modules.cli
10
11
  import tinybird.tb.modules.common
11
12
  import tinybird.tb.modules.connection
12
13
  import tinybird.tb.modules.create
13
14
  import tinybird.tb.modules.datasource
15
+ import tinybird.tb.modules.deploy
14
16
  import tinybird.tb.modules.fmt
15
17
  import tinybird.tb.modules.job
16
18
  import tinybird.tb.modules.local
@@ -28,22 +28,20 @@ from tinybird.tb.modules.watch import watch_files
28
28
  @click.option(
29
29
  "--folder",
30
30
  default=".",
31
- help="Folder from where to execute the command. By default the current folder",
31
+ help="Folder from where to execute the command. By default the current folder.",
32
32
  hidden=True,
33
33
  type=click.types.STRING,
34
34
  )
35
35
  @click.option(
36
36
  "--watch",
37
37
  is_flag=True,
38
- help="Watch for changes in the files and re-check them.",
38
+ help="Watch for changes in the files and rebuild them.",
39
39
  )
40
40
  def build(
41
41
  folder: str,
42
42
  watch: bool,
43
43
  ) -> None:
44
- """
45
- Watch for changes in the files and re-check them.
46
- """
44
+ """Build the project in Tinybird Local."""
47
45
  ignore_sql_errors = FeatureFlags.ignore_sql_errors()
48
46
  context.disable_template_security_validation.set(True)
49
47
  is_internal = has_internal_datafiles(folder)
@@ -78,23 +76,40 @@ def build(
78
76
  ignore_sql_errors=ignore_sql_errors,
79
77
  is_internal=is_internal,
80
78
  watch=watch,
79
+ folder=folder,
81
80
  )
82
81
  if len(filenames) > 0:
83
82
  filename = filenames[0]
84
83
  if filename.endswith(".ndjson"):
85
84
  fixture_path = Path(filename)
86
- name = "_".join(fixture_path.stem.split("_")[:-1])
87
- ds_path = Path(folder) / "datasources" / f"{name}.datasource"
85
+ datasources_path = Path(folder) / "datasources"
86
+ ds_name = fixture_path.stem
87
+ ds_path = datasources_path / f"{ds_name}.datasource"
88
+
89
+ if not ds_path.exists():
90
+ try:
91
+ ds_name = "_".join(fixture_path.stem.split("_")[:-1])
92
+ ds_path = datasources_path / f"{ds_name}.datasource"
93
+ except Exception:
94
+ pass
95
+
88
96
  if ds_path.exists():
89
- await append_datasource(tb_client, name, str(fixture_path), silent=True)
97
+ await append_datasource(tb_client, ds_name, str(fixture_path))
90
98
 
91
99
  if watch:
92
100
  if filename.endswith(".datasource"):
93
101
  ds_path = Path(filename)
94
- name = build_fixture_name(filename, ds_path.stem, ds_path.read_text())
95
- fixture_path = get_fixture_dir() / f"{name}.ndjson"
102
+ ds_name = ds_path.stem
103
+ name = build_fixture_name(filename, ds_name, ds_path.read_text())
104
+ fixture_folder = get_fixture_dir(folder)
105
+ fixture_path = fixture_folder / f"{name}.ndjson"
106
+
107
+ if not fixture_path.exists():
108
+ fixture_path = fixture_folder / f"{ds_name}.ndjson"
109
+
96
110
  if fixture_path.exists():
97
- await append_datasource(tb_client, ds_path.stem, str(fixture_path), silent=True)
111
+ await append_datasource(tb_client, ds_name, str(fixture_path))
112
+
98
113
  if not filename.endswith(".ndjson"):
99
114
  await build_and_print_resource(tb_client, filename)
100
115
 
@@ -113,13 +128,26 @@ def build(
113
128
  for filename in filenames:
114
129
  if filename.endswith(".datasource"):
115
130
  ds_path = Path(filename)
116
- name = build_fixture_name(filename, ds_path.stem, ds_path.read_text())
117
- fixture_path = get_fixture_dir() / f"{name}.ndjson"
131
+ ds_name = ds_path.stem
132
+ name = build_fixture_name(filename, ds_name, ds_path.read_text())
133
+ fixture_folder = get_fixture_dir(folder)
134
+ fixture_path = fixture_folder / f"{name}.ndjson"
135
+
136
+ if not fixture_path.exists():
137
+ fixture_path = fixture_folder / f"{ds_name}.ndjson"
138
+
118
139
  if fixture_path.exists():
119
- await append_datasource(tb_client, ds_path.stem, str(fixture_path), silent=True)
140
+ await append_datasource(tb_client, ds_name, str(fixture_path))
120
141
  click.echo(FeedbackManager.success(message=f"\n✓ Build completed in {elapsed_time:.1f}s"))
121
142
  ok = True
122
143
  except Exception as e:
144
+ error_path = Path(".tb_error.txt")
145
+ if error_path.exists():
146
+ content = error_path.read_text()
147
+ content += f"\n\n{str(e)}"
148
+ error_path.write_text(content)
149
+ else:
150
+ error_path.write_text(str(e))
123
151
  click.echo(FeedbackManager.error_exception(error=e))
124
152
  ok = False
125
153
  return ok
@@ -174,13 +202,13 @@ async def append_datasource(
174
202
  tb_client: TinyB,
175
203
  datasource_name: str,
176
204
  url: str,
177
- silent: bool = False,
178
205
  ):
206
+ await tb_client.datasource_truncate(datasource_name)
179
207
  await push_data(
180
208
  tb_client,
181
209
  datasource_name,
182
210
  url,
183
211
  mode="append",
184
212
  concurrency=1,
185
- silent=silent,
213
+ silent=True,
186
214
  )
@@ -0,0 +1,75 @@
1
+ import asyncio
2
+ import glob
3
+ import json
4
+ import logging
5
+ from pathlib import Path
6
+ from typing import List
7
+
8
+ import click
9
+ import requests
10
+
11
+ from tinybird.tb.modules.cli import cli
12
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
13
+ from tinybird.tb.modules.local_common import get_tinybird_local_client
14
+
15
+
16
+ def project_files(project_path: Path) -> List[str]:
17
+ project_file_extensions = ("datasource", "pipe")
18
+ project_files = []
19
+ for extension in project_file_extensions:
20
+ for project_file in glob.glob(f"{project_path}/**/*.{extension}", recursive=True):
21
+ logging.debug(f"Found project file: {project_file}")
22
+ project_files.append(project_file)
23
+ return project_files
24
+
25
+
26
+ @cli.command()
27
+ @click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
28
+ def build_server(project_path: Path) -> None:
29
+ """
30
+ Validate and build the project server side.
31
+ """
32
+
33
+ MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
34
+ DATAFILE_TYPE_TO_CONTENT_TYPE = {
35
+ ".datasource": "text/plain",
36
+ ".pipe": "text/plain",
37
+ }
38
+
39
+ tb_client = asyncio.run(get_tinybird_local_client(str(project_path)))
40
+ TINYBIRD_API_URL = tb_client.host + "/v1/build"
41
+ TINYBIRD_API_KEY = tb_client.token
42
+
43
+ files = [
44
+ ("context://", ("cli-version", "1.0.0", "text/plain")),
45
+ ]
46
+ fds = []
47
+ for file_path in project_files(project_path):
48
+ relative_path = str(Path(file_path).relative_to(project_path))
49
+ fd = open(file_path, "rb")
50
+ fds.append(fd)
51
+ content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
52
+ files.append((MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd, content_type)))
53
+
54
+ try:
55
+ HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
56
+
57
+ r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS)
58
+ result = r.json()
59
+ logging.debug(json.dumps(result, indent=2))
60
+
61
+ build_result = result.get("result")
62
+ if build_result == "success":
63
+ click.echo(FeedbackManager.success(message="Build completed successfully"))
64
+ elif build_result == "failed":
65
+ click.echo(FeedbackManager.error(message="Build failed"))
66
+ build_errors = result.get("errors")
67
+ for build_error in build_errors:
68
+ click.echo(
69
+ FeedbackManager.error(message=f"{build_error.get('filename')}\n\n{build_error.get('error')}")
70
+ )
71
+ else:
72
+ click.echo(FeedbackManager.error(message=f"Unknown build result. Error: {result.get('error')}"))
73
+ finally:
74
+ for fd in fds:
75
+ fd.close()
@@ -48,6 +48,7 @@ from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
48
48
  from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
49
49
  from tinybird.tb.modules.datafile.pull import folder_pull
50
50
  from tinybird.tb.modules.feedback_manager import FeedbackManager
51
+ from tinybird.tb.modules.llm import LLM
51
52
  from tinybird.tb.modules.local_common import get_tinybird_local_client
52
53
 
53
54
  __old_click_echo = click.echo
@@ -486,6 +487,27 @@ async def sql(
486
487
  click.echo(FeedbackManager.info_no_rows())
487
488
 
488
489
 
490
+ @cli.command(hidden=True)
491
+ @click.argument("prompt")
492
+ @click.option("--model", default="gpt-4o", help="The model to use for the LLM")
493
+ @click.option("--folder", default=".", help="The folder to use for the project")
494
+ @coro
495
+ async def llm(prompt: str, model: str, folder: str) -> None:
496
+ """Run a prompt against the LLM api."""
497
+ try:
498
+ config = CLIConfig.get_project_config(folder)
499
+ user_token = config.get_user_token()
500
+
501
+ if not user_token:
502
+ raise CLIException("This action requires authentication. Run 'tb login' first.")
503
+
504
+ client = config.get_client()
505
+ llm = LLM(user_token=user_token, client=client)
506
+ click.echo(await llm.ask(prompt, model=model))
507
+ except Exception as e:
508
+ raise CLIException(FeedbackManager.error_exception(error=e))
509
+
510
+
489
511
  def __patch_click_output():
490
512
  import re
491
513
 
@@ -175,7 +175,7 @@ def generate_datafile(
175
175
  if not f.exists() or force:
176
176
  with open(f"{f}", "w") as ds_file:
177
177
  ds_file.write(datafile)
178
- click.echo(FeedbackManager.info_file_created(file=str(f)))
178
+ click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
179
179
 
180
180
  if data and (base / "fixtures").exists():
181
181
  # Generating a fixture for Parquet files is not so trivial, since Parquet format
@@ -289,7 +289,7 @@ def load_connector_config(ctx: Context, connector_name: str, debug: bool, check_
289
289
  click.echo(FeedbackManager.warning_connector_not_installed(connector=connector_name))
290
290
  return
291
291
  ctx.ensure_object(dict)[connector_name] = create_connector(connector_name, config)
292
- except IOError:
292
+ except OSError:
293
293
  if debug:
294
294
  click.echo(f"** {connector_name} connector not configured")
295
295
  pass
@@ -43,12 +43,10 @@ def compare_versions(a: str, b: str) -> int:
43
43
 
44
44
 
45
45
  class ConfigValueOrigin(Enum):
46
- # Sources for config values (environment variables, .tinyb file or default value)
47
-
48
- ENVIRONMENT: str = "env"
49
- CONFIG: str = "conf"
50
- DEFAULT: str = "default"
51
- NONE: str = ""
46
+ ENVIRONMENT = "env"
47
+ CONFIG = "conf"
48
+ DEFAULT = "default"
49
+ NONE = ""
52
50
 
53
51
 
54
52
  @dataclass
@@ -152,7 +150,7 @@ class CLIConfig:
152
150
  for k, v in values.items():
153
151
  self[k] = v
154
152
  return True
155
- except IOError:
153
+ except OSError:
156
154
  return False
157
155
 
158
156
  def override_with_environment(self) -> None:
@@ -306,13 +304,14 @@ class CLIConfig:
306
304
 
307
305
  Note: the `_path` argument is mainly intended to help during testing.
308
306
  """
309
- if not CLIConfig._global:
310
- path: Optional[str] = _path or os.environ.get("XDG_CONFIG_HOME", None)
311
- if not path:
312
- path = os.path.join(os.environ.get("HOME", "~"), ".config")
313
- path = os.path.join(path, APP_CONFIG_NAME, ".tinyb")
314
- CLIConfig._global = CLIConfig(path, parent=None)
315
- return CLIConfig._global
307
+ path: Optional[str] = _path or os.environ.get("XDG_CONFIG_HOME", None)
308
+ if not path:
309
+ path = os.path.join(os.environ.get("HOME", "~"), ".config")
310
+ path = os.path.join(path, APP_CONFIG_NAME, ".tinyb")
311
+ exists_path = os.path.exists(path)
312
+ if not exists_path:
313
+ path = os.path.join(os.getcwd(), ".tinyb")
314
+ return CLIConfig(path, parent=None)
316
315
 
317
316
  @staticmethod
318
317
  def get_project_config(working_dir: Optional[str] = None) -> "CLIConfig":