tinybird 0.0.1.dev28__py3-none-any.whl → 0.0.1.dev30__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.

tinybird/tb/cli.py CHANGED
@@ -6,7 +6,7 @@ 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
+ import tinybird.tb.modules.build_client
10
10
  import tinybird.tb.modules.cli
11
11
  import tinybird.tb.modules.common
12
12
  import tinybird.tb.modules.connection
@@ -22,6 +22,7 @@ import tinybird.tb.modules.pipe
22
22
  import tinybird.tb.modules.tag
23
23
  import tinybird.tb.modules.test
24
24
  import tinybird.tb.modules.token
25
+ import tinybird.tb.modules.update
25
26
  import tinybird.tb.modules.workspace
26
27
  import tinybird.tb.modules.workspace_members
27
28
 
@@ -1,214 +1,74 @@
1
1
  import asyncio
2
- import os
3
- import threading
4
- import time
2
+ import glob
3
+ import json
4
+ import logging
5
5
  from pathlib import Path
6
6
  from typing import List
7
7
 
8
8
  import click
9
+ import requests
9
10
 
10
- import tinybird.context as context
11
- from tinybird.client import TinyB
12
- from tinybird.config import FeatureFlags
13
11
  from tinybird.tb.modules.cli import cli
14
- from tinybird.tb.modules.common import push_data
15
- from tinybird.tb.modules.datafile.build import folder_build
16
- from tinybird.tb.modules.datafile.common import get_project_filenames, get_project_fixtures, has_internal_datafiles
17
- from tinybird.tb.modules.datafile.exceptions import ParseException
18
- from tinybird.tb.modules.datafile.fixture import build_fixture_name, get_fixture_dir
19
- from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
20
- from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
21
12
  from tinybird.tb.modules.feedback_manager import FeedbackManager
22
13
  from tinybird.tb.modules.local_common import get_tinybird_local_client
23
- from tinybird.tb.modules.shell import Shell, print_table_formatted
24
- from tinybird.tb.modules.watch import watch_files
25
14
 
26
15
 
27
- @cli.command()
28
- @click.option(
29
- "--folder",
30
- default=".",
31
- help="Folder from where to execute the command. By default the current folder.",
32
- hidden=True,
33
- type=click.types.STRING,
34
- )
35
- @click.option(
36
- "--watch",
37
- is_flag=True,
38
- help="Watch for changes in the files and rebuild them.",
39
- )
40
- def build(
41
- folder: str,
42
- watch: bool,
43
- ) -> None:
44
- """Build the project in Tinybird Local."""
45
- ignore_sql_errors = FeatureFlags.ignore_sql_errors()
46
- context.disable_template_security_validation.set(True)
47
- is_internal = has_internal_datafiles(folder)
48
- folder_path = os.path.abspath(folder)
49
- tb_client = asyncio.run(get_tinybird_local_client(folder_path))
50
-
51
- def check_filenames(filenames: List[str]):
52
- parser_matrix = {".pipe": parse_pipe, ".datasource": parse_datasource}
53
- incl_suffix = ".incl"
54
-
55
- for filename in filenames:
56
- if os.path.isdir(filename):
57
- check_filenames(filenames=get_project_filenames(filename))
58
-
59
- file_suffix = Path(filename).suffix
60
- if file_suffix == incl_suffix:
61
- continue
62
-
63
- parser = parser_matrix.get(file_suffix)
64
- if not parser:
65
- raise ParseException(FeedbackManager.error_unsupported_datafile(extension=file_suffix))
66
-
67
- parser(filename)
68
-
69
- async def process(filenames: List[str], watch: bool = False):
70
- datafiles = [f for f in filenames if f.endswith(".datasource") or f.endswith(".pipe")]
71
- if len(datafiles) > 0:
72
- check_filenames(filenames=datafiles)
73
- await folder_build(
74
- tb_client,
75
- filenames=datafiles,
76
- ignore_sql_errors=ignore_sql_errors,
77
- is_internal=is_internal,
78
- watch=watch,
79
- folder=folder,
80
- )
81
- if len(filenames) > 0:
82
- filename = filenames[0]
83
- if filename.endswith(".ndjson"):
84
- fixture_path = Path(filename)
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
-
96
- if ds_path.exists():
97
- await append_datasource(tb_client, ds_name, str(fixture_path))
98
-
99
- if watch:
100
- if filename.endswith(".datasource"):
101
- ds_path = Path(filename)
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
-
110
- if fixture_path.exists():
111
- await append_datasource(tb_client, ds_name, str(fixture_path))
112
-
113
- if not filename.endswith(".ndjson"):
114
- await build_and_print_resource(tb_client, filename)
115
-
116
- datafiles = get_project_filenames(folder)
117
- fixtures = get_project_fixtures(folder)
118
- filenames = datafiles + fixtures
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
119
24
 
