tinybird 0.0.1.dev42__py3-none-any.whl → 0.0.1.dev44__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 +1 -1
- tinybird/connectors.py +3 -3
- tinybird/feedback_manager.py +1 -1
- tinybird/prompts.py +31 -3
- tinybird/sql.py +1 -1
- tinybird/sql_template_fmt.py +1 -1
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +1 -1
- tinybird/tb/modules/build.py +39 -21
- tinybird/tb/modules/cicd.py +2 -2
- tinybird/tb/modules/cli.py +8 -61
- tinybird/tb/modules/common.py +2 -1
- tinybird/tb/modules/copy.py +96 -5
- tinybird/tb/modules/create.py +105 -46
- tinybird/tb/modules/datafile/build.py +64 -247
- tinybird/tb/modules/datasource.py +1 -1
- tinybird/tb/modules/deployment.py +86 -61
- tinybird/tb/modules/endpoint.py +90 -3
- tinybird/tb/modules/llm_utils.py +2 -2
- tinybird/tb/modules/materialization.py +146 -0
- tinybird/tb/modules/mock.py +56 -16
- tinybird/tb/modules/pipe.py +2 -411
- tinybird/tb/modules/project.py +31 -1
- tinybird/tb/modules/test.py +72 -37
- tinybird/tb/modules/update.py +1 -1
- tinybird/tb/modules/watch.py +54 -5
- tinybird/tb_cli_modules/common.py +1 -1
- tinybird/tornado_template.py +2 -2
- {tinybird-0.0.1.dev42.dist-info → tinybird-0.0.1.dev44.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev42.dist-info → tinybird-0.0.1.dev44.dist-info}/RECORD +33 -33
- tinybird/tb/modules/build_client.py +0 -199
- {tinybird-0.0.1.dev42.dist-info → tinybird-0.0.1.dev44.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev42.dist-info → tinybird-0.0.1.dev44.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev42.dist-info → tinybird-0.0.1.dev44.dist-info}/top_level.txt +0 -0
tinybird/client.py
CHANGED
|
@@ -72,7 +72,7 @@ def parse_error_response(response: Response) -> str:
|
|
|
72
72
|
if content.get("error", None):
|
|
73
73
|
error = content["error"]
|
|
74
74
|
if content.get("errors", None):
|
|
75
|
-
error += f
|
|
75
|
+
error += f" -> errors: {content.get('errors')}"
|
|
76
76
|
else:
|
|
77
77
|
error = json.dumps(response, indent=4)
|
|
78
78
|
return error
|
tinybird/connectors.py
CHANGED
|
@@ -246,7 +246,7 @@ class BigQuery(Connector):
|
|
|
246
246
|
uri='{self.gcs.gs_url()}{destination}*.csv',
|
|
247
247
|
format='CSV',
|
|
248
248
|
overwrite=true,
|
|
249
|
-
header={
|
|
249
|
+
header={"true" if with_headers else "false"},
|
|
250
250
|
field_delimiter=',') AS
|
|
251
251
|
{sql}
|
|
252
252
|
"""
|
|
@@ -319,7 +319,7 @@ class Snowflake(Connector):
|
|
|
319
319
|
|
|
320
320
|
def create_stage(self):
|
|
321
321
|
sql = f"""
|
|
322
|
-
create stage "{self.options[
|
|
322
|
+
create stage "{self.options["schema"]}".{self.stage()}
|
|
323
323
|
url='{self.gcs.gcs_url()}'
|
|
324
324
|
storage_integration = {self.storage_integration()};
|
|
325
325
|
"""
|
|
@@ -337,7 +337,7 @@ class Snowflake(Connector):
|
|
|
337
337
|
from ({sql})
|
|
338
338
|
overwrite = true
|
|
339
339
|
file_format = (TYPE=CSV COMPRESSION=NONE ESCAPE_UNENCLOSED_FIELD=NONE FIELD_DELIMITER='|' FIELD_OPTIONALLY_ENCLOSED_BY='"' null_if=())
|
|
340
|
-
header = {"true" if with_headers else "false"
|
|
340
|
+
header = {"true" if with_headers else "false"}
|
|
341
341
|
max_file_size = 2500000000;
|
|
342
342
|
"""
|
|
343
343
|
self.execute(sql)
|
tinybird/feedback_manager.py
CHANGED
|
@@ -951,7 +951,7 @@ Ready? """
|
|
|
951
951
|
)
|
|
952
952
|
success_datasource_alter = success_message("** The Data Source has been correctly updated.")
|
|
953
953
|
success_datasource_kafka_connected = success_message(
|
|
954
|
-
"** Data Source '{id}' created\n
|
|
954
|
+
"** Data Source '{id}' created\n** Kafka streaming connection configured successfully!"
|
|
955
955
|
)
|
|
956
956
|
success_datasource_shared = success_message(
|
|
957
957
|
"** The Data Source {datasource} has been correctly shared with {workspace}"
|
tinybird/prompts.py
CHANGED
|
@@ -373,6 +373,7 @@ You are a Tinybird expert. You will be given a pipe containing different nodes w
|
|
|
373
373
|
- The parameter within Tinybird templating syntax looks like this one {{String(my_param_name, default_value)}}.
|
|
374
374
|
- If there are no parameters, you can omit parameters and generate a single test.
|
|
375
375
|
- The format of the parameters is the following: ?param1=value1¶m2=value2¶m3=value3
|
|
376
|
+
- If some parameters are provided by the user and you need to use them, preserve in the same format as they were provided, like case sensitive.
|
|
376
377
|
</instructions>
|
|
377
378
|
|
|
378
379
|
This is an example of a test with parameters:
|
|
@@ -396,9 +397,19 @@ Follow the instructions and generate the following response with no additional t
|
|
|
396
397
|
"""
|
|
397
398
|
|
|
398
399
|
|
|
399
|
-
def create_prompt(existing_resources: str) -> str:
|
|
400
|
+
def create_prompt(existing_resources: str, feedback: str = "", history: str = "") -> str:
|
|
401
|
+
feedback_history = ""
|
|
402
|
+
if feedback and history:
|
|
403
|
+
feedback_history = f"""In case the <feedback> and <history> tags are present and not empty,
|
|
404
|
+
it means there was a previous attempt to generate the resources and the user provided feedback and history about previous responses.
|
|
405
|
+
Use the following feedback and history to regenerate the response:
|
|
406
|
+
Feedback to improve the response:
|
|
407
|
+
{feedback}
|
|
408
|
+
History of previous results:
|
|
409
|
+
{history}"""
|
|
410
|
+
|
|
400
411
|
return """
|
|
401
|
-
You are a Tinybird expert. You will be given a prompt to generate Tinybird resources: datasources and/or pipes.
|
|
412
|
+
You are a Tinybird expert. You will be given a prompt to generate new or update existing Tinybird resources: datasources and/or pipes.
|
|
402
413
|
<existing_resources>{existing_resources}</existing_resources>
|
|
403
414
|
{datasource_instructions}
|
|
404
415
|
{pipe_instructions}
|
|
@@ -407,6 +418,9 @@ You are a Tinybird expert. You will be given a prompt to generate Tinybird resou
|
|
|
407
418
|
{pipe_example}
|
|
408
419
|
{copy_pipe_instructions}
|
|
409
420
|
{materialized_pipe_instructions}
|
|
421
|
+
|
|
422
|
+
{feedback_history}
|
|
423
|
+
|
|
410
424
|
Use the following format to generate the response and do not wrap it in any other text, including the <response> tag.
|
|
411
425
|
<response>
|
|
412
426
|
<resource>
|
|
@@ -425,10 +439,21 @@ Use the following format to generate the response and do not wrap it in any othe
|
|
|
425
439
|
pipe_example=pipe_example,
|
|
426
440
|
copy_pipe_instructions=copy_pipe_instructions,
|
|
427
441
|
materialized_pipe_instructions=materialized_pipe_instructions,
|
|
442
|
+
feedback_history=feedback_history,
|
|
428
443
|
)
|
|
429
444
|
|
|
430
445
|
|
|
431
|
-
def mock_prompt(rows: int) -> str:
|
|
446
|
+
def mock_prompt(rows: int, feedback: str = "", history: str = "") -> str:
|
|
447
|
+
feedback_history = ""
|
|
448
|
+
if feedback and history:
|
|
449
|
+
feedback_history = f"""In case the <feedback> and <history> tags are present and not empty,
|
|
450
|
+
it means there was a previous attempt to generate the resources and the user provided feedback and history about previous responses.
|
|
451
|
+
Use the following feedback and history to regenerate the response:
|
|
452
|
+
Feedback to improve the response:
|
|
453
|
+
{feedback}
|
|
454
|
+
History of previous results:
|
|
455
|
+
{history}"""
|
|
456
|
+
|
|
432
457
|
return f"""
|
|
433
458
|
Given the schema for a Tinybird datasource, return a can you create a clickhouse sql query to generate some random data that matches that schema.
|
|
434
459
|
|
|
@@ -569,6 +594,9 @@ Follow the instructions and generate the following response with no additional t
|
|
|
569
594
|
<response>
|
|
570
595
|
<sql>[raw sql query here]</sql>
|
|
571
596
|
</response>
|
|
597
|
+
|
|
598
|
+
{feedback_history}
|
|
599
|
+
|
|
572
600
|
"""
|
|
573
601
|
|
|
574
602
|
|
tinybird/sql.py
CHANGED
|
@@ -241,7 +241,7 @@ def format_parse_error(
|
|
|
241
241
|
message += f" found at position {adjusted_position - len(keyword)}"
|
|
242
242
|
else:
|
|
243
243
|
message += (
|
|
244
|
-
f" found {repr(table_structure[i]) if len(table_structure)>i else 'EOF'} at position {adjusted_position}"
|
|
244
|
+
f" found {repr(table_structure[i]) if len(table_structure) > i else 'EOF'} at position {adjusted_position}"
|
|
245
245
|
)
|
|
246
246
|
return message
|
|
247
247
|
|
tinybird/sql_template_fmt.py
CHANGED
|
@@ -100,7 +100,7 @@ def _format_jinja_node(self, node: Node, max_length: int) -> bool:
|
|
|
100
100
|
parts = tag.code.split("\n")
|
|
101
101
|
prefix = INDENT * (node.depth[0] + node.depth[1])
|
|
102
102
|
if len(parts) > 1:
|
|
103
|
-
tag.code = "\n".join([f
|
|
103
|
+
tag.code = "\n".join([f"{prefix if i != 0 else ''}{part}" for i, part in enumerate(parts)])
|
|
104
104
|
|
|
105
105
|
node.value = str(tag)
|
|
106
106
|
|
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.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '0.0.1.dev44'
|
|
8
|
+
__revision__ = '637cb4c'
|
tinybird/tb/cli.py
CHANGED
|
@@ -6,7 +6,6 @@ if sys.platform == "win32":
|
|
|
6
6
|
|
|
7
7
|
import tinybird.tb.modules.auth
|
|
8
8
|
import tinybird.tb.modules.build
|
|
9
|
-
import tinybird.tb.modules.build_client
|
|
10
9
|
import tinybird.tb.modules.cli
|
|
11
10
|
import tinybird.tb.modules.common
|
|
12
11
|
import tinybird.tb.modules.copy
|
|
@@ -18,6 +17,7 @@ import tinybird.tb.modules.fmt
|
|
|
18
17
|
import tinybird.tb.modules.job
|
|
19
18
|
import tinybird.tb.modules.local
|
|
20
19
|
import tinybird.tb.modules.login
|
|
20
|
+
import tinybird.tb.modules.materialization
|
|
21
21
|
import tinybird.tb.modules.mock
|
|
22
22
|
import tinybird.tb.modules.pipe
|
|
23
23
|
import tinybird.tb.modules.tag
|
tinybird/tb/modules/build.py
CHANGED
|
@@ -12,6 +12,7 @@ import requests
|
|
|
12
12
|
from tinybird.client import TinyB
|
|
13
13
|
from tinybird.tb.modules.cli import cli
|
|
14
14
|
from tinybird.tb.modules.common import push_data
|
|
15
|
+
from tinybird.tb.modules.datafile.build import folder_build
|
|
15
16
|
from tinybird.tb.modules.datafile.fixture import build_fixture_name, get_fixture_dir
|
|
16
17
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
17
18
|
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
@@ -31,17 +32,17 @@ def build(ctx: click.Context, watch: bool) -> None:
|
|
|
31
32
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
32
33
|
tb_client = asyncio.run(get_tinybird_local_client(str(project.path)))
|
|
33
34
|
click.echo(FeedbackManager.highlight(message="\n» Building project..."))
|
|
34
|
-
|
|
35
35
|
time_start = time.time()
|
|
36
36
|
|
|
37
|
-
def process(file_changed: Optional[str] = None) -> None:
|
|
37
|
+
def process(file_changed: Optional[str] = None, diff: Optional[str] = None) -> None:
|
|
38
38
|
if file_changed and file_changed.endswith(".ndjson"):
|
|
39
39
|
rebuild_fixture(project, tb_client, file_changed)
|
|
40
40
|
else:
|
|
41
|
-
build_project(project, tb_client)
|
|
41
|
+
build_project(project, tb_client, file_changed)
|
|
42
42
|
try:
|
|
43
43
|
if file_changed:
|
|
44
|
-
|
|
44
|
+
asyncio.run(folder_build(project, filenames=[file_changed]))
|
|
45
|
+
build_and_print_resource(tb_client, file_changed, diff)
|
|
45
46
|
except Exception:
|
|
46
47
|
pass
|
|
47
48
|
|
|
@@ -62,7 +63,7 @@ def build(ctx: click.Context, watch: bool) -> None:
|
|
|
62
63
|
shell.run()
|
|
63
64
|
|
|
64
65
|
|
|
65
|
-
def build_project(project: Project, tb_client: TinyB) -> None:
|
|
66
|
+
def build_project(project: Project, tb_client: TinyB, file_changed: Optional[str] = None) -> None:
|
|
66
67
|
MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
|
|
67
68
|
DATAFILE_TYPE_TO_CONTENT_TYPE = {
|
|
68
69
|
".datasource": "text/plain",
|
|
@@ -79,6 +80,9 @@ def build_project(project: Project, tb_client: TinyB) -> None:
|
|
|
79
80
|
project_path = project.path
|
|
80
81
|
project_files = project.get_project_files()
|
|
81
82
|
|
|
83
|
+
if not project_files:
|
|
84
|
+
return
|
|
85
|
+
|
|
82
86
|
for file_path in project_files:
|
|
83
87
|
relative_path = str(Path(file_path).relative_to(project_path))
|
|
84
88
|
fd = open(file_path, "rb")
|
|
@@ -101,17 +105,23 @@ def build_project(project: Project, tb_client: TinyB) -> None:
|
|
|
101
105
|
if build_result == "success":
|
|
102
106
|
datasources = result.get("datasources", [])
|
|
103
107
|
pipes = result.get("pipes", [])
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
if not file_changed:
|
|
109
|
+
for ds in datasources:
|
|
110
|
+
ds_path_str: Optional[str] = next(
|
|
111
|
+
(p for p in project_files if p.endswith(ds.get("name") + ".datasource")), None
|
|
112
|
+
)
|
|
113
|
+
if ds_path_str:
|
|
114
|
+
ds_path = Path(ds_path_str)
|
|
115
|
+
ds_path_str = ds_path_str.replace(f"{project.folder}/", "")
|
|
116
|
+
click.echo(FeedbackManager.info(message=f"✓ {ds_path_str} created"))
|
|
117
|
+
for pipe in pipes:
|
|
118
|
+
pipe_name = pipe.get("name")
|
|
119
|
+
pipe_path_str: Optional[str] = next(
|
|
120
|
+
(p for p in project_files if p.endswith(pipe_name + ".pipe")), None
|
|
121
|
+
)
|
|
122
|
+
if pipe_path_str:
|
|
123
|
+
pipe_path_str = pipe_path_str.replace(f"{project.folder}/", "")
|
|
124
|
+
click.echo(FeedbackManager.info(message=f"✓ {pipe_path_str} created"))
|
|
115
125
|
|
|
116
126
|
try:
|
|
117
127
|
for filename in project_files:
|
|
@@ -185,9 +195,17 @@ def rebuild_fixture(project: Project, tb_client: TinyB, fixture: str) -> None:
|
|
|
185
195
|
click.echo(FeedbackManager.error_exception(error=e))
|
|
186
196
|
|
|
187
197
|
|
|
188
|
-
def build_and_print_resource(tb_client: TinyB, filename: str):
|
|
198
|
+
def build_and_print_resource(tb_client: TinyB, filename: str, diff: Optional[str] = None):
|
|
199
|
+
table_name = diff
|
|
189
200
|
resource_path = Path(filename)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
201
|
+
resource_name = resource_path.stem
|
|
202
|
+
|
|
203
|
+
pipeline = resource_name if filename.endswith(".pipe") else None
|
|
204
|
+
|
|
205
|
+
if not table_name:
|
|
206
|
+
table_name = resource_name
|
|
207
|
+
|
|
208
|
+
sql = f"SELECT * FROM {table_name} FORMAT JSON"
|
|
209
|
+
|
|
210
|
+
res = asyncio.run(tb_client.query(sql, pipeline=pipeline))
|
|
211
|
+
print_table_formatted(res, table_name)
|
tinybird/tb/modules/cicd.py
CHANGED
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
39
39
|
working-directory: '{{ data_project_dir }}'
|
|
40
40
|
services:
|
|
41
41
|
tinybird:
|
|
42
|
-
image: tinybirdco/tinybird-local:
|
|
42
|
+
image: tinybirdco/tinybird-local:beta
|
|
43
43
|
ports:
|
|
44
44
|
- 80:80
|
|
45
45
|
steps:
|
|
@@ -83,7 +83,7 @@ tinybird_ci_workflow:
|
|
|
83
83
|
- tb build
|
|
84
84
|
- tb test run
|
|
85
85
|
services:
|
|
86
|
-
- name: tinybirdco/tinybird-local:
|
|
86
|
+
- name: tinybirdco/tinybird-local:beta
|
|
87
87
|
alias: tinybird-local
|
|
88
88
|
"""
|
|
89
89
|
|
tinybird/tb/modules/cli.py
CHANGED
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
-
import pprint
|
|
10
9
|
from os import getcwd
|
|
11
10
|
from pathlib import Path
|
|
12
|
-
from typing import Any, Callable, Dict,
|
|
11
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
13
12
|
|
|
14
13
|
import click
|
|
15
14
|
import humanfriendly
|
|
@@ -35,13 +34,7 @@ from tinybird.tb.modules.common import (
|
|
|
35
34
|
)
|
|
36
35
|
from tinybird.tb.modules.config import CLIConfig
|
|
37
36
|
from tinybird.tb.modules.datafile.build import build_graph
|
|
38
|
-
from tinybird.tb.modules.datafile.common import Datafile, DatafileSyntaxError
|
|
39
37
|
from tinybird.tb.modules.datafile.diff import diff_command
|
|
40
|
-
from tinybird.tb.modules.datafile.exceptions import (
|
|
41
|
-
ParseException,
|
|
42
|
-
)
|
|
43
|
-
from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
|
|
44
|
-
from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
|
|
45
38
|
from tinybird.tb.modules.datafile.pull import folder_pull
|
|
46
39
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
47
40
|
from tinybird.tb.modules.llm import LLM
|
|
@@ -66,15 +59,17 @@ VERSION = f"{__cli__.__version__} (rev {__cli__.__revision__})"
|
|
|
66
59
|
@click.option("--host", help="Use custom host, defaults to TB_HOST envvar, then to https://api.tinybird.co")
|
|
67
60
|
@click.option("--show-tokens", is_flag=True, default=False, help="Enable the output of tokens")
|
|
68
61
|
@click.option("--prod/--local", is_flag=True, default=False, help="Run against production or local")
|
|
69
|
-
@click.option("--folder", type=str,
|
|
62
|
+
@click.option("--folder", type=str, help="Folder where files will be placed")
|
|
70
63
|
@click.version_option(version=VERSION)
|
|
71
64
|
@click.pass_context
|
|
72
65
|
@coro
|
|
73
|
-
async def cli(
|
|
66
|
+
async def cli(
|
|
67
|
+
ctx: Context, debug: bool, token: str, host: str, show_tokens: bool, prod: bool, folder: Optional[str]
|
|
68
|
+
) -> None:
|
|
74
69
|
"""
|
|
75
70
|
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.
|
|
76
71
|
"""
|
|
77
|
-
project = Project(folder=folder)
|
|
72
|
+
project = Project(folder=folder or os.getcwd())
|
|
78
73
|
# We need to unpatch for our tests not to break
|
|
79
74
|
if show_tokens or not prod or ctx.invoked_subcommand == "build":
|
|
80
75
|
__unpatch_click_output()
|
|
@@ -87,7 +82,7 @@ async def cli(ctx: Context, debug: bool, token: str, host: str, show_tokens: boo
|
|
|
87
82
|
if debug:
|
|
88
83
|
logging.basicConfig(level=logging.DEBUG)
|
|
89
84
|
|
|
90
|
-
config_temp = CLIConfig.get_project_config(project.path)
|
|
85
|
+
config_temp = CLIConfig.get_project_config(str(project.path))
|
|
91
86
|
if token:
|
|
92
87
|
config_temp.set_token(token)
|
|
93
88
|
if host:
|
|
@@ -130,55 +125,7 @@ async def cli(ctx: Context, debug: bool, token: str, host: str, show_tokens: boo
|
|
|
130
125
|
ctx.ensure_object(dict)["project"] = project
|
|
131
126
|
|
|
132
127
|
|
|
133
|
-
@cli.command(
|
|
134
|
-
@click.argument("filenames", type=click.Path(exists=True), nargs=-1, default=None)
|
|
135
|
-
@click.option("--debug", is_flag=True, default=False, help="Print internal representation")
|
|
136
|
-
@click.pass_context
|
|
137
|
-
def check(ctx: Context, filenames: List[str], debug: bool) -> None:
|
|
138
|
-
"""Check file syntax."""
|
|
139
|
-
|
|
140
|
-
if not filenames:
|
|
141
|
-
project: Project = ctx.ensure_object(dict)["project"]
|
|
142
|
-
filenames = project.get_project_files()
|
|
143
|
-
|
|
144
|
-
def process(filenames: Iterable):
|
|
145
|
-
parser_matrix = {".pipe": parse_pipe, ".datasource": parse_datasource}
|
|
146
|
-
incl_suffix = ".incl"
|
|
147
|
-
try:
|
|
148
|
-
for filename in filenames:
|
|
149
|
-
if os.path.isdir(filename):
|
|
150
|
-
process(filenames=filename)
|
|
151
|
-
|
|
152
|
-
click.echo(FeedbackManager.info_processing_file(filename=filename))
|
|
153
|
-
|
|
154
|
-
file_suffix = Path(filename).suffix
|
|
155
|
-
if file_suffix == incl_suffix:
|
|
156
|
-
click.echo(FeedbackManager.info_ignoring_incl_file(filename=filename))
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
doc: Datafile
|
|
160
|
-
parser = parser_matrix.get(file_suffix)
|
|
161
|
-
if not parser:
|
|
162
|
-
raise ParseException(FeedbackManager.error_unsupported_datafile(extension=file_suffix))
|
|
163
|
-
|
|
164
|
-
doc = parser(filename)
|
|
165
|
-
|
|
166
|
-
click.echo(FeedbackManager.success_processing_file(filename=filename))
|
|
167
|
-
if debug:
|
|
168
|
-
pp = pprint.PrettyPrinter()
|
|
169
|
-
for x in doc.nodes:
|
|
170
|
-
pp.pprint(x)
|
|
171
|
-
|
|
172
|
-
except DatafileSyntaxError as e:
|
|
173
|
-
# TODO(eclbg): add the filename to the error message
|
|
174
|
-
raise CLIException(str(e))
|
|
175
|
-
except ParseException as e:
|
|
176
|
-
raise CLIException(FeedbackManager.error_exception(error=e))
|
|
177
|
-
|
|
178
|
-
process(filenames=filenames)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
@cli.command(hidden=True)
|
|
128
|
+
@cli.command()
|
|
182
129
|
@click.option(
|
|
183
130
|
"--folder", default=None, type=click.Path(exists=True, file_okay=False), help="Folder where files will be placed"
|
|
184
131
|
)
|
tinybird/tb/modules/common.py
CHANGED
|
@@ -163,7 +163,7 @@ def generate_datafile(
|
|
|
163
163
|
force: Optional[bool] = False,
|
|
164
164
|
_format: Optional[str] = "csv",
|
|
165
165
|
folder: Optional[str] = None,
|
|
166
|
-
):
|
|
166
|
+
) -> Path:
|
|
167
167
|
p = Path(filename)
|
|
168
168
|
base = Path("datasources")
|
|
169
169
|
if folder:
|
|
@@ -190,6 +190,7 @@ def generate_datafile(
|
|
|
190
190
|
fixture_file.write(data[: data.rfind(newline)])
|
|
191
191
|
else:
|
|
192
192
|
click.echo(FeedbackManager.error_file_already_exists(file=f))
|
|
193
|
+
return f
|
|
193
194
|
|
|
194
195
|
|
|
195
196
|
async def get_current_workspace(config: CLIConfig) -> Optional[Dict[str, Any]]:
|
tinybird/tb/modules/copy.py
CHANGED
|
@@ -5,19 +5,20 @@
|
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
7
|
import re
|
|
8
|
+
from typing import Optional, Tuple
|
|
8
9
|
|
|
9
10
|
import click
|
|
10
11
|
from click import Context
|
|
11
12
|
|
|
12
|
-
from tinybird.client import TinyB
|
|
13
|
+
from tinybird.client import AuthNoTokenException, TinyB
|
|
13
14
|
from tinybird.tb.modules.cli import cli
|
|
14
|
-
from tinybird.tb.modules.common import coro, echo_safe_humanfriendly_tables_format_smart_table
|
|
15
|
+
from tinybird.tb.modules.common import coro, echo_safe_humanfriendly_tables_format_smart_table, wait_job
|
|
15
16
|
from tinybird.tb.modules.datafile.common import get_name_version
|
|
16
17
|
from tinybird.tb.modules.exceptions import CLIPipeException
|
|
17
18
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
@cli.group(
|
|
21
|
+
@cli.group()
|
|
21
22
|
@click.pass_context
|
|
22
23
|
def copy(ctx):
|
|
23
24
|
"""Copy pipe commands"""
|
|
@@ -38,10 +39,10 @@ async def copy_ls(ctx: Context, match: str, format_: str):
|
|
|
38
39
|
"""List copy pipes"""
|
|
39
40
|
|
|
40
41
|
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
41
|
-
pipes = await client.pipes(dependencies=False, node_attrs="name", attrs="name,updated_at")
|
|
42
|
+
pipes = await client.pipes(dependencies=False, node_attrs="name", attrs="name,updated_at,type")
|
|
42
43
|
copies = [p for p in pipes if p.get("type") == "copy"]
|
|
43
44
|
copies = sorted(copies, key=lambda p: p["updated_at"])
|
|
44
|
-
columns = ["name", "updated at", "nodes"
|
|
45
|
+
columns = ["name", "updated at", "nodes"]
|
|
45
46
|
table_human_readable = []
|
|
46
47
|
table_machine_readable = []
|
|
47
48
|
pattern = re.compile(match) if match else None
|
|
@@ -66,3 +67,93 @@ async def copy_ls(ctx: Context, match: str, format_: str):
|
|
|
66
67
|
click.echo(json.dumps({"pipes": table_machine_readable}, indent=2))
|
|
67
68
|
else:
|
|
68
69
|
raise CLIPipeException(FeedbackManager.error_pipe_ls_type())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@copy.command(name="run", short_help="Run an on-demand copy job")
|
|
73
|
+
@click.argument("pipe_name_or_id")
|
|
74
|
+
@click.option("--wait", is_flag=True, default=False, help="Wait for the copy job to finish")
|
|
75
|
+
@click.option(
|
|
76
|
+
"--mode", type=click.Choice(["append", "replace"], case_sensitive=True), default=None, help="Copy strategy"
|
|
77
|
+
)
|
|
78
|
+
@click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
|
|
79
|
+
@click.option(
|
|
80
|
+
"--param",
|
|
81
|
+
nargs=1,
|
|
82
|
+
type=str,
|
|
83
|
+
multiple=True,
|
|
84
|
+
default=None,
|
|
85
|
+
help="Key and value of the params you want the Copy pipe to be called with. For example: tb pipe copy run <my_copy_pipe> --param foo=bar",
|
|
86
|
+
)
|
|
87
|
+
@click.pass_context
|
|
88
|
+
@coro
|
|
89
|
+
async def copy_run(
|
|
90
|
+
ctx: click.Context, pipe_name_or_id: str, wait: bool, mode: str, yes: bool, param: Optional[Tuple[str]]
|
|
91
|
+
):
|
|
92
|
+
"""Run an on-demand copy job"""
|
|
93
|
+
|
|
94
|
+
params = dict(key_value.split("=") for key_value in param) if param else {}
|
|
95
|
+
|
|
96
|
+
if yes or click.confirm(FeedbackManager.warning_confirm_copy_pipe(pipe=pipe_name_or_id)):
|
|
97
|
+
click.echo(FeedbackManager.info_copy_job_running(pipe=pipe_name_or_id))
|
|
98
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
response = await client.pipe_run_copy(pipe_name_or_id, params, mode)
|
|
102
|
+
|
|
103
|
+
job_id = response["job"]["job_id"]
|
|
104
|
+
job_url = response["job"]["job_url"]
|
|
105
|
+
target_datasource_id = response["tags"]["copy_target_datasource"]
|
|
106
|
+
target_datasource = await client.get_datasource(target_datasource_id)
|
|
107
|
+
target_datasource_name = target_datasource["name"]
|
|
108
|
+
click.echo(
|
|
109
|
+
FeedbackManager.success_copy_job_created(target_datasource=target_datasource_name, job_url=job_url)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if wait:
|
|
113
|
+
await wait_job(client, job_id, job_url, "** Copying data")
|
|
114
|
+
click.echo(FeedbackManager.success_data_copied_to_ds(target_datasource=target_datasource_name))
|
|
115
|
+
|
|
116
|
+
except AuthNoTokenException:
|
|
117
|
+
raise
|
|
118
|
+
except Exception as e:
|
|
119
|
+
raise CLIPipeException(FeedbackManager.error_creating_copy_job(error=e))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@copy.command(name="resume", short_help="Resume a paused copy pipe")
|
|
123
|
+
@click.argument("pipe_name_or_id")
|
|
124
|
+
@click.pass_context
|
|
125
|
+
@coro
|
|
126
|
+
async def copy_resume(ctx: click.Context, pipe_name_or_id: str):
|
|
127
|
+
"""Resume a paused copy pipe"""
|
|
128
|
+
|
|
129
|
+
click.echo(FeedbackManager.info_copy_pipe_resuming(pipe=pipe_name_or_id))
|
|
130
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
await client.pipe_resume_copy(pipe_name_or_id)
|
|
134
|
+
click.echo(FeedbackManager.success_copy_pipe_resumed(pipe=pipe_name_or_id))
|
|
135
|
+
|
|
136
|
+
except AuthNoTokenException:
|
|
137
|
+
raise
|
|
138
|
+
except Exception as e:
|
|
139
|
+
raise CLIPipeException(FeedbackManager.error_resuming_copy_pipe(error=e))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@copy.command(name="pause", short_help="Pause a running copy pipe")
|
|
143
|
+
@click.argument("pipe_name_or_id")
|
|
144
|
+
@click.pass_context
|
|
145
|
+
@coro
|
|
146
|
+
async def copy_pause(ctx: click.Context, pipe_name_or_id: str):
|
|
147
|
+
"""Pause a running copy pipe"""
|
|
148
|
+
|
|
149
|
+
click.echo(FeedbackManager.info_copy_pipe_pausing(pipe=pipe_name_or_id))
|
|
150
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
await client.pipe_pause_copy(pipe_name_or_id)
|
|
154
|
+
click.echo(FeedbackManager.success_copy_pipe_paused(pipe=pipe_name_or_id))
|
|
155
|
+
|
|
156
|
+
except AuthNoTokenException:
|
|
157
|
+
raise
|
|
158
|
+
except Exception as e:
|
|
159
|
+
raise CLIPipeException(FeedbackManager.error_pausing_copy_pipe(error=e))
|