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 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' -> errors: {content.get("errors")}'
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={ "true" if with_headers else "false" },
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['schema']}".{self.stage()}
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)
@@ -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" "** Kafka streaming connection configured successfully!"
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&param2=value2&param3=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
 
@@ -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'{prefix if i != 0 else ""}{part}' for i, part in enumerate(parts)])
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.dev42'
8
- __revision__ = '14b255c'
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
@@ -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
- build_and_print_resource(tb_client, file_changed)
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
- for ds in datasources:
105
- ds_path = next((p for p in project_files if p.endswith(ds.get("name") + ".datasource")), None)
106
- if ds_path:
107
- ds_path = ds_path.replace(f"{project.folder}/", "")
108
- click.echo(FeedbackManager.info(message=f"✓ {ds_path} created"))
109
- for pipe in pipes:
110
- pipe_name = pipe.get("name")
111
- pipe_path = next((p for p in project_files if p.endswith(pipe_name + ".pipe")), None)
112
- if pipe_path:
113
- pipe_path = pipe_path.replace(f"{project.folder}/", "")
114
- click.echo(FeedbackManager.info(message=f"✓ {pipe_path} created"))
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
- name = resource_path.stem
191
- pipeline = name if filename.endswith(".pipe") else None
192
- res = asyncio.run(tb_client.query(f"SELECT * FROM {name} FORMAT JSON", pipeline=pipeline))
193
- print_table_formatted(res, name)
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)
@@ -39,7 +39,7 @@ jobs:
39
39
  working-directory: '{{ data_project_dir }}'
40
40
  services:
41
41
  tinybird:
42
- image: tinybirdco/tinybird-local:latest
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:latest
86
+ - name: tinybirdco/tinybird-local:beta
87
87
  alias: tinybird-local
88
88
  """
89
89
 
@@ -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, Iterable, List, Optional, Tuple, Union
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, default=os.getcwd(), help="Folder where files will be placed")
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(ctx: Context, debug: bool, token: str, host: str, show_tokens: bool, prod: bool, folder: str) -> None:
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(hidden=True)
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
  )
@@ -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]]:
@@ -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(hidden=True)
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", "url"]
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))