120
- async def build_once(filenames: List[str]):
121
- ok = False
122
- try:
123
- click.echo(FeedbackManager.highlight(message="» Building project...\n"))
124
- time_start = time.time()
125
- await process(filenames=filenames, watch=False)
126
- time_end = time.time()
127
- elapsed_time = time_end - time_start
128
- for filename in filenames:
129
- if filename.endswith(".datasource"):
130
- ds_path = Path(filename)
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
25
 
136
- if not fixture_path.exists():
137
- fixture_path = fixture_folder / f"{ds_name}.ndjson"
138
-
139
- if fixture_path.exists():
140
- await append_datasource(tb_client, ds_name, str(fixture_path))
141
- click.echo(FeedbackManager.success(message=f"\n✓ Build completed in {elapsed_time:.1f}s"))
142
- ok = True
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))
151
- click.echo(FeedbackManager.error_exception(error=e))
152
- ok = False
153
- return ok
154
-
155
- build_ok = asyncio.run(build_once(filenames))
156
-
157
- if watch:
158
- paths = [Path(f) for f in get_project_filenames(folder, with_vendor=True)]
159
-
160
- def is_vendor(f: Path) -> bool:
161
- return f.parts[0] == "vendor"
162
-
163
- def get_vendor_workspace(f: Path) -> str:
164
- return f.parts[1]
165
-
166
- def is_endpoint(f: Path) -> bool:
167
- return f.suffix == ".pipe" and not is_vendor(f) and f.parts[0] == "endpoints"
168
-
169
- def is_pipe(f: Path) -> bool:
170
- return f.suffix == ".pipe" and not is_vendor(f)
171
-
172
- datasource_paths = [f for f in paths if f.suffix == ".datasource"]
173
- datasources = [f.stem for f in datasource_paths if not is_vendor(f)]
174
- shared_datasources = [f"{get_vendor_workspace(f)}.{f.stem}" for f in datasource_paths if is_vendor(f)]
175
- pipes = [f.stem for f in paths if is_pipe(f) and not is_endpoint(f)]
176
- endpoints = [f.stem for f in paths if is_endpoint(f)]
177
- shell = Shell(
178
- folder=folder,
179
- client=tb_client,
180
- datasources=datasources,
181
- shared_datasources=shared_datasources,
182
- pipes=pipes,
183
- endpoints=endpoints,
184
- )
185
- click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
186
- watcher_thread = threading.Thread(
187
- target=watch_files, args=(filenames, process, shell, folder, build_ok), daemon=True
188
- )
189
- watcher_thread.start()
190
- shell.run()
191
-
192
-
193
- async def build_and_print_resource(tb_client: TinyB, filename: str):
194
- resource_path = Path(filename)
195
- name = resource_path.stem
196
- pipeline = name if filename.endswith(".pipe") else None
197
- res = await tb_client.query(f"SELECT * FROM {name} FORMAT JSON", pipeline=pipeline)
198
- print_table_formatted(res, name)
199
-
200
-
201
- async def append_datasource(
202
- tb_client: TinyB,
203
- datasource_name: str,
204
- url: str,
205
- ):
206
- await tb_client.datasource_truncate(datasource_name)
207
- await push_data(
208
- tb_client,
209
- datasource_name,
210
- url,
211
- mode="append",
212
- concurrency=1,
213
- silent=True,
214
- )
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
+ """
32
+ MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
33
+ DATAFILE_TYPE_TO_CONTENT_TYPE = {
34
+ ".datasource": "text/plain",
35
+ ".pipe": "text/plain",
36
+ }
37
+
38
+ tb_client = asyncio.run(get_tinybird_local_client(str(project_path)))
39
+ TINYBIRD_API_URL = tb_client.host + "/v1/build"
40
+ 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
+ try:
54
+ HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
55
+
56
+ r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS)
57
+ result = r.json()
58
+ logging.debug(json.dumps(result, indent=2))
59
+
60
+ build_result = result.get("result")
61
+ if build_result == "success":
62
+ click.echo(FeedbackManager.success(message="Build completed successfully"))
63
+ elif build_result == "failed":
64
+ click.echo(FeedbackManager.error(message="Build failed"))
65
+ build_errors = result.get("errors")
66
+ for build_error in build_errors:
67
+ filename_bit = f"{build_error.get('filename', '')}"
68
+ error_msg = (filename_bit + "\n\n") if filename_bit else "" + build_error.get("error")
69
+ click.echo(FeedbackManager.error(message=error_msg))
70
+ else:
71
+ click.echo(FeedbackManager.error(message=f"Unknown build result. Error: {result.get('error')}"))
72
+ finally:
73
+ for fd in fds:
74
+ fd.close()
@@ -0,0 +1,219 @@
1
+ import asyncio
2
+ import os
3
+ import threading
4
+ import time
5
+ from pathlib import Path
6
+ from typing import List
7
+
8
+ import click
9
+
10
+ import tinybird.context as context
11
+ from tinybird.client import TinyB
12
+ from tinybird.config import FeatureFlags
13
+ from tinybird.tb.modules.cli import cli
14
+ from tinybird.tb.modules.common import push_data
15
+ from tinybird.tb.modules.datafile.build import folder_build
16
+ from tinybird.tb.modules.datafile.common import get_project_filenames, get_project_fixtures, has_internal_datafiles
17
+ from tinybird.tb.modules.datafile.exceptions import ParseException
18
+ from tinybird.tb.modules.datafile.fixture import build_fixture_name, get_fixture_dir
19
+ from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
20
+ from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
21
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
22
+ from tinybird.tb.modules.local_common import get_tinybird_local_client
23
+ from tinybird.tb.modules.shell import Shell, print_table_formatted
24
+ from tinybird.tb.modules.watch import watch_files
25
+
26
+
27
+ def is_vendor(f: Path) -> bool:
28
+ return f.parts[0] == "vendor"
29
+
30
+
31
+ def get_vendor_workspace(f: Path) -> str:
32
+ return f.parts[1]
33
+
34
+
35
+ def is_endpoint(f: Path) -> bool:
36
+ return f.suffix == ".pipe" and not is_vendor(f) and f.parts[0] == "endpoints"
37
+
38
+
39
+ def is_pipe(f: Path) -> bool:
40
+ return f.suffix == ".pipe" and not is_vendor(f)
41
+
42
+
43
+ def check_filenames(filenames: List[str]):
44
+ parser_matrix = {".pipe": parse_pipe, ".datasource": parse_datasource}
45
+ incl_suffix = ".incl"
46
+
47
+ for filename in filenames:
48
+ if os.path.isdir(filename):
49
+ check_filenames(filenames=get_project_filenames(filename))
50
+
51
+ file_suffix = Path(filename).suffix
52
+ if file_suffix == incl_suffix:
53
+ continue
54
+
55
+ parser = parser_matrix.get(file_suffix)
56
+ if not parser:
57
+ raise ParseException(FeedbackManager.error_unsupported_datafile(extension=file_suffix))
58
+
59
+ parser(filename)
60
+
61
+
62
+ @cli.command()
63
+ @click.option(
64
+ "--folder",
65
+ default=".",
66
+ help="Folder from where to execute the command. By default the current folder.",
67
+ hidden=True,
68
+ type=click.types.STRING,
69
+ )
70
+ @click.option(
71
+ "--watch",
72
+ is_flag=True,
73
+ help="Watch for changes in the files and rebuild them.",
74
+ )
75
+ def build_client(
76
+ folder: str,
77
+ watch: bool,
78
+ ) -> None:
79
+ """Build the project in Tinybird Local."""
80
+ ignore_sql_errors = FeatureFlags.ignore_sql_errors()
81
+ context.disable_template_security_validation.set(True)
82
+ is_internal = has_internal_datafiles(folder)
83
+ folder_path = os.path.abspath(folder)
84
+ tb_client = asyncio.run(get_tinybird_local_client(folder_path))
85
+
86
+ async def process(filenames: List[str], watch: bool = False):
87
+ datafiles = [f for f in filenames if f.endswith(".datasource") or f.endswith(".pipe")]
88
+ if len(datafiles) > 0:
89
+ check_filenames(filenames=datafiles)
90
+ await folder_build(
91
+ tb_client,
92
+ filenames=datafiles,
93
+ ignore_sql_errors=ignore_sql_errors,
94
+ is_internal=is_internal,
95
+ watch=watch,
96
+ folder=folder,
97
+ )
98
+ if len(filenames) > 0:
99
+ filename = filenames[0]
100
+ if filename.endswith(".ndjson"):
101
+ fixture_path = Path(filename)
102
+ datasources_path = Path(folder) / "datasources"
103
+ ds_name = fixture_path.stem
104
+ ds_path = datasources_path / f"{ds_name}.datasource"
105
+
106
+ if not ds_path.exists():
107
+ try:
108
+ ds_name = "_".join(fixture_path.stem.split("_")[:-1])
109
+ ds_path = datasources_path / f"{ds_name}.datasource"
110
+ except Exception:
111
+ pass
112
+
113
+ if ds_path.exists():
114
+ await append_datasource(tb_client, ds_name, str(fixture_path))
115
+
116
+ if watch:
117
+ if filename.endswith(".datasource"):
118
+ ds_path = Path(filename)
119
+ ds_name = ds_path.stem
120
+ name = build_fixture_name(filename, ds_name, ds_path.read_text())
121
+ fixture_folder = get_fixture_dir(folder)
122
+ fixture_path = fixture_folder / f"{name}.ndjson"
123
+
124
+ if not fixture_path.exists():
125
+ fixture_path = fixture_folder / f"{ds_name}.ndjson"
126
+
127
+ if fixture_path.exists():
128
+ await append_datasource(tb_client, ds_name, str(fixture_path))
129
+
130
+ if not filename.endswith(".ndjson"):
131
+ await build_and_print_resource(tb_client, filename)
132
+
133
+ datafiles = get_project_filenames(folder)
134
+ fixtures = get_project_fixtures(folder)
135
+ filenames = datafiles + fixtures
136
+
137
+ async def build_once(filenames: List[str]):
138
+ ok = False
139
+ try:
140
+ click.echo(FeedbackManager.highlight(message="» Building project...\n"))
141
+ time_start = time.time()
142
+ await process(filenames=filenames, watch=False)
143
+ time_end = time.time()
144
+ elapsed_time = time_end - time_start
145
+ for filename in filenames:
146
+ if filename.endswith(".datasource"):
147
+ ds_path = Path(filename)
148
+ ds_name = ds_path.stem
149
+ name = build_fixture_name(filename, ds_name, ds_path.read_text())
150
+ fixture_folder = get_fixture_dir(folder)
151
+ fixture_path = fixture_folder / f"{name}.ndjson"
152
+
153
+ if not fixture_path.exists():
154
+ fixture_path = fixture_folder / f"{ds_name}.ndjson"
155
+
156
+ if fixture_path.exists():
157
+ await append_datasource(tb_client, ds_name, str(fixture_path))
158
+ click.echo(FeedbackManager.success(message=f"\n✓ Build completed in {elapsed_time:.1f}s"))
159
+ ok = True
160
+ except Exception as e:
161
+ error_path = Path(".tb_error.txt")
162
+ if error_path.exists():
163
+ content = error_path.read_text()
164
+ content += f"\n\n{str(e)}"
165
+ error_path.write_text(content)
166
+ else:
167
+ error_path.write_text(str(e))
168
+ click.echo(FeedbackManager.error_exception(error=e))
169
+ ok = False
170
+ return ok
171
+
172
+ build_ok = asyncio.run(build_once(filenames))
173
+
174
+ if watch:
175
+ paths = [Path(f) for f in get_project_filenames(folder, with_vendor=True)]
176
+
177
+ datasource_paths = [f for f in paths if f.suffix == ".datasource"]
178
+ datasources = [f.stem for f in datasource_paths if not is_vendor(f)]
179
+ shared_datasources = [f"{get_vendor_workspace(f)}.{f.stem}" for f in datasource_paths if is_vendor(f)]
180
+ pipes = [f.stem for f in paths if is_pipe(f) and not is_endpoint(f)]
181
+ endpoints = [f.stem for f in paths if is_endpoint(f)]
182
+ shell = Shell(
183
+ folder=folder,
184
+ client=tb_client,
185
+ datasources=datasources,
186
+ shared_datasources=shared_datasources,
187
+ pipes=pipes,
188
+ endpoints=endpoints,
189
+ )
190
+ click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
191
+ watcher_thread = threading.Thread(
192
+ target=watch_files, args=(filenames, process, shell, folder, build_ok), daemon=True
193
+ )
194
+ watcher_thread.start()
195
+ shell.run()
196
+
197
+
198
+ async def build_and_print_resource(tb_client: TinyB, filename: str):
199
+ resource_path = Path(filename)
200
+ name = resource_path.stem
201
+ pipeline = name if filename.endswith(".pipe") else None
202
+ res = await tb_client.query(f"SELECT * FROM {name} FORMAT JSON", pipeline=pipeline)
203
+ print_table_formatted(res, name)
204
+
205
+
206
+ async def append_datasource(
207
+ tb_client: TinyB,
208
+ datasource_name: str,
209
+ url: str,
210
+ ):
211
+ await tb_client.datasource_truncate(datasource_name)
212
+ await push_data(
213
+ tb_client,
214
+ datasource_name,
215
+ url,
216
+ mode="append",
217
+ concurrency=1,
218
+ silent=True,
219
+ )
@@ -9,7 +9,7 @@ import os
9
9
  import pprint
10
10
  from os import getcwd
11
11
  from pathlib import Path
12
- from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
12
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
13
13
 
14
14
  import click
15
15
  import humanfriendly
@@ -20,7 +20,8 @@ from tinybird.client import (
20
20
  AuthNoTokenException,
21
21
  TinyB,
22
22
  )
23
- from tinybird.config import SUPPORTED_CONNECTORS, get_config
23
+ from tinybird.config import get_config
24
+ from tinybird.prompts import ask_prompt
24
25
  from tinybird.tb import __cli__
25
26
  from tinybird.tb.modules.common import (
26
27
  CatchAuthExceptions,
@@ -30,7 +31,6 @@ from tinybird.tb.modules.common import (
30
31
  echo_safe_format_table,
31
32
  get_current_main_workspace,
32
33
  getenv_bool,
33
- load_connector_config,
34
34
  try_update_config_with_remote,
35
35
  )
36
36
  from tinybird.tb.modules.config import CLIConfig
@@ -94,7 +94,7 @@ VERSION = f"{__cli__.__version__} (rev {__cli__.__revision__})"
94
94
  "--with-headers", help="Flag to enable connector to export with headers", is_flag=True, default=False, hidden=True
95
95
  )
96
96
  @click.option("--show-tokens", is_flag=True, default=False, help="Enable the output of tokens")
97
- @click.option("--local", is_flag=True, default=False, help="Run in local mode")
97
+ @click.option("--prod", is_flag=True, default=False, help="Run against production")
98
98
  @click.version_option(version=VERSION)
99
99
  @click.pass_context
100
100
  @coro
@@ -117,14 +117,13 @@ async def cli(
117
117
  sf_stage,
118
118
  with_headers: bool,
119
119
  show_tokens: bool,
120
- local: bool,
120
+ prod: bool,
121
121
  ) -> None:
122
122
  """
