tinybird 0.0.1.dev69__py3-none-any.whl → 0.0.1.dev71__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/client.py CHANGED
@@ -93,6 +93,7 @@ class TinyB:
93
93
  send_telemetry: bool = False,
94
94
  semver: Optional[str] = None,
95
95
  env: Optional[str] = "production",
96
+ preview: bool = False,
96
97
  ):
97
98
  ctx = ssl.create_default_context()
98
99
  ctx.check_hostname = False
@@ -105,6 +106,7 @@ class TinyB:
105
106
  self.send_telemetry = send_telemetry
106
107
  self.semver = semver
107
108
  self.env = env
109
+ self.preview = preview
108
110
 
109
111
  async def _req_raw(
110
112
  self,
@@ -125,6 +127,8 @@ class TinyB:
125
127
  url += ("&" if "?" in url else "?") + "cli_version=" + quote(self.version)
126
128
  if self.semver:
127
129
  url += ("&" if "?" in url else "?") + "__tb__semver=" + self.semver
130
+ if self.preview:
131
+ url += ("&" if "?" in url else "?") + "__tb__deployment=preview"
128
132
 
129
133
  verify_ssl = not self.disable_ssl_checks
130
134
  try:
@@ -197,7 +201,7 @@ class TinyB:
197
201
  if not token_to_use:
198
202
  raise AuthNoTokenException(f"Forbidden: {error}")
199
203
  raise AuthException(f"Forbidden: {error}")
200
- if response.status_code == 204 or response.status_code == 205:
204
+ if response.status_code in (204, 205):
201
205
  return None
202
206
  if response.status_code == 404:
203
207
  error = parse_error_response(response)
tinybird/sql_template.py CHANGED
@@ -385,9 +385,12 @@ def array_type(types):
385
385
  x = default
386
386
  else:
387
387
  if _type and _type in types:
388
- x = ",".join(map(str, [types[_type](x) for _ in range(2)]))
388
+ if _type == "String":
389
+ x = ""
390
+ else:
391
+ x = ",".join(map(str, [types[_type](x) for _ in range(2)]))
389
392
  else:
390
- x = ",".join([f"__no_value__{i}" for i in range(2)])
393
+ x = ""
391
394
  elif x is None:
392
395
  x = default
393
396
  if x is None:
@@ -405,7 +408,7 @@ def array_type(types):
405
408
  values.append(expression_wrapper(types[_type](t), str(t)))
406
409
  else:
407
410
  raise SQLTemplateException(
408
- f"Error validating {x}[{i}]({t}) to type {_type}",
411
+ f"Error validating [{x}][{i}] ({'empty string' if t == '' else t}) to type {_type}",
409
412
  documentation="/cli/advanced-templates.html",
410
413
  )
411
414
  else:
@@ -2056,7 +2059,11 @@ def render_sql_template(
2056
2059
  >>> render_sql_template("SELECT {{ Array(embedding, 'Float32') }}", {'token':'testing', 'embedding': '1,2,3,4, null'})
2057
2060
  Traceback (most recent call last):
2058
2061
  ...
2059
- tinybird.sql_template.SQLTemplateException: Template Syntax Error: Error validating 1,2,3,4, null[4]( null) to type Float32
2062
+ tinybird.sql_template.SQLTemplateException: Template Syntax Error: Error validating [1,2,3,4, null][4] ( null) to type Float32
2063
+ >>> render_sql_template("SELECT {{ Array(embedding, 'Int32', '') }}", {'token':'testing', 'embedding': ''})
2064
+ Traceback (most recent call last):
2065
+ ...
2066
+ tinybird.sql_template.SQLTemplateException: Template Syntax Error: Error validating [][0] (empty string) to type Int32
2060
2067
  >>> render_sql_template('{% if test %}SELECT 1{% else %} select 2 {% end %}')
2061
2068
  (' select 2 ', {}, [])
2062
2069
  >>> render_sql_template('{% if Int32(test, 1) %}SELECT 1{% else %} select 2 {% end %}')
@@ -2096,7 +2103,7 @@ def render_sql_template(
2096
2103
  >>> render_sql_template("SELECT {{Array(foo, 'Int32')}}", test_mode=True)
2097
2104
  ('SELECT [0,0]', {}, [])
2098
2105
  >>> render_sql_template("SELECT {{Array(foo)}}", test_mode=True)
2099
- ("SELECT ['__no_value__0','__no_value__1']", {}, [])
2106
+ ("SELECT ['']", {}, [])
2100
2107
  >>> render_sql_template("{{max_threads(2)}} SELECT 1")
2101
2108
  ('-- max_threads 2\\n SELECT 1', {'max_threads': 2}, [])
2102
2109
  >>> render_sql_template("SELECT {{String(foo)}}", test_mode=True)
@@ -2183,6 +2190,14 @@ def render_sql_template(
2183
2190
  ("% SELECT ['pre','pro']", {}, [])
2184
2191
  >>> render_sql_template("% SELECT {{Array(click_selector, 'String', 'pre,pro')}}", {'click_selector': 'hi,hello'})
2185
2192
  ("% SELECT ['hi','hello']", {}, [])
2193
+ >>> render_sql_template("% SELECT {{Array(click_selector, 'String', '')}}")
2194
+ ("% SELECT ['']", {}, [])
2195
+ >>> render_sql_template("SELECT {{ Array(embedding, 'Int32', '') }}")
2196
+ Traceback (most recent call last):
2197
+ ...
2198
+ tinybird.sql_template.SQLTemplateException: Template Syntax Error: Error validating [][0] (empty string) to type Int32
2199
+ >>> render_sql_template("% SELECT {{Array(click_selector, 'String', '')}}", test_mode=True)
2200
+ ("% SELECT ['']", {}, [])
2186
2201
  >>> render_sql_template("% SELECT now() > {{DateTime64(variable, '2020-09-09 10:10:10.000')}}", {})
2187
2202
  ("% SELECT now() > '2020-09-09 10:10:10.000'", {}, [])
2188
2203
  >>> render_sql_template("% SELECT {% if defined(x) %} x, 1", {})
@@ -2241,6 +2256,16 @@ def render_sql_template(
2241
2256
  Traceback (most recent call last):
2242
2257
  ...
2243
2258
  tinybird.sql_template.SQLTemplateException: Template Syntax Error: Required parameter is not defined. Check the parameters test. Please provide a value or set a default value in the pipe code.
2259
+ >>> render_sql_template("SELECT * FROM test WHERE {% for item in JSON(filters, '[{}]') %} {{item.get('operand')}} {% end %}")
2260
+ Traceback (most recent call last):
2261
+ ...
2262
+ tinybird.sql_template.SQLTemplateException: Template Syntax Error: expression "item.get('operand')" evaluated to null
2263
+ >>> render_sql_template("SELECT * FROM test WHERE {% for item in JSON(filters, '[{\\\"operand\\\":\\\"test\\\"}]') %} {{item.get('operand')}} {% end %}")
2264
+ ("SELECT * FROM test WHERE 'test' ", {}, [])
2265
+ >>> render_sql_template("SELECT * FROM test WHERE {% for item in JSON(filters, '[\\\"test\\\"]') %} {{item.get('operator')}} {% end %}")
2266
+ Traceback (most recent call last):
2267
+ ...
2268
+ tinybird.sql_template.SQLTemplateException: Template Syntax Error: 'str' object has no attribute 'get'. Make sure you're using an object/dictionary where trying to use .get()
2244
2269
  """
2245
2270
  escape_split_to_array = ff_split_to_array_escape.get(False)
2246
2271
 
@@ -2308,6 +2333,14 @@ def render_sql_template(
2308
2333
  except SQLTemplateException as e:
2309
2334
  format_SQLTemplateException_message(e, vars_and_types=template_variables_with_types)
2310
2335
  raise
2336
+ except AttributeError as e:
2337
+ # This happens when trying to use `get` on a string or when the object is None
2338
+ if "'str' object has no attribute 'get'" in str(e):
2339
+ raise SQLTemplateException(
2340
+ "'str' object has no attribute 'get'. Make sure you're using an object/dictionary where trying to use .get()",
2341
+ documentation="/cli/advanced-templates.html",
2342
+ )
2343
+ raise SQLTemplateException(str(e), documentation="/cli/advanced-templates.html")
2311
2344
  except Exception as e:
2312
2345
  # errors might vary here, we need to support as much as possible
2313
2346
  # https://gitlab.com/tinybird/analytics/-/issues/943
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.dev69'
8
- __revision__ = 'f90d907'
7
+ __version__ = '0.0.1.dev71'
8
+ __revision__ = '4feda71'
@@ -15,7 +15,7 @@ from tinybird.client import TinyB
15
15
  from tinybird.tb.modules.cli import cli
16
16
  from tinybird.tb.modules.common import push_data
17
17
  from tinybird.tb.modules.datafile.build import folder_build
18
- from tinybird.tb.modules.datafile.fixture import get_fixture_dir
18
+ from tinybird.tb.modules.datafile.fixture import get_fixture_dir, persist_fixture
19
19
  from tinybird.tb.modules.feedback_manager import FeedbackManager
20
20
  from tinybird.tb.modules.project import Project
21
21
  from tinybird.tb.modules.shell import Shell, print_table_formatted
@@ -210,6 +210,8 @@ def process(
210
210
  build_failed = False
211
211
  if file_changed and file_changed.endswith(".ndjson"):
212
212
  rebuild_fixture(project, tb_client, file_changed)
213
+ elif file_changed and file_changed.endswith(".sql"):
214
+ rebuild_fixture_sql(project, tb_client, file_changed)
213
215
  else:
214
216
  try:
215
217
  build_project(project, tb_client, file_changed)
@@ -247,3 +249,12 @@ def run_watch(
247
249
  )
248
250
  watcher_thread.start()
249
251
  shell.run()
252
+
253
+
254
+ def rebuild_fixture_sql(project: Project, tb_client: TinyB, sql_file: str) -> None:
255
+ sql_path = Path(sql_file)
256
+ datasource_name = sql_path.stem
257
+ sql = sql_path.read_text()
258
+ result = asyncio.run(tb_client.query(f"{sql} FORMAT JSON"))
259
+ data = result.get("data", [])
260
+ persist_fixture(datasource_name, data, project.folder)
@@ -57,6 +57,7 @@ VERSION = f"{__cli__.__version__} (rev {__cli__.__revision__})"
57
57
  @click.option("--show-tokens", is_flag=True, default=False, help="Enable the output of tokens")
58
58
  @click.option("--cloud/--local", is_flag=True, default=False, help="Run against cloud or local")
59
59
  @click.option("--build", is_flag=True, default=False, help="Run against build mode")
60
+ @click.option("--preview", is_flag=True, default=False, help="Run against new deployment")
60
61
  @click.version_option(version=VERSION)
61
62
  @click.pass_context
62
63
  @coro
@@ -68,6 +69,7 @@ async def cli(
68
69
  show_tokens: bool,
69
70
  cloud: bool,
70
71
  build: bool,
72
+ preview: bool,
71
73
  ) -> None:
72
74
  """
73
75
  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.
@@ -121,12 +123,13 @@ async def cli(
121
123
 
122
124
  logging.debug("debug enabled")
123
125
 
124
- client = await create_ctx_client(ctx, config, cloud, build)
126
+ client = await create_ctx_client(ctx, config, cloud, build, preview)
125
127
 
126
128
  if client:
127
129
  ctx.ensure_object(dict)["client"] = client
128
130
 
129
131
  ctx.ensure_object(dict)["project"] = project
132
+ ctx.ensure_object(dict)["env"] = get_target_env(cloud, build)
130
133
 
131
134
 
132
135
  @cli.command(hidden=True)
@@ -385,7 +388,7 @@ def __unpatch_click_output():
385
388
  click.secho = __old_click_secho
386
389
 
387
390
 
388
- async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, build: bool):
391
+ async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, build: bool, preview: bool):
389
392
  commands_without_ctx_client = ["auth", "check", "login", "local", "upgrade"]
390
393
  command = ctx.invoked_subcommand
391
394
  if command in commands_without_ctx_client:
@@ -393,7 +396,7 @@ async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, b
393
396
 
394
397
  commands_always_cloud = ["pull"]
395
398
  commands_always_build = ["build", "test", "dev"]
396
- commands_always_local = ["create", "mock"]
399
+ commands_always_local = ["create"]
397
400
  if (
398
401
  (cloud or command in commands_always_cloud)
399
402
  and command not in commands_always_build
@@ -402,8 +405,16 @@ async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, b
402
405
  click.echo(
403
406
  FeedbackManager.gray(message=f"Running against Tinybird Cloud: Workspace {config.get('name', 'default')}")
404
407
  )
405
- return _get_tb_client(config.get("token", None), config["host"])
408
+ return _get_tb_client(config.get("token", None), config["host"], preview=preview)
406
409
  build = command in commands_always_build or build
407
410
  if not build and command not in commands_always_local and command not in commands_always_build:
408
411
  click.echo(FeedbackManager.gray(message="Running against Tinybird Local\n"))
409
- return await get_tinybird_local_client(config, build=build)
412
+ return await get_tinybird_local_client(config, build=build, preview=preview)
413
+
414
+
415
+ def get_target_env(cloud: bool, build: bool) -> str:
416
+ if cloud:
417
+ return "cloud"
418
+ if build:
419
+ return "build"
420
+ return "local"
@@ -375,9 +375,9 @@ def getenv_bool(key: str, default: bool) -> bool:
375
375
  return v.lower() == "true" or v == "1"
376
376
 
377
377
 
378
- def _get_tb_client(token: str, host: str) -> TinyB:
378
+ def _get_tb_client(token: str, host: str, preview: bool = False) -> TinyB:
379
379
  disable_ssl: bool = getenv_bool("TB_DISABLE_SSL_CHECKS", False)
380
- return TinyB(token, host, version=VERSION, disable_ssl_checks=disable_ssl, send_telemetry=True)
380
+ return TinyB(token, host, version=VERSION, disable_ssl_checks=disable_ssl, send_telemetry=True, preview=preview)
381
381
 
382
382
 
383
383
  def create_tb_client(ctx: Context) -> TinyB:
@@ -554,7 +554,7 @@ def format_host(host: str, subdomain: Optional[str] = None) -> str:
554
554
  if subdomain and not is_localhost:
555
555
  url_info = urlparse(host)
556
556
  current_subdomain = url_info.netloc.split(".")[0]
557
- if current_subdomain == "api" or current_subdomain == "ui":
557
+ if current_subdomain in ("api", "ui"):
558
558
  host = host.replace(current_subdomain, subdomain)
559
559
  if "localhost" in host or is_localhost:
560
560
  host = f"http://{host}" if "http" not in host else host
@@ -219,7 +219,7 @@ class CLIConfig:
219
219
  return CLIConfig.DEFAULTS["host"]
220
220
  return None
221
221
 
222
- def get_client(self, token: Optional[str] = None, host: Optional[str] = None) -> tbc.TinyB:
222
+ def get_client(self, token: Optional[str] = None, host: Optional[str] = None, preview: bool = False) -> tbc.TinyB:
223
223
  """Returns a new TinyB client configured with:
224
224
 
225
225
  - token:
@@ -242,6 +242,7 @@ class CLIConfig:
242
242
  version=CURRENT_VERSION,
243
243
  disable_ssl_checks=FeatureFlags.ignore_ssl_errors(),
244
244
  send_telemetry=FeatureFlags.send_telemetry(),
245
+ preview=preview,
245
246
  )
246
247
 
247
248
  def get_user_client(self, host: Optional[str] = None) -> tbc.TinyB:
@@ -8,6 +8,13 @@ def get_fixture_dir(folder: str) -> Path:
8
8
  return Path(folder) / "fixtures"
9
9
 
10
10
 
11
+ def persist_fixture_sql(fixture_name: str, sql: str, folder: str) -> Path:
12
+ fixture_dir = get_fixture_dir(folder)
13
+ fixture_file = fixture_dir / f"{fixture_name}.sql"
14
+ fixture_file.write_text(sql)
15
+ return fixture_file
16
+
17
+
11
18
  def persist_fixture(fixture_name: str, data: Union[List[Dict[str, Any]], str], folder: str, format="ndjson") -> Path:
12
19
  fixture_dir = get_fixture_dir(folder)
13
20
  fixture_file = fixture_dir / f"{fixture_name}.{format}"
@@ -5,6 +5,7 @@
5
5
 
6
6
  import asyncio
7
7
  import json
8
+ import os
8
9
  import re
9
10
  from typing import Optional
10
11
 
@@ -23,8 +24,10 @@ from tinybird.tb.modules.common import (
23
24
  push_data,
24
25
  )
25
26
  from tinybird.tb.modules.datafile.common import get_name_version
27
+ from tinybird.tb.modules.datafile.fixture import persist_fixture
26
28
  from tinybird.tb.modules.exceptions import CLIDatasourceException
27
29
  from tinybird.tb.modules.feedback_manager import FeedbackManager
30
+ from tinybird.tb.modules.project import Project
28
31
 
29
32
 
30
33
  @cli.group()
@@ -391,3 +394,63 @@ async def datasource_data(ctx: Context, datasource: str, limit: int):
391
394
  echo_safe_humanfriendly_tables_format_smart_table(
392
395
  data=[d.values() for d in res["data"]], column_names=res["data"][0].keys()
393
396
  )
397
+
398
+
399
+ @datasource.command(name="export")
400
+ @click.argument("datasource")
401
+ @click.option(
402
+ "--format",
403
+ "format_",
404
+ type=click.Choice(["csv", "ndjson"], case_sensitive=False),
405
+ default="ndjson",
406
+ help="Output format (csv or ndjson)",
407
+ )
408
+ @click.option("--rows", type=int, default=100, help="Number of rows to export (default: 100)")
409
+ @click.option("--where", type=str, default=None, help="Condition to filter data")
410
+ @click.option("--target", type=str, help="Target file path (default: datasource_name.{format})")
411
+ @click.pass_context
412
+ @coro
413
+ async def datasource_export(
414
+ ctx: Context,
415
+ datasource: str,
416
+ format_: str,
417
+ rows: int,
418
+ where: Optional[str],
419
+ target: Optional[str],
420
+ ):
421
+ """Export data from a datasource to a file in CSV or NDJSON format
422
+
423
+ Example usage:
424
+ - Export all rows as CSV: tb datasource export my_datasource
425
+ - Export 1000 rows as NDJSON: tb datasource export my_datasource --format ndjson --rows 1000
426
+ - Export to specific file: tb datasource export my_datasource --output ./data/export.csv
427
+ """
428
+ client: TinyB = ctx.ensure_object(dict)["client"]
429
+ project: Project = ctx.ensure_object(dict)["project"]
430
+
431
+ # Determine output filename if not provided
432
+ if not target:
433
+ target = f"{datasource}.{format_}"
434
+
435
+ # Build query with optional row limit
436
+ query = f"SELECT * FROM {datasource} WHERE {where or 1} LIMIT {rows}"
437
+
438
+ click.echo(FeedbackManager.highlight(message=f"\n» Exporting {datasource} to {target}"))
439
+
440
+ try:
441
+ if format_ == "csv":
442
+ query += " FORMAT CSVWithNames"
443
+ else:
444
+ query += " FORMAT JSONEachRow"
445
+
446
+ res = await client.query(query)
447
+
448
+ fixture_path = persist_fixture(datasource, res, project.folder)
449
+ file_size = os.path.getsize(fixture_path)
450
+
451
+ click.echo(
452
+ FeedbackManager.success(message=f"✓ Exported data to {target} ({humanfriendly.format_size(file_size)})")
453
+ )
454
+
455
+ except Exception as e:
456
+ raise CLIDatasourceException(FeedbackManager.error(message=str(e)))
@@ -266,13 +266,13 @@ def deployment_rollback(ctx: click.Context, wait: bool) -> None:
266
266
  @click.option(
267
267
  "--wait/--no-wait",
268
268
  is_flag=True,
269
- default=False,
269
+ default=True,
270
270
  help="Wait for deploy to finish. Disabled by default.",
271
271
  )
272
272
  @click.option(
273
273
  "--auto/--no-auto",
274
274
  is_flag=True,
275
- default=False,
275
+ default=True,
276
276
  help="Auto-promote the deployment. Only works if --wait is enabled. Disabled by default.",
277
277
  )
278
278
  @click.option(
@@ -311,6 +311,7 @@ def create_deployment(
311
311
  }
312
312
  project: Project = ctx.ensure_object(dict)["project"]
313
313
  client = ctx.ensure_object(dict)["client"]
314
+ config = ctx.ensure_object(dict)["config"]
314
315
  TINYBIRD_API_URL = f"{client.host}/v1/deploy"
315
316
  TINYBIRD_API_KEY = client.token
316
317
 
@@ -357,8 +358,21 @@ def create_deployment(
357
358
  deploy_result = result.get("result")
358
359
  if deploy_result == "success":
359
360
  print_changes(result, project)
360
- click.echo(FeedbackManager.success(message="Deployment submitted successfully"))
361
- deployment = result.get("deployment")
361
+ deployment = result.get("deployment", {})
362
+ # We show the url in the case of region is public
363
+ if client.host == "https://api.europe-west2.gcp.tinybird.co":
364
+ click.echo(
365
+ FeedbackManager.gray(message="Deployment URL: ")
366
+ + FeedbackManager.info(
367
+ message=f"https://cloud.tinybird.co/gcp/europe-west2/{config.get('name')}/deployments/{deployment.get('id')}"
368
+ )
369
+ )
370
+
371
+ if wait:
372
+ click.echo(FeedbackManager.info(message="\n✓ Deployment submitted successfully"))
373
+ else:
374
+ click.echo(FeedbackManager.success(message="\n✓ Deployment submitted successfully"))
375
+
362
376
  feedback = deployment.get("feedback", [])
363
377
  for f in feedback:
364
378
  click.echo(
@@ -15,10 +15,10 @@ TB_LOCAL_PORT = int(os.getenv("TB_LOCAL_PORT", 80))
15
15
  TB_LOCAL_HOST = f"http://localhost:{TB_LOCAL_PORT}"
16
16
 
17
17
 
18
- async def get_tinybird_local_client(config_obj: Dict[str, Any], build: bool = False) -> TinyB:
18
+ async def get_tinybird_local_client(config_obj: Dict[str, Any], build: bool = False, preview: bool = False) -> TinyB:
19
19
  """Get a Tinybird client connected to the local environment."""
20
20
  config = await get_tinybird_local_config(config_obj, build=build)
21
- return config.get_client(host=TB_LOCAL_HOST)
21
+ return config.get_client(host=TB_LOCAL_HOST, preview=preview)
22
22
 
23
23
 
24
24
  async def get_tinybird_local_config(config_obj: Dict[str, Any], build: bool = False) -> CLIConfig:
@@ -1,6 +1,4 @@
1
1
  import glob
2
- import logging
3
- import os
4
2
  from pathlib import Path
5
3
 
6
4
  import click
@@ -8,9 +6,9 @@ import click
8
6
  from tinybird.client import TinyB
9
7
  from tinybird.prompts import mock_prompt
10
8
  from tinybird.tb.modules.cli import cli
11
- from tinybird.tb.modules.common import CLIException, check_user_token_with_client, coro
9
+ from tinybird.tb.modules.common import CLIException, check_user_token_with_client, coro, push_data
12
10
  from tinybird.tb.modules.config import CLIConfig
13
- from tinybird.tb.modules.datafile.fixture import persist_fixture
11
+ from tinybird.tb.modules.datafile.fixture import persist_fixture, persist_fixture_sql
14
12
  from tinybird.tb.modules.feedback_manager import FeedbackManager
15
13
  from tinybird.tb.modules.llm import LLM
16
14
  from tinybird.tb.modules.llm_utils import extract_xml
@@ -41,11 +39,11 @@ async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str) -> N
41
39
  try:
42
40
  tb_client: TinyB = ctx.ensure_object(dict)["client"]
43
41
  project: Project = ctx.ensure_object(dict)["project"]
42
+ env = ctx.ensure_object(dict)["env"]
44
43
  datasource_path = Path(datasource)
45
44
  datasource_name = datasource
46
45
  folder = project.folder
47
46
  click.echo(FeedbackManager.highlight(message=f"\n» Creating fixture for {datasource_name}..."))
48
-
49
47
  if datasource_path.suffix == ".datasource":
50
48
  datasource_name = datasource_path.stem
51
49
  else:
@@ -56,14 +54,6 @@ async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str) -> N
56
54
  if not datasource_path.exists():
57
55
  raise CLIException(f"Datasource '{datasource_path.stem}' not found")
58
56
 
59
- prompt_path = Path(folder) / "fixtures" / f"{datasource_name}.prompt"
60
- if not prompt or prompt == "Use the datasource schema to generate sample data":
61
- # load the prompt from the fixture.prompt file if it exists
62
- if prompt_path.exists():
63
- prompt = prompt_path.read_text()
64
- else:
65
- prompt_path.write_text(prompt)
66
-
67
57
  datasource_content = datasource_path.read_text()
68
58
  config = CLIConfig.get_project_config()
69
59
  user_client = config.get_client()
@@ -76,21 +66,39 @@ async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str) -> N
76
66
  except Exception:
77
67
  click.echo(FeedbackManager.error(message="This action requires authentication. Run 'tb login' first."))
78
68
  return
69
+
79
70
  llm = LLM(user_token=user_token, host=user_client.host)
80
71
  prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
81
72
  sql = ""
82
-
83
73
  response = llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
84
74
  sql = extract_xml(response, "sql")
85
75
  result = await tb_client.query(f"{sql} FORMAT JSON")
86
76
  data = result.get("data", [])[:rows]
87
- persist_fixture(datasource_name, data, folder)
88
- click.echo(FeedbackManager.info(message=f"✓ /fixtures/{datasource_name}.ndjson created"))
77
+ if env == "build":
78
+ persist_fixture_sql(datasource_name, sql, folder)
79
+
80
+ fixture_path = persist_fixture(datasource_name, data, folder)
89
81
 
90
- if os.environ.get("TB_DEBUG", "") != "":
91
- logging.debug(sql)
82
+ click.echo(FeedbackManager.info(message=f" /fixtures/{datasource_name}.ndjson created"))
83
+ if env == "cloud":
84
+ await append_fixture(tb_client, datasource_name, str(fixture_path))
92
85
 
93
86
  click.echo(FeedbackManager.success(message=f"✓ Sample data for {datasource_name} created with {rows} rows"))
94
87
 
95
88
  except Exception as e:
96
89
  click.echo(FeedbackManager.error_exception(error=f"Error: {e}"))
90
+
91
+
92
+ async def append_fixture(
93
+ tb_client: TinyB,
94
+ datasource_name: str,
95
+ url: str,
96
+ ):
97
+ await push_data(
98
+ tb_client,
99
+ datasource_name,
100
+ url,
101
+ mode="append",
102
+ concurrency=1,
103
+ silent=True,
104
+ )
@@ -206,7 +206,7 @@ class TelemetryHelper:
206
206
 
207
207
  self.log(f"Received status {r.status_code}: {r.text}")
208
208
 
209
- if r.status_code == 200 or r.status_code == 202:
209
+ if r.status_code in (200, 202):
210
210
  self.log(f"Successfully sent {len(events)} events to {self.tb_host}")
211
211
  self.events.clear()
212
212
  return
@@ -5,6 +5,7 @@
5
5
 
6
6
  import difflib
7
7
  import glob
8
+ import sys
8
9
  import urllib.parse
9
10
  from pathlib import Path
10
11
  from typing import Any, Dict, List, Optional, Tuple
@@ -265,7 +266,7 @@ async def run_tests(ctx: click.Context, name: Tuple[str, ...]) -> None:
265
266
 
266
267
  if failed_tests_count:
267
268
  click.echo(FeedbackManager.error(message=f"\n✗ {test_count - failed_tests_count}/{test_count} passed"))
268
- exit(1)
269
+ sys.exit(1)
269
270
  else:
270
271
  click.echo(FeedbackManager.success(message=f"\n✓ {test_count}/{test_count} passed"))
271
272
 
@@ -190,8 +190,6 @@ def generate_file(file: str, overwrite: bool = False) -> None:
190
190
  else:
191
191
  raise CLIException(FeedbackManager.error_file_already_exists(file=p))
192
192
 
193
- return
194
-
195
193
 
196
194
  async def run_test_file(tb_client: TinyB, file: str) -> List[TestResult]:
197
195
  results: List[TestResult] = []
@@ -29,7 +29,7 @@ class WatchProjectHandler(FileSystemEventHandler):
29
29
  if event.is_directory:
30
30
  return None
31
31
 
32
- valid_extensions = [".datasource", ".pipe", ".ndjson"]
32
+ valid_extensions = [".datasource", ".pipe", ".ndjson", ".sql"]
33
33
 
34
34
  if not any(event.src_path.endswith(ext) for ext in valid_extensions):
35
35
  return None
@@ -600,7 +600,7 @@ def format_host(host: str, subdomain: Optional[str] = None) -> str:
600
600
  if subdomain and not is_localhost:
601
601
  url_info = urlparse(host)
602
602
  current_subdomain = url_info.netloc.split(".")[0]
603
- if current_subdomain == "api" or current_subdomain == "ui":
603
+ if current_subdomain in ("api", "ui"):
604
604
  host = host.replace(current_subdomain, subdomain)
605
605
  if "localhost" in host or is_localhost:
606
606
  host = f"http://{host}" if "http" not in host else host
@@ -206,7 +206,7 @@ class TelemetryHelper:
206
206
 
207
207
  self.log(f"Received status {r.status_code}: {r.text}")
208
208
 
209
- if r.status_code == 200 or r.status_code == 202:
209
+ if r.status_code in (200, 202):
210
210
  self.log(f"Successfully sent {len(events)} events to {self.tb_host}")
211
211
  self.events.clear()
212
212
  return
@@ -1144,7 +1144,7 @@ def check_valid_expr(expr):
1144
1144
  return
1145
1145
  if isinstance(expr.value, str):
1146
1146
  return
1147
- if isinstance(expr.value, type(None)):
1147
+ if expr.value is None:
1148
1148
  return
1149
1149
  raise SecurityException(f"Invalid Constant: {ast.dump(expr)}")
1150
1150
  elif isinstance(expr, ast.Name):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev69
3
+ Version: 0.0.1.dev71
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -1,5 +1,5 @@
1
1
  tinybird/__cli__.py,sha256=esPl5QDTzuQgHe5FuxWLm-fURFigGGwjnYLh9GuWUw4,232
2
- tinybird/client.py,sha256=W5Xttnz0bzwqqVGNJJs-4Ca2AbtCMh3URuQYPJ5APsE,52013
2
+ tinybird/client.py,sha256=ewNqHPN4mM54-Z_QHHRIYf7J80XRAg92CA7mutHM4sI,52153
3
3
  tinybird/config.py,sha256=cd_RH7ZjqGjpWwu0efPkhS8VxD9K6Jix4QY2W3w1Pvs,5811
4
4
  tinybird/connectors.py,sha256=7Gjms7b5MAaBFGi3xytsJurCylprONpFcYrzp4Fw2Rc,15241
5
5
  tinybird/context.py,sha256=VaMhyHruH-uyMypPDfxtuo4scS18b7rxCCdeUVm6ysg,1301
@@ -8,25 +8,25 @@ tinybird/feedback_manager.py,sha256=ADuy1yC3fCFM_ii_Haphg8Gdzapa5pX9dlRTVbuUqxs,
8
8
  tinybird/git_settings.py,sha256=Sw_8rGmribEFJ4Z_6idrVytxpFYk7ez8ei0qHULzs3E,3934
9
9
  tinybird/prompts.py,sha256=LQfE0ruY9A7z1iqtt6xTkW-1NlHbXl7LbKqzfdOsPxA,30612
10
10
  tinybird/sql.py,sha256=LBi74GxhNAYTb6m2-KNGpAkguSKh7rcvBbERbE7nalA,46195
11
- tinybird/sql_template.py,sha256=zUK-fsrvKISNsTquEFL40iD6-fMoL0RF3vxHn4PZYvw,93500
11
+ tinybird/sql_template.py,sha256=hHz-EbHWlWVMS4e6hTpoieJ1qRAzzZLDq9-fnjT60dw,95732
12
12
  tinybird/sql_template_fmt.py,sha256=KUHdj5rYCYm_rKKdXYSJAE9vIyXUQLB0YSZnUXHeBlY,10196
13
13
  tinybird/sql_toolset.py,sha256=32SNvxRFKQYWTvYPMJ_u3ukcd1hKZyEqx8T2cv2412w,14697
14
14
  tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
15
- tinybird/tornado_template.py,sha256=fUKIFrZLe0WyMFmV-mkjIdCSeFcZMBL3Uet4eHcids0,41960
15
+ tinybird/tornado_template.py,sha256=KmW_VD7y-NVqrc8YZKwIaxoJB0XpCcB2izdmxmtmApM,41944
16
16
  tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
17
17
  tinybird/ch_utils/engine.py,sha256=AUAww-KjGOZg9h0IBlKA3FeacJYB4rOtqcTGJhFM-g8,40392
18
- tinybird/tb/__cli__.py,sha256=oDKDPPvR2NMgaawv4tiYkQ17clB9vn1VDY4J4rqpHxI,251
18
+ tinybird/tb/__cli__.py,sha256=VczMDcdJLOacMcmU2_4nzvVgYOpRHt6Z-v07TP3wI38,251
19
19
  tinybird/tb/cli.py,sha256=FD1pfbzu9YHJHEG6Vtn_EwPLTYhwqw-I6AxXeTaRHU8,926
20
20
  tinybird/tb/modules/auth.py,sha256=vBA-KsrjAp77kFunGSM-4o7AFdfO7ac4dnrHKrx0alI,9020
21
- tinybird/tb/modules/build.py,sha256=UN1d7EZ93VOlPCrtsay-KLgZnzxn2NCBDY3wvrUSP1Q,9198
21
+ tinybird/tb/modules/build.py,sha256=GIXpIAHxulB-PgJ2jeZnwIXPbL7anp3rPi5a6DxfWuQ,9674
22
22
  tinybird/tb/modules/cicd.py,sha256=xxXwy-QekJcG14kkJeGNl7LkHduhZXfvBZE8WrU6-t4,5351
23
- tinybird/tb/modules/cli.py,sha256=AvqH7x3wiLShaXWxIsMRidPCmJs03ZYwuFzQZz-nMyU,15919
24
- tinybird/tb/modules/common.py,sha256=XZY0nWkK68vgh2Vml5JzyQuEWIyf2igq7IrAu0n94Lo,73203
25
- tinybird/tb/modules/config.py,sha256=mie3oMVTf5YOUFEiLs88P16U4LkJafJjSpjwyAkFHog,10979
23
+ tinybird/tb/modules/cli.py,sha256=r0_HNx2hMlbIOmmJjhflF_lAUHNhmBxuVR4rHx2onOY,16293
24
+ tinybird/tb/modules/common.py,sha256=CFERBZukoUEKzRON9hvLqCyTpPucKIyaVgHsKR_vQQM,73222
25
+ tinybird/tb/modules/config.py,sha256=J305DWh7zKpQZFjvI6x-YvK7F4RPRxyTIiZ8quc1h-0,11031
26
26
  tinybird/tb/modules/copy.py,sha256=Aq6wh_wjRiyLQtEOKF9pKLPgJhSvbGTFWIw_LJB0t0U,5801
27
27
  tinybird/tb/modules/create.py,sha256=I01JDENOyGKK0Umd2_1Om_nFGP8Uk9vxaOw7PygK02o,12302
28
- tinybird/tb/modules/datasource.py,sha256=9NxmicauGwLwfcKAe3Vyiu5JHmYDl6iYmBJweu4Hl2s,14735
29
- tinybird/tb/modules/deployment.py,sha256=Xtaty4vPoSFk189cuupei3h3ze1FpPvi_dA7koPvjoI,16222
28
+ tinybird/tb/modules/datasource.py,sha256=aOvjTVH9EdGNAw_lqhtZdJQhYnMOTGIqD3Pe4GtakFw,16905
29
+ tinybird/tb/modules/deployment.py,sha256=qGkFwPR1TGtcbqUcKkhsfzK--sNzXo6JV8Ny8HrlSfo,16873
30
30
  tinybird/tb/modules/endpoint.py,sha256=zQJgJXTzMB3hPO-3Xnppi0Pv8byZunBNhbbmMgKtijE,11915
31
31
  tinybird/tb/modules/exceptions.py,sha256=4A2sSjCEqKUMqpP3WI00zouCWW4uLaghXXLZBSw04mY,3363
32
32
  tinybird/tb/modules/feedback_manager.py,sha256=mrw5tdYycfvg6WLXlM0KIjfJardm_aNpnJkUg2vH0cA,68463
@@ -35,20 +35,20 @@ tinybird/tb/modules/job.py,sha256=956Pj8BEEsiD2GZsV9RKKVM3I_CveOLgS82lykO5ukk,29
35
35
  tinybird/tb/modules/llm.py,sha256=AC0VSphTOM2t-v1_3NLvNN_FIbgMo4dTyMqIv5nniPo,835
36
36
  tinybird/tb/modules/llm_utils.py,sha256=nS9r4FAElJw8yXtmdYrx-rtI2zXR8qXfi1QqUDCfxvg,3469
37
37
  tinybird/tb/modules/local.py,sha256=_PIa-1M-72bv9rhLwqaNthJM1ZhvcjWXFChZAfEPXRs,5658
38
- tinybird/tb/modules/local_common.py,sha256=W1fEnB1vBQ4YC5U1PdA0w0g3cTV78bQ5R-lRxdDj5-Y,2868
38
+ tinybird/tb/modules/local_common.py,sha256=9ON3do1oiAmulWXjAmn9ep0coMNbtJbcSRhZ6XQk6ZQ,2908
39
39
  tinybird/tb/modules/login.py,sha256=EGxwVRmMX1Y7ZeCRyA8fqaCWpYYk7NvnZ3x_1g0NlYA,6063
40
40
  tinybird/tb/modules/materialization.py,sha256=dESybok66Fn7XLzQQr-fBCDNf5xQL8wkRiD2QBr5lOg,5748
41
- tinybird/tb/modules/mock.py,sha256=DVZjPv9p8k5fntHcLHnIsWS7KBa1jF02BYA_Ey4XWf4,3824
41
+ tinybird/tb/modules/mock.py,sha256=7alBqLfQLb7JSZJth9JDXGUOvABWXr8Gx23edgyrW8w,3869
42
42
  tinybird/tb/modules/pipe.py,sha256=PbCM31q9lVB9xPWvPBhlpW53MGFGvzVv81trxC06kDY,2428
43
43
  tinybird/tb/modules/project.py,sha256=QdOG65Hcc6r_eQ938CfZeIXyp38RkiTYNPupgqy2gP0,2948
44
44
  tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
45
45
  tinybird/tb/modules/shell.py,sha256=qWpZ9TwDSDq3YsBxueNtezsslp3N7pwUPRF8HIlTjOo,13828
46
46
  tinybird/tb/modules/table.py,sha256=4XrtjM-N0zfNtxVkbvLDQQazno1EPXnxTyo7llivfXk,11035
47
47
  tinybird/tb/modules/tag.py,sha256=anPmMUBc-TbFovlpFi8GPkKA18y7Y0GczMsMms5TZsU,3502
48
- tinybird/tb/modules/telemetry.py,sha256=iEGnMuCuNhvF6ln__j6X9MSTwL_0Hm-GgFHHHvhfknk,10466
49
- tinybird/tb/modules/test.py,sha256=tY7_FJRSL6EdztypWlvDJi5QpXDLS5dpO0i_dpGuzkI,11528
48
+ tinybird/tb/modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
49
+ tinybird/tb/modules/test.py,sha256=4nB11SXv3HC33HaORdg1Erf5GXV2Teyedgs9u750qfM,11543
50
50
  tinybird/tb/modules/token.py,sha256=WEB2xqRgOierJ_S7TrZLEpLpQ9mkXcYLH2-xipjoGDc,12814
51
- tinybird/tb/modules/watch.py,sha256=90FUrSD6cYfOi7GsEHfq_oTI6kap7uc8CbG0t3j_Tus,4953
51
+ tinybird/tb/modules/watch.py,sha256=IIahyfmVNNu_EY2dVKZ_zjdSEFocwsmpvvgki4EmU-w,4961
52
52
  tinybird/tb/modules/workspace.py,sha256=7gOgt10j9emleqQ43BA6jU0CZjJxw7L5gDZhCxGafI4,6400
53
53
  tinybird/tb/modules/workspace_members.py,sha256=Vb5XEaKmkfONyfg2MS5EcpwolMvv7GLwFS5m2EuobT8,8726
54
54
  tinybird/tb/modules/datafile/build.py,sha256=seGFSvmgyRrAM1-icsKBkuog3WccfGUYFTPT-xoA5W8,50940
@@ -58,7 +58,7 @@ tinybird/tb/modules/datafile/build_pipe.py,sha256=Jgv3YKIvMfjPiSIdw1k2mpaoDdAWMi
58
58
  tinybird/tb/modules/datafile/common.py,sha256=1sKkQnyKOaFARQzORncpL6cRVpV9GDOD4oC36daX-Hk,79343
59
59
  tinybird/tb/modules/datafile/diff.py,sha256=-0J7PsBO64T7LOZSkZ4ZFHHCPvT7cKItnJkbz2PkndU,6754
60
60
  tinybird/tb/modules/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
61
- tinybird/tb/modules/datafile/fixture.py,sha256=XQpbppUQ4cow7dMzHs7OZBYBXA2PI5AOCmNdMDClbKo,833
61
+ tinybird/tb/modules/datafile/fixture.py,sha256=si-9LB-LdKQSWDtVW82xDrHtFfko5bgBG1cvjqqrcPU,1064
62
62
  tinybird/tb/modules/datafile/format_common.py,sha256=WaNV4tXrQU5gjV6MJP-5TGqg_Bre6ilNS8emvFl-X3c,1967
63
63
  tinybird/tb/modules/datafile/format_datasource.py,sha256=gpRsGnDEMxEo0pIlEHXKvyuwKIpqJJUCN9JRSiDYs_4,6156
64
64
  tinybird/tb/modules/datafile/format_pipe.py,sha256=58iSTrJ5lg-IsbpX8TQumQTuZ6UIotMsCIkNJd1M-pM,7418
@@ -66,16 +66,16 @@ tinybird/tb/modules/datafile/parse_datasource.py,sha256=kk35PzesoJOd0LKjYp4kOyCw
66
66
  tinybird/tb/modules/datafile/parse_pipe.py,sha256=snoy8Ac_Sat7LIXLAKzxjJSl2-TKg9FaZTooxrx6muE,3420
67
67
  tinybird/tb/modules/datafile/pipe_checker.py,sha256=LnDLGIHLJ3N7qHb2ptEbPr8CoczNfGwpjOY8EMdxfHQ,24649
68
68
  tinybird/tb/modules/datafile/pull.py,sha256=vcjMUbjnZ9XQMGmL33J3ElpbXBTat8Yzp-haeDggZd4,5967
69
- tinybird/tb/modules/tinyunit/tinyunit.py,sha256=LZGrsvIAUy5O2bZtGbi9O80QGIfXe_Du8c0PDNxeQcc,11727
69
+ tinybird/tb/modules/tinyunit/tinyunit.py,sha256=GlDgEXc6TDO3ODxgfATAL2fvbKy-b_CzqoeDrApRm0g,11715
70
70
  tinybird/tb/modules/tinyunit/tinyunit_lib.py,sha256=hGh1ZaXC1af7rKnX7222urkj0QJMhMWclqMy59dOqwE,1922
71
71
  tinybird/tb_cli_modules/cicd.py,sha256=0lMkb6CVOFZl5HOwgY8mK4T4mgI7O8335UngLXtCc-c,13851
72
- tinybird/tb_cli_modules/common.py,sha256=NpR56fwwj6u--gLdTIZodvMe4LsWAtnphX-kw4hoKGs,78726
72
+ tinybird/tb_cli_modules/common.py,sha256=VZA_NQA4ct20TQ9DxOt1065Mt4YBt10xPRVVYkoBytY,78705
73
73
  tinybird/tb_cli_modules/config.py,sha256=6u6B5QCdiQLbJkCkwtnKGs9H3nP-KXXhC75mF7B-1DQ,11464
74
74
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
75
75
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
76
- tinybird/tb_cli_modules/telemetry.py,sha256=iEGnMuCuNhvF6ln__j6X9MSTwL_0Hm-GgFHHHvhfknk,10466
77
- tinybird-0.0.1.dev69.dist-info/METADATA,sha256=Qy1Ob_W4u71B64I99GYQSlFunPcsP6kz7ie9WBl1I5I,2585
78
- tinybird-0.0.1.dev69.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
79
- tinybird-0.0.1.dev69.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
80
- tinybird-0.0.1.dev69.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
81
- tinybird-0.0.1.dev69.dist-info/RECORD,,
76
+ tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
77
+ tinybird-0.0.1.dev71.dist-info/METADATA,sha256=ACRsG8C4j9Jdl_zf65waD4t88jgCMrIUcktNq7mhvNM,2585
78
+ tinybird-0.0.1.dev71.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
79
+ tinybird-0.0.1.dev71.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
80
+ tinybird-0.0.1.dev71.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
81
+ tinybird-0.0.1.dev71.dist-info/RECORD,,