tinybird 0.0.1.dev300__py3-none-any.whl → 0.0.1.dev302__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/connectors.py CHANGED
@@ -369,13 +369,7 @@ class Snowflake(Connector):
369
369
  the_type = "String"
370
370
  if t.startswith("NUMBER"):
371
371
  the_type = "Int32"
372
- if (
373
- t.startswith("FLOAT")
374
- or t.startswith("DOUBLE")
375
- or t.startswith("REAL")
376
- or t.startswith("NUMERIC")
377
- or t.startswith("DECIMAL")
378
- ):
372
+ if t.startswith(("FLOAT", "DOUBLE", "REAL", "NUMERIC", "DECIMAL")):
379
373
  the_type = "Float32"
380
374
  if t == "DATE":
381
375
  the_type = "Date"
@@ -2051,11 +2051,7 @@ def parse(
2051
2051
  lexer = list(sa)
2052
2052
  if lexer:
2053
2053
  cmd, args = lexer[0], lexer[1:]
2054
- if (
2055
- parser_state.multiline
2056
- and cmd.lower() in cmds
2057
- and not (line.startswith(" ") or line.startswith("\t"))
2058
- ):
2054
+ if parser_state.multiline and cmd.lower() in cmds and not line.startswith((" ", "\t")):
2059
2055
  cmds[parser_state.command](
2060
2056
  parser_state.multiline_string,
2061
2057
  lineno=lineno,
@@ -2508,7 +2504,7 @@ def is_file_a_datasource(filename: str) -> bool:
2508
2504
 
2509
2505
  for line in lines:
2510
2506
  trimmed_line = line.strip().lower()
2511
- if trimmed_line.startswith("schema") or trimmed_line.startswith("engine"):
2507
+ if trimmed_line.startswith(("schema", "engine")):
2512
2508
  return True
2513
2509
 
2514
2510
  return False
@@ -446,7 +446,6 @@ def get_tinybird_service_datasources() -> List[Dict[str, Any]]:
446
446
  {"name": "workspace_name", "type": "String"},
447
447
  {"name": "user_email", "type": "String"},
448
448
  {"name": "request_id", "type": "String"},
449
- {"name": "model", "type": "LowCardinality(String)"},
450
449
  {"name": "prompt_tokens", "type": "UInt32"},
451
450
  {"name": "completion_tokens", "type": "UInt32"},
452
451
  {"name": "total_tokens", "type": "UInt32"},
@@ -927,7 +926,6 @@ def get_organization_service_datasources() -> List[Dict[str, Any]]:
927
926
  {"name": "workspace_name", "type": "String"},
928
927
  {"name": "user_email", "type": "String"},
929
928
  {"name": "request_id", "type": "String"},
930
- {"name": "model", "type": "LowCardinality(String)"},
931
929
  {"name": "prompt_tokens", "type": "UInt32"},
932
930
  {"name": "completion_tokens", "type": "UInt32"},
933
931
  {"name": "total_tokens", "type": "UInt32"},
tinybird/tb/__cli__.py CHANGED
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/forward/commands'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev300'
8
- __revision__ = '9791d55'
7
+ __version__ = '0.0.1.dev302'
8
+ __revision__ = 'cf4d28e'
tinybird/tb/client.py CHANGED
@@ -1390,7 +1390,3 @@ class TinyB:
1390
1390
 
1391
1391
  def delete_tag(self, name: str):
1392
1392
  self._req(f"/v0/tags/{name}", method="DELETE")
1393
-
1394
- def explore_data(self, prompt: str) -> str:
1395
- params = urlencode({"prompt": prompt, "host": self.host, "origin": "cli"})
1396
- return self._req(f"/v1/agents/explore?{params}")
@@ -6,8 +6,7 @@ import sys
6
6
  import urllib.parse
7
7
  import uuid
8
8
  from functools import partial
9
- from pathlib import Path
10
- from typing import Any, Optional
9
+ from typing import Any, Callable, Optional
11
10
 
12
11
  import click
13
12
  import humanfriendly
@@ -27,7 +26,6 @@ from tinybird.tb.modules.agent.file_agent import FileAgent
27
26
  from tinybird.tb.modules.agent.memory import (
28
27
  clear_history,
29
28
  clear_messages,
30
- get_last_messages_from_last_user_prompt,
31
29
  save_messages,
32
30
  )
33
31
  from tinybird.tb.modules.agent.mock_agent import MockAgent
@@ -67,16 +65,17 @@ from tinybird.tb.modules.common import (
67
65
  echo_safe_humanfriendly_tables_format_pretty_table,
68
66
  get_region_from_host,
69
67
  get_regions,
68
+ sys_exit,
70
69
  update_cli,
71
70
  )
72
71
  from tinybird.tb.modules.config import CLIConfig
73
72
  from tinybird.tb.modules.deployment_common import create_deployment
74
- from tinybird.tb.modules.exceptions import CLIBuildException, CLIDeploymentException, CLIMockException
73
+ from tinybird.tb.modules.exceptions import CLIBuildException, CLIDeploymentException
75
74
  from tinybird.tb.modules.feedback_manager import FeedbackManager
76
75
  from tinybird.tb.modules.llm import LLM
77
76
  from tinybird.tb.modules.local_common import get_tinybird_local_client
78
77
  from tinybird.tb.modules.login_common import login
79
- from tinybird.tb.modules.mock_common import append_mock_data, create_mock_data
78
+ from tinybird.tb.modules.mock_common import append_mock_data
80
79
  from tinybird.tb.modules.project import Project
81
80
  from tinybird.tb.modules.test_common import run_tests as run_tests_common
82
81
 
@@ -91,6 +90,7 @@ class TinybirdAgent:
91
90
  project: Project,
92
91
  dangerously_skip_permissions: bool,
93
92
  prompt_mode: bool,
93
+ feature: Optional[str] = None,
94
94
  ):
95
95
  self.token = token
96
96
  self.user_token = user_token
@@ -100,14 +100,12 @@ class TinybirdAgent:
100
100
  self.project = project
101
101
  self.thinking_animation = ThinkingAnimation()
102
102
  self.confirmed_plan_id: Optional[str] = None
103
- if prompt_mode:
104
- self.messages: list[ModelMessage] = get_last_messages_from_last_user_prompt()
105
- else:
106
- self.messages = []
103
+ self.feature = feature
104
+ self.messages: list[ModelMessage] = []
107
105
  cli_config = CLIConfig.get_project_config()
108
106
  regions = get_regions(cli_config)
109
107
  self.agent = Agent(
110
- model=create_model(user_token, host, workspace_id),
108
+ model=create_model(user_token, host, workspace_id, feature=feature),
111
109
  deps_type=TinybirdAgentContext,
112
110
  instructions=[agent_system_prompt],
113
111
  tools=[
@@ -381,7 +379,6 @@ class TinybirdAgent:
381
379
  build_project=partial(build_project, project=project, config=config),
382
380
  deploy_project=partial(deploy_project, project=project, config=config),
383
381
  deploy_check_project=partial(deploy_check_project, project=project, config=config),
384
- mock_data=partial(mock_data, project=project, config=config),
385
382
  append_data_local=partial(append_data_local, config=config),
386
383
  append_data_cloud=partial(append_data_cloud, config=config),
387
384
  analyze_fixture=partial(analyze_fixture, config=config),
@@ -428,7 +425,6 @@ class TinybirdAgent:
428
425
  save_messages(new_messages)
429
426
  self.thinking_animation.stop()
430
427
  click.echo(result.output)
431
- self.echo_usage(config)
432
428
 
433
429
  async def run_iter(self, user_prompt: str, config: dict[str, Any], run_id: Optional[str] = None) -> None:
434
430
  model = create_model(self.user_token, self.host, self.workspace_id, run_id=run_id)
@@ -455,34 +451,67 @@ class TinybirdAgent:
455
451
  self.messages.extend(new_messages)
456
452
  save_messages(new_messages)
457
453
  self.thinking_animation.stop()
458
- self.echo_usage(config)
459
454
 
460
- def echo_usage(self, config: dict[str, Any]) -> None:
455
+ def echo_usage(self, config: dict[str, Any], show_credits: bool = False) -> None:
461
456
  try:
462
457
  client = _get_tb_client(config["user_token"], config["host"])
463
458
  workspace_id = config.get("id", "")
464
459
  workspace = client.workspace(workspace_id, with_organization=True, version="v1")
460
+ is_free_plan = workspace["organization"]["plan"].get("billing") == "free_shared_infrastructure_usage"
461
+
462
+ if not is_free_plan and not show_credits:
463
+ return
464
+
465
465
  limits_data = client.organization_limits(workspace["organization"]["id"])
466
466
  llm_usage_limits = limits_data.get("limits", {}).get("llm_usage", {})
467
467
  current_llm_usage = llm_usage_limits.get("quantity") or 0
468
468
  llm_usage = llm_usage_limits.get("max") or 0
469
469
  remaining_credits = round(max(llm_usage - current_llm_usage, 0), 2)
470
470
  current_llm_usage = round(min(llm_usage, current_llm_usage), 2)
471
+
471
472
  if not llm_usage:
472
473
  return
473
- warning_threshold = llm_usage * 0.8
474
- message_color = FeedbackManager.warning if current_llm_usage >= warning_threshold else FeedbackManager.gray
474
+
475
+ def get_message(current_llm_usage, llm_usage: int) -> tuple[Callable[..., str], str, bool]:
476
+ warning_threshold = llm_usage * 0.8
477
+ ui_host = get_display_cloud_host(config["host"])
478
+
479
+ if is_free_plan:
480
+ upgrade_link = f"{ui_host}/organizations/{workspace['organization']['name']}/upgrade?from=agent"
481
+ if current_llm_usage >= llm_usage:
482
+ return (
483
+ FeedbackManager.error,
484
+ f" You have reached the maximum number of credits. Please upgrade to continue using Tinybird Code: {upgrade_link}",
485
+ True,
486
+ )
487
+ if current_llm_usage >= warning_threshold:
488
+ return (
489
+ FeedbackManager.warning,
490
+ f" You are reaching the maximum number of credits. Please upgrade to continue using Tinybird Code: {upgrade_link}",
491
+ False,
492
+ )
493
+ return FeedbackManager.gray, "", False
494
+
495
+ message_color, upgrade_message, should_exit = get_message(current_llm_usage, llm_usage)
475
496
  click.echo(
476
497
  message_color(
477
- message=f"{remaining_credits} credits left ({current_llm_usage}/{llm_usage}). You can continue using Tinybird Code. Limits will be enforced soon."
498
+ message=f"{remaining_credits} credits left ({current_llm_usage}/{llm_usage}).{upgrade_message}"
478
499
  )
479
500
  )
501
+
502
+ if should_exit:
503
+ sys_exit("tinybird_code_error", "Maximum number of credits reached")
504
+
480
505
  except Exception:
481
506
  pass
482
507
 
483
508
 
484
509
  def run_agent(
485
- config: dict[str, Any], project: Project, dangerously_skip_permissions: bool, prompt: Optional[str] = None
510
+ config: dict[str, Any],
511
+ project: Project,
512
+ dangerously_skip_permissions: bool,
513
+ prompt: Optional[str] = None,
514
+ feature: Optional[str] = None,
486
515
  ):
487
516
  if not prompt:
488
517
  latest_version = CheckPypi().get_latest_version()
@@ -497,7 +526,10 @@ def run_agent(
497
526
  )
498
527
  if yes:
499
528
  update_cli()
500
- click.echo(FeedbackManager.highlight(message="» Initializing Tinybird Code..."))
529
+
530
+ if not prompt:
531
+ click.echo(FeedbackManager.highlight(message="» Initializing Tinybird Code..."))
532
+
501
533
  token = config.get("token", "")
502
534
  host = config.get("host", "")
503
535
  user_token = config.get("user_token", "")
@@ -556,6 +588,7 @@ def run_agent(
556
588
  project,
557
589
  dangerously_skip_permissions,
558
590
  prompt_mode,
591
+ feature,
559
592
  )
560
593
 
561
594
  # Print mode: run once with the provided prompt and exit
@@ -577,7 +610,6 @@ def run_agent(
577
610
  )
578
611
  )
579
612
  agent.echo_usage(config)
580
- click.echo()
581
613
 
582
614
  except Exception as e:
583
615
  click.echo(FeedbackManager.error(message=f"Failed to initialize agent: {e}"))
@@ -630,6 +662,9 @@ def run_agent(
630
662
  elif user_input.lower() == "/help":
631
663
  subprocess.run(["tb", "--help"], check=True)
632
664
  continue
665
+ elif user_input.lower() == "/usage":
666
+ agent.echo_usage(config, show_credits=True)
667
+ continue
633
668
  elif user_input.strip() == "":
634
669
  continue
635
670
  else:
@@ -732,36 +767,6 @@ def append_data_cloud(config: dict[str, Any], datasource_name: str, path: str) -
732
767
  append_mock_data(client, datasource_name, path)
733
768
 
734
769
 
735
- def mock_data(
736
- config: dict[str, Any],
737
- project: Project,
738
- datasource_name: str,
739
- data_format: str,
740
- rows: int,
741
- context: Optional[str] = None,
742
- ) -> list[dict[str, Any]]:
743
- client = get_tinybird_local_client(config, test=False, silent=False)
744
- cli_config = CLIConfig.get_project_config()
745
- datasource_path = project.get_resource_path(datasource_name, "datasource")
746
-
747
- if not datasource_path:
748
- raise CLIMockException(f"Datasource {datasource_name} not found")
749
-
750
- datasource_content = Path(datasource_path).read_text()
751
- return create_mock_data(
752
- datasource_name,
753
- datasource_content,
754
- rows,
755
- context or "",
756
- cli_config,
757
- config,
758
- cli_config.get_user_token() or "",
759
- client,
760
- data_format,
761
- project.folder,
762
- )
763
-
764
-
765
770
  def analyze_fixture(config: dict[str, Any], fixture_path: str, format: str = "json") -> dict[str, Any]:
766
771
  local_client = get_tinybird_local_client(config, test=False, silent=True)
767
772
  meta, _data = _analyze(fixture_path, local_client, format)
@@ -8,7 +8,7 @@ from pydantic_ai.retries import AsyncTenacityTransport, wait_retry_after
8
8
  from tenacity import AsyncRetrying, retry_if_exception_type, stop_after_attempt, wait_exponential
9
9
 
10
10
 
11
- def create_retrying_client(token: str, workspace_id: str):
11
+ def create_retrying_client(token: str, workspace_id: str, feature: Optional[str] = None):
12
12
  """Create a client with smart retry handling for multiple error types."""
13
13
 
14
14
  def should_retry_status(response):
@@ -29,7 +29,10 @@ def create_retrying_client(token: str, workspace_id: str):
29
29
  ),
30
30
  validate_response=should_retry_status,
31
31
  )
32
- return AsyncClient(transport=transport, params={"token": token, "workspace_id": workspace_id})
32
+ params = {"token": token, "workspace_id": workspace_id}
33
+ if feature:
34
+ params["feature"] = feature
35
+ return AsyncClient(transport=transport, params=params)
33
36
 
34
37
 
35
38
  def create_model(
@@ -38,6 +41,7 @@ def create_model(
38
41
  workspace_id: str,
39
42
  model: AnthropicModelName = "claude-4-sonnet-20250514",
40
43
  run_id: Optional[str] = None,
44
+ feature: Optional[str] = None,
41
45
  ):
42
46
  default_headers = {}
43
47
  if run_id:
@@ -44,7 +44,6 @@ class TinybirdAgentContext(BaseModel):
44
44
  build_project_test: Callable[..., None]
45
45
  deploy_project: Callable[..., None]
46
46
  deploy_check_project: Callable[[], None]
47
- mock_data: Callable[..., list[dict[str, Any]]]
48
47
  append_data_local: Callable[..., None]
49
48
  append_data_cloud: Callable[..., None]
50
49
  analyze_fixture: Callable[..., dict[str, Any]]
@@ -512,7 +511,7 @@ def create_terminal_box(content: str, new_content: Optional[str] = None, title:
512
511
  new_line_num = int(match.group(2))
513
512
  old_index = old_line_num - 1
514
513
  new_index = new_line_num - 1
515
- elif line.startswith("---") or line.startswith("+++"):
514
+ elif line.startswith(("---", "+++")):
516
515
  # Skip file headers
517
516
  pass
518
517
  elif line.startswith(" "):
@@ -8,7 +8,7 @@ from urllib.parse import urlencode
8
8
 
9
9
  import click
10
10
 
11
- import tinybird.context as context
11
+ from tinybird import context
12
12
  from tinybird.datafile.exceptions import ParseException
13
13
  from tinybird.datafile.parse_datasource import parse_datasource
14
14
  from tinybird.datafile.parse_pipe import parse_pipe
@@ -140,7 +140,7 @@ def dev_cloud(
140
140
  context.disable_template_security_validation.set(True)
141
141
 
142
142
  def process(filenames: List[str], watch: bool = False):
143
- datafiles = [f for f in filenames if f.endswith(".datasource") or f.endswith(".pipe")]
143
+ datafiles = [f for f in filenames if f.endswith((".datasource", ".pipe"))]
144
144
  if len(datafiles) > 0:
145
145
  check_filenames(filenames=datafiles)
146
146
  folder_playground(
@@ -51,7 +51,7 @@ def process(
51
51
  return build_status.error
52
52
  else:
53
53
  build_status.building = True
54
- if file_changed and (file_changed.endswith(FixtureExtension.NDJSON) or file_changed.endswith(FixtureExtension.CSV)):
54
+ if file_changed and file_changed.endswith((FixtureExtension.NDJSON, FixtureExtension.CSV)):
55
55
  rebuild_fixture(project, tb_client, file_changed)
56
56
  if build_status:
57
57
  build_status.building = False
@@ -61,7 +61,7 @@ def process(
61
61
  if build_status:
62
62
  build_status.building = False
63
63
  build_status.error = None
64
- elif file_changed and (file_changed.endswith(".env.local") or file_changed.endswith(".env")):
64
+ elif file_changed and file_changed.endswith((".env.local", ".env")):
65
65
  if build_status:
66
66
  build_status.building = False
67
67
  build_status.error = None
@@ -302,7 +302,7 @@ def sql(
302
302
  )
303
303
 
304
304
  query = ""
305
- for _, elem in dependencies_graph.to_run.items():
305
+ for elem in dependencies_graph.to_run.values():
306
306
  for _node in elem["nodes"]:
307
307
  if _node["params"]["name"].lower() == node.lower():
308
308
  query = "".join(_node["sql"])
@@ -488,7 +488,6 @@ def create_ctx_client(
488
488
  "logout",
489
489
  "update",
490
490
  "upgrade",
491
- "create",
492
491
  "info",
493
492
  "tag",
494
493
  "push",
@@ -502,7 +501,7 @@ def create_ctx_client(
502
501
  return None
503
502
 
504
503
  commands_always_cloud = ["infra"]
505
- commands_always_local = ["build", "dev"]
504
+ commands_always_local = ["build", "dev", "create"]
506
505
  command_always_test = ["test"]
507
506
 
508
507
  if (
@@ -908,7 +908,7 @@ def get_format_from_filename_or_url(filename_or_url: str) -> str:
908
908
  'csv'
909
909
  """
910
910
  filename_or_url = filename_or_url.lower()
911
- if filename_or_url.endswith("json") or filename_or_url.endswith("ndjson"):
911
+ if filename_or_url.endswith(("json", "ndjson")):
912
912
  return "ndjson"
913
913
  if filename_or_url.endswith("parquet"):
914
914
  return "parquet"
@@ -916,7 +916,7 @@ def get_format_from_filename_or_url(filename_or_url: str) -> str:
916
916
  return "csv"
917
917
  try:
918
918
  parsed = urlparse(filename_or_url)
919
- if parsed.path.endswith("json") or parsed.path.endswith("ndjson"):
919
+ if parsed.path.endswith(("json", "ndjson")):
920
920
  return "ndjson"
921
921
  if parsed.path.endswith("parquet"):
922
922
  return "parquet"