123
123
  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
124
  """
125
-
126
125
  # We need to unpatch for our tests not to break
127
- if show_tokens or local or ctx.invoked_subcommand == "build":
126
+ if show_tokens or not prod or ctx.invoked_subcommand == "build":
128
127
  __unpatch_click_output()
129
128
  else:
130
129
  __patch_click_output()
@@ -211,12 +210,10 @@ async def cli(
211
210
 
212
211
  logging.debug("debug enabled")
213
212
 
214
- ctx.ensure_object(dict)["client"] = (
215
- await get_tinybird_local_client() if local else _get_tb_client(config.get("token", None), config["host"])
216
- )
213
+ client = await create_ctx_client(config, prod)
217
214
 
218
- for connector in SUPPORTED_CONNECTORS:
219
- load_connector_config(ctx, connector, debug, check_uninstalled=True)
215
+ if client:
216
+ ctx.ensure_object(dict)["client"] = client
220
217
 
221
218
 
222
219
  @cli.command(hidden=True)
@@ -489,11 +486,10 @@ async def sql(
489
486
 
490
487
  @cli.command(hidden=True)
491
488
  @click.argument("prompt")
492
- @click.option("--model", default="gpt-4o", help="The model to use for the LLM")
493
489
  @click.option("--folder", default=".", help="The folder to use for the project")
494
490
  @coro
495
- async def llm(prompt: str, model: str, folder: str) -> None:
496
- """Run a prompt against the LLM api."""
491
+ async def ask(prompt: str, folder: str) -> None:
492
+ """Ask things about your data project."""
497
493
  try:
498
494
  config = CLIConfig.get_project_config(folder)
499
495
  user_token = config.get_user_token()
@@ -501,9 +497,39 @@ async def llm(prompt: str, model: str, folder: str) -> None:
501
497
  if not user_token:
502
498
  raise CLIException("This action requires authentication. Run 'tb login' first.")
503
499
 
500
+ datasource_paths = [
501
+ Path(folder) / "datasources" / f
502
+ for f in os.listdir(Path(folder) / "datasources")
503
+ if f.endswith(".datasource")
504
+ ]
505
+ pipe_folders = ["endpoints", "pipes", "copies", "materializations", "sinks"]
506
+ pipe_paths = [
507
+ Path(folder) / pipe_folder / f
508
+ for pipe_folder in pipe_folders
509
+ if (Path(folder) / pipe_folder).exists()
510
+ for f in os.listdir(Path(folder) / pipe_folder)
511
+ if f.endswith(".pipe")
512
+ ]
513
+ resources_xml = "\n".join(
514
+ [
515
+ f"<resource><type>{resource_type}</type><name>{resource_name}</name><content>{resource_content}</content></resource>"
516
+ for resource_type, resource_name, resource_content in [
517
+ ("datasource", ds.stem, ds.read_text()) for ds in datasource_paths
518
+ ]
519
+ + [
520
+ (
521
+ "pipe",
522
+ pipe.stem,
523
+ pipe.read_text(),
524
+ )
525
+ for pipe in pipe_paths
526
+ ]
527
+ ]
528
+ )
529
+
504
530
  client = config.get_client()
505
531
  llm = LLM(user_token=user_token, client=client)
506
- click.echo(await llm.ask(prompt, model=model))
532
+ click.echo(await llm.ask(prompt, system_prompt=ask_prompt(resources_xml)))
507
533
  except Exception as e:
508
534
  raise CLIException(FeedbackManager.error_exception(error=e))
509
535
 
@@ -543,3 +569,13 @@ def __patch_click_output():
543
569
  def __unpatch_click_output():
544
570
  click.echo = __old_click_echo
545
571
  click.secho = __old_click_secho
572
+
573
+
574
+ async def create_ctx_client(config: Dict[str, Any], prod: bool):
575
+ if prod:
576
+ return _get_tb_client(config.get("token", None), config["host"])
577
+
578
+ try:
579
+ return await get_tinybird_local_client()
580
+ except Exception:
581
+ return None