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.

@@ -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=f.relative_to(folder)))
178
+ click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder or ".")))
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
@@ -1177,31 +1177,6 @@ async def print_current_workspace(config: CLIConfig) -> None:
1177
1177
  echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
1178
1178
 
1179
1179
 
1180
- async def print_current_branch(config: CLIConfig) -> None:
1181
- _ = await try_update_config_with_remote(config, only_if_needed=True)
1182
-
1183
- response = await config.get_client().user_workspaces_and_branches()
1184
-
1185
- columns = ["name", "id", "workspace"]
1186
- table = []
1187
-
1188
- for workspace in response["workspaces"]:
1189
- if config["id"] == workspace["id"]:
1190
- click.echo(FeedbackManager.info_current_branch())
1191
- if workspace.get("is_branch"):
1192
- name = workspace["name"]
1193
- main_workspace = await get_current_main_workspace(config)
1194
- assert isinstance(main_workspace, dict)
1195
- main_name = main_workspace["name"]
1196
- else:
1197
- name = MAIN_BRANCH
1198
- main_name = workspace["name"]
1199
- table.append([name, workspace["id"], main_name])
1200
- break
1201
-
1202
- echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
1203
-
1204
-
1205
1180
  class ConnectionReplacements:
1206
1181
  _PARAMS_REPLACEMENTS: Dict[str, Dict[str, str]] = {
1207
1182
  "s3": {
@@ -323,14 +323,6 @@ class CLIConfig:
323
323
  path: str = os.path.join(working_dir, ".tinyb")
324
324
  return CLIConfig(path, parent=CLIConfig.get_global_config())
325
325
 
326
- @staticmethod
327
- def get_llm_config(working_dir: Optional[str] = None) -> Dict[str, Any]:
328
- return (
329
- CLIConfig.get_project_config(working_dir)
330
- .get("llms", {})
331
- .get("openai", {"model": "gpt-4o-mini", "api_key": None})
332
- )
333
-
334
326
  @staticmethod
335
327
  def reset() -> None:
336
328
  CLIConfig._global = None
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import re
2
3
  from os import getcwd
3
4
  from pathlib import Path
4
5
  from typing import Optional
@@ -260,7 +261,25 @@ def init_git(folder: str):
260
261
 
261
262
 
262
263
  def generate_pipe_file(name: str, content: str, folder: str):
263
- base = Path(folder) / "endpoints"
264
+ def is_copy(content: str) -> bool:
265
+ return re.search(r"TYPE copy", content, re.IGNORECASE) is not None
266
+
267
+ def is_materialization(content: str) -> bool:
268
+ return re.search(r"TYPE materialized", content, re.IGNORECASE) is not None
269
+
270
+ def is_sink(content: str) -> bool:
271
+ return re.search(r"TYPE sink", content, re.IGNORECASE) is not None
272
+
273
+ if is_copy(content):
274
+ pathname = "copies"
275
+ elif is_materialization(content):
276
+ pathname = "materializations"
277
+ elif is_sink(content):
278
+ pathname = "sinks"
279
+ else:
280
+ pathname = "endpoints"
281
+
282
+ base = Path(folder) / pathname
264
283
  if not base.exists():
265
284
  base = Path()
266
285
  f = base / (f"{name}.pipe")
@@ -768,13 +768,13 @@ async def process(
768
768
  raise click.ClickException(FeedbackManager.error_forkdownstream_pipes_with_engine(pipe=resource_name))
769
769
 
770
770
  to_run[resource_name] = r
771
- file_deps = r.get("deps", [])
771
+ file_deps: List[str] = r.get("deps", [])
772
772
  deps += file_deps
773
773
  # calculate and look for deps
774
774
  dep_list = []
775
775
  for x in file_deps:
776
776
  if x not in INTERNAL_TABLES or is_internal:
777
- f, ds = find_file_by_name(dir_path, x, verbose, vendor_paths=vendor_paths, resource=r)
777
+ f, ds = find_file_by_name(dir_path or ".", x, verbose, vendor_paths=vendor_paths, resource=r)
778
778
  if f:
779
779
  dep_list.append(f.rsplit(".", 1)[0])
780
780
  if ds:
@@ -266,7 +266,19 @@ async def new_pipe(
266
266
 
267
267
  if data.get("type") == "endpoint":
268
268
  token = tb_client.token
269
- click.echo(f"""** => Test endpoint with:\n** $ curl {host}/v0/pipes/{p["name"]}.json?token={token}""")
269
+ try:
270
+ example_params = {
271
+ "format": "json",
272
+ "pipe": p["name"],
273
+ "q": "",
274
+ "token": token,
275
+ }
276
+ endpoint_url = await tb_client._req(f"/examples/query.http?{urlencode(example_params)}")
277
+ if endpoint_url:
278
+ endpoint_url = endpoint_url.replace("http://localhost:8001", host)
279
+ click.echo(f"""** => Test endpoint with:\n** $ curl {endpoint_url}""")
280
+ except Exception:
281
+ pass
270
282
 
271
283
 
272
284
  async def get_token_from_main_branch(branch_tb_client: TinyB) -> Optional[str]:
@@ -542,7 +542,7 @@ async def datasource_share(ctx: Context, datasource_name: str, workspace_name_or
542
542
  """Share a datasource"""
543
543
 
544
544
  config = CLIConfig.get_project_config()
545
- client = config.get_client()
545
+ client: TinyB = ctx.ensure_object(dict)["client"]
546
546
  host = config.get_host() or CLIConfig.DEFAULTS["host"]
547
547
  ui_host = get_display_host(host)
548
548
 
@@ -3,7 +3,7 @@ import json
3
3
  import logging
4
4
  import time
5
5
  from pathlib import Path
6
- from typing import List
6
+ from typing import List, Optional
7
7
 
8
8
  import click
9
9
  import requests
@@ -24,8 +24,8 @@ def project_files(project_path: Path) -> List[str]:
24
24
  return project_files
25
25
 
26
26
 
27
- def promote_deployment(host: str, headers: dict) -> None:
28
- TINYBIRD_API_URL = host + "/v1/deployments"
27
+ def promote_deployment(host: Optional[str], headers: dict) -> None:
28
+ TINYBIRD_API_URL = f"{host}/v1/deployments"
29
29
  r = requests.get(TINYBIRD_API_URL, headers=headers)
30
30
  result = r.json()
31
31
  logging.debug(json.dumps(result, indent=2))
@@ -50,14 +50,14 @@ def promote_deployment(host: str, headers: dict) -> None:
50
50
  else:
51
51
  click.echo(FeedbackManager.success(message="Promoting deployment"))
52
52
 
53
- TINYBIRD_API_URL = host + f"/v1/deployments/{candidate_deployment.get('id')}/set-live"
53
+ TINYBIRD_API_URL = f"{host}/v1/deployments/{candidate_deployment.get('id')}/set-live"
54
54
  r = requests.post(TINYBIRD_API_URL, headers=headers)
55
55
  result = r.json()
56
56
  logging.debug(json.dumps(result, indent=2))
57
57
 
58
58
  click.echo(FeedbackManager.success(message="Removing old deployment"))
59
59
 
60
- TINYBIRD_API_URL = host + f"/v1/deployments/{last_deployment.get('id')}"
60
+ TINYBIRD_API_URL = f"{host}/v1/deployments/{last_deployment.get('id')}"
61
61
  r = requests.delete(TINYBIRD_API_URL, headers=headers)
62
62
  result = r.json()
63
63
  logging.debug(json.dumps(result, indent=2))
@@ -65,8 +65,8 @@ def promote_deployment(host: str, headers: dict) -> None:
65
65
  click.echo(FeedbackManager.success(message="Deployment promoted successfully"))
66
66
 
67
67
 
68
- def rollback_deployment(host: str, headers: dict) -> None:
69
- TINYBIRD_API_URL = host + "/v1/deployments"
68
+ def rollback_deployment(host: Optional[str], headers: dict) -> None:
69
+ TINYBIRD_API_URL = f"{host}/v1/deployments"
70
70
  r = requests.get(TINYBIRD_API_URL, headers=headers)
71
71
  result = r.json()
72
72
  logging.debug(json.dumps(result, indent=2))
@@ -91,14 +91,14 @@ def rollback_deployment(host: str, headers: dict) -> None:
91
91
  else:
92
92
  click.echo(FeedbackManager.success(message="Promoting previous deployment"))
93
93
 
94
- TINYBIRD_API_URL = host + f"/v1/deployments/{previous_deployment.get('id')}/set-live"
94
+ TINYBIRD_API_URL = f"{host}/v1/deployments/{previous_deployment.get('id')}/set-live"
95
95
  r = requests.post(TINYBIRD_API_URL, headers=headers)
96
96
  result = r.json()
97
97
  logging.debug(json.dumps(result, indent=2))
98
98
 
99
99
  click.echo(FeedbackManager.success(message="Removing current deployment"))
100
100
 
101
- TINYBIRD_API_URL = host + f"/v1/deployments/{current_deployment.get('id')}"
101
+ TINYBIRD_API_URL = f"{host}/v1/deployments/{current_deployment.get('id')}"
102
102
  r = requests.delete(TINYBIRD_API_URL, headers=headers)
103
103
  result = r.json()
104
104
  logging.debug(json.dumps(result, indent=2))
@@ -106,7 +106,15 @@ def rollback_deployment(host: str, headers: dict) -> None:
106
106
  click.echo(FeedbackManager.success(message="Deployment rolled back successfully"))
107
107
 
108
108
 
109
- @cli.command()
109
+ @cli.group(name="deploy")
110
+ def deploy_group() -> None:
111
+ """
112
+ Deploy commands.
113
+ """
114
+ pass
115
+
116
+
117
+ @deploy_group.command(name="create")
110
118
  @click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
111
119
  @click.option(
112
120
  "--wait/--no-wait",
@@ -120,7 +128,7 @@ def rollback_deployment(host: str, headers: dict) -> None:
120
128
  default=False,
121
129
  help="Auto-promote the deployment. Only works if --wait is enabled. Disabled by default.",
122
130
  )
123
- def deploy(project_path: Path, wait: bool, auto: bool) -> None:
131
+ def create(project_path: Path, wait: bool, auto: bool) -> None:
124
132
  """
125
133
  Validate and deploy the project server side.
126
134
  """
@@ -133,7 +141,7 @@ def deploy(project_path: Path, wait: bool, auto: bool) -> None:
133
141
  }
134
142
 
135
143
  config = CLIConfig.get_project_config(str(project_path))
136
- TINYBIRD_API_URL = (config.get_host() or "") + "/v1/deploy"
144
+ TINYBIRD_API_URL = f"{config.get_host()}/v1/deploy"
137
145
  TINYBIRD_API_KEY = config.get_token()
138
146
 
139
147
  files = [
@@ -178,7 +186,7 @@ def deploy(project_path: Path, wait: bool, auto: bool) -> None:
178
186
  if deployment and wait:
179
187
  while deployment.get("status") != "data_ready":
180
188
  time.sleep(5)
181
- TINYBIRD_API_URL = (config.get_host() or "") + f"/v1/deployments/{deployment.get('id')}"
189
+ TINYBIRD_API_URL = f"{config.get_host()}/v1/deployments/{deployment.get('id')}"
182
190
  r = requests.get(TINYBIRD_API_URL, headers=HEADERS)
183
191
  result = r.json()
184
192
  deployment = result.get("deployment")
@@ -189,28 +197,20 @@ def deploy(project_path: Path, wait: bool, auto: bool) -> None:
189
197
  click.echo(FeedbackManager.success(message="Deployment is ready"))
190
198
 
191
199
  if auto:
192
- promote_deployment((config.get_host() or ""), HEADERS)
193
-
194
-
195
- @cli.group(name="releases")
196
- def releases_group() -> None:
197
- """
198
- Release commands.
199
- """
200
- pass
200
+ promote_deployment(config.get_host(), HEADERS)
201
201
 
202
202
 
203
- @releases_group.command(name="list")
203
+ @deploy_group.command(name="list")
204
204
  @click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
205
- def release_list(project_path: Path) -> None:
205
+ def deploy_list(project_path: Path) -> None:
206
206
  """
207
- List all the releases you have in the project.
207
+ List all the deployments you have in the project.
208
208
  """
209
209
  config = CLIConfig.get_project_config(str(project_path))
210
210
 
211
211
  TINYBIRD_API_KEY = config.get_token()
212
212
  HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
213
- TINYBIRD_API_URL = (config.get_host() or "") + "/v1/deployments"
213
+ TINYBIRD_API_URL = f"{config.get_host()}/v1/deployments"
214
214
 
215
215
  r = requests.get(TINYBIRD_API_URL, headers=HEADERS)
216
216
  result = r.json()
@@ -226,9 +226,9 @@ def release_list(project_path: Path) -> None:
226
226
  echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
227
227
 
228
228
 
229
- @releases_group.command(name="promote")
229
+ @deploy_group.command(name="promote")
230
230
  @click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
231
- def release_promote(project_path: Path) -> None:
231
+ def deploy_promote(project_path: Path) -> None:
232
232
  """
233
233
  Promote last deploy to ready and remove old one.
234
234
  """
@@ -237,18 +237,18 @@ def release_promote(project_path: Path) -> None:
237
237
  TINYBIRD_API_KEY = config.get_token()
238
238
  HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
239
239
 
240
- promote_deployment((config.get_host() or ""), HEADERS)
240
+ promote_deployment(config.get_host(), HEADERS)
241
241
 
242
242
 
243
- @releases_group.command(name="rollback")
243
+ @deploy_group.command(name="rollback")
244
244
  @click.argument("project_path", type=click.Path(exists=True), default=Path.cwd())
245
- def release_rollback(project_path: Path) -> None:
245
+ def deploy_rollback(project_path: Path) -> None:
246
246
  """
247
- Rollback to the previous release.
247
+ Rollback to the previous deployment.
248
248
  """
249
249
  config = CLIConfig.get_project_config(str(project_path))
250
250
 
251
251
  TINYBIRD_API_KEY = config.get_token()
252
252
  HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
253
253
 
254
- rollback_deployment((config.get_host() or ""), HEADERS)
254
+ rollback_deployment(config.get_host(), HEADERS)
@@ -1,8 +1,10 @@
1
1
  import json
2
+ import os
2
3
  import urllib.parse
3
4
  from copy import deepcopy
4
- from typing import List
5
+ from typing import Any, List
5
6
 
7
+ from anthropic import AnthropicVertex
6
8
  from pydantic import BaseModel
7
9
 
8
10
  from tinybird.client import TinyB
@@ -37,7 +39,7 @@ class LLM:
37
39
  self.user_client = deepcopy(client)
38
40
  self.user_client.token = user_token
39
41
 
40
- async def ask(self, prompt: str, system_prompt: str = "", model: str = "o1-mini") -> str:
42
+ async def ask(self, prompt: str, system_prompt: str = "") -> str:
41
43
  """
42
44
  Calls the model with the given prompt and returns the response.
43
45
 
@@ -47,7 +49,7 @@ class LLM:
47
49
  Returns:
48
50
  str: The response from the language model.
49
51
  """
50
- messages = []
52
+ messages: List[Any] = []
51
53
 
52
54
  if system_prompt:
53
55
  messages.append({"role": "user", "content": system_prompt})
@@ -55,8 +57,21 @@ class LLM:
55
57
  if prompt:
56
58
  messages.append({"role": "user", "content": prompt})
57
59
 
60
+ if gcloud_access_token := os.getenv("GCLOUD_ACCESS_TOKEN"):
61
+ client = AnthropicVertex(
62
+ region="europe-west1",
63
+ project_id="gen-lang-client-0705305160",
64
+ access_token=gcloud_access_token,
65
+ )
66
+ message = client.messages.create(
67
+ max_tokens=8000,
68
+ messages=messages,
69
+ model="claude-3-5-sonnet-v2@20241022",
70
+ )
71
+ return message.content[0].text or "" # type: ignore
72
+
58
73
  data = {
59
- "model": model,
74
+ "model": "o1-mini",
60
75
  "messages": messages,
61
76
  }
62
77
  response = await self.user_client._req(
@@ -82,7 +82,7 @@ def get_docker_client():
82
82
  client.ping()
83
83
  return client
84
84
  except Exception:
85
- raise CLIException("Docker is not running or installed. Make sure Docker is installed and running.")
85
+ raise CLIException("No container runtime is running. Make sure a Docker-compatible runtime is installed and running.")
86
86
 
87
87
 
88
88
  def stop_tinybird_local(docker_client):
@@ -135,7 +135,12 @@ async def login(host: str, workspace: str):
135
135
  server_thread.start()
136
136
 
137
137
  # Open the browser to the auth page
138
- client_id = "T6excMo8IKguvUw4vFNYfqlt9pe6msCU"
138
+ if "wadus" in host:
139
+ client_id = "Rpl7Uy9aSjqoPCSvHgGl3zNQuZcSOXBe"
140
+ base_auth_url = "https://auth.wadus1.tinybird.co"
141
+ else:
142
+ client_id = "T6excMo8IKguvUw4vFNYfqlt9pe6msCU"
143
+ base_auth_url = "https://auth.tinybird.co"
139
144
  callback_url = f"http://localhost:{AUTH_SERVER_PORT}"
140
145
  params = {
141
146
  "client_id": client_id,
@@ -143,7 +148,7 @@ async def login(host: str, workspace: str):
143
148
  "response_type": "token",
144
149
  "scope": "openid profile email",
145
150
  }
146
- auth_url = f"https://auth.tinybird.co/authorize?{urlencode(params)}"
151
+ auth_url = f"{base_auth_url}/authorize?{urlencode(params)}"
147
152
  webbrowser.open(auth_url)
148
153
 
149
154
  # Wait for the authentication to complete or timeout
@@ -274,7 +274,7 @@ class Shell:
274
274
  elif arg.startswith("mock"):
275
275
  self.handle_mock(arg)
276
276
  else:
277
- subprocess.run(f"tb --local {arg}", shell=True, text=True)
277
+ subprocess.run(f"tb {arg}", shell=True, text=True)
278
278
 
279
279
  def default(self, argline):
280
280
  click.echo("")
@@ -286,7 +286,7 @@ class Shell:
286
286
  elif len(arg.split()) == 1 and arg in self.endpoints + self.pipes + self.datasources + self.shared_datasources:
287
287
  self.run_sql(f"select * from {arg}")
288
288
  else:
289
- subprocess.run(f"tb --local {arg}", shell=True, text=True)
289
+ subprocess.run(f"tb {arg}", shell=True, text=True)
290
290
 
291
291
  def run_sql(self, query, rows_limit=20):
292
292
  try:
@@ -6,11 +6,13 @@
6
6
  import difflib
7
7
  import glob
8
8
  import os
9
+ import urllib.parse
9
10
  from pathlib import Path
10
- from typing import Any, Dict, Iterable, List, Optional, Tuple
11
+ from typing import Any, Dict, List, Optional, Tuple
11
12
 
12
13
  import click
13
14
  import yaml
15
+ from requests import Response
14
16
 
15
17
  from tinybird.prompts import test_create_prompt
16
18
  from tinybird.tb.modules.cli import cli
@@ -66,9 +68,9 @@ def test(ctx: click.Context) -> None:
66
68
  type=click.Path(exists=True, file_okay=False),
67
69
  help="Folder where datafiles will be placed",
68
70
  )
69
- @click.option("--prompt", type=str, default=None, help="Prompt to be used to create the test")
71
+ @click.option("--prompt", type=str, default="", help="Prompt to be used to create the test")
70
72
  @coro
71
- async def test_create(name_or_filename: str, prompt: Optional[str], folder: str) -> None:
73
+ async def test_create(name_or_filename: str, prompt: str, folder: str) -> None:
72
74
  """
73
75
  Create a test for an existing pipe
74
76
  """
@@ -80,15 +82,13 @@ async def test_create(name_or_filename: str, prompt: Optional[str], folder: str)
80
82
  raise CLIException(FeedbackManager.error(message=f"Pipe {name_or_filename} not found"))
81
83
  else:
82
84
  pipe_folders = ("endpoints", "copies", "materializations", "sinks", "pipes")
83
- pipe_path = next(
84
- (
85
+ try:
86
+ pipe_path = next(
85
87
  root_path / folder / f"{name_or_filename}.pipe"
86
88
  for folder in pipe_folders
87
89
  if (root_path / folder / f"{name_or_filename}.pipe").exists()
88
- ),
89
- None,
90
- )
91
- if not pipe_path:
90
+ )
91
+ except Exception:
92
92
  raise CLIException(FeedbackManager.error(message=f"Pipe {name_or_filename} not found"))
93
93
 
94
94
  pipe_name = pipe_path.stem
@@ -97,8 +97,8 @@ async def test_create(name_or_filename: str, prompt: Optional[str], folder: str)
97
97
  pipe_content = pipe_path.read_text()
98
98
 
99
99
  client = await get_tinybird_local_client(os.path.abspath(folder))
100
- pipe_nodes = await client._req(f"/v0/pipes/{pipe_name}")
101
- parameters = set([param["name"] for node in pipe_nodes["nodes"] for param in node["params"]])
100
+ pipe = await client._req(f"/v0/pipes/{pipe_name}")
101
+ parameters = set([param["name"] for node in pipe["nodes"] for param in node["params"]])
102
102
 
103
103
  system_prompt = test_create_prompt.format(
104
104
  name=pipe_name,
@@ -107,24 +107,26 @@ async def test_create(name_or_filename: str, prompt: Optional[str], folder: str)
107
107
  )
108
108
  config = CLIConfig.get_project_config(folder)
109
109
  user_token = config.get_user_token()
110
+ if not user_token:
111
+ raise CLIException(FeedbackManager.error(message="No user token found"))
110
112
  llm = LLM(user_token=user_token, client=config.get_client())
111
113
 
112
- response = await llm.ask(prompt, system_prompt=system_prompt)
113
- response = extract_xml(response, "response")
114
- tests_content = parse_xml(response, "test")
114
+ response_llm = await llm.ask(prompt, system_prompt=system_prompt)
115
+ response_xml = extract_xml(response_llm, "response")
116
+ tests_content = parse_xml(response_xml, "test")
115
117
 
116
118
  tests: List[Dict[str, Any]] = []
117
119
  for test_content in tests_content:
118
- test = {}
120
+ test: Dict[str, Any] = {}
119
121
  test["name"] = extract_xml(test_content, "name")
120
122
  test["description"] = extract_xml(test_content, "description")
121
- parameters = extract_xml(test_content, "parameters")
122
- test["parameters"] = parameters.split("?")[1] if "?" in parameters else parameters
123
+ parameters_api = extract_xml(test_content, "parameters")
124
+ test["parameters"] = parameters_api.split("?")[1] if "?" in parameters_api else parameters_api
123
125
  test["expected_result"] = ""
124
126
 
125
127
  response = None
126
128
  try:
127
- response = await client._req_raw(f"/v0/pipes/{pipe_name}.ndjson?{parameters}")
129
+ response = await get_pipe_data(client, pipe_name=pipe_name, test_params=test["parameters"])
128
130
  except Exception:
129
131
  pass
130
132
 
@@ -181,7 +183,7 @@ async def test_update(pipe: str, folder: str) -> None:
181
183
  test_params = test["parameters"].split("?")[1] if "?" in test["parameters"] else test["parameters"]
182
184
  response = None
183
185
  try:
184
- response = await client._req_raw(f"/v0/pipes/{pipe_name}.ndjson?{test_params}")
186
+ response = await get_pipe_data(client, pipe_name=pipe_name, test_params=test_params)
185
187
  except Exception:
186
188
  continue
187
189
 
@@ -221,20 +223,21 @@ async def run_tests(name: Tuple[str, ...], folder: str) -> None:
221
223
  client = await get_tinybird_local_client(os.path.abspath(folder))
222
224
  paths = [Path(n) for n in name]
223
225
  endpoints = [f"./tests/{p.stem}.yaml" for p in paths]
224
- test_files: Iterable[str] = endpoints if len(endpoints) > 0 else glob.glob("./tests/**/*.y*ml", recursive=True)
226
+ test_files: List[str] = endpoints if len(endpoints) > 0 else glob.glob("./tests/**/*.y*ml", recursive=True)
225
227
 
226
228
  async def run_test(test_file):
227
229
  test_file_path = Path(test_file)
228
230
  click.echo(FeedbackManager.info(message=f"\n* {test_file_path.stem}{test_file_path.suffix}"))
229
231
  test_file_content = yaml.safe_load(test_file_path.read_text())
232
+
230
233
  for test in test_file_content:
231
234
  try:
232
235
  test_params = test["parameters"].split("?")[1] if "?" in test["parameters"] else test["parameters"]
233
236
  response = None
234
237
  try:
235
- response = await client._req_raw(f"/v0/pipes/{test_file_path.stem}.ndjson?{test_params}")
238
+ response = await get_pipe_data(client, pipe_name=test_file_path.stem, test_params=test_params)
236
239
  except Exception:
237
- raise Exception("Expected to not fail but got an error")
240
+ continue
238
241
 
239
242
  expected_result = response.text
240
243
  if response.status_code >= 400:
@@ -271,3 +274,19 @@ async def run_tests(name: Tuple[str, ...], folder: str) -> None:
271
274
  exit(1)
272
275
  else:
273
276
  click.echo(FeedbackManager.success(message=f"\n✓ {test_count}/{test_count} passed"))
277
+
278
+
279
+ async def get_pipe_data(client, pipe_name: str, test_params: str) -> Response:
280
+ pipe = await client._req(f"/v0/pipes/{pipe_name}")
281
+ output_node = next(
282
+ (node for node in pipe["nodes"] if node["node_type"] != "default" and node["node_type"] != "standard"),
283
+ {"name": "not_found"},
284
+ )
285
+ if output_node["node_type"] == "endpoint":
286
+ return await client._req_raw(f"/v0/pipes/{pipe_name}.ndjson?{test_params}")
287
+
288
+ params = {
289
+ "q": output_node["sql"],
290
+ "pipeline": pipe_name,
291
+ }
292
+ return await client._req_raw(f"""/v0/sql?{urllib.parse.urlencode(params)}&{test_params}""")