tinybird 0.0.1.dev291__py3-none-any.whl → 1.0.5__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.
- tinybird/ch_utils/constants.py +5 -0
- tinybird/connectors.py +1 -7
- tinybird/context.py +3 -3
- tinybird/datafile/common.py +10 -8
- tinybird/datafile/parse_pipe.py +2 -2
- tinybird/feedback_manager.py +3 -0
- tinybird/prompts.py +1 -0
- tinybird/service_datasources.py +223 -0
- tinybird/sql_template.py +26 -11
- tinybird/sql_template_fmt.py +14 -4
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +1 -0
- tinybird/tb/client.py +104 -26
- tinybird/tb/config.py +24 -0
- tinybird/tb/modules/agent/agent.py +103 -67
- tinybird/tb/modules/agent/banner.py +15 -15
- tinybird/tb/modules/agent/explore_agent.py +5 -0
- tinybird/tb/modules/agent/mock_agent.py +5 -1
- tinybird/tb/modules/agent/models.py +6 -2
- tinybird/tb/modules/agent/prompts.py +49 -2
- tinybird/tb/modules/agent/tools/deploy.py +1 -1
- tinybird/tb/modules/agent/tools/execute_query.py +15 -18
- tinybird/tb/modules/agent/tools/request_endpoint.py +1 -1
- tinybird/tb/modules/agent/tools/run_command.py +9 -0
- tinybird/tb/modules/agent/utils.py +38 -48
- tinybird/tb/modules/branch.py +150 -0
- tinybird/tb/modules/build.py +58 -13
- tinybird/tb/modules/build_common.py +209 -25
- tinybird/tb/modules/cli.py +129 -16
- tinybird/tb/modules/common.py +172 -146
- tinybird/tb/modules/connection.py +125 -194
- tinybird/tb/modules/connection_kafka.py +382 -0
- tinybird/tb/modules/copy.py +3 -1
- tinybird/tb/modules/create.py +83 -150
- tinybird/tb/modules/datafile/build.py +27 -38
- tinybird/tb/modules/datafile/build_datasource.py +21 -25
- tinybird/tb/modules/datafile/diff.py +1 -1
- tinybird/tb/modules/datafile/format_pipe.py +46 -7
- tinybird/tb/modules/datafile/playground.py +59 -68
- tinybird/tb/modules/datafile/pull.py +2 -3
- tinybird/tb/modules/datasource.py +477 -308
- tinybird/tb/modules/deployment.py +2 -0
- tinybird/tb/modules/deployment_common.py +84 -44
- tinybird/tb/modules/deprecations.py +4 -4
- tinybird/tb/modules/dev_server.py +33 -12
- tinybird/tb/modules/exceptions.py +14 -0
- tinybird/tb/modules/feedback_manager.py +1 -1
- tinybird/tb/modules/info.py +69 -12
- tinybird/tb/modules/infra.py +4 -5
- tinybird/tb/modules/job_common.py +15 -0
- tinybird/tb/modules/local.py +143 -23
- tinybird/tb/modules/local_common.py +347 -19
- tinybird/tb/modules/local_logs.py +209 -0
- tinybird/tb/modules/login.py +21 -2
- tinybird/tb/modules/login_common.py +254 -12
- tinybird/tb/modules/mock.py +5 -54
- tinybird/tb/modules/mock_common.py +0 -54
- tinybird/tb/modules/open.py +10 -5
- tinybird/tb/modules/project.py +14 -5
- tinybird/tb/modules/shell.py +15 -7
- tinybird/tb/modules/sink.py +3 -1
- tinybird/tb/modules/telemetry.py +11 -3
- tinybird/tb/modules/test.py +13 -9
- tinybird/tb/modules/test_common.py +13 -87
- tinybird/tb/modules/tinyunit/tinyunit.py +0 -14
- tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -6
- tinybird/tb/modules/watch.py +5 -3
- tinybird/tb_cli_modules/common.py +2 -2
- tinybird/tb_cli_modules/telemetry.py +1 -1
- tinybird/tornado_template.py +6 -7
- {tinybird-0.0.1.dev291.dist-info → tinybird-1.0.5.dist-info}/METADATA +32 -6
- tinybird-1.0.5.dist-info/RECORD +132 -0
- {tinybird-0.0.1.dev291.dist-info → tinybird-1.0.5.dist-info}/WHEEL +1 -1
- tinybird-0.0.1.dev291.dist-info/RECORD +0 -128
- {tinybird-0.0.1.dev291.dist-info → tinybird-1.0.5.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev291.dist-info → tinybird-1.0.5.dist-info}/top_level.txt +0 -0
tinybird/tb/modules/create.py
CHANGED
|
@@ -3,25 +3,22 @@ import os
|
|
|
3
3
|
import re
|
|
4
4
|
import shutil
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Any, Dict, List, Optional
|
|
6
|
+
from typing import Any, Dict, List, Optional, Set
|
|
7
7
|
from urllib.parse import urlparse
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
|
-
from tinybird.prompts import claude_rules_prompt,
|
|
13
|
-
from tinybird.tb.
|
|
12
|
+
from tinybird.prompts import claude_rules_prompt, rules_prompt
|
|
13
|
+
from tinybird.tb.modules.agent import run_agent
|
|
14
14
|
from tinybird.tb.modules.cicd import init_cicd
|
|
15
15
|
from tinybird.tb.modules.cli import cli
|
|
16
|
-
from tinybird.tb.modules.common import _generate_datafile
|
|
16
|
+
from tinybird.tb.modules.common import _generate_datafile
|
|
17
17
|
from tinybird.tb.modules.config import CLIConfig
|
|
18
18
|
from tinybird.tb.modules.datafile.fixture import persist_fixture
|
|
19
19
|
from tinybird.tb.modules.exceptions import CLICreateException
|
|
20
20
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
21
|
-
from tinybird.tb.modules.llm import LLM
|
|
22
|
-
from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
|
|
23
21
|
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
24
|
-
from tinybird.tb.modules.mock_common import create_mock_data
|
|
25
22
|
from tinybird.tb.modules.project import Project
|
|
26
23
|
|
|
27
24
|
|
|
@@ -67,13 +64,9 @@ def create(
|
|
|
67
64
|
folder_path.mkdir()
|
|
68
65
|
|
|
69
66
|
try:
|
|
70
|
-
tb_client = config.get_client(token=ctx_config.get("token"), host=ctx_config.get("host"))
|
|
71
|
-
user_token: str = ""
|
|
72
67
|
created_something = False
|
|
73
|
-
if prompt:
|
|
74
|
-
|
|
75
|
-
if not user_token:
|
|
76
|
-
raise Exception("This action requires authentication. Run 'tb login' first.")
|
|
68
|
+
if prompt and not ctx_config.get("user_token"):
|
|
69
|
+
raise Exception("This action requires authentication. Run 'tb login' first.")
|
|
77
70
|
|
|
78
71
|
if not validate_project_structure(project):
|
|
79
72
|
click.echo(FeedbackManager.highlight(message="\n» Creating new project structure..."))
|
|
@@ -99,28 +92,20 @@ def create(
|
|
|
99
92
|
|
|
100
93
|
prompt_result: List[Path] = []
|
|
101
94
|
if prompt:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if readme_path.exists():
|
|
106
|
-
click.echo(FeedbackManager.highlight(message="\n» Updating project description..."))
|
|
107
|
-
else:
|
|
108
|
-
click.echo(FeedbackManager.highlight(message="\n» Creating project description..."))
|
|
109
|
-
readme_path.touch()
|
|
110
|
-
llm = LLM(user_token=str(user_token), host=tb_client.host)
|
|
111
|
-
readme_user_prompt = prompt or ""
|
|
112
|
-
all_resources_xml = get_resources_xml(project)
|
|
113
|
-
readme_response = llm.ask(
|
|
114
|
-
system_prompt=readme_prompt(
|
|
115
|
-
readme_path.read_text(), tb_client.host, "$TB_ADMIN_TOKEN", all_resources_xml
|
|
116
|
-
),
|
|
117
|
-
prompt=readme_user_prompt,
|
|
118
|
-
feature="tb_create_readme",
|
|
95
|
+
prompt_instructions = (
|
|
96
|
+
"Create or update the Tinybird datasources, pipes, and connections required to satisfy the following request. "
|
|
97
|
+
"Do not generate mock data or append data; those steps will run later programmatically."
|
|
119
98
|
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
99
|
+
prompt_result = create_resources_from_prompt(
|
|
100
|
+
ctx_config,
|
|
101
|
+
project,
|
|
102
|
+
prompt,
|
|
103
|
+
feature="tb_create",
|
|
104
|
+
instructions=prompt_instructions,
|
|
105
|
+
)
|
|
106
|
+
result.extend(prompt_result)
|
|
107
|
+
if prompt_result:
|
|
108
|
+
created_something = True
|
|
124
109
|
|
|
125
110
|
if data or prompt:
|
|
126
111
|
click.echo(FeedbackManager.success(message="✓ Resources created!\n"))
|
|
@@ -180,24 +165,30 @@ def create(
|
|
|
180
165
|
datasource_name = datasource_path.stem
|
|
181
166
|
datasource_content = datasource_path.read_text()
|
|
182
167
|
has_json_path = "`json:" in datasource_content
|
|
183
|
-
if has_json_path:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
168
|
+
if not has_json_path:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
fixture_path = Path(folder) / "fixtures" / f"{datasource_name}.ndjson"
|
|
172
|
+
fixture_existed = fixture_path.exists()
|
|
173
|
+
fixture_prompt = (
|
|
174
|
+
f"Generate {rows} rows of representative sample data for the Tinybird datasource defined in {datasource_path}. "
|
|
175
|
+
f"Store the data in ndjson format at fixtures/{datasource_name}.ndjson."
|
|
176
|
+
)
|
|
177
|
+
if prompt.strip():
|
|
178
|
+
fixture_prompt += f"\n\nOriginal project request:\n{prompt.strip()}"
|
|
179
|
+
|
|
180
|
+
run_agent(
|
|
181
|
+
ctx_config,
|
|
182
|
+
project,
|
|
183
|
+
True,
|
|
184
|
+
prompt=fixture_prompt,
|
|
185
|
+
feature="tb_mock",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if fixture_path.exists() and not fixture_existed:
|
|
189
|
+
click.echo(FeedbackManager.info_file_created(file=f"fixtures/{datasource_name}.ndjson"))
|
|
190
|
+
click.echo(FeedbackManager.success(message="✓ Done!"))
|
|
191
|
+
created_something = True
|
|
201
192
|
|
|
202
193
|
if not created_something and not len(result) > 0:
|
|
203
194
|
click.echo(FeedbackManager.warning(message="△ No resources created\n"))
|
|
@@ -291,86 +282,39 @@ def create_project_structure(folder: str):
|
|
|
291
282
|
|
|
292
283
|
|
|
293
284
|
def create_resources_from_prompt(
|
|
294
|
-
|
|
295
|
-
user_token: str,
|
|
296
|
-
prompt: str,
|
|
285
|
+
config: Dict[str, Any],
|
|
297
286
|
project: Project,
|
|
298
|
-
|
|
287
|
+
prompt: str,
|
|
288
|
+
feature: str = "tb_create",
|
|
289
|
+
instructions: Optional[str] = None,
|
|
299
290
|
) -> List[Path]:
|
|
300
|
-
|
|
301
|
-
datasource_paths = [Path(ds_file) for ds_file in project.get_datasource_files()]
|
|
302
|
-
pipes_paths = [Path(pipe_file) for pipe_file in project.get_pipe_files()]
|
|
303
|
-
connections_paths = [Path(conn_file) for conn_file in project.get_connection_files()]
|
|
304
|
-
resources_xml = "\n".join(
|
|
305
|
-
[
|
|
306
|
-
f"<resource><type>{resource_type}</type><name>{resource_name}</name><content>{resource_content}</content></resource>"
|
|
307
|
-
for resource_type, resource_name, resource_content in [
|
|
308
|
-
("datasource", ds.stem, ds.read_text()) for ds in datasource_paths
|
|
309
|
-
]
|
|
310
|
-
+ [
|
|
311
|
-
(
|
|
312
|
-
"pipe",
|
|
313
|
-
pipe.stem,
|
|
314
|
-
pipe.read_text(),
|
|
315
|
-
)
|
|
316
|
-
for pipe in pipes_paths
|
|
317
|
-
]
|
|
318
|
-
+ [
|
|
319
|
-
(
|
|
320
|
-
"connection",
|
|
321
|
-
conn.stem,
|
|
322
|
-
conn.read_text(),
|
|
323
|
-
)
|
|
324
|
-
for conn in connections_paths
|
|
325
|
-
]
|
|
326
|
-
]
|
|
327
|
-
)
|
|
328
|
-
llm = LLM(user_token=user_token, host=tb_client.host)
|
|
329
|
-
prompt_result = llm.ask(system_prompt=create_prompt(resources_xml), prompt=prompt, feature=feature)
|
|
330
|
-
prompt_result = extract_xml(prompt_result, "response")
|
|
331
|
-
resources = parse_xml(prompt_result, "resource")
|
|
332
|
-
datasources = []
|
|
333
|
-
pipes = []
|
|
334
|
-
connections = []
|
|
335
|
-
for resource_xml in resources:
|
|
336
|
-
resource_type = extract_xml(resource_xml, "type")
|
|
337
|
-
name = extract_xml(resource_xml, "name")
|
|
338
|
-
content = extract_xml(resource_xml, "content")
|
|
339
|
-
resource = {
|
|
340
|
-
"name": name,
|
|
341
|
-
"content": content,
|
|
342
|
-
}
|
|
343
|
-
if resource_type.lower() == "datasource":
|
|
344
|
-
datasources.append(resource)
|
|
345
|
-
elif resource_type.lower() == "pipe":
|
|
346
|
-
pipes.append(resource)
|
|
347
|
-
elif resource_type.lower() == "connection":
|
|
348
|
-
connections.append(resource)
|
|
349
|
-
|
|
350
|
-
for ds in datasources:
|
|
351
|
-
content = ds["content"].replace("```", "")
|
|
352
|
-
filename = f"{ds['name']}.datasource"
|
|
353
|
-
ds_file = generate_datafile(
|
|
354
|
-
content,
|
|
355
|
-
filename=filename,
|
|
356
|
-
data=None,
|
|
357
|
-
_format="ndjson",
|
|
358
|
-
force=True,
|
|
359
|
-
folder=project.folder,
|
|
360
|
-
)
|
|
361
|
-
result.append(ds_file)
|
|
362
|
-
for pipe in pipes:
|
|
363
|
-
content = pipe["content"].replace("```", "")
|
|
364
|
-
pipe_file = generate_pipe_file(pipe["name"], content, project.folder)
|
|
365
|
-
result.append(pipe_file)
|
|
291
|
+
"""Run the agent in prompt mode and report newly created project resources."""
|
|
366
292
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
293
|
+
agent_prompt = prompt.strip()
|
|
294
|
+
if instructions:
|
|
295
|
+
instructions = instructions.strip()
|
|
296
|
+
if agent_prompt:
|
|
297
|
+
agent_prompt = f"{instructions}\n\n{agent_prompt}"
|
|
298
|
+
else:
|
|
299
|
+
agent_prompt = instructions
|
|
300
|
+
|
|
301
|
+
if not agent_prompt:
|
|
302
|
+
return []
|
|
303
|
+
|
|
304
|
+
resources_before = _collect_project_resource_paths(project)
|
|
305
|
+
run_agent(config, project, True, prompt=agent_prompt, feature=feature)
|
|
306
|
+
resources_after = _collect_project_resource_paths(project)
|
|
307
|
+
|
|
308
|
+
created_resources = [Path(path) for path in sorted(resources_after - resources_before)]
|
|
309
|
+
return created_resources
|
|
372
310
|
|
|
373
|
-
|
|
311
|
+
|
|
312
|
+
def _collect_project_resource_paths(project: Project) -> Set[Path]:
|
|
313
|
+
resources: Set[Path] = set()
|
|
314
|
+
resources.update(Path(path) for path in project.get_datasource_files())
|
|
315
|
+
resources.update(Path(path) for path in project.get_pipe_files())
|
|
316
|
+
resources.update(Path(path) for path in project.get_connection_files())
|
|
317
|
+
return resources
|
|
374
318
|
|
|
375
319
|
|
|
376
320
|
def init_git(folder: str):
|
|
@@ -513,28 +457,6 @@ def save_context(prompt: str, feedback: str):
|
|
|
513
457
|
context_file.write_text(f"- {prompt}\n{feedback}")
|
|
514
458
|
|
|
515
459
|
|
|
516
|
-
def get_resources_xml(project: Project) -> str:
|
|
517
|
-
datasource_paths = [Path(f) for f in project.get_datasource_files()]
|
|
518
|
-
pipes_paths = [Path(f) for f in project.get_pipe_files()]
|
|
519
|
-
resources_xml = "\n".join(
|
|
520
|
-
[
|
|
521
|
-
f"<resource><type>{resource_type}</type><name>{resource_name}</name><content>{resource_content}</content></resource>"
|
|
522
|
-
for resource_type, resource_name, resource_content in [
|
|
523
|
-
("datasource", ds.stem, ds.read_text()) for ds in datasource_paths
|
|
524
|
-
]
|
|
525
|
-
+ [
|
|
526
|
-
(
|
|
527
|
-
"pipe",
|
|
528
|
-
pipe.stem,
|
|
529
|
-
pipe.read_text(),
|
|
530
|
-
)
|
|
531
|
-
for pipe in pipes_paths
|
|
532
|
-
]
|
|
533
|
-
]
|
|
534
|
-
)
|
|
535
|
-
return resources_xml
|
|
536
|
-
|
|
537
|
-
|
|
538
460
|
def create_resources_from_data(
|
|
539
461
|
data: str,
|
|
540
462
|
project: Project,
|
|
@@ -602,6 +524,9 @@ def generate_kafka_connection_with_secrets(
|
|
|
602
524
|
tb_secret_secret: Optional[str],
|
|
603
525
|
security_protocol: str,
|
|
604
526
|
sasl_mechanism: str,
|
|
527
|
+
ssl_ca_pem: Optional[str],
|
|
528
|
+
tb_secret_ssl_ca_pem: Optional[str],
|
|
529
|
+
schema_registry_url: Optional[str],
|
|
605
530
|
folder: str,
|
|
606
531
|
) -> Path:
|
|
607
532
|
kafka_bootstrap_servers = (
|
|
@@ -609,6 +534,7 @@ def generate_kafka_connection_with_secrets(
|
|
|
609
534
|
)
|
|
610
535
|
kafka_key = inject_tb_secret(tb_secret_key) if tb_secret_key else key
|
|
611
536
|
kafka_secret = inject_tb_secret(tb_secret_secret) if tb_secret_secret else secret
|
|
537
|
+
kafka_ssl_ca_pem = inject_tb_secret(tb_secret_ssl_ca_pem) if tb_secret_ssl_ca_pem else ssl_ca_pem
|
|
612
538
|
content = f"""TYPE kafka
|
|
613
539
|
KAFKA_BOOTSTRAP_SERVERS {kafka_bootstrap_servers}
|
|
614
540
|
KAFKA_SECURITY_PROTOCOL {security_protocol or "SASL_SSL"}
|
|
@@ -616,6 +542,13 @@ KAFKA_SASL_MECHANISM {sasl_mechanism or "PLAIN"}
|
|
|
616
542
|
KAFKA_KEY {kafka_key}
|
|
617
543
|
KAFKA_SECRET {kafka_secret}
|
|
618
544
|
"""
|
|
545
|
+
if schema_registry_url:
|
|
546
|
+
content += f"""KAFKA_SCHEMA_REGISTRY_URL {schema_registry_url}\n"""
|
|
547
|
+
if kafka_ssl_ca_pem:
|
|
548
|
+
content += f"""KAFKA_SSL_CA_PEM >\n {kafka_ssl_ca_pem}\n"""
|
|
549
|
+
content += """# Learn more at https://www.tinybird.co/docs/forward/get-data-in/connectors/kafka#kafka-connection-settings
|
|
550
|
+
"""
|
|
551
|
+
|
|
619
552
|
return generate_connection_file(name, content, folder, skip_feedback=True)
|
|
620
553
|
|
|
621
554
|
|
|
@@ -128,41 +128,33 @@ def folder_build(
|
|
|
128
128
|
filename = to_run[name]["filename"]
|
|
129
129
|
filename = filename.replace(f"{folder}/", "")
|
|
130
130
|
click.echo(FeedbackManager.info(message=f"✓ {filename}"))
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
name=name if to_run[name]["version"] is None else f"{name}__v{to_run[name]['version']}"
|
|
136
|
-
)
|
|
131
|
+
elif raise_on_exists:
|
|
132
|
+
raise AlreadyExistsException(
|
|
133
|
+
FeedbackManager.warning_name_already_exists(
|
|
134
|
+
name=name if to_run[name]["version"] is None else f"{name}__v{to_run[name]['version']}"
|
|
137
135
|
)
|
|
136
|
+
)
|
|
137
|
+
elif name_matches_existing_resource(resource, name, tb_client):
|
|
138
|
+
if resource == "pipes":
|
|
139
|
+
click.echo(FeedbackManager.error_pipe_cannot_be_pushed(name=name))
|
|
138
140
|
else:
|
|
139
|
-
|
|
140
|
-
if resource == "pipes":
|
|
141
|
-
click.echo(FeedbackManager.error_pipe_cannot_be_pushed(name=name))
|
|
142
|
-
else:
|
|
143
|
-
click.echo(FeedbackManager.error_datasource_cannot_be_pushed(name=name))
|
|
144
|
-
else:
|
|
145
|
-
click.echo(
|
|
146
|
-
FeedbackManager.warning_name_already_exists(
|
|
147
|
-
name=(
|
|
148
|
-
name
|
|
149
|
-
if to_run[name]["version"] is None
|
|
150
|
-
else f"{name}__v{to_run[name]['version']}"
|
|
151
|
-
)
|
|
152
|
-
)
|
|
153
|
-
)
|
|
154
|
-
else:
|
|
155
|
-
if should_push_file(name, remote_resource_names, force, run_tests):
|
|
156
|
-
extension = "pipe" if resource == "pipes" else "datasource"
|
|
157
|
-
click.echo(FeedbackManager.info_building_resource(name=f"{name}.{extension}", version=""))
|
|
141
|
+
click.echo(FeedbackManager.error_datasource_cannot_be_pushed(name=name))
|
|
158
142
|
else:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
143
|
+
click.echo(
|
|
144
|
+
FeedbackManager.warning_name_already_exists(
|
|
145
|
+
name=(name if to_run[name]["version"] is None else f"{name}__v{to_run[name]['version']}")
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
elif should_push_file(name, remote_resource_names, force, run_tests):
|
|
149
|
+
extension = "pipe" if resource == "pipes" else "datasource"
|
|
150
|
+
click.echo(FeedbackManager.info_building_resource(name=f"{name}.{extension}", version=""))
|
|
151
|
+
elif name_matches_existing_resource(resource, name, tb_client):
|
|
152
|
+
if resource == "pipes":
|
|
153
|
+
click.echo(FeedbackManager.warning_pipe_cannot_be_pushed(name=name))
|
|
154
|
+
else:
|
|
155
|
+
click.echo(FeedbackManager.warning_datasource_cannot_be_pushed(name=name))
|
|
156
|
+
else:
|
|
157
|
+
click.echo(FeedbackManager.warning_dry_name_already_exists(name=name))
|
|
166
158
|
|
|
167
159
|
def push_files(
|
|
168
160
|
dependency_graph: GraphDependencies,
|
|
@@ -932,11 +924,8 @@ def process_file(
|
|
|
932
924
|
raise click.ClickException(FeedbackManager.error_missing_table_arn(datasource=datasource["name"]))
|
|
933
925
|
if not params.get("import_export_bucket", None):
|
|
934
926
|
raise click.ClickException(FeedbackManager.error_missing_export_bucket(datasource=datasource["name"]))
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
raise click.ClickException(
|
|
938
|
-
FeedbackManager.error_missing_external_datasource(datasource=datasource["name"])
|
|
939
|
-
)
|
|
927
|
+
elif not params.get("import_external_datasource", None):
|
|
928
|
+
raise click.ClickException(FeedbackManager.error_missing_external_datasource(datasource=datasource["name"]))
|
|
940
929
|
|
|
941
930
|
return params
|
|
942
931
|
|
|
@@ -1059,7 +1048,7 @@ def process_file(
|
|
|
1059
1048
|
#
|
|
1060
1049
|
# Note: any unknown import_ parameter is leaved as is.
|
|
1061
1050
|
for key in ImportReplacements.get_datafile_parameter_keys():
|
|
1062
|
-
replacement, default_value = ImportReplacements.get_api_param_for_datafile_param(
|
|
1051
|
+
replacement, default_value = ImportReplacements.get_api_param_for_datafile_param(key)
|
|
1063
1052
|
if not replacement:
|
|
1064
1053
|
continue # We should not reach this never, but just in case...
|
|
1065
1054
|
|
|
@@ -301,32 +301,28 @@ def new_ds(
|
|
|
301
301
|
if alter_response and make_changes:
|
|
302
302
|
# alter operation finished
|
|
303
303
|
pass
|
|
304
|
+
elif (
|
|
305
|
+
os.getenv("TB_I_KNOW_WHAT_I_AM_DOING")
|
|
306
|
+
and click.prompt(FeedbackManager.info_ask_for_datasource_confirmation()) == ds_name
|
|
307
|
+
): # TODO move to CLI
|
|
308
|
+
try:
|
|
309
|
+
client.datasource_delete(ds_name)
|
|
310
|
+
click.echo(FeedbackManager.success_delete_datasource(datasource=ds_name))
|
|
311
|
+
except Exception:
|
|
312
|
+
raise click.ClickException(FeedbackManager.error_removing_datasource(datasource=ds_name))
|
|
313
|
+
return
|
|
314
|
+
elif alter_error_message:
|
|
315
|
+
raise click.ClickException(
|
|
316
|
+
FeedbackManager.error_datasource_already_exists_and_alter_failed(
|
|
317
|
+
datasource=ds_name, alter_error_message=alter_error_message
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
elif promote_error_message:
|
|
321
|
+
raise click.ClickException(
|
|
322
|
+
FeedbackManager.error_promoting_datasource(datasource=ds_name, error=promote_error_message)
|
|
323
|
+
)
|
|
304
324
|
else:
|
|
305
|
-
|
|
306
|
-
# removed and all the references needs to be updated
|
|
307
|
-
if (
|
|
308
|
-
os.getenv("TB_I_KNOW_WHAT_I_AM_DOING")
|
|
309
|
-
and click.prompt(FeedbackManager.info_ask_for_datasource_confirmation()) == ds_name
|
|
310
|
-
): # TODO move to CLI
|
|
311
|
-
try:
|
|
312
|
-
client.datasource_delete(ds_name)
|
|
313
|
-
click.echo(FeedbackManager.success_delete_datasource(datasource=ds_name))
|
|
314
|
-
except Exception:
|
|
315
|
-
raise click.ClickException(FeedbackManager.error_removing_datasource(datasource=ds_name))
|
|
316
|
-
return
|
|
317
|
-
else:
|
|
318
|
-
if alter_error_message:
|
|
319
|
-
raise click.ClickException(
|
|
320
|
-
FeedbackManager.error_datasource_already_exists_and_alter_failed(
|
|
321
|
-
datasource=ds_name, alter_error_message=alter_error_message
|
|
322
|
-
)
|
|
323
|
-
)
|
|
324
|
-
if promote_error_message:
|
|
325
|
-
raise click.ClickException(
|
|
326
|
-
FeedbackManager.error_promoting_datasource(datasource=ds_name, error=promote_error_message)
|
|
327
|
-
)
|
|
328
|
-
else:
|
|
329
|
-
click.echo(FeedbackManager.warning_datasource_already_exists(datasource=ds_name))
|
|
325
|
+
click.echo(FeedbackManager.warning_datasource_already_exists(datasource=ds_name))
|
|
330
326
|
|
|
331
327
|
|
|
332
328
|
def share_and_unshare_datasource(
|
|
@@ -168,7 +168,7 @@ def diff_command(
|
|
|
168
168
|
sys.stdout.writelines(diff_lines)
|
|
169
169
|
click.echo("")
|
|
170
170
|
|
|
171
|
-
for rfilename
|
|
171
|
+
for rfilename in local_resources.keys():
|
|
172
172
|
if rfilename not in changed:
|
|
173
173
|
for resource in remote_datasources + remote_pipes:
|
|
174
174
|
properties = get_name_version(resource["name"])
|
|
@@ -25,11 +25,25 @@ from tinybird.tb.modules.datafile.format_datasource import format_engine
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def format_node_sql(
|
|
28
|
-
file_parts: List[str],
|
|
28
|
+
file_parts: List[str],
|
|
29
|
+
node: Dict[str, Any],
|
|
30
|
+
line_length: Optional[int] = None,
|
|
31
|
+
lower_keywords: bool = False,
|
|
32
|
+
resource_name: Optional[str] = None,
|
|
33
|
+
resource_source: Optional[str] = None,
|
|
29
34
|
) -> List[str]:
|
|
30
35
|
file_parts.append("SQL >")
|
|
31
36
|
file_parts.append(DATAFILE_NEW_LINE)
|
|
32
|
-
file_parts.append(
|
|
37
|
+
file_parts.append(
|
|
38
|
+
format_sql(
|
|
39
|
+
node["sql"],
|
|
40
|
+
DATAFILE_INDENT,
|
|
41
|
+
line_length=line_length,
|
|
42
|
+
lower_keywords=lower_keywords,
|
|
43
|
+
resource_name=resource_name,
|
|
44
|
+
resource_source=resource_source,
|
|
45
|
+
)
|
|
46
|
+
)
|
|
33
47
|
file_parts.append(DATAFILE_NEW_LINE)
|
|
34
48
|
file_parts.append(DATAFILE_NEW_LINE)
|
|
35
49
|
return file_parts
|
|
@@ -93,8 +107,21 @@ def format_pipe_include(file_parts: List[str], node: Dict[str, Any], includes: D
|
|
|
93
107
|
return file_parts
|
|
94
108
|
|
|
95
109
|
|
|
96
|
-
def format_sql(
|
|
97
|
-
sql
|
|
110
|
+
def format_sql(
|
|
111
|
+
sql: str,
|
|
112
|
+
DATAFILE_INDENT: str,
|
|
113
|
+
line_length: Optional[int] = None,
|
|
114
|
+
lower_keywords: bool = False,
|
|
115
|
+
resource_name: Optional[str] = None,
|
|
116
|
+
resource_source: Optional[str] = None,
|
|
117
|
+
) -> str:
|
|
118
|
+
sql = format_sql_template(
|
|
119
|
+
sql.strip(),
|
|
120
|
+
line_length=line_length,
|
|
121
|
+
lower_keywords=lower_keywords,
|
|
122
|
+
resource_name=resource_name,
|
|
123
|
+
resource_source=resource_source,
|
|
124
|
+
)
|
|
98
125
|
return "\n".join([f"{DATAFILE_INDENT}{part}" for part in sql.split("\n") if len(part.strip())])
|
|
99
126
|
|
|
100
127
|
|
|
@@ -105,6 +132,8 @@ def format_node(
|
|
|
105
132
|
line_length: Optional[int] = None,
|
|
106
133
|
unroll_includes: bool = False,
|
|
107
134
|
lower_keywords: bool = False,
|
|
135
|
+
resource_name: Optional[str] = None,
|
|
136
|
+
resource_source: Optional[str] = None,
|
|
108
137
|
) -> None:
|
|
109
138
|
if not unroll_includes:
|
|
110
139
|
format_pipe_include(file_parts, node, includes)
|
|
@@ -119,7 +148,14 @@ def format_node(
|
|
|
119
148
|
|
|
120
149
|
Doc = namedtuple("Doc", ["description"])
|
|
121
150
|
format_description(file_parts, Doc(node.get("description", "")))
|
|
122
|
-
format_node_sql(
|
|
151
|
+
format_node_sql(
|
|
152
|
+
file_parts,
|
|
153
|
+
node,
|
|
154
|
+
line_length=line_length,
|
|
155
|
+
lower_keywords=lower_keywords,
|
|
156
|
+
resource_name=resource_name,
|
|
157
|
+
resource_source=resource_source,
|
|
158
|
+
)
|
|
123
159
|
format_node_type(file_parts, node)
|
|
124
160
|
|
|
125
161
|
|
|
@@ -132,6 +168,7 @@ def format_pipe(
|
|
|
132
168
|
for_deploy_diff: bool = False,
|
|
133
169
|
skip_eval: bool = False,
|
|
134
170
|
content: Optional[str] = None,
|
|
171
|
+
resource_source: Optional[str] = None,
|
|
135
172
|
) -> str:
|
|
136
173
|
if datafile:
|
|
137
174
|
doc = datafile
|
|
@@ -162,7 +199,7 @@ def format_pipe(
|
|
|
162
199
|
if "." in include_file
|
|
163
200
|
else eval_var(include_file)
|
|
164
201
|
)
|
|
165
|
-
included_pipe = parse_pipe(include_file, skip_eval=skip_eval).datafile
|
|
202
|
+
included_pipe = parse_pipe(str(include_file), skip_eval=skip_eval).datafile
|
|
166
203
|
pipe_nodes = doc.nodes.copy()
|
|
167
204
|
for included_node in included_pipe.nodes.copy():
|
|
168
205
|
unrolled_included_node = next(
|
|
@@ -178,10 +215,12 @@ def format_pipe(
|
|
|
178
215
|
line_length=line_length,
|
|
179
216
|
unroll_includes=unroll_includes,
|
|
180
217
|
lower_keywords=bool(for_deploy_diff),
|
|
218
|
+
resource_name=filename,
|
|
219
|
+
resource_source=resource_source,
|
|
181
220
|
)
|
|
182
221
|
|
|
183
222
|
if not unroll_includes:
|
|
184
|
-
for k
|
|
223
|
+
for k in doc.includes.keys():
|
|
185
224
|
if ".incl" not in k:
|
|
186
225
|
continue
|
|
187
226
|
file_parts.append(f"INCLUDE {k}")